04 python之函数详解

转载 Tammy~ 2019/9/11 21:06:25

一、函数初识函数的产生:函数就是封装一个功能的代码片段。关键字定义一个函数函数名的书写规则与变量一样。括号是用来传参的。函数体就是函数里面的逻辑代码代码从上至下执行,执行到时将这个变量名加载到临时内存中,但它

一、函数初识

函数的产生:函数就是封装一个功能的代码片段。

li = ['spring', 'summer', 'autumn', 'winter']
def function():
    count = 0
    for j in li:
        count += 1
    print(count)
function()        # 4
  • def 关键字,定义一个函数
  • function 函数名的书写规则与变量一样。
  • 括号是用来传参的。
  • 函数体,就是函数里面的逻辑代码

代码从上至下执行,执行到def function() 时, 将function这个变量名加载到临时内存中,但它不执行

函数的执行:函数名 + () 

 

使用__name__方法获取函数名 ,使用__doc___方法获取函数的解释  

def func1():
    """
    此函数是完成登陆的功能,参数分别是...作用。
    return: 返回值是登陆成功与否(True,False)
    """
    print(666)

func1()
print(func1.__name__)         #获取函数名
print(func1.__doc__)         #获取函数名注释说明 
执行输出:
666
func1
此函数是完成登陆的功能,参数分别是...作用。
return: 返回值是登陆成功与否(True,False)

这个有什么用呢?比如日志功能,需要打印出谁在什么时间,调用了什么函数,函数是干啥的,花费了多次时间,这个时候,就需要获取函数的有用信息了

 

1. 函数返回值

写函数,不要在函数中写print(),  函数是以功能为导向的,除非测试的时候,才可以写print()

  • 在函数中,遇到return结束函数
def fun():
    print(111)
    return
    print(444)
fun() 
执行输出:111
  • 将值返回给函数的调用者
def fun():
    a = 134
    return a
print(fun()) 
执行输出:123

1)无 return

def fun():
    pass
print(fun()) 
执行输出:None

2)return 1个值该值是什么,就直接返回给函数的调用者,函数名()

def fun():
    return [1,2,3]
print(fun()) 
执行输出:[1, 2, 3]

3)return 多个值 将多个值放到一个元组,返回给函数的调用者。

def fun():
    return 1,2,[33,44],'abc'
print(fun()) 
执行输出:
(1, 2, [33, 44], 'abc')

2. 函数的传参

(1)实参:在函数执行者里面的参数叫实参

位置参数:按顺序一一对应

def func(a,b,c):
    print(a)
    print(b)
    print(c)

func('fdsafdas',3,4) 
执行输出:
fdsafdas
3
4

如果少一个参数呢?

def func(a,b,c):
    print(a)
    print(b)
    print(c)

func(3,4) 
执行报错:TypeError: func() missing 1 required positional argument: 'c'

必须是一一对应的。

def compare(x,y):
    ret = x if x > y else y   #三元运算,针对简单的if else才能使用
    return ret

print(compare(123,122334))        # 122334

关键字参数:可以不按顺序,但是必须一一对应

def compare(x,y):
    ret = x if x > y else y
    return ret
print(compare(y=13,x=1))
执行结果:13

混合参数:关键字参数一定要在位置参数后面

def func1(a,b,c,d,e):
    print(a)
    print(b)
    print(c)
    print(d)
    print(e)

func1(1,4,d=2,c=3,e=5) 
执行输出:
1
4
3
2
5

(2) 形参:

位置参数:按顺序和实参一一对应,位置参数必须传值

def func(a,b,c):
    print(a)
    print(b)
print(c)

func('fdsafdas',3,4) 
执行输出:
fdsafdas
3
4

默认参数:传参则覆盖,不传则默认,默认参数永远在位置参数后面

1.

def func(a,b=666):
    print(a,b)
func(1,2) 
执行输出:1 2

2.

def func(a,b=666):
    print(a,b)
func(1) 

执行输出:1 666

举一个场景:班主任录入员工信息表,有2个问题:第一,男生居多;第二,完成函数功能 *****

def  Infor(username,sex='男'):
    with open('name_list',encoding='utf-8',mode='a') as f1:
        f1.write('{}\t{}\n'.format(username,sex))

while True:
    username = input('请输入姓名(男生以1开头):').strip()
    if '1' in username:
        username = username[1:]        #去除1
        Infor(username)
    else:
        Infor(username,'女') 

③动态参数当函数的形参数量不一定时,可以使用动态参数。用*args和**kwargs接收,args是元组类型,接收除键值对以外的参数(接收位置参数),kwargs是字典类型,接收键值对(关键字参数)并保存在字典中。

def func(*args,**kwargs):
    print(args,type(args))
    print(kwargs,type(kwargs))

func(1,2,3,4,'alex',name = 'alex')
输出结果是:
(1, 2, 3, 4, 'alex') <class 'tuple'>
{'name': 'alex'} <class 'dict'>

“ * "的魔性作用

(1)在函数定义时:*位置参数和**关键字参数代表聚合

