名称空间
命名空间(又称“名称空间”): 存放名字的地方 (概念性的东西)。例如:变量x = 1, 1存放在内存中,命名空间就是存放名字x与1绑定关系的地方。
名称空间有3种:
- locals:是函数内的(或者是locals所在的那一层的)名称空间|,包括局部变量和形参
- globals:全局变量
- builtins: 内置模块的名字空间
不同变量的作用域不同就是由这个变量所在的命名空间决定的
作用域即范围:
1. 全局范围: 全局存活,全局有效
2. 局部范围: 临时存活, 局部有效
查看作用域方法:
globals() locals()
作用域的查找顺序: LEGB
L: locals E: enclosing (相邻的) G:globals B: builtins
闭包:
def func(): n = 10 def func2(): print('func2:', n) return func2 # 没有执行func2, 只是把func2的函数名(func2的内存地址) f = func() #执行func函数, 此时func()得到的结果是func2的内存地址,即f就是func2的内存地址 f() # 执行f, 由于f就是func2的内存地址,此时执行的就是func2函数 # 输出结果: # func2: 10 # 在函数(func)外部执行了函数内部的子函数(func2),而且子函数(func2)还能够调用其父级函数(func)作用域里面所有的值。 这种现象就是闭包
装饰器:
软件开发“开放-封闭” 原则:已经实现的功能代码不允许被修改,但可以被扩展。
另外,不要改变别人函数的调用方式
现在公司有一个网站模块功能如下:
def home(): print('---------主页----------') def america(): print('--------欧美专区-------') def japan(): print('--------日韩专区-------')
现在需要在america 和 japan这两个板块前加上以下登录认证功能:
login_status = False def login(): info = ['neo', 'abc123'] # 储存的用户信息 global login_status if not login_status: name_input = input('用户名:') password_input = input('密码:') if name_input == info[0] and password_input == info[1]: print('登陆成功') login_status = True else: print('密码错误') exit() else: print('用户已登录,通过检测')
现有如下几种方法可选:
1. 直接在America和Japan模块里面加上login认证程序, 如:
def japan(): login() # 把自己写的认证程序加到别人写好的模块里面 print('--------日韩专区-------')
这种方式违反“封闭”原则,pass。
2. 把america和japan这两个函数名当做参数传到login函数里面,如:
login_status = False def login(func): info = ['neo', 'abc123'] # 储存的用户信息 global login_status if login_status == False: name_input = input('用户名:') password_input = input('密码:') if name_input == info[0] and password_input == info[1]: print('登陆成功') login_status = True else: print('密码错误') exit() if login_status == True: print('用户已登录,通过检测') func() #把需要认证的模块函数名传进来调用执行 def home(): print('---------主页----------') def america(): print('--------欧美专区-------') def japan(): print('--------日韩专区-------') login(japan) #需要认证时,就把函数名当做参数变量传给login函数
这种方式改变了原模块(如japan())的调用方式
3. 装饰器:
login_status = False def login(func): def inner(): info = ['neo', 'abc123'] # 储存的用户信息 global login_status if login_status == False: name_input = input('用户名:') password_input = input('密码:') if name_input == info[0] and password_input == info[1]: print('登陆成功') login_status = True else: print('密码错误') exit() if login_status == True: print('用户已登录,通过检测') func() #把需要认证的模块函数名传进来调用执行 return inner #login()执行的时候,得到的结果只是 把inner的函数名(内存地址)返回给login(func) def home(): print('---------主页----------') def america(): print('--------欧美专区-------') def japan(): print('--------日韩专区-------') japan = login(japan) # login(japan)的执行结果是得到了inner函数的内存地址,再赋值给前面的japan 变量后,变量japan就是inner函数的内存地址。 japan() # 此时执行japan函数其实执行的里面的inner函数,由于闭包的父级函数执行完后里面的变量内存并不会释放、子函数inner里面能够使用login函数里面的所有参数变量,所以inner里面的变量func执行的时候会向login函数调用其传入的先前的japan函数。 这就是装饰器的原理。 #装饰器的正规写法: @login def japan(): print('---------日韩专区--------') # 1.两个函数组成一个闭包,装饰函数做父级,被装饰的函数放在子级函数里面,子级函数名return给父级 # 2. 被修饰的函数名作为参数传给父级函数 # 3.子级函数里面包括装饰代码(如:登录认证)和 被装饰的函数(如:japan())
附: 装饰器参数传递
利用非固定参数 *args和 **kwargs 传不固定个数的参数
login_status = False def login(func): def inner(*args, **kwargs): info = ['neo', 'abc123'] # 储存的用户信息 global login_status if login_status == False: name_input = input('用户名:') password_input = input('密码:') if name_input == info[0] and password_input == info[1]: print('登陆成功') login_status = True else: print('密码错误') exit() if login_status == True: print('用户已登录,通过检测') func(*args, **kwargs) #把需要认证的模块函数名传进来调用执行 return inner #login()执行的时候,得到的结果只是 把inner的函数名(内存地址)返回给login(func) def home(): print('---------主页----------') @login def america(x,y,z): print('--------欧美专区-------',x,y,z) @login def japan(sytle): # 由于inner中的形参是非固定参数*args和**kwargs,所以传给inner函数多少个变量都行 print('--------日韩专区-------',style) japan('3p'’) # 此时japan(‘3p’)执行的是inner函数,由于原先的japan函数里面需要传一个变量,这个japan函数也需要传一个变量。变量传的顺序是: 新生成的这个japan函数 ---> inner函数 ---> inner里面的func函数(此时的func函数是原先的japan函数) ---> 原先的japan函数
带参数的装饰器:
login_status = False def login(style): # 由于装饰器带参数,这里需要加一个形参 def outer(func): #这一层需要传入被装饰的函数名并利用return调用里面的inner函数 def inner(*args, **kwargs): info = ['neo', 'abc123'] # 储存的用户信息 global login_status if login_status == False: name_input = input('用户名:') password_input = input('密码:') if name_input == info[0] and password_input == info[1]: print('登陆成功') login_status = True else: print('密码错误') exit() if login_status == True: print('用户已登录,通过检测') func(*args, **kwargs) return inner #把inner的内存地址返回给outer函数 return outer #把outer的内存地址返回给login函数 def home(): print('---------主页----------') @login('weixin') def america(x,y,z): print('--------欧美专区-------',x,y,z) @login('qq') # 这句话可分解成两步理解: 1. 先是运行login('qq'),此时得到的结果是 outer函数的内存地址 2. login('qq')变成outer的内存地址后,就变成了不带参数的装饰器的情况(例如前面分析的那一种) def japan(style): print('--------日韩专区-------') japan('3p') #执行装饰器# 注:即使是多层闭包,最里层的变量也可以去它的爷爷级去调用参数。 哪怕是多层闭包,在程序彻底运行完前,参数变量的内存地址也不会释放,还可以被里面的各层函数调用。
生成器:
现有需求: 给列表 [0,1,2,3,4,5,6,7,8,9] 中的每个值加1
可以利用如下代码实现:
a = [0,1,2,3,4,5,6,7,8,9]print(list(map(lambda x:x+1,a)))
还有一种新写法:
a = [0,1,2,3,4,5,6,7,8,9] a = [ i+1 for i in range(10)] # 这种写法就叫列表生成式 print(a) # 输出结果: # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
列表生成式语法:
[ 生成规则(三元运算) for i in 原先的列表 ]
如:
a = [0,1,2,3,4,5,6,7,8,9] a = [ i if i<5 else i*i for i in range(10)] # for 后面可以循环任何iterables, 如字典、列表、字符串等, 通过前面的运算把结果放到一个列表(或元祖)里面,但不能放到字典里面 print(a) # 输出结果: # [0, 1, 2, 3, 4, 25, 36, 49, 64, 81]
a = {'neo':'abc','alex':123} b = [a[i] for i in a ] print(b) # 输出结果: # ['abc', 123]
生成器(generator):
列表生成式:
a = [ i for i in range(10)]
我们把上面的列表生成式改动一下:
a = ( i for i in range(10)) # 把列表符号[]改成括号() # 利用这种方式就变成了一个生成器print(a)# 输出结果:# <generator object <genexpr> at 0x0000001885E70EB8> # 打印结果显示a是一个生成器(generator)的内存地址, 此时a里面没有任何东西,它只是一个算法,但它已经有了生成数据的能力,让a产生数据的方式是利用next()去调用aprint(next(a)) # 输出结果为: 0
print(next(a)) # 输出结果为: 1
print(next(a)) # 输出结果为: 2
print(next(a)) # 输出结果为: 3
print(next(a)) # 输出结果为: 4
print(next(a)) # 输出结果为: 5
print(next(a)) # 输出结果为: 6
print(next(a)) # 输出结果为: 7
print(next(a)) # 输出结果为: 8
print(next(a)) # 输出结果为: 9 # 每利用next()调用一次a,a都会向下产生一个数据,并且不能后退。每生产完一个数据,生成器就停在了这个位置,下次再调用生产时,它会接着前一次的数据继续向下一个生产。所以,每个数据只能生产一次。
print(next(a)) # 输出结果为: 报错,并显示“StopIteration” # 所以 利用next的方式在调用完生成器里面的数据后会报错
生成器的特性:
1. 我想要什么数据它不会立即产生,我取一次它才创建一次
2. 生成器只能往前走(往下一个生产),不能往回退,原先生产过的值不可能再生产一次; 并且, 生产完数据后再利用next调用会报错
调用生成器一种是利用next(),还可以利用for循环调用。 例如:
a = ( i for i in range(10)) for i in a: # a为生成器内存地址 print(i) # 输出结果: # 0 # 1 # 2 # 3 # 4 # 5 # 6 # 7 # 8 # 9 # 利用for循环取生成器里面的地址,取完之后也不会报错,所以通常利用这种方式。
另外,next()可以手动调用生成器,也可以利用while调用,如:
a = ( i for i in range(5)) while True: print(next(a)) # 0 # 1 # 2 # 3 # 4 # 报错,StopIteration # 注: next()调用生成器有两种方式: 1. 手动一个个调用; 2. 利用while调用。 这两种方式的本质都是在利用next(),所以调用完之后都会报错。
range() 底层就是用生成器实现的 (Python3中)
Python2中 range(10)直接就是个列表(立即生成)
a = range(10) print(a) # 输出结果: # [0,1,2,3,4,5,6,7,8,9] #在Python2中range()直接就是列表, 但在Python3中,range()只是一个生成器,一种算法,根本没有创建列表# 在Python2中,xrange()的功能就相当于Python3中的range()# Python3中已经没有xrange()了
利用函数实现斐波那契数列(前两数为1,后面的数等于前面的两个数之和):
# 需求: 打印斐波那契数列的第10个值: def fib(max): a = 0 b = 1 count = 0 while count < max: print(b) a,b = b,a+b # 这种写法要注意: 此时b赋值给a,是把原先b的值赋值给a;a+b赋值给b,是把原先a的值和原先b的值赋给b count += 1 fib(10) # 输出结果: # 1 # 1 # 2 # 3 # 5 # 8 # 13 # 21 # 34 # 55
上面的fib()函数稍作修改就能变成生成器:
def fib(max): a = 0 b = 1 count = 0 while count < max: yield b # 只要加了yield,函数就会变成生成器。并且yield后面的对象(如:b)会return给调用fiber(max)函数的对象(例如:fiber(10)),只有在利用next()或for循环调用生成器的时候,fib()函数里面的代码才会一次次执行(就是说 不调用生成器的话,程序就永远冻结在了yield这一步,只有调用生成器(如next)它才会继续执行)。 a,b = b,a+b count += 1 f = fib(10) # 由于fib()函数里面有yield,所以fib(10)执行fib(max)函数时,函数内部的代码并没有任何的执行,只是把这个函数变成了一个生成器。 # 此时fib(10)的打印结果是:<generator object fib at 0x000000E6605D0EB8> # 需要把生成器的内存地址赋值给一个变量 print(next(f)) # 输出结果: 1 print(next(f)) # 输出结果: 1 print(next(f)) # 输出结果: 2 print(next(f)) # 输出结果: 3 print(next(f)) # 输出结果: 5 print(next(f)) # 输出结果: 8 print(next(f)) # 输出结果: 13 print(next(f)) # 输出结果: 21 print(next(f)) # 输出结果: 34 print(next(f)) # 输出结果: 55
总结1:
1. 原先函数只能在里面执行,你要是想要里面的执行结果只能return,一return整个函数就停止执行了;但生成器可以把函数里面执行的每一个值、每一个结果返回出来
2. 只要函数内部有yield, 函数名加()后,函数内部的代码根本不执行,只是生成了一个生成器对象
总结2:
生成器的创建方法:
1. 类似列表生成式的方法(但不是列表)
2. 函数内带有yield
这两种创建方式的区别: 类似列表生成式的方法最复杂只能处理一个三元运算
总结3:
Python2中: range == list xrange == 生成器 Python3中: range == 生成器 xrange 不存在
总结4: return vs yield
return: 返回数据,并终止函数
yield: 返回数据,并冻结函数当前的执行过程; 如果想要继续执行生成器,只能利用类似next()的方法再次唤醒程序。
def fib(max): a = 0 b = 1 count = 0 while count < max: yield b a,b = b,a+b count += 1 f = fib(10) for i in f: print(i)
next() 的作用: 唤醒被冻结(如yield)的函数执行过程,直到遇到下一个yield。
注: 类似列表生成式的方法底层也是yield函数
循环读取文件的原理也是生成器:
f = open('test.txt','r') for i in f: #在循环f的时候就是在循环一个生成器 print(i) # 循环一边就读取一行
注: next(生成器)的另一种写法: 生成器.__next__() (next两个各有两个下划线)
只要函数里面有yield,它就会变成一个生成器,遇到return生成器就会终止,生成器就会报错:
def range2(n): count=0 while count < n: yield count count += 1 new_range = range2(3) print(next(new_range)) print(next(new_range)) print(next(new_range)) print(next(new_range)) # 输出结果: # 0 # 1 # 2 # 报错, stopiteration # 上面生成器中假如了return: def range2(n): return 33333 count=0 while count < n: yield count count += 1 new_range = range2(3) print(next(new_range)) # 输出结果: # 报错,StopIteration: 33333 # return让生成器终止执行,就会报错
所以,函数有了yield之后:
1. 函数名加()就得到了生成器
2. return在生成器里,代表生成器的终止,就会报错
生成器的send方法:
可以利用 “ 生成器.send( '指令 ' ) ” 的方法给生成器内部发送指令, 发送的指令是发送到了 yield那一句。
def range2(n): count=0 while count < n: sign = yield count # send的指令发送给yield语句 print('-----检测-----',sign) count += 1 new_range = range2(5) print(next(new_range)) print(next(new_range)) print(new_range.send('test')) # 输出结果: # 0 # -----检测----- None # 1 # -----检测----- test # 2# 注: 1. send发送的括号里面的内容不能为空# 2. can't send non-None value to a just-started generator (大致意思就是说,你要是想用send方法调用生成器的第一次执行,send括号里面的内容必须为“None”(生成器起始的第1次并没有yield的等待)# 3. 可以这么理解: next()这种调用方式的底层其实就是调用的send方法,只不过send的内容为“None”# 4. next()只能唤醒程序执行,发送给生成器内部的指令只能是“None”; 而send不但可以唤醒生成器继续执行,而且可以给生成器内部发送任何指令
迭代器:
可直接作用于for循环的数据类型有:
1. 集合数据类型,如 list、tuple、dict、set、str等
2. generator,包括生成器和带yield的generator function
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用 isinstance()判断一个对象是不是Iterable对象:
from collections import Iterable print(isinstance(['a','b','c'],Iterable)) # 输出结果: # True from collections import Iterable print(isinstance((i for i in range(10)),Iterable)) # 输出结果: # True
定义:可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator (生成器只是迭代器的一种)
可以用 isinstance()判断一个对象是不是Iterator对象:
from collections import Iterator print(isinstance(['a','b','c'],Iterator)) # 输出结果: # False from collections import Iterator print(isinstance((i for i in range(10)),Iterator)) # 输出结果: # True
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、str、dict等Iterable变成Iterator可以使用iter()函数:
from collections import Iterator print(isinstance(iter(['a','b','c']),Iterator)) # 输出结果: # True a = ['a','b','c'] b = iter(a) print(b) print(a) print(next(b)) print(next(b)) # 输出结果: # <list_iterator object at 0x0000004C9B7BA080> # 生成器内存地址 # ['a', 'b', 'c'] # a # b # 可利用next()函数调用该生成器
注: Python3中的for循环本质上就是通过不断调用next()函数实现的
来源:https://www.cnblogs.com/neozheng/p/8366725.html