Python-迭代器、生成器、装饰器(1)
迭代器
迭代概念
概念
- 通过for循环遍历对象的每一个元素的过程。
- Python的for语法功能非常强大,可以遍历任何可迭代的对象。
- 在Python中,list/tuple/string/dict/set/bytes都是可以迭代的数据类型。
- 可以通过collections模块的Iterable类型来判断一个对象是否可迭代:
1 |
|
迭代器
概念
迭代器是一种可以被遍历的对象,并且能作用于next()函数。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束
迭代器只能往后遍历不能回溯,不像列表,你随时可以取后面的数据,也可以返回头取前面的数据。迭代器通常要实现两个基本的方法:iter() 和 next()。
字符串,列表或元组对象,甚至自定义对象都可用于创建迭代器:
1 |
|
- 或者使用for循环遍历迭代器:
1 |
|
很多时候,为了让我们自己写的类成为一个迭代器,需要在类里实现__iter__()和__next__()方法。
总结:Python的迭代器表示的是一个元素流,可以被next()函数调用并不断返回下一个元素,直到没有元素时抛出StopIteration错误。可以把这个元素流看做是一个有序序列,但却不能提前知道序列的长度,只能不断通过next()函数得到下一个元素,所以迭代器可以节省内存和空间。
迭代器(Iterator)和可迭代(Iterable)的区别:
凡是可作用于for循环的对象都是可迭代类型;
凡是可作用于next()函数的对象都是迭代器类型;
list、dict、str等是可迭代的但不是迭代器,因为next()函数无法调用它们。可以通过iter()函数将它们转换成迭代器。
Python的for循环本质上就是通过不断调用next()函数实现的。
生成器
概念
有时候,序列或集合内的元素的个数非常巨大,如果全制造出来并放入内存,对计算机的压力是非常大的。
比如,假设需要获取一个10**20次方如此巨大的数据序列,把每一个数都生成出来,并放在一个内存的列表内,这是粗暴的方式,有如此大的内存么?
如果元素可以按照某种算法推算出来,需要就计算到哪个,就可以在循环的过程中不断推算出后续的元素,而不必创建完整的元素集合,从而节省大量的空间。在Python中,这种一边循环一边计算出元素的机制,称为生成器:generator。
前面我们说过,通过圆括号可以编写生成器推导式:
1 |
|
- 可以通过next()函数获得generator的下一个返回值,这点和迭代器非常相似:
1 |
|
但更多情况下,我们使用for循环。
1
2for i in g:
print(i)除了使用生成器推导式,我们还可以使用yield关键字。
在 Python中,使用yield返回的函数会变成一个生成器(generator)。 在调用生成器的过程中,每次遇到yield时函数会暂停并保存当前所有的运行信息,返回yield的值。并在下一次执行**next()**方法时从当前位置继续运行。
1 |
|
装饰器
概念
装饰器(Decorator):从字面上理解,就是装饰对象的器件。可以在不修改原有代码的情况下,为被装饰的对象增加新的功能或者附加限制条件或者帮助输出。
装饰器有很多种,有函数的装饰器,也有类的装饰器。装饰器在很多语言中的名字也不尽相同,它体现的是设计模式中的装饰模式,强调的是开放封闭原则。装饰器的语法是将@装饰器名,放在被装饰对象上面。
1
2
3@dec
def func():
pass在进行装饰器的介绍之前,我们必须先明确几个概念和原则:
首先,Python程序是从上往下顺序执行的,而且碰到函数的定义代码块是不会立即执行的,只有等到该函数被调用时,才会执行其内部的代码块。
1 |
|
- 其次,由于顺序执行的原因,如果你真的对同一个函数定义了两次,那么,后面的定义会覆盖前面的定义。因此,在Python中代码的放置位置是有区别的,不能随意摆放,通常函数体要放在调用的语句之前。
1 |
|
- 然后,我们还要先搞清楚几样东西:函数名、函数体、返回值,函数的内存地址、函数名加括号、函数名被当作参数、函数名加括号被当作参数、返回函数名、返回函数名加括号。
1 |
|
- 函数名: foo、outer、inner
- 函数体:函数的整个代码结构
- 返回值: return后面的表达式
- 函数的内存地址:id(foo)、id(outer)等等
- 函数名加括号:对函数进行调用,比如foo()、outer(foo)
- 函数名作为参数: outer(foo)中的foo本身是个函数,但作为参数被传递给了outer函数
- 函数名加括号被当做参数:其实就是先调用函数,再将它的返回值当做别的函数的参数,例如outer(foo())
- 返回函数名:return inner
- 返回函数名加括号:return inner(),其实就是先执行inner函数,再将其返回值作为别的函数的返回值。
- 函数也是一个对象,通过一个实例来讲解Python中装饰器的作用了。下例是针对函数的装饰器。
虚拟场景
- 有一个大公司,下属的基础平台部负责内部应用程序及API的开发。另外还有上百个业务部门负责不同的业务
- 这些业务部门各自调用基础平台部提供的不同函数,也就是API处理自己的业务,情况如下:
1 |
|
公司还在创业初期时,基础平台部就开发了这些函数。由于各种原因,比如时间紧,比如人手不足,比如架构缺陷,比如考虑不周等等,没有为函数的调用进行安全认证。现在,公司发展壮大了,不能再像初创时期的“草台班子”一样将就下去了,基础平台部主管决定弥补这个缺陷,于是(以下场景纯属虚构,调侃之言,切勿对号入座):
第一天:主管叫来了一个运维工程师,工程师跑上跑下逐个部门进行通知,让他们在代码里加上认证功能,然后,当天他被开除了。
第二天:主管又叫来了一个运维工程师,工程师用shell写了个复杂的脚本,勉强实现了功能。但他很快就回去接着做运维了,不会开发的运维不是好运维….
第三天:主管叫来了一个python自动化开发工程师。哥们是这么干的,只对基础平台的代码进行重构,让N个业务部门无需做任何修改。这哥们很快也被开了,连运维也没得做
1 |
|
- 第四天:主管又换了个开发工程师。他是这么干的:定义个认证函数,在原来其他的函数中调用它,代码如下。
1 |
|
但是主管依然不满意,不过这一次他解释了为什么。主管说:写代码要遵循开放封闭原则,简单来说,已经实现的功能代码内部不允许被修改,但外部可以被扩展。如果将开放封闭原则应用在上面的需求中,那么就是不允许在函数f1 、f2、f3……f100的内部进行代码修改,但是可以在外部对它们进行扩展。
第五天:已经没有时间让主管找别人来干这活了,他决定亲自上阵,使用装饰器完成这一任务,并且打算在函数执行后再增加个日志功能。主管的代码如下:
1 |
|
- 使用装饰器@outer,也是仅需对基础平台的代码进行拓展,就可以实现在其他部门调用函数API之前都进行认证操作,在操作结束后保存日志,并且其他业务部门无需对他们自己的代码做任何修改,调用方式也不用变。
- 下章接着说装饰器分析