一、迭代器
1、迭代器简介
迭代操作是访问集合元素的一种方式,是 Python最强大的功能之一。
迭代器是用来迭代取值的工具,是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
1.1 可迭代对象(Iterable)
通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串,列表,元组。
对于没有索引的字典、集合等非序列类型,必须找到一种不依赖索引来进行迭代取值的方式。
迭代器提供了一种通用的且不依赖于索引的迭代取值方式的功能。
字符串、列表、元组、字典、集合、打开的文件都是可迭代对象。
1.2 迭代器对象(Iterator)
迭代器对象是内置有 iter和 next方法:
- iter():返回迭代器对象,即得到的仍然是迭代器本身。
- next():返回迭代器中的下一个元素值。
2、创建迭代器
2.1 创建迭代器
list = [1, 2, 3, 4]
# 创建迭代器对象
# it = iter(list)
it = list.__iter__()
print(type(it)) # <class 'list_iterator'>
# 输出迭代器的下一个元素
print(next(it)) # 1
print(next(it)) # 2
print(it.__next__()) # 3
了解了迭代器,就可以不依赖索引迭代取值了。
使用while循环的实现遍历:
import sys # 引入 sys 模块
list = [1, 2, 3, 4]
it = iter(list) # 创建迭代器对象
while True:
try:
print(next(it))
except StopIteration:
# sys.exit()
break
上述 while循环可以使用 for语句遍历简写:in后可以跟任意可迭代对象
list = [1, 2, 3, 4]
it = iter(list) # 创建迭代器对象
for x in it:
print(x, end=" ")
2.2 创建类迭代器
把一个类作为一个迭代器使用时,需要在类中实现两个方法:
__iter__()
与__next__()
。
__iter__()
方法:返回一个特殊的迭代器对象, 这个迭代器对象实现了__next__()
方法并通过 StopIteration 异常标识迭代的完成。__next__()
方法:返回下一个迭代器对象。
Python 的构造函数为__init__()
,它会在对象初始化的时候执行。
**示例代码如下:**创建一个返回数字的迭代器,初始值为 1,逐步递增 1。
class MyClass:
def __iter__(self):
self.a = 1 # 初始化一个变量
return self
def __next__(self):
x = self.a
self.a += 1 # 每次迭代加1
return x
obj = MyClass()
myIter = iter(obj)
print(next(myIter))
print(next(myIter))
print(next(myIter))
print(next(myIter))
**StopIteration:**
StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况。
在__next__()
方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。
示例代码如下:在第 4次迭代后停止执行:
class MyClass2:
def __iter__(self):
self.a = 1
return self
def __next__(self):
if self.a <= 4:
x = self.a
self.a += 1
return x
else:
raise StopIteration # 结束迭代
obj = MyClass2()
myIter = iter(obj)
for x in myIter:
print(x)
二、生成器
1、生成器简介
在 Python 中,使用了
yield 关键字
的函数被称为生成器(generator)。
生成器是一个返回迭代器的函数,只能用于迭代操作,可理解为生成器就是一个自定义迭代器。
yield 能够临时挂起当前函数,记下其上下文(包括局部变量、待决的 try catch 等),将控制权返回给函数调用者。当下一次再调用其所在生成器时,会恢复保存的上下文,继续执行剩下的语句,直到再遇到 yield 或者退出为止。
生成器有一个基本的方法:
- next() :返回迭代器中的下一个元素值。
生成器调用过程:
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值,并在下一次执行 next() 方法时从当前位置继续运行。
1.2 yield 和 return 区别
- 函数一旦遇到 return就结束了,销毁上下文(弹出栈帧),将控制权返回给调用者。
- yield只能在函数内使用
- yield可以保存函数的运行状态,挂起函数,用来返回多次值。值的类型没有限制。
2、创建生成器
示例代码如下:使用 yield 实现斐波那契数列。
# 创建生成器函数实现 - 斐波那契
def fibonacci(n):
# n 代表数列的个数
a, b, counter = 0, 1, 0
while counter <= n:
yield a # 返回
a, b = b, a + b
counter += 1 # 个数加1
# f 是一个迭代器,由生成器返回生成
f = fibonacci(10)
print()
print(type(f)) # <class 'generator'>
print('=====for遍历====')
for value in f:
print(value, end=" ")
print()
f = fibonacci(10)
print('=====while遍历====')
while True:
try:
# 获取迭代器中的下一个元素值
value = next(f)
print(value, end=" ")
except StopIteration:
break
三、装饰器
1、装饰器简介
装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。即装饰器,就是可以让我们拓展一些原有函数没有的功能。
装饰器的使用符合了面向对象编程的开放封闭原则
。
- 对扩展开放:意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
- 对修改封闭:意味着类一旦设计完成,就可以独立工作,而不要对类进行任何修改。
2、创建无参装饰器
2.1 原函数是无参数
假设原函数(被装饰的函数)是无参数的,对其创建一个不带参数的无参装饰器函数。
示例代码如下:
import time
def say1():
print("我是say1, Hello Python")
time.sleep(2)
def say2():
print("我是say2, Hello Python")
time.sleep(2)
# 计时装饰器
def count_time(func):
# 装饰函数名:可以自定义
def wrapper():
t1 = time.time()
func()
print("执行时间为:", time.time() - t1)
return wrapper
if __name__ == '__main__':
say1 = count_time(say1) # 因为装饰器 count_time(say1) 返回的是装饰函数对象 wrapper,
say1() # 执行 say1()就相当于执行wrapper()
print("---------")
say2 = count_time(say2)
say2()
注意:
这里的wrapper装饰函数名是可以自定义的,只要你定义的函数名,跟return的函数名是相同即可。
2.2 原函数有参数
假设原函数(被装饰的函数)是有参数的,对其创建一个带参数的有参装饰器函数。
示例代码如下:
import time
def say1(name):
print("我是%s, Hello Python" % name)
time.sleep(2)
def say2(name):
print("我是%s, Hello Python" % name)
time.sleep(2)
# 计时装饰器
def count_time(func):
# 装饰的函数名:可以自定义
def wrapper(*args, **kwargs):
t1 = time.time()
func(*args, **kwargs)
print("执行时间为:", time.time() - t1)
return wrapper
if __name__ == '__main__':
say1 = count_time(say1) # 因为装饰器 count_time(say1) 返回的是装饰函数对象 wrapper,
say1("say111") # 执行 say1()就相当于执行wrapper()
print("---------")
say2 = count_time(say2)
say2("say222")
注意:
上面装饰器函数wrapper的参数为 *args和 **kwargs,表示可以接受任意参数。
2.2 装饰器的语法糖@
在 Python项目中,难免会看到@符号的代码,这个
@符号
就是装饰器的语法糖。
如果装饰器的语法糖@来实现定义装饰器,我们就可以直接调用这个原函数名。其实底层实现也是创建装饰器函数。本质上没有变化。方便了我们的使用。
(1)原函数是无参数,使用语法糖创建装饰器
import time
# 计时装饰器
def count_time(func):
# 装饰的函数名:可以自定义
def wrapper():
t1 = time.time()
func()
print("执行时间为:", time.time() - t1)
return wrapper
# count_time必须定义在前面
@count_time
def say1():
print("我是say1, Hello Python")
time.sleep(2)
@count_time
def say2():
print("我是say2, Hello Python")
time.sleep(2)
if __name__ == '__main__':
say1() # 使用用语法糖之后,就可以直接调用该函数来执行扩展的装饰功能。
print("---------")
say2()
(2)原函数是有参数,使用语法糖创建装饰器
import time
# 计时装饰器
def count_time(func):
# 装饰的函数名:可以自定义
def wrapper(*args, **kwargs):
t1 = time.time()
func(*args, **kwargs)
print("执行时间为:", time.time() - t1)
return wrapper
# count_time必须定义在前面
@count_time
def say1(name):
print("我是%s, Hello Python" % name)
time.sleep(2)
@count_time
def say2(name):
print("我是%s, Hello Python" % name)
time.sleep(2)
if __name__ == '__main__':
say1("say111") # 使用用语法糖之后,就可以直接调用该函数来执行扩展的装饰功能。
print("---------")
say2("say222")
2、创建有参装饰器
装饰器也是函数,既然是函数,那么就可以进行参数传递。
示例代码如下:假设我们在使用装饰器的时候,传入一些备注的msg信息。
import time
# 计时装饰器
# 参数自行定义,这里使用的是默认值参数。
def count_time_args(msg=None):
def count_time(func):
def wrapper(*args, **kwargs):
t1 = time.time()
func(*args, **kwargs)
print(f"[{msg}]执行时间为:", time.time() - t1)
return wrapper
return count_time
@count_time_args("say1")
def say1(name):
print("我是%s, Hello Python" % name)
time.sleep(2)
@count_time_args("say2")
def say2(name):
print("我是%s, Hello Python" % name)
time.sleep(2)
if __name__ == '__main__':
say1("say111")
print("---------")
say2("say222")
基于原来的 count_time函数外部再包一层用于接收参数的 count_time_args函数,接收回来的参数就可以直接在内部的函数里面调用了。
执行结果如下:
3、类装饰器
在 Python中,其实也可以用类来实现装饰器的功能,称之为类装饰器。
类装饰器的实现是调用了类里面的 __call__
函数。
当我们将类作为一个装饰器,工作流程:
- 通过
__init__()
方法初始化类 - 通过
__call__()
方法调用真正的装饰方法
类装饰器的写法比我们写装饰器函数更加简单。
3.1 无参类装饰器
示例代码如下:
import time
# 定义无参数的类装饰器
class MyClassCountTimeDecorator:
def __init__(self, func):
self.func = func
print("执行类的__init__方法")
def __call__(self, *args, **kwargs):
print('进入__call__函数')
t1 = time.time()
self.func(*args, **kwargs)
print("执行时间为:", time.time() - t1)
@MyClassCountTimeDecorator
def say1():
print("我是say1, Hello Python")
time.sleep(2)
def say2():
print("我是say2, Hello Python")
time.sleep(2)
if __name__ == '__main__':
say1()
print('--------------')
say2()
3.2 有参类装饰器
当装饰器有参数的时候,__init__()
函数就不能传入func(func代表要装饰的函数)了,而 func是在__call__
函数调用的时候传入的。
import time
# 定义带参数的类装饰器
class MyClassCountTimeDecorator:
def __init__(self, arg1, arg2): # init()方法里面的参数都是装饰器的参数
print('执行类Decorator的__init__()方法')
self.arg1 = arg1
self.arg2 = arg2
def __call__(self, func): # 因为装饰器带了参数,所以接收传入函数变量的位置是这里
print('执行类Decorator的__call__()方法')
def wrapper(*args): # 装饰器的函数名字可以自定义,只要跟return的函数名相同即可
print('执行wrap()')
print('装饰器参数:', self.arg1, self.arg2)
print('执行' + func.__name__ + '()')
func(*args)
print(func.__name__ + '()执行完毕')
return wrapper
@MyClassCountTimeDecorator('say', '1')
def say1(a1, a2, a3):
print("我是say1, 传入的参数:", a1, a2, a3)
time.sleep(2)
@MyClassCountTimeDecorator('say', '2')
def say2(a1, a2):
print("我是say2, 传入的参数:", a1, a2)
time.sleep(2)
if __name__ == '__main__':
say1("鲁班", "后裔", "妲己")
print('--------------')
say2("赵云", "赵子龙")
注意:
多理解无参与有参数的装饰器/类装饰器区别,加深理解代码。
四、装饰器的顺序
一个函数可以被多个装饰器进行装饰,那么装饰器的执行顺序是怎么调用的?
答案是:在装饰器修饰完的函数,在执行的时候
先执行原函数的功能,然后再由里到外依次执行装饰器的内容。
示例代码如下:
def Decorator_1(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器1')
return wrapper
def Decorator_2(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器2')
return wrapper
def Decorator_3(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器3')
return wrapper
@Decorator_1
@Decorator_2
@Decorator_3
def say1():
print("我是say1, Hello Python")
if __name__ == '__main__':
say1()
– 求知若饥,虚心若愚。