将所有实参的位置参数聚合到一个元组中,并将这个元组赋值给args。在关键参数前加“ ** ”代表将实参的关键字参数聚合到一个字典中,并将这个字典赋值给kwargs。

将2个列表的所有元素赋值给args

def func(*args):
    print(args)

l1 = [1,2,30]
l2 = [1,2,33,21,45,66]
func(*l1)
func(*l1,*l2) 
执行输出:
(1, 2, 30)
(1, 2, 30, 1, 2, 33, 21, 45, 66)

传两个字典给**kwargs

def func(**kwargs):
    print(kwargs)

dic1 = {'name':'jack','age':22}
dic2 = {'name1':'rose','age1':21}
func(**dic1,**dic2) 
执行输出:
{'name': 'jack', 'age': 22, 'name1': 'rose', 'age1': 21}
def func(*args,**kwargs):
    print(args)
    print(kwargs)

func(*[1,2,3], *[4,5,6], **{'name':'alex'}, **{'age':18})    #相当于func([1,2,3,4,5,6], {'name':'alex','age':18})

(2)在函数的调用执行时,打散

   *可迭代对象,代表打散(list,tuple,str,dict(键))将元素一一添加到args。

   **字典,代表打散,将所有键值对放到一个kwargs字典里。

def func(*args,**kwargs):
    print(args,kwargs)

dic1 = {'name':'jack','age':22}
dic2 = {'name1':'rose','age1':21}

func(*[1,2,3,4],*'asdk',**dic1,**dic2) 
执行输出:(1, 2, 3, 4, 'a', 's', 'd', 'k') {'age1': 21, 'name': 'jack', 'age': 22, 'name1': 'rose'}

形参的顺序:位置参数 ----> *args ----->关键字参数-------->默认参数 ------->**kwargs

*args参数,可以不传,默认为空(),**kwargs 动态传参,他将所有的关键字参数(未定义的)放到一个字典中

def func(a,b,c,d,*args,e='男',**kwargs):
    print(a,b,c,d,args,e,kwargs)

func(1,2,3,4,5,6,7,v=3,m=7,h=9,e='女') 
执行输出:1 2 3 4 (5, 6, 7) 女 {'v': 3, 'h': 9, 'm': 7}
def func(a,b,c,**kwargs):
    print(kwargs)
func(1,2,r=4,b1=5,c1=6,c=7) 

执行输出:{'r': 4, 'c1': 6, 'b1': 5}

执行没有报错,是因为函数接收参数后,它会从左边到右找,最后找到了c,c=7参数,在a,b,c里面已经定义好了,所以在输出的字典中,并未出现。因为kwargs返回的是未定义的关键字参数。

如果函数含有多个未知参数,一般使用如下格式:

def func1(*args,**kwargs):
    pass
func1() 

二、命名空间和作用域

  当执行函数的时候,他会在内存中开辟一个临时名称空间,存放函数体内的所有变量与值的关系,随着函数的执行完毕,临时空间自动关闭。

 

函数里面的变量,在函数外面能直接引用么?不能

def func1():
    m = 1
    print(m)

print(m)                          # NameError: name 'm' is not defined

上面为什么会报错呢?现在我们来分析一下python内部的原理是怎么样:

我们首先回忆一下Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟里一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读入内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。

1. 命名空间和作用域

命名空间:存放”名字与值关系的空间“

①全局命名空间:代码在运行时,创建的存储”变量名与值的关系“的内存空间

②局部命名空间:在函数调用时临时开辟出来的空间,会随着函数的执行完毕而被清空

③内置命名空间:存放了python解释器为我们提供的名字:input,print,str,list,tuple...它们都是我们熟悉 的,拿过来就可以用的方法。

 

作用域:就是作用范围

①全局作用域:全局命名空间、内置命名空间。在整个文件的任意位置都能被引用、全局有效

②局部作用域:局部命名空间,只能在局部范围内生效

 

加载顺序:

内置命名空间(程序运行前加载)----->   全局命名空间(程序运行中从上至下加载) -----> 局部命名空间(程序运行中:调用时才加载)

 

取值顺序:

  在局部调用:局部命名空间->全局命名空间->内置命名空间

  在全局调用:全局命名空间->内置命名空间

 

综上所述,在找寻变量时,从小范围,一层一层到大范围去找寻。取值顺序:就近原则

 

局部变量举例

name = 'summer'
def func1():
    name = 'spring'
    print(name)
func1()
执行输出:spring
取值是从内到外
name = 'summer'
def func1():
    print(name)
func1() 

 执行输出:老男孩

代码从上至下依次执行, 调用函数:函数里面从上至下依次执行。

print(111)
def func1():
    print(333)
    func2()
    print(666)
def func2():
    print(444)
def func3():
    print(555)
    func2()

func1()
print(222) 
执行输出:
111
333
444
666
222
def f1():
    def f2():
        def f3():
            print("in f3")
        print("in f2")
        f3()
    print("in f1")
    f2()
f1() 
执行输出:
in f1
in f2
in f3

2. globals和locals方法

print(globals())         #全局名称空间所有变量,字典
print(locals())        #局部名称空间所有变量,字典 (当前)

globals()和locals()一般很少用,在函数逻辑比较复杂的情况下,可能会用到。

