【Python】Python学习(十)常用语法形式:迭代器、生成器和装饰器

Chapter 7 迭代器、生成器和装饰器

迭代器、生成器和装饰器可以说是Python的三大法器,是Python的常用语法形式。下面分别来看一下各自的用途。

7.1 迭代器

7.1.1 迭代器概述

(1)迭代器简介

迭代器是一个数据流对象或容器,使用其中的数据时,每次从中取出一个数据,直至数据被取完。
迭代器能记住遍历位置的对象,只能往前遍历,保证数据不会被重复使用。优点是能简化循环程序的代码,并节约内存。

1.4 Python控制语句 中介绍了for语句的使用,实际上与迭代器的模式类似,按要求生成迭代对象并在for语句中使用。

要区分两个概念,一个是可迭代的对象,一个是迭代器对象。

  • 可迭代的(Iterable):可以用for语句循环取出数据,例如listtupledictsetstr
  • 迭代器(iterator):依次迭代取出数据的对象,内存空间存储方式:<list_iterator object at 0x01E35770>

可以用isinstance()判断对象是是否是Iterable对象

>>> isinstance([],Iterable)
True
>>> isinstance({},Iterable)
True
>>> isinstance('abc',Iterable)
True
>>> isinstance((x for x in range(10)),Iterable)
True
>>> isinstance(100,Iterable)
False
(2)迭代器协议

迭代器主要有两个协议方法:__iter____next__。任何一个类实现或具有这两种方法,就可以称之为迭代器。

  • __iter__ 用于创建一个迭代器,返回对象本身,是for语句使用迭代器的要求。
  • __next__用于返回迭代器中下一个元素,当数据用尽时会出现StopIteration异常。

7.1.2 创建和使用迭代器

以Fibonacci(斐波那契数列,递推公式为an+2=an+1+an)为例,创建 Fibonacci 迭代器对象:

# define a Fib class 定义迭代器类
class Fib(object):
    def __init__(self, max):      #定义构造方法
        self.max = max     #记录生成数列的个数
        self.n, self.a, self.b = 0, 0, 1   #生成数列的索引n,记录数列前2个值a和b

    def __iter__(self): #定义迭代器协议方法
        return self     #返回类的本身

    def __next__(self):  #定义迭代器协议方法
        if self.n < self.max: #当索引小于数列个数
            r = self.b        #r记录数列第2个值
            self.a, self.b = self.b, self.a + self.b    #a1 = a2, a2 = a1 + a2
            self.n = self.n + 1  #索引增加1
            return r
        raise StopIteration()    #需要在某个条件下引发StopIteration错误,才能结束循环
# using Fib object   使用类
for i in Fib(5):
    print(i)

将会输出前 5 个 Fibonacci 数据 1,1, 2, 3, 5
实现2个数累加的功能

7.1.3 内置迭代器方法iter()

(1)iter(iterable)

iter(iterable)原型中只有一个参数,为可迭代的类型,如前文提到的listtupledictsetstr等。

>>> list=[1,2,3,4]
>>> it = iter(list)
>>> for i in it:
	print(i)
1
2
3
4

可以将Iterable类型的对象用iter()转换为Iterator迭代器

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

(2)iter(callable,sentinel)

iter(callable,sentinel)原型包含两个参数,callable表示可调用类型,一般为函数;sentinel是一个标记,当第一个参数的返回值等于第二个参数的值时,则停止迭代或遍历

>>> class Count:    #定义类
	def __init__(self,x=0):    #定义构造方法
		seld.x=x    #初始化实例属性x
>>> count = Count()    #实例化
def use_iter():    #定义用于iter()的函数
	count.x += 2    #修改实例属性x的值
	return count.x
>>> for i in iter(use_iter,12):    #通过迭代方法iter()产生的迭代器
	print(i)
2
4
6
8
10

7.1.4 方法next()

可迭代的对象Iterable 不可以使用next方法,但迭代器Iterator可以使用next方法依次取出数据。因为迭代器是一个数据流,能够被调用并不断返回数据,直至出现 StopIteration错误。
Iterator的计算时惰性的,只有在需要返回下一个数据才会进行计算。
for循环语句的本质就是通过不断调用next函数实现。

