命名空间(又称“名称空间”): 存放名字的地方 (概念性的东西)
例如:变量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()去调用a
print(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()函数实现的