li = ['spring', 'summer', 'autumn', 'winter']

def func():
    a = 1
    b = 2
    print('func', globals())
    print('func', locals())

    def func1():
        c = 3
        d = 4
        print('func1', globals())
        print('func1', locals())

    func1()

func()

输出结果

func {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x011CC410>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:/Users/Administrator/houseinfo/test.py', '__cached__': None, 'li': ['spring', 'summer', 'autumn', 'winter'], 'func': <function func at 0x03542E40>}
func {'b': 2, 'a': 1}
func1 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x011CC410>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:/Users/Administrator/houseinfo/test.py', '__cached__': None, 'li': ['spring', 'summer', 'autumn', 'winter'], 'func': <function func at 0x03542E40>}
func1 {'d': 4, 'c': 3}

 (1)global:

①在局部命名空间声明全局变量

def func2():
    global name
    name = 'summer'

func2()
print(name)
执行结果:summer

②在局部命名空间对全局变量进行修改(限于字符串,数字)。

count = 1
def func1():
    global count
    count = count + 1
    print(count)
func1()
print(count)
执行结果:

2

因为全局变量count被函数体的global count 覆盖了

(2)nonlocal

子函数对父函数的变量进行修改,此变量不能是全局变量

a = 4
def func1():
    nonlocal a
    a = 5             #修改全局变量
    #print(name)
func1()
print(a) 
执行输出:SyntaxError: no binding for nonlocal 'a' found

在局部作用域中,对父级作用域的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。

1

def func1():
    b = 6
    def func2():
        b = 666
        print(b)
    func2()
    print(b)                 #父级不受影响
func1() 
执行输出:
666
6

例2

def func1():
    b = 6
    def func2():
        nonlocal b          #表示可以影响父级,也就是func1()
        b = 666             #重新赋值
        print(b)
    func2()    
print(b)                 #这个时候,影响了b的值,输出666
func1() 
执行输出:
666
666

3******

def aa():                     #不受ccl影响
    b = 42
    def bb():
        b = 10                 #影响子级函数,b都是10
        print(b)
        def cc():
            nonlocal b             #只能影响父级,也就是bb()
            b = b + 20             #b=10+20 也就是30
            print(b)
        cc()
        print(b)
    bb()
    print(b)
aa()
执行输出:
10
30
30
42

注意

a = 5
def func1():
    a += 1
    print(a)
func1() 

执行报错。这里函数对全局变量做了改变,是不允许操作的。函数内部可以引用全局变量,不能修改。如果要修改,必须要global一下

a = 5
def func1():
    global a
    a += 1
    print(a)

func1()     #输出6

三、装饰器

1. 函数名应用

函数名是什么?函数名是函数的名字,本质:变量,特殊的变量。

1函数名就是函数的内存地址,直接打印函数名,就是打印内存地址

def func1():
    print(123)
print(func1)         # <function func1 at 0x0000029042E02E18>

2函数名可以作为变量

def func1():
    print(111)

f = func1
f()                 # f() 就是func1()    

3函数名可以作为函数的参数

def func1():
    print(111)

def func2(x):
    x()

func2(func1)         #func1作为func2的参数 

4函数名可以作为函数的返回值

def wrapper():
    def inner():
        print('inner')
    return inner
f = wrapper()
f()

5函数名可以作为容器类类型的元素

使用for循环批量执行函数
def func1():
    print('func1')
def func2():
    print('func2')
def func3():
    print('func3')

l1 = [func1,func2,func3]
for i in l1:
    i()

像上面函数名这种,叫做第一类对象。

 

第一类对象( first-class object)指:

1.可在运行期创建

2.可用作函数参数或返回值

3.可存入变量的实体

*不明白?那就记住一句话,就当普通变量用

 

2. 闭包

1、什么是闭包:内层函数对外层函数变量(非全局变量)的引用,并返回内层函数名,就形成了闭包

2、闭包的作用:爬虫、装饰器

当程序执行遇到函数执行时,会在内存空间开辟局部命名空间,当函数执行完毕,该命名空间会被销毁。但是如果这个函 数内部形成闭包,则该内存空间不会随着函数执行完而消失。

3、如何判断是否是闭包:print(函数名.__closure__) 结果是cell说明是闭包,结果是None说明不是闭包。

 

闭包函数:内部函数包含对外部作用域而非全剧作用域变量的引用,该内部函数称为闭包函数

 

闭包举例

def wrapper():
    name = 'summer'
    def inner():
        print(name)
    inner()

wrapper()     # summer

如何判断它是否是一个闭包函数呢? 内层函数名.__closure__  cell 就是=闭包

1.

def wrapper():
    name = 'summer'
    def inner():
        print(name)
    inner()
    print(inner.__closure__)

wrapper()     
执行输出:
summer
(<cell at 0x0000017FC9C90B58: str object at 0x0000017FCA349AD0>,)

2.

name = 'summer'
def wrapper():
    def inner():
        print(name)
    inner()
    print(inner.__closure__)

wrapper() 
结果输出:
summer
None

返回值为None 表示它不是闭包,因为name是一个全局变量,如果函数调用了外层变量而非全局变量,那么它就是闭包。

3.

name = 'summer'
def wrapper2():
    name1 = 'spring'
    def inner():
        print(name)
        print(name1)
    inner()
    print(inner.__closure__)

wrapper2()
结果输出:
summer
spring
(<cell at 0x030B7310: str object at 0x03043680>,)

只要引用了外层变量至少一次,非全局的,它就是闭包

4:判断下面的函数,是一个闭包吗?******

name = 'summer'
def wraaper2(n):        #相当于n = 'summer' 
  def inner():
        print(n)
    inner()
    print(inner.__closure__)  

wraaper2(name)
结果输出:
summer
(<cell at 0x03867350: str object at 0x037F3680>,)

它也是一个闭包. 虽然wraaper2传了一个全局变量,但是在函数wraaper2内部,inner引用了外层变量,相当于在函数inner外层定义了 n = 'summer',所以inner是一个闭包函数

 

闭包的好处当函数开始执行时,如果遇到了闭包,他有一个机制,他会永远开辟一个内存空间,将闭包中的变量等值放入其中,不会随着函数的执行完毕而消失。

 举一个例子:爬3次,内存开了3次,很占用内存

from urllib.request import urlopen
content1 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')
content2 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')
content3 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')

把它封装成闭包

from urllib.request import urlopen

def index():
    url = "https://www.cnblogs.com/"
    def get():
        return urlopen(url).read()
    return get        #return的是get,就是一个函数名

cnblog = index()
print(cnblog)               # <function index.<locals>.get at 0x02F46978>
content = cnblog()
print(content)              # 页面源码

这个例子,只有第一遍,是从网站抓取的。之后的执行,直接从内存中加载,节省内存空间

 

3. 装饰器

3.1 装饰器初识

装饰器本质就是一个python函数,他可以让其他函数在不需要做任何代码变动的前提下,增加额外的功能,装饰器的返回值也是一个函数对象。

 

装饰器的应用场景:比如插入日志,性能测试,事务处理,缓存等等场景。

import time
def timmer(f):                           #  2接收参数 f = func1
    def inner():
        start_time = time.time()             # 5.进入inner函数
        f()                             # 6.执行f(),也就是原来的func1函数。虽然func1被覆盖了,但是之前的值还存在。                            请参考上面a,b赋值的例子
        end_time = time.time()             # 10 获取当前时间
        print('此函数的执行时间为{}'.format(end_time - start_time))         # 11.输出差值
    return inner                       # 3.将inner函数返回给函数调用者timmer(func1),此时程序结束,继续执行func1()

def func1():                           # 7.进入函数
    print('in func1')               #  8.打印输出
    time.sleep(1)                    #  9.等待1秒

func1 = timmer(func1)               # 1.等式计算右边的,将func1函数传给timmer函数,此时func1被覆盖了,原来func1的不存在了。
print(func1)
func1()                           # 4.这里的func1是全新的func1,就是上面的赋值,此时相当于执行 inner函数
输出结果:
<function timmer.<locals>.inner at 0x03822DF8>
in func1
此函数的执行时间为1.0003533363342285

代码从上至下执行

 

语法糖:想测试谁,前面加@装饰器函数,即可。写装饰器,约定俗成,函数名为wrapper

def wrapper(func):
    def inner(*args,**kwargs):
        '''被装饰函数之前'''
        ret = func(*args,**kwargs)
        '''被装饰函数之后'''
        return ret
    return inner

@wrapper
def func(*args,**kwargs):
    print(args,kwargs)
    return 666

print(func())
输出结果:
() {}
666

装饰器利用return制造了一个假象,func()执行,其实是执行inner()func()把原来的func()给覆盖了

3.2 装饰器传参

1:上面装饰器的例子,func1,要传2个参数a,b

import time
def timmer(f):
    def inner(a,b):
        start_time = time.time()
        f(a,b)
        end_time = time.time()
        print('此函数的执行时间为{}'.format(end_time - start_time))
    return inner

@timmer
def func1(a,b):
    print('in func1 {}{}'.format(a,b))
    time.sleep(1)  # 模拟程序逻辑

func1(1,2) 
执行输出:
in func1 12
此函数的执行时间为1.0006024837493896

2:如果有多个参数呢?改成动态参数

import time
def timmer(f):
    def inner(*args,**kwargs):
        start_time = time.time()
        f(*args,**kwargs)
        end_time = time.time()
        print('此函数的执行时间为{}'.format(end_time - start_time))
    return inner

@timmer
def func1(*args,**kwargs):
    print('in func1 {}{}'.format(args,kwargs))
    time.sleep(1)  # 模拟程序逻辑

func1(1,2,a='3',b=4) 
执行输出:
in func1 (1, 2){'b': 4, 'a': '3'}
此函数的执行时间为1.000101089477539

函数的执行时,*打散; 函数的定义时,*聚合。

from functools import wraps
def wrapper(f):                  # f = func1

    @wraps(f)
    def inner(*args,**kwargs):               #聚合,args (1,2,3)
        '''执行函数之前的相关操作'''
        ret = f(*args,**kwargs)               # 打散 1,2,3
        '''执行函数之后的相关操作'''
        return ret
    return inner

@wrapper  # func1 = wrapper(func1)  func1 = inner
def func1(*args):                           #args (1,2,3) 聚合
    print(666)
    return args

print(func1(*[1,2,3]))  
执行输出:
666
(1, 2, 3)

3*****

import time                                 #1.加载模块

def timmer(*args,**kwargs):                     #2.加载变量  5.接收参数True,2,3

    def wrapper(f):                             #6.加载变量  8.f = func1

        print(args, kwargs)                     #9.接收timmer函数的值True,2,3

        def inner(*args,**kwargs):                 #10.加载变量. 13.执行函数inner
            if flag:                         #14 flag = True
                start_time = time.time()             #15 获取当前时间
                ret = f(*args,**kwargs)             #16 执行func1
                time.sleep(0.3)                 #19 等待0.3秒
                end_time = time.time()             #20 获取当前时间
                print('此函数的执行效率%f' % (end_time-start_time)) #21 打印差值
            else:
                ret = f(*args, **kwargs)

            return ret                         #22 返回给函数调用者func1()
        return inner                         #11 返回给函数调用者wrapper
    return wrapper                         #7.返回给函数调用timmer(flag,2,3)

flag = True                                 #3 加载变量
@timmer(flag,2,3)      # 4.执行函数timmer(flag,2,3) 17.执行函数func1 两步:1,timmer(flag,2,3) 相当于执行wrapper                                                     2.@wrapper 装饰器 func1 = wrapper(func1)
def func1(*args,**kwargs):
    return 666                             #18 返回给函数调用者f(*args,**kwargs)

print(func1())                             #12 执行函数 

4:假定现在有100个函数,都加上了装饰器,增加了显示函数执行时间的功能,现在需要去掉!直接在装饰器函数加一个参数即可。

import time
flag = True
def wrapper(f):
    def inner(*args,**kwargs):
        if flag:
            start_time = time.time()
            ret = f(*args,**kwargs)
            time.sleep(0.3)
            end_time = time.time()
            print('此函数的执行效率%f' % (end_time-start_time))
        else:
            ret = f(*args, **kwargs)
        return ret
    return inner

@wrapper

def func1(*args,**kwargs):
    print(args,kwargs)
    return 666

print(func1()) 

执行输出:
此函数的执行效率0.300431
666
View Code

5:现在需要关闭显示执行时间直接将flag改成false

import time
flag = False
def wrapper(f):
    def inner(*args,**kwargs):
        if flag:
            start_time = time.time()
            ret = f(*args,**kwargs)
            time.sleep(0.3)
            end_time = time.time()
            print('此函数的执行效率%f' % (end_time-start_time))
        else:
            ret = f(*args, **kwargs)
        return ret
    return inner

@wrapper

def func1(*args,**kwargs):
    print(args,kwargs)
    return 666

print(func1()) 

执行输出:
() {}
666
View Code

这样,所有调用的地方,就全部关闭了,非常方便

写装饰器,一般嵌套3层就可以了

3.3 多个装饰器,装饰一个函数

def wrapper1(func):                  # func ==  f函数名
    def inner1():
        print('wrapper1 ,before func')          # 2
        func()
        print('wrapper1 ,after func')          # 4
    return inner1

def wrapper2(func):  # func == inner1
    def inner2():
        print('wrapper2 ,before func')          # 1
        func()
        print('wrapper2 ,after func')          # 5
    return inner2

@wrapper2                      #  f = wrapper2(f)  里面的f==inner1  外面的f == inner2
@wrapper1                      #  f = wrapper1(f)   里面的f==函数名f  外面的f == inner1

def f():                          # 3
    print('in f')

f()                              # inner2() 
执行输出:
wrapper2 ,before func
wrapper1 ,before func
in f
wrapper1 ,after func
wrapper2 ,after func

哪个离函数近,哪个先计算最底下的先执行

执行顺序如下图:

 

 多个装饰器,都是按照上图的顺序来的

4. 装饰器的__name____doc___

__name__:函数名

__doc___:函数的解释  

普通函数

def func1():
    """
    此函数是完成登陆的功能,参数分别是...作用。
    return: 返回值是登陆成功与否(True,False)
    """
    print(666)

func1()
print(func1.__name__)         #获取函数名
print(func1.__doc__)         #获取函数名注释说明 

执行输出:
666
func1
此函数是完成登陆的功能,参数分别是...作用。
return: 返回值是登陆成功与否(True,False)

这个有什么用呢?比如日志功能,需要打印出谁在什么时间,调用了什么函数,函数是干啥的,花费了多次时间,这个时候,就需要获取函数的有用信息了

带装饰器的函数

def wrapper(f):      # f = func1

    def inner(*args,**kwargs):             #聚合, args (1,2,3)
        '''执行函数之前的相关操作'''
        ret = f(*args,**kwargs)              # 打散 1,2,3
        '''执行函数之后的相关操作'''
        return ret
    return inner

@wrapper
def func1():
    """
    此函数是完成登陆的功能,参数分别是...作用。
    return: 返回值是登陆成功与否(True,False)
    """
    print(666)
    return True

func1()
print(func1.__name__)
print(func1.__doc__) 
执行输出:
666
inner
执行函数之前的相关操作

咦?为什么输出了inner,我要的是func1啊。因为函数装饰之后,相当于执行了inner函数,所以输出inner

为了解决这个问题,需要调用一个模块wraps

wraps将 被修饰的函数(wrapped) 的一些属性值赋值给修饰器函数(wrapper) ,最终让属性的显示更符合我们的直觉

from functools import wraps

def wrapper(f):                  # f = func1
    @wraps(f)                 #f是被装饰的函数
    def inner(*args,**kwargs):         #聚合args (1,2,3)
        '''执行函数之前的相关操作'''
        ret = f(*args,**kwargs)          # 打散 1,2,3
        '''执行函数之后的相关操作'''
        return ret
    return inner

@wrapper
def func1():
    """
    此函数是完成登陆的功能,参数分别是...作用。
    return: 返回值是登陆成功与否(True,False)
    """
    print(666)
    return True

func1()
print(func1.__name__)
print(func1.__doc__) 
执行输出:
666
func1
此函数是完成登陆的功能,参数分别是...作用。
return: 返回值是登陆成功与否(True,False)

四、迭代器

python 一切皆对象, 能被for循环的对象就是可迭代对象。

迭代器: f1文件句柄

 

dir打印该对象的所有操作方法

s = 'python'
print(dir(s))

执行输出:

['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

1.可迭代对象

对象内部含有__iter__方法就是可迭代对象,例如 str、list、dict、tuple、set、range()

 查看某个对象是否是可迭代对象,有2种方式

  •   print('__iter__' in dir(对象))

 

  •   from collections import Iterable

         print(isinstance(对象,Interable))

 

第一种:

s = 'python'
print('__iter__' in dir(s))
执行输出:True

第二种:

from collections import Iterable

l = [1, 2, 3, 4]
print(isinstance(l, Iterable))           # True

2. 迭代器

对象内部含有__iter____next__方法就是迭代器,文件句柄就是迭代器。

 

(1)可迭代对象转化成迭代器:可迭代对象.__iter__() --->迭代器

l1 = [1,2,3]
l1_obj = l1.__iter__()                                   # 迭代器
print(l1_obj) <list_iterator object at 0x000001987D5EB668>            # 表示它是一个列表迭代器对象

(2)判断方法是否是迭代器的方法

①print('__iter__' in dir(对象))

  print('__next__' in dir(对象))

两个都必须是True, 这种方法居多

 

② from collection import Iterator

    print(isinstance(对象,Iterator))

 

方法1:

with open('a', encoding='utf-8') as f1:
    print('__iter__'in dir(f1))
    print('__next__'in dir(f1))

方法2:

from collections import Iterator
print(isinstance(f1,Iterator))         # True

例:

l1 = [1,2,3]
l1_obj = l1.__iter__()              # 转换为迭代器
print('__iter__' in  dir(l1_obj))          #是否含有__iter__方法
print('__next__' in  dir(l1))          #是否含有__next__方法
print('__next__' in  dir(l1_obj)) 
执行输出:从结果中,可以看出l1_obj是同时含有__iter__和__next__的对象,所以它是迭代器
True
False
True 

(3)迭代器使用__next__获取一个值

l1 = [1,2,3]
l1_obj = l1.__iter__()          # 迭代器
print(l1_obj.__next__())         # 获取一个元素
print(l1_obj.__next__())
print(l1_obj.__next__())
print(l1_obj.__next__())

多取了一个,就会报错,因为列表只有3个元素

s1 = 'sadda'
ite1 = iter(s1)
while 1:
    try:
        print(ite1.__next__())

    except StopIteration:
        break

使用while循环,指定用__next__方法遍历列表

l2 = [1, 2, 3, 4, 5, 6, 7, 8]
l2_obj = l2.__iter__()                     #1.将可迭代对象转化成迭代器
while True:
    try:
        i = l2_obj.__next__()                 #内部使用__next__方法取值
        print(i)

    except Exception:                     #运用了异常处理去处理报错
        break 

try里面的代码,出现报错,不会提示红色文字。Exception 可以接收所有报错,表示报错的时候,该怎么处理,这里直接使用breck跳出循环

面试题:使用whlie循环去遍历一个有限对象,直接使用上述代码即可。

 

for循环的内部运行机制

① 将可迭代对象转化为迭代器

② 调用__next__方法取值

③ 利用异常处理机制停止报错

l1 = [1,2,3]
l1_obj = l1.__iter__()  # 转换为迭代器
for i in l1_obj:
    print(i) 

 

执行输出:for循环的内部机制,就是用__next__方法执行的。为什么没有报错呢?它内部有异常处理机制
1
2
3

3.迭代器的好处

1)节省内存空间。迭代器最大的好处好的程序员,会在内存优化方面考虑,比如迭代器。

2)满足惰性机制。

