今天继续函数的讲解:
目录:
1.函数对象
2.函数嵌套
3.名称空间和作用域
4.闭包
5.装饰器
6.迭代器
7.生成器
8.内置函数
第一部分:函数对象
在python中,一切皆对象,想int,str,list,dict,tuple等等,所以函数也不例外,对象都具有属性。作为对象,它可以赋值给其他函数名,也可以当作参数传递给其他函数。下面给一个例子:
1 def foo(): 2 print('hello,world') 3 4 #foo 这只是一个函数名字,该名字指向foo()函数的内存地址 5 6 bar=foo #将foo()函数的地址赋值给bar,则bar也指向foo()函数的地址 7 8 bar() 9 10 结果: 11 hello,world
再看一个例子,这个例子将函数作为参数传递给其他函数
1 def test(func): 2 func() #执行func函数。 3 4 def hello(): 5 print('hello,world') 6 7 test(hello) #将函数作为参数传递个test函数, 8 9 结果: 10 hello,world
第二部分:函数嵌套:
函数的嵌套就是在一个函数内部再定义一个或多个函数,并在调用外部函数的时候,外部函数接着调用内部的函数。类似下面的样子:
1 def fun1(): #定义一个函数 2 a='txt' 3 def fun2(): #在原来的函数内再定义一个函数, 4 print(a) 5 fun2() 6 7 fun1() 8 9 10 结果: 11 txt
先了解上面的定义方式,下面会用到函数嵌套的知识。
第三部分:名称空间和作用域
1.名称空间(namespace)
名称空间就是变量名字与值的绑定关系。python中有三类名称空间:局部名称空间/全局名称空间/内置名称空间,下面对他们进行解释:
局部名称空间:每个函数或类都有的自己的名称空间,这个自己的名称空间就叫局部名称空间,它随着函数的执行和类的实例的调用而产生,在函数结束或类实例消亡后被删除。它包括了函数的参数和定义的变量。
全局名称空间:每个模块拥有自己的名称空间,这个叫做全局名称空间,它记录了模块的变量,包括函数/类/其他导入的模块/模块级别的变量和常量
内置名称空间:催着python解释器运行而创建的产生的名称空间,所有模块都可以访问,它存放内置的函数和一些异常。
1 x=5 2 y=10 3 def change_x(): 4 x=10 #在局部名称空间创建变量x 5 def print_xy(): 6 print(x,y) #首先在print_x的局部名称空间找x,找不到的话从上层名称空间查找.从局部名称空间找y,找不到到全局名称空间找, 7 print_xy() 8 9 change_x() 10 print(x,y) #打印全局名称空间的x,从这里看出局部名称空间的x并没有覆盖全局名称空间中的x 11 12 #结果 13 10,10 14 5,10
从上面的例子,我们也可以得到在名称空间中查找变量的顺序:
1.现在局部名称空间查找变量,若找到则使用该变量,停止搜索;若未找到,则到上一层的局部名称空间查找。
2.在局部名称空间找不到的情况下,到全局名称空间查找,如果找到则使用,放弃搜索;若未找到,则到内置命名空间查找
2.作用域:
作用域分两类:
全局作用域:对应内置名称空间和全局名称空间
局部作用域:对应局部名称空间
上面已经说过,变量名的查找顺序为:局部名称空间->全局名称空间->内置名称空间
我们可以通过两个内置函数查看作用域的变量:globals() 和locals(),其中globals()可以查看全局作用域中的变量,locals()可以查看局部作用域中的变量。下面一个例子:
1 x=1000 2 print(globals()) 3 4 结果: 5 {'__file__': 'D:/PycharmProjects/untitled8/user.py', 6 '__cached__': None, 7 '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000000009D6CC0>, 8 '__doc__': None, 9 '__name__': '__main__', 10 '__package__': None, 11 '__spec__': None, 12 '__builtins__': <module 'builtins' (built-in)>, #这里就是内置名称空间的变量的引用 13 'x': 1000 #我们在全局名称空间声明的变量x也可以看到 14 }
下面看一个locals()的例子:
1 x=1000 2 def foo(): 3 x=100 4 print(locals()) 5 6 foo() 7 8 #结果 9 {'x': 100} #这里只有一个局部名称空间中声明的变量
在全局作用域声明的变量在全局有效,在任何位置都可以访问到,除非使用del删除,否则一直存活,知道文件执行完毕即python解释器退出;
在局部作用域声明的变量在局部有效,只能在局部范围内访问,只在函数调时的后有效,函数调用结束后失效
第四部分:闭包
闭包的定义:内部函数包含外部作用域而非对全局作用域的引用,该内部函数就是闭包:
1 x=1000 #声明一个全局变量 2 def f1(): 3 x=10 #声明一个局部变量 4 def f2(): #定义一个闭包函数 5 print(x) 6 return f2 #返回f2函数的内存地址 7 8 f=f1() #将f1函数的执行结果赋值给f.即f和f1都指向同一个内存地址 9 print(f) 10 f() #调用函数f,间接调用函数f2() 11 12 #结果 13 <function f1.<locals>.f2 at 0x00000000006CE1E0> #从此处可以看到f1的内部函数f2的内存地址,也是f指向的地址 14 10
包含__closure__方法的对象就成为闭包对象,我们通过下面的例子认识下:
1 x=2 2 def f1(): 3 y=2 4 def f2(): 5 print(x,y) 6 return f2 7 f=f1() 8 f() 9 print(f) #返回f指向的内容,即f2()函数的内存地址 10 print(f.__closure__) 11 print(dir(f.__closure__[0])) #__closure__ 返回闭包的指针 12 print(f.__closure__[0].cell_contents) #cell_contents 返回闭包的结果 13 14 #结果 15 2 2 16 <function f1.<locals>.f2 at 0x00000000007DE1E0> 17 (<cell at 0x00000000007A5D38: int object at 0x0000000052F301F0>,) 18 ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__','__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', 20 '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__','__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', 22 '__subclasshook__', 'cell_contents'] 23 2
第五部分:装饰器
装饰器的概念:装饰其他的函数,修饰添加功能,返回一个增强版本的函数。装饰器本身可以是任何可调用的对象,被装饰的对象是任何可调用的对象。
看下面的例子,我们已经开发了一个函数,现在想添加一个功能,在调用函数的时候,打印函数运行的时间。完成的前提是不能修改我们之前写好的函数,如下:
1 import time 2 def timer(fun): #将一个函数作为参数传递进来 3 def wrapper(): #闭包函数wrapper 4 start_time=time.time() 5 res=fun() #调用函数,并获取返回值 6 end_time=time.time() 7 print('%s' % (end_time-start_time)) 8 return res #将返回值返回 9 return wrapper 10 11 @timer # 这句的功能类似这样 index=wrapper(index) 12 def index(): 13 time.sleep(3) 14 print('welcome') 15 return 1 16 17 res=index() 18 print(res) 19 20 21 结果: 22 welcome 23 3.010805368423462 24 1
上面是一个没有参数的装饰器的应用,在原函数 ”index()“ 代码没有变动的情况下,实现了计算时间的功能,并且不影响对 index() 的函数的调用,非常方便。看了上面的例子,我们可以得出为什么会使用装饰器的原因:1,开放封闭原则:对程序的修改是封闭的,对程序的扩展是开放的;2,装饰器就是为了在不修改被装饰对象的源代码以及调用当时的前提下,为其添加新功能。
当装饰器需要参数时,必须使用第二季封装,再来看下面的一个例子有参数的装饰器,该例子为原来的函数添加认证的功能:
1 def check_user(*args,**kwargs): #从db文件中获取用户名/密码,如果正确,则返回True,否则返回False 2 res=[] 3 with open('db','r',encoding='utf-8') as f: 4 res=f.read().split() 5 for i in res: 6 if args[0]==i.split(',')[0] and args[1] == i.split(',')[1]: 7 return True 8 else: 9 return False 10 11 12 def check_auth(driver='file'): #装饰器成了三层,因为在调用装饰器函数的时候有传递参数,所以需要在原来两层装饰器的基础上在加一层 13 def _check_auth(func): #闭包函数 14 def __check_auth(*args,**kwargs): #闭包函数,如果原来函数接受参数,则将参数传递到闭包函数中,*args,**kwargs会从全局变量中找。 15 if driver =='file': 16 result=check_user(*args,**kwargs) 17 if result: #如果验证通过,则放行用户 18 res=func(*args,**kwargs) #调用原来的函数。实现页面的显示 19 return res 19 elif driver == 'mysql': 20 print('=========mysql==========') 21 elif driver == 'ldap': #根据装饰器获得的参数进行不同的操作。这里简写了 22 print('==========ldap==========') 23 return __check_auth 24 return _check_auth 25 26 @check_auth('file') #这里的装饰器添加了参数。 27 def index(): 28 print('welcome %s your password is %s' % (name,password)) 29 30 31 name=input('Input username: ').strip() 32 password=input('Input password:').strip() 33 index(name,password) #函数调用处没有改变
第六部分:迭代器
迭代器的概念:重复的过程称为迭代,且每次迭代的结果作为下次的初始值。重点在重复,且本次迭代的结果作为下次迭代的初始值。
我们先来看一个便利数组的例子:
1 a=['a','b','c','d','e'] 2 3 for i in a: 4 print(i) 5 6 结果: 7 a 8 b 9 c 10 d 11 e
上面就是一个迭代的例子,重复读取列表a的内容,并且下次从本次读取的索引的下一个进行取数,并打印。
下面介绍两个概念:可迭代对象,迭代器。
可迭代对象:实现了__iter__()方法的对象成为可迭代对象,向list,tuple,dict
迭代器:实现了__iter__()方法,并且实现了__next__方法的对象就是迭代器。如file
这也就是说迭代器肯定是可迭代对象,而迭代对象不一定是迭代器。
既然已经有可迭代对象用来迭代数据了,那么为什么要有迭代器呢?答案是对于没有索引的数据类型,必须提供一种不依赖于索引的迭代方式,即下面讲到的next()方法调用。
迭代对象通过执行__iter__()方法,就会获得一个迭代器:
1 a=list([1,2,3,4,5]) 2 b=a.__iter__() 3 print(next(b)) #1 4 print(next(b)) #2 5 print(next(b)) #3 6 print(next(b)) #4 7 print(next(b)) #5 8 print(next(b)) #StopIteration
如上:对list执行__iter__()方法,生成一个迭代器,迭代器使用next()获取下一个值,当迭代器中的值取完后,继续取值会触发一个StopIteration的异常。
那么有没有现成的函数说明一个对象是可迭代对象还是一个迭代器,有 collections模块的 Iterable,Iterator 方法:
1 from collections import Iterable,Iterator 2 a=list([1,2,3,4,5]) #a是一个列表 3 print(isinstance(a,Iterable)) #True 4 print(isinstance(a,Iterator)) #False 5 f=open('db',mode='r',encoding='utf-8') #f是一个文件对象 6 print(isinstance(f,Iterator)) #True 7 print(isinstance(f,Iterable)) #True 8 f.close()
其实,迭代器有一些优点和缺点,如下:
优点:1.提供一种不依赖下标的迭代方式
2.就迭代器本身来说,更节省内存,因为一次迭代一个值,不会因为原对象有大量的值而占用大量内存
缺点:1.对于有下标的对象来说(如list,tuple),迭代器只能取一次下标的值,不能取多次同一下标的值,不如序列类型对象取值灵活
2.迭代器无法获取迭代器对象长度
第七部分:生成器
概念:只要函数体包含yield关键字,则该函数就是生成器函数。下面举一个例子:
1 def foo(): 2 print('one') 3 yield 1 4 print('two') 5 yield 2 6 print('three') 7 yield 3 8 print('for') 9 yield 4 10 11 g=foo() 12 next(g) #输出one,卡在 yield 1执行后就是说,打印’one',返回1,然后停止执行 13 print(next(g)) #输出two ,输出返回值2,再次停止
生成器很普通函数的最大区别就是普通函数执行return后函数立即终止了,下次调用函数还是从函数起始部分开始调用,而生成器在执行到yield那里会返回数值,下载再次调用函数,函数从上次停止的地方继续执行。
在看一个好玩的例子:
1 #输出9x9乘法表 2 def get_num(li): 3 for i in li: 4 for j in li: 5 if i!=j: 6 yield i,j 7 def shiqi(): 8 a=['1','2','3','4','5','6','7','8'] 9 new_num=[] 10 11 for i,j in get_num(a): 12 new_num.append(i+j) 13 print(new_num) 14 shiqi()
综上:
yield的功能:
1.为函数封装好__inter__和next方法
2.return只能返回一次值,函数就终止了;而生成器(yield)能返回多次值,每次返回将函数暂停,下次next会从上次暂停位置继续执行
第八部分:内置函数
python的内置函数如下:
1 def check_user(*args,**kwargs): #从db文件中获取用户名/密码,如果正确,则返回True,否则返回False 2 res=[] 3 with open('db','r',encoding='utf-8') as f: 4 res=f.read().split() 5 for i in res: 6 if args[0]==i.split(',')[0] and args[1] == i.split(',')[1]: 7 return True 8 else: 9 return False
next(b)