自学Python第七天- 闭包、装饰器、推导式、迭代器、生成器
闭包
当满足以下三个条件时就形成了闭包:
- 在一个外函数中定义了一个内函数
- 内函数里运用了外函数的临时变量
- 外函数的返回值是内函数的引用
# 闭包
def out(i): # 一个外层函数
def inner(j): # 内层函数
return i*j
return inner
使用时候先调用外层函数,返回结果就是内层函数。
func = out(3) # 此时,func 相当于是函数 inner ,只是实参 3 传入函数形参 i
res = func(4) # 相当于 inner(4),并且形参已经赋值 3
print(res) # 结果 12
闭包较普通函数有以下优点:
- 间接实现在外部使用局部变量,保留下了局部变量的信息
一个函数结束的时候,会把自己的临时变量都释放给内存。通过闭包可以实现保留这些信息并继续使用。 - 构造一些结构清晰、语义简单的函数,提升部分代码和变量的复用性
# 没用闭包
def mypow(x, y):
return x**y
print(mypow(5, 2))
print(mypow(2, 3))
# 使用闭包
def anypow(y):
def inner(x):
return x**y
return inner
square = anypow(2)
cube = anypow(3)
print(square(5))
print(cube(3))
装饰器
装饰器 (@) 是一种语法糖,主要用于在函数或类的基础上添加一些可重用的额外功能。从应用开发的角度来开,使用装饰器来对我们的应用代码进行功能性扩展和包装,以提高代码的可重用性和可维护性。
装饰器可以在不改变函数源代码的情况下,为函数添加新的功能。常用于日志记录、在Web应用中进行登录验证或路由注册、调用跟踪管理及参数验证、性能优化中记录任务执行时间等。
创建装饰器
# 正常的将函数作为参数传递式写法
def print_hw(f):
print('hello world')
return f
def test():
sum_li = sum((12, 3, 4))
print(sum_li)
return sum_li
test2 = print_hw(test) # 函数引用作为实参,即 test2 = f = test
print(test2()) # 相当于 test2() = f() = test()
使用装饰器,可以改成
# 直接改装饰器
def print_hw(f):
print('hello world')
return f
@print_hw # 装饰器
def test():
sum_li = sum((12, 3, 4))
print(sum_li)
return sum_li
print(test()) # 直接调用函数,就可以先调用 print_hw
装饰器函数也可以这样写:
# 通用装饰器
def print_hw(f):
def wrapper(*args, **kwargs):
print('hello world')
return f()
return wrapper
@print_hw # 装饰器
def test():
sum_li = sum((12, 3, 4))
print(sum_li)
return sum_li
print(test()) # 直接调用
由此,可以写成:
def print_hw(func):
def print_in():
print('hello world')
return func()
return print_in
def test():
sum_li = sum((12, 3, 4))
print(sum_li)
return sum_li
test1 = print_hw(test)
print(test1())
这四个例子是等价的。由最后一个列子可以看到,形成了一个闭包。所以装饰器是传递的参数不是变量而是函数的特殊形式的闭包。装饰器的两种写法(第2/3种)的区别与优劣:
- 上一种(示例中第2种写法)结构明晰,传参明确,但是扩展的功能只能在原函数之前(因为原函数定义在 return 里)。
- 下一种(示例中第3种写法)可以在原函数执行完成后增加扩展功能,但是如果是写通用函数时,需注意传参的问题(因为实参传给了
*args
和**kwargs
)
带参数的装饰器
装饰器本身可以附带参数
def a(msg):
def wrapper(func):
def inner(*args, **kwargs):
print('装饰器的参数内容:', msg)
func(*args, **kwargs)
return inner
return wrapper
@a('123')
def b(msg):
print('调用函数的参数内容:', msg)
b(456)
"""
输出结果
装饰器的参数内容: 123
调用函数的参数内容: 456
"""
类作为装饰器使用
class A:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('类装饰器输出的内容')
self.func(*args, **kwargs)
@A
def b(msg):
print('函数的参数内容:', msg)
b('456')
"""
输出结果:
类装饰器输出的内容:
函数的参数内容: 456
"""
带参数的类作为装饰器使用
class A:
def __init__(self, msg):
self.msg = msg
def __call__(self, func):
def wrapper(*args, **kwargs):
print('装饰器的参数内容:', self.msg)
func(*args, **kwargs)
return wrapper
@A('123')
def b(msg):
print('函数的参数内容:', msg)
b('456')
"""
输出结果:
装饰器的参数内容: 123
函数的参数内容: 456
"""
装饰器叠加
装饰器可以叠加使用。叠加使用时,会将被叠加的装饰器及原函数作为统一的一个函数,被外层的装饰器调用
# 装饰器的嵌套使用
def decorator_outer(func): # 外部装饰器
def wrapper(*args, **kwargs): # 通用不定长参数
print('--外部装饰器开始--')
res = func(*args, **kwargs)
print('--外部装饰器结束--')
return res
return wrapper
def decorator_inner(func): # 内部装饰器
def wrapper(*args, **kwargs):
print('--内部装饰器开始--')
res = func(*args, **kwargs)
print('--内部装饰器结束__')
return res
return wrapper
@decorator_outer
@decorator_inner
def orig_func(a):
print(f'这是原函数,参数是{a}')
return a * 3
print(f'返回结果为:{orig_func(5)}')
# 执行结果
# --外部装饰器开始--
# --内部装饰器开始--
# 这是原函数,参数是5
# --内部装饰器结束__
# --外部装饰器结束--
# 返回结果为:15
装饰器常用于将现有的函数增加功能,而不改变函数使用方式和代码。例如增加时间记录、日志记录等。如果在原有函数内部更改,可能会有混乱,写新的函数在新函数内部调用旧函数,则需要在所有调用的地方更改调用函数名。使用装饰器则可以避免这些问题。
推导式
推导式(又称为解析式)是 python 的一种特有的语法,可以从一个数据序列构建另一个新的数据序列的结构体。其主结构是一个简单的循环判断语句。Python 支持各种数据结构的推导式:
- 列表(list)推导式
- 字典(dict)推导式
- 集合(set)推导式
- 元组(tuple)推导式
列表推导式
列表推导式的格式为:
- [表达式 for 变量 in 列表]
- [表达式 for 变量 in 列表 if 条件]
- [表达式 for 可迭代对象 in 矩阵 for 变量 in 可迭代对象]
- [表达式 for 变量1 in 列表1 for 变量2 in 列表2]
>>> names = ['Bob','Tom','alice','Jerry','Wendy','Smith']
>>> new_names = [name.upper() for name in names if len(name)>3]
>>> print(new_names)
['ALICE', 'JERRY', 'WENDY', 'SMITH']
>>> multiples = [i for i in range(30) if i % 3 == 0]
>>> print(multiples)
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
li = [i+j for i in '123' for j in 'abc']
table1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
li = [i*i for row in table1 for i in row]
列表推导式的嵌套没有数量限制,但是嵌套越多复杂性越高,使用时需注意。
字典推导式
字典推导式的格式为
- { 键表达式 : 值表达式 for 变量 in 集合 }
- { 键表达式 : 值表达式 for 变量 in 集合 if 条件 }
listdemo = ['Google','Runoob', 'Taobao']
# 将列表中各字符串值为键,各字符串的长度为值,组成键值对
>>> newdict = {key:len(key) for key in listdemo}
>>> newdict
{'Google': 6, 'Runoob': 6, 'Taobao': 6}
>>> dic = {x: x**2 for x in (2, 4, 6)}
>>> dic
{2: 4, 4: 16, 6: 36}
>>> type(dic)
<class 'dict'>
dic = {k:v for k,v in i.items()}
集合推导式
集合推导式的格式为:
- { 表达式 for 元素 in 序列 }
- { 表达式 for 元素 in 序列 if 条件 }
>>> setnew = {i**2 for i in (1,2,3)}
>>> setnew
{1, 4, 9}
>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'d', 'r'}
>>> type(a)
<class 'set'>
元组推导式
元组本身没有推导式,实际上是生成器推导式,但是可以将其转为元组。
元组推导式可以利用 range 区间、元组、列表、字典和集合等数据类型,快速生成一个满足指定需求的元组。其格式为:
- ( 表达式 for 元素 in 序列 )
- ( 表达式 for 元素 in 序列 if 条件 )
>>> a = (x for x in range(1,10))
>>> a
<generator object <genexpr> at 0x7faf6ee20a50> # 返回的是生成器对象
>>> tuple(a) # 使用 tuple() 函数,可以直接将生成器对象转换成元组
(1, 2, 3, 4, 5, 6, 7, 8, 9)
需要注意的是元组推导式返回的结果是一个生成器对象,需要转换类型使用。
迭代器
迭代是Python最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。字符串,列表或元组对象都可用于创建迭代器。
需要注意的是,迭代器对象因为不会后退,所以只能遍历1次。当该迭代器对象遍历完成后就无法重新使用了。可以说迭代器对象是一次性的。
可迭代对象和迭代器
迭代器是一个可迭代对象,可迭代对象不一定是迭代器,可以是定义了迭代器的对象。使用 for in 遍历对象必须是可迭代对象,也就是定义了迭代器的对象。
迭代器的使用
迭代器有两个基本的方法:iter()
和 next()
,分别用于创建迭代器对象和调用迭代器的下一个元素:
>>> list=[1,2,3,4]
>>> it = iter(list) # 创建迭代器对象 it
>>> print (next(it)) # 输出迭代器的下一个元素
1
>>> print (next(it))
2
迭代器对象可以使用常规for语句进行遍历:
list=[1,2,3,4]
it = iter(list) # 创建迭代器对象
for x in it:
print (x, end=" ")
# 执行结果:
# 1 2 3 4
迭代器使用 next()
函数遍历,并通过 StopIteration 结束遍历。
import sys # 引入 sys 模块
list=[1,2,3,4]
it = iter(list) # 创建迭代器对象
while True:
try:
print (next(it))
except StopIteration: # 完成遍历
sys.exit()
自定义对象创建迭代器
把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__()
与 __next__()
。__iter__()
方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__()
方法并通过 StopIteration 异常标识迭代的完成。__next__()
方法会返回下一个迭代器对象。
class MyNumbers:
def __iter__(self):
self.a = 1
return self # 必须要有返回值,可迭代对象
def __next__(self):
x = self.a
self.a += 1
return x
myclass = MyNumbers()
myiter = iter(myclass)
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
# 执行结果:
# 1
# 2
# 3
# 4
# 5
StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__()
方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。
class MyNumbers:
def __iter__(self):
self.a = 1
return self
def __next__(self):
if self.a <= 20:
x = self.a
self.a += 1
return x
else:
raise StopIteration
myclass = MyNumbers()
myiter = iter(myclass)
for x in myiter:
print(x,end=' ')
# 执行结果:
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
可迭代对象的循环遍历
python 中遍历循环实际上就是循环使用可遍历对象的迭代器,可迭代对象只是告诉我们支持循环,真正在循环中取值的是迭代器。
# 遍历循环的本质
nums = iter([1, 2, 3, 4]) # 创建一个迭代器
while True:
try:
print(nums.__next__())
except StopIteration:
break
虽然列表、字符串等对象没有 __iter__()
和 __next__()
方法,但是使用 for
进行循环遍历时,在底层会直接遍历其转换的迭代器对象(因为迭代器对象是一次性的,所以每次遍历时会新生成一个迭代器对象)。
生成器
在 Python 中,使用了 yield 的函数被称为生成器(generator)。跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。调用一个生成器函数,返回的是一个迭代器对象。
创建生成器
import sys
def fibonacci(n): # 生成器函数 - 斐波那契
a, b, counter = 0, 1, 0
while True:
if (counter > n):
return
yield a # 暂停,并返回 yield 的值 a
a, b = b, a + b
counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
while True:
try:
print (next(f), end=" ")
except StopIteration:
sys.exit()
# 执行结果:
# 0 1 1 2 3 5 8 13 21 34 55
元组推导式的方式能够生成一个生成器对象,所以可以用此方法快速的创建生成器对象。当其中的元素特别巨大时,能够快速的生成,并且不占用存储空间。
myList = [i for i in range(99999999)] # 执行后立即生成列表对象,耗时长,立即占用大量空间
myGenerator = (i for i in range(99999999)) # 生成一个生成器对象,不占空间和时间,可以遍历,但是不支持下标索引
生成器的 send()
方法和 close()
方法
生成器有2个默认方法 send()
用来发送信息给生成器, close()
用来关闭生成器
def my_range(num):
i = 0
while i < num:
value = yield i # 每次生成器运行时,执行 yield i 时停止,下次执行才会进行赋值运算
print('value = ', value, end = ' ---- ')
i += 1
my_ = my_range(5)
print(next(my_))
print(next(my_))
print(my_.send('执行send方法')) # 执行 yield,并发送数据
my_.close() # 关闭生成器对象,再进行迭代会有 StopIteration 错误
"""
执行结果:
0
value = None ---- 1 # yield 本身并没有值,所以是 None
value = 执行send方法 ---- 2 # send()方法发送了数据给 yield,可以赋到值了
"""
协程
协程因其特征就是执行到特殊操作时(通常是耗时的IO操作)暂停当前操作转而执行其他操作,这种机制就是一个特殊的生成器。具体的可以参考协程。