for x in [1, 2, 3, 4, 5]:
    pass

等价于

# 创建并获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环
while True:
    try:
        # 获得下一个值
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break

这里捕获StopIteration错误的处理方法,会在后面详细介绍。

凡是可作用于for循环的对象都是Iterable类型
凡是可作用于next()函数的对象都是Iterator类型,表示一个惰性计算的序列
集合数据类型如list、dict、str等是Iterable但不是Iterator,可以通过iter()函数获得一个Iterator对象。

7.2 生成器

7.2.1 生成器概述

生成器(generator)可以生成值的序列用于迭代。由于值的序列是使用完一个再生成一个,能够使程序节省内存。
在Python中,使用了yield关键字定义的函数称为生成器。yield关键字能实现类似迭代的效果。每次调用生成器时,遇到yield关键字函数就会暂停并保存当前状态信息,返回yield的值,并在下一次执行next方法时从当前位置继续执行。
因此,调用一个生成器函数,则返回一个迭代器对象。
生成器能够一边循环一边计算,可以得到庞大的数据;因为当前运行信息依然保留,便于下次循环调用,能够节省硬件资源。

7.2.2 创建和使用生成器

创建生成器有很多种方法。

(1)将列表[]改成()

将列表生成式的[]改成()

>>> List = [x * x for x in range(10)]
>>> List
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> generator = (x * x for x in range(10))
>>> generator
<generator object <genexpr> at 0x0000023130E6F048>

如上,List的最外层为[],而generator最外层为()。我们可以通过调用list直接打印每个元素。要打印generator的每个元素,可以用next()函数获得生成器的下一个返回值,直至报错。

>>> next(generator)
0
>>> next(generator)
1
>>> next(generator)
4
>>> next(generator)
9
>>> next(generator)
16
>>> next(generator)
25
>>> next(generator)
36
>>> next(generator)
49
>>> next(generator)
64
>>> next(generator)
81
>>> next(generator)
Traceback (most recent call last):
  File "<pyshell#61>", line 1, in <module>
    next(generator)
StopIteration

因为生成器保存的是算法而不是数据,每次调用next()函数时才会计算下一个元素的值,直至计算到最后一个元素,并报错StopIteration

但是不断调用next()函数会非常麻烦,一般会使用for循环来进行数据输出。

>>> generator = (x * x for x in range(10))
>>> for n in generator:
	print(n)
0
1
4
9
16
25
36
49
64
81

使用for循环也能够避免调用next()函数出现StopIteration错误。

(2)函数包含yield

函数定义包含yield关键字
在迭代器一节介绍了斐波拉契数列(Fibonacci)的代码,用迭代器类表示,也可以将其写成函数。

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        r = b
        a, b = b, a+b
        n = n + 1
        yield r

其中赋值语句a, b = b, a+b
相当于

t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

但是不必显式写出临时变量t,相当于a=b b=a+b

for i in fib(5):
    print(i)
1
1
2
3
5

输出前 5 个 Fibonacci 数据 1, 1, 2, 3, 5

与函数执行流程不同的是,函数为顺序执行,遇到return语句或最后一行语句就会返回;而生成器执行时,每次调用next()函数就会执行,遇到yield语句就会返回,再次执行则从上次返回的yield语句处继续执行。

7.2.3 send()方法

send()方法可以重置生成器的生成序列,称为协程,能够解决程序并发和同步设计。send()方法可以再次转入一个值。

下面以生产者和消费者的模型来介绍send()方法的用途。

def consumer():
	print('等待接收任务...')
	while True: 
		data = (yield)  #返回yield后面的值,()转化为迭代器
		print('收到任务:',data)

def producer():
	c = consumer()
	c.__next__()
	for i in range(3):
		print('发送一个任务...','任务%d' %i)
		c.send('任务%d' %i)

if __name__ =='__main__':
	producer()

输出

等待接收任务...
发送一个任务... 任务0
收到任务: 任务0
发送一个任务... 任务1
收到任务: 任务1
发送一个任务... 任务2
收到任务: 任务2