3)不能反复取值,不可逆。不可逆,表示,已经取过的值,不能再次取,它只能取下一个。

 

仅含有__iter__方法的,就是可迭代对象 包含__iter__和__next__方法的,就是迭代器

 

总结:

1.什么是可迭代对象,什么是迭代器

内部含有__iter__方法的对象就叫做可迭代对象

内部必须有__iter__方法和__next__方法的对象,叫做迭代器

 

2.可迭代对象如何转化成迭代器

转化成迭代器:可迭代对象.__iter__() 

例如

l1 = [1,2,3]

l1_obj = l1.__iter__()

 

3.迭代器如何取值 

迭代器使用__next__()方法

五、 生成器

生成器本质上是迭代器,包含__iter__和__next__功能

 

生成器的产生方式:

1,生成器函数构造。

2,生成器推导式构造。

3,数据类型的转化。

 

通过构造生成器函数,就是将函数return变为yield

def func2(x):
    x += 1
    print(111)
    yield x

    x += 1
    print(222)
    yield x

func2(2)                #函数不会执行
g_obj  =func2(3)          #将func2(3)赋值给g_obj,g_obj是个迭代器
g_obj.__next__()          #输出结果是111,一个next对一个一个yield,只有遇到next,函数才会执行
print(g_obj.__next__())    #输出结果是222   \n    5
print(g_obj.__next__())    #超出,将报错
def func1():
    for i in range(10):
        yield i

