一、函数初识 函数的产生:函数就是封装一个功能的代码片段。 复制代码 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') {'name': 'alex'} “ * "的魔性作用 (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__': , '__file__': 'C:/Users/Administrator/houseinfo/test.py', '__cached__': None, 'li': ['spring', 'summer', 'autumn', 'winter'], 'func': } func {'b': 2, 'a': 1} func1 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x011CC410>, '__spec__': None, '__annotations__': {}, '__builtins__': , '__file__': 'C:/Users/Administrator/houseinfo/test.py', '__cached__': None, 'li': ['spring', 'summer', 'autumn', 'winter'], 'func': } 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 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)         # 复制代码 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 (,) 例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 (,) 只要引用了外层变量至少一次,非全局的,它就是闭包 例4:判断下面的函数,是一个闭包吗?****** 复制代码 name = 'summer' def wraaper2(n):        #相当于n = 'summer'   def inner(): print(n) inner() print(inner.__closure__) wraaper2(name) 复制代码 结果输出: summer (,) 它也是一个闭包. 虽然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) # .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函数 复制代码 输出结果: .inn