这里消费者模型consumer()是一个生成器。调用生产者模型producer()后,consumer()先打印等待信息,后运行__next__()返回迭代器下一个元素,每次循环后就利用send()发送一个任务给consumer(),生成器调用函数执行,返回yield后面的值输出收到任务。这里运用yield语句的hold特性完成任务。

注意生成器需要被挂起才能运行send(),也就是说第一次要用next()send(None)启动

7.3 装饰器

装饰器(Decorator)是一种增加函数或类的功能的简单方法,可以给不同函数或类插入相同功能。

7.3.1 装饰函数

装饰器可为不同的函数或类增加相同的功能,且不需要修改函数的定义,此时可使用装饰器,在代码运行期间动态增加功能。本质上装饰器是返回函数的高阶函数。
装饰器表示语法:用@装饰器名称来实现。

(1)两层嵌套装饰器

定义一个now()函数,输出当前日期。

def now():
	print('2020-9-15')

调用

>>> f = now
>>> f()
2020-9-15

函数对象有一个__name__属性,可获取函数的名字:

>>> now.__name__
'now'
>>> f.__name__
'now'

现在想要增加now()函数的功能,如在函数调用前后自动打印日志。

定义一个log()函数。wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

(详见1.5.2 参数类型-(3)可变参数(4)关键字参数

# 参数为func,在log()内定义一个新的函数wrapper(),返回形参func
def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

定义装饰器与定义函数在形式上基本一致,但装饰器函数的参数必须含有函数或类对象,然后再装饰器函数中重新定义新的函数或类。

log()作为decorator置于now()函数的定义处:

@log
def now():
	print('2020-9-15')

调用now()函数,运行now()函数本身并在运行now()函数前打印一行日志

>>> now()
call now():
2020-9-15

@log放到now()函数的定义处,相当于执行了语句:

now = log(now)

原来的now()函数仍然存在,但是现在的now()变量指向新的函数,调用now()执行新函数,即在log()函数中返回wrapper()函数。

(2)三层嵌套装饰器

装饰器本身需要传入参数时是可以带参数的,同时使用时可嵌套装饰。编写一个高阶函数。

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

now()函数中使用装饰器:

# text的内容为execute
@log('execute')
def now():
    print('2020-9-15')

执行结果

>>> now()
execute now():
2020-9-15

3层嵌套装饰器的效果如下

>>> now = log('execute')(now)

剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

(3)对象属性修改

由于对象含有__name__等属性,但是经过装饰器装饰之后,函数的__name__发生了改变。

>>> now.__name__
'wrapper'

__name__已经从原来的’now’变成了’wrapper’。因为返回的'wrapper()函数名为wrapper,因此需要把原始函数的属性复制到wrapper()函数中,否则依赖函数签名的代码执行就会出错。

通过Python内置的functools.wraps模块可完成wrapper.__name__ = func.__name__属性赋值的操作。因此完整的装饰器代码如下:

两层嵌套的装饰器:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

三层嵌套的装饰器(装饰器带参数):

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

7.3.2 装饰类

装饰器可以装饰函数和类。定义装饰类的装饰器的方法:定义内嵌类的函数,并返回新类。

# 定义类装饰器
def decorator_abc(myclass):
	# 定义内嵌类
	class InnerClass:
		def __init__(self,z=0):
			self.z = 0
			self.wrapper = myclass()	# 实例化被装饰的类
		
		def position(self):
			self.wrapper.position()
			print('z axis:',self.z)

	return InnerClass	# 返回新定义的类
@decorator_abc		# 使用装饰器
#定义普通的类
class Coordination:
	def __init__(self,x=0,y=0):
		self.x = x
		self.y = y

	def position(self):
		print('x axis:',self.x)
		print('y axis:',self.y)
if __name__=='__main__':
	coor = Coordination()	# 实例化被装饰的类
	coor.position()			# 调用方法position()

输出

>>> 
x axis: 0
y axis: 0
z axis: 0

====================================================================
Python学习的内容参考
《Python编程:从入门到实践》-[美] Eric Matthes
《21天学通PYTHON》
莫烦Python
廖雪峰的Python教程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值