g=func1()
for i in range(10):
    print(g.__next__())         #打印0-9,一行一个

return 与 yield的区别

① 自定制的区别

② 内存级别的区别

     迭代器是需要可迭代对象进行转化,可迭代对象非常占内存

     生成器是直接转化,从本质上节省内存

 

 next 和send 功能一样,都是执行一次

send 与 next 的区别

① send 与 next 一样,也是对生成器进行取值

② send 可以给上一个yield 传值

③ 第一次取值只能用next

④ 最后一个yield永远得不到send传的值

例1

def func1():
    print(1)
    count = yield 1
    print(count)
    print(2)
    count2 = yield 2
    print(count2)
    print(3)
    count3 = yield 3

g=func1()            # g称作生成器对象。
g.__next__()
g.send('alex')
g.send('hello')
输出结果:
1
alex
2
hello
3

例2

def generator():
    print(123)
    content = yield 1
    print('=======',content)
    print(456)
    yield

g = generator()
ret = g.__next__()            # 123
print('***',ret)            # *** 1
ret = g.send('hello')           #send的效果和next一样   ======= hello        \n    456
print('***',ret)            # *** None
执行输出:
123
*** 1
======= hello
456
*** None

send 获取下一个值的效果和next基本一致, 只是在获取下一个值时,给上一yield的位置传递一个数据

 

使用send的注意事项:第一次使用生成器的时候 是用next获取下一个值。最后一个yield不能接受外部的值

例:比如生产10000套服装

 一个厂商直接生产出10000套了

def func1():
    for i in range(1,10001):
        print('ARMAIN服装%d套' % i)
func1() 
执行输出:
...

ARMAIN服装9998套
ARMAIN服装9999套
ARMAIN服装10000套

第二个厂商,先生产出50套,给老板看

def func1():
    for i in range(1,10001):
        yield 'ARMAIN服装%d套' % i

g = func1()
for i in range(1,51):
    print(g.__next__()) 
执行输出:
...
ARMAIN服装48套
ARMAIN服装49套
ARMAIN服装50套

最终老板只要200套先50套,再150套

def func1():
    for i in range(1,10001):
        yield 'ARMAIN服装%d套' % i

g = func1()
for i in range(1,51):
    print(g.__next__())

#再执行150次,注意,它是从51开始的
for j in range(150):
    print(g.__next__()) 
  • 对于列表而言,for循环是从开始
  • 对于生成器而言,它是有指针的,__next__一次,指针向前一次。它不能从头开始。必须依次执行

 

生成器和迭代器的区别
  迭代器: 有内置方法
  生成器: 开发者自定义

 

问题:什么是生成器?如何写一个生成器?生成器怎么取值?

生成器,即生成一个容器。在Python中,一边循环,一边计算的机制,称为生成器。

生成器示例

def fun1():
    yield 1

生成器使用__next__()方法取值,或者for循环

 

、三元运算

三元运算符就是在赋值变量的时候,可以直接加判断,然后赋值

格式:[on_true] if [expression] else [on_false]

三元运算只适用于简单的if else判断,再多一层if判断就不适用了。

 

举例说明:比大小,大者返回

写一个函数

def max_min(a,b):
    if int(a) > int(b):
        return a
    else:
        return b
print(max_min(1,3)) 

三元运算

def max_min(a,b):
    z = a if a > b else b
    return z 

再进一步简写

def max_min(a,b):
    return a if a > b else b 

、列表生成式

优点:一行代码几乎可以搞定所需要的任何列表

缺点:容易着迷,不易排错,不能超过三个循环

 

  • 用列表推导式能构建的任何列表,用别的都可以构建,比如for循环
  • 列表推导式,最多不超过3个for循环。判断只能用一个

 

1. 循环模式:模板:[经过加工的i for i in 可迭代对象]

使用for循环方法

li = []
for i in range(1,4):
    li.append('还珠格格第'+str(i)+'部')

print(li)                   #['还珠格格第1部', '还珠格格第2部', '还珠格格第3部']

第二种写法

li = []
for i in range(1,4):
    li.append('还珠格格第%s部' % i)

print(li)

上面的代码,可以一行搞定用列表推导式就可以了

li = ['还珠格格第%s部' %i for i in range(1,4)]
print(li)

例:求1~10平方结果

li = [i ** 2 for i in range(1,11)]
print(li) 
执行输出:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

2. 筛选模式:[经过加工的i for i in 可迭代对象 if 条件 筛选] 

30以内所有能被3整除的数
l3 = [i for i in range(1,31) if i % 3 == 0]
print(l3)                                 # [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
30以内所有能被3整除的数的平方
li = [i**2 for i in range(1,31) if i % 3 == 0]
print(li)                                 # [9, 36, 81, 144, 225, 324, 441, 576, 729, 900]
找到嵌套列表中名字含有两个'e'的所有名字
names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry']]
l4 = [name for i in names for name in i if name.count('e') == 2 ]
print(l4)                                 # ['Jefferson', 'Wesley', 'Steven', 'Jennifer']

常用的是列表推导式

 

字典推导式

将一个字典的key和value对调

dict = {'a': 10, 'b': 34}
dict_frequency = {dict[k]: k for k in mcase}
print(dict_frequency) 
执行输出:{10: 'a', 34: 'b'}

相当于

dict = {'a': 10, 'b': 34}
dict_frequency = {}
for k in dict:
    dict_frequency[k]=dict[k]

print(dict_frequency) 
如果Key和value是一样的,不适合上面的代码

集合推导式

计算列表中每个值的平方,自带去重功能

squared = {x**2 for x in [1, -1, 2]}
print(squared)
执行输出:{1, 4}
a = {1,1,4}
print(type(a)) 
执行输出:<class 'set'>  结果是一个集合,它也是用{}表示的。

集合和字典是有区别的:

  有键值对的,是字典,比如{'k1':1,'k1':2}

  没有键值对的,是集合,比如{1,2,3,4}

 

八、生成器表达式

与列表推导式相同,只是将[ ]改为( )

l_obj = ('还珠格格第%s部' % i for i in range(1,4))
print(l_obj)
结果是一个生成器对象:<generator object <genexpr> at 0x000002DDBEBADE60>

取值使用__next__方法

l_obj = ('还珠格格第%s部' % i for i in range(1,4))

print(l_obj.__next__())
print(l_obj.__next__())
print(l_obj.__next__())
输出结果:
还珠格格第1部
还珠格格第2部
还珠格格第3部

列表推导式:一目了然,占内存

生成器表达式: 不便看出,节省内存。

、递归函数

递归函数:在一个函数中,调用这个函数本身。递归的默认最大深度为998。

 

它是执行到多少次时,报错呢加一个计数器。默认递归深度为998

count = 0
def func1():
    global count
    count += 1
    print(count)
    func1()

func1()

递归深度是可以改的

import sys
sys.setrecursionlimit(100000)                #更改默认递归深度
count = 0
def func1():
    global count
    count += 1
    print(count)
    func1() 

func1()

问年龄

def func(n):
    if n == 1:
        return 18
    else:
        return func(n-1) +2

print(func(4))                    # 24

十、匿名函数

匿名函数:lambda 表达式。普通函数有且只有返回值的函数才能用匿名函数进行简化成一行函数。

匿名函数不单独使用,一般和内置函数结合使用。内置函数中,可加入函数的有min、max、sorted、map、filter

 

关于匿名函数格式的说明:

  函数名 = lambda 参数 :返回值

  参数可以有多个,用逗号隔开

  匿名函数不管逻辑多复杂,只能写一行,且逻辑执行结束后的内容就是返回值

  返回值和正常的函数一样可以是任意数据类型

 

 

 1.简单使用

返回一个数的平方

使用函数方式
def func1(x):
    return x ** 2 

使用匿名函数一行搞定
func = lambda x:x ** 2
print(func(5))             # 25 

x+y

使用函数方式
def func2(x,y):
    return x + y 

改成匿名函数
fun = lambda x,y:x+y
print(fun(1,3))             #  4

 

2. lambda 函数与内置函数的结合。sorted,map,fiter,max,min,reversed

比较字典值的大小,并输出key的值

dic={'k1': 10, 'k2': 100, 'k3': 30}
print(max(dic, key=lambda x: dic[x]))         # k2

X2

res = map(lambda x:x**2,[1,5,7,4,8])
for i in res:
    print(i) 

打印出大于10的元素

l1 = [1,2,3,11,12,40,20,50,79]
ret = filter(lambda x:x > 10,l1)

for i in ret:
    print(i) 

如果l1列表的数据,有上百万,不能使用列表推导式,非常占用内存。建议使用lamdba,它只占用一行

 

上一篇:3、pytest中文文档--编写断言

下一篇:python每日经典算法题5(基础题)+1(中难题)

赞(0)

共有 条评论 网友评论

验证码: 看不清楚?
    扫一扫关注最新编程教程