python迭代器、生成器、装饰器

1 迭代器

这里我们先来回顾一下什么是可迭代对象(Iterable)

可以直接作用于for循环的对象统称为可迭代对象,即Iterable。   # 一是集合数据类型,如list、tuple、dict、set、str等;   # 二是generator,包括生成器和带yield的generator function。

那么什么又是迭代器(Iterator)?

可以被next()函数调用不断返回下一个值(直到没有数据时抛出StopIteration错误)的对象称为迭代器,即Iterator。

1 import collections
2 print(isinstance([], collections.Iterable))            # True
3 print(isinstance(iter([]), collections.Iterator))      # True
4 print(isinstance(iter([]), collections.Iterable))      # True
5 print(isinstance([], collections.Iterator))            # False
6 print(isinstance((x * x for x in range(10)), collections.Iterable))
# isinstance() 是python内建函数,返回对象是否是类或其子类的实例。若是,返回True,反之返回False。
# Iterable 英文是‘可迭代的’,形容词;Iterator英文是‘迭代器’,名词。

# 那么当 isinstance()的第二个参数是collections.Iterable时,是判断第一个参数是不是Iterable对象(可迭代对象)
# 当 isinstance()的第二个参数是collections.Iterator时,是判断第一个参数是不是Iterator对象(迭代器对象)

# 那么什么是可迭代对象?可以直接作用于for循环的对象统称为可迭代对象,即Iterable。
# 一是集合数据类型,如list、tuple、dict、set、str等;
# 二是generator,包括生成器和带yield的generator function。

# 是么是迭代器?可以被next()函数调用并不断返回下一个值(直到没有数据时抛出StopIteration错误)的对象称为迭代器:Iterator。

# 你可能会问,为什么list、dict、str等数据类型不是Iterator?
# 这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
# 可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,
# 所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
# Iterable 可以通过iter()函数转换得到 Iterator,但Iterable不一定是Iterator;而Iterator可以直接作用于for循环,所以Iterator是Iterable。

2 生成器

首先先理清几个概念:

  generator : A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.

  generator iterator : An object created by a generator funcion.

  generator expression : An expression that returns an iterator.

  可见,我们常说的生成器,就是带有 yield 的函数,而 generator iterator 则是 generator function 的返回值,即一个 generator 对象;

      形如 (elem for elem in [1, 2, 3]) 的表达式,称为 generator expression ,实际使用与 generator 无异。

  Python’s generators provide a convenient way to implement the iterator protocol.

  也就是说: generator 就是 iterator 的一种,以更优雅的方式实现的 iterator 。我们来一个例子:

 1 from collections import Iterable
 2 from collections import Iterator
 3 from collections import Generator
 4 
 5 def odd():
 6     print('step 1')
 7     yield 1
 8     print('step 2')
 9     yield(3)
10     print('step 3')
11     yield(5)
12 
13 ge = odd()
14 print(isinstance(ge, Iterator))
15 print(isinstance(ge, Iterable))
16 print(isinstance(ge, Generator))
17 print(type(ge))
18 # 结果
19 # True
20 # True
21 # True
22 # <class 'generator'>

这也充分印证了上面的说法,generator就是一种iterator。而且Gennerator这个类是继承了Iterator的。

3 装饰器

什么是装饰器(Decorator)?

本质上:是一个返回函数的高阶函数。

生产上,什么时候用装饰器?

  当我们想要给一个函数func()增加某些功能,但又不希望修改func()函数的源代码的时候就需要用装饰器了。(在代码运行期间动态增加功能)

假如,你有一个网站,之前是免费开放的,谁都可以访问。但是有一天你不想免费开放了,你想让大家必须登陆后才能访问,但是呢,网站已经上线了,一直是跑着的,不能修改源码。这个时候就要用这个装饰器了。

前奏:

假设你原先的网站首页是这个函数:

def home():
    print("欢迎来到XX首页!")

home()

首先我们必须得明白:函数也是一个对象(python里一切皆对象),且可以赋值给其他变量。例如:

def home():
    print("欢迎来到XX首页!")

f = home
f()

这和直接调用home()结果是一样的。

那么怎么做到,不改变home的源码给它加上添加登录功能呢?看下面的代码,注意其中的讲解:

 1 def login(func):
 2     """
 3     在这里从新定义一个高阶函数,
 4     这就是decorator。
 5     我们一会儿会仔细分析。
 6     """
 7     def wrapper(*args, **kwargs):
 8         user = "zingp"   # 假设这是数据库中的用户名和密码
 9         passwd = "123"
10         username = input("输入用户名:")
11         password = input("输入密码:")
12         if username == user and password == passwd:
13             return func(*args, **kwargs)
14         else:
15             print("用户名或密码错误。")
16     return wrapper
17 
18 
19 @login     # 利用python的@语法,把decorator置于home函数的定义处 相当于home = login(home)
20 def home():
21     print("欢迎来到XX首页!")
22 
23 home()

运行看看,是不是没改变home源码和home的调用方式,给home添加了登录验证的功能?

其实,这里@login相当于做一这么一件事:home = login(home)

那么当执行23行时就是这样的:login(home)()

login(home)是什么?他就是调用login这个函数后的返回值,即wrapper

      此时,login(home)()即变成了 wrapper()

  执行wrapper() ,返回home()函数并执行home()

整个过程就是这样。

 但是这里还有个问题,就是当没加装饰器的时候print(home.__name__)得到的函数名是home,加了装饰器后print(home.__name__)得到的结果就是wrapper了。

我们虽然没有改home函数的源代码,但是更改了__name__属性,所以,最美好的结果是连属性也不更改吧?

你可能想写个wrapper.__name__ = func.__name__这样的代码,就可以了嘛。但是呢,Python内置的functools.wraps就是专门干这个事的。

请看完整的装饰器代码

 1 import functools  # 先得导入这个工具
 2 
 3 
 4 def login(func):
 5 
 6     @functools.wraps(func)
 7     def wrapper(*args, **kw):
 8         user = "zingp"   # 假设这是数据库中的用户名和密码
 9         passwd = "123"
10         username = input("输入用户名:")
11         password = input("输入密码:")
12         if username == user and password == passwd:
13             return func(*args, **kw)
14         else:
15             print("用户名或密码错误。")
16     return wrapper
17 
18 
19 @login
20 def home():
21     print("欢迎来到XX首页!")
22 
23 home()
24 print(home.__name__)

现在是不是属性也没改了?

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阿杜的世界

Java泛型的局限和使用经验泛型的局限泛型的常用经验参考资料

//使用泛型类 @Data @Builder @AllArgsConstructor @NoArgsConstructor public class Data...

812
来自专栏开发与安全

C++中四种类型转换以及const_cast是否能改变常量的问题

we have four specific casting operators:dynamic_cast, reinterpret_cast, static_c...

21810
来自专栏用户画像

外部排序的方法

在实际应用中,由于外存设备的不同,通常又可分配磁盘文件排序和磁带文件排序两大类。磁带排序和磁盘排序的基本步骤相类似,主要的不同之处在于初始归并段在外存介质中的分...

1211
来自专栏算法修养

位运算总结

用了那么多位运算,这里总结一下把。 先看常用的位运算有哪些吧: 1 & a&b 就是a的二进制形式与b的二进制形式,相同的位置必须两个都是1,那么结果...

3458
来自专栏CDA数据分析师

工具丨用C语言扩展Python的功能

一、简介 Python是一门功能强大的高级脚本语言,它的强大不仅表现在其自身的功能上,而且还表现在其良好的可扩展性上,正因如此,Python已经开始受到越来越多...

2779
来自专栏吴裕超

es6 常用总结

在ES6之前,我们都是用var关键字声明变量。无论声明在何处,都会被视为声明在函数的最顶部(不在函数的最顶部就在全局作用域的最顶部)。这就是函数变量提升例如:

1664
来自专栏专注 Java 基础分享

全面理解java异常机制

      在理想状态下,程序会按照我们预想的步骤一步一步的执行,但是即使你是大牛,你也不可避免出错,所以java为我们提供了异常机制。本文将会从以下几个方面...

2777
来自专栏决胜机器学习

设计模式专题(二十三) ——解释器模式

设计模式专题(二十三)——解释器模式 (原创内容,转载请注明来源,谢谢) 一、概述 解释器模式(interpreter)是给定一个语言,定义它的文法的一种表示...

32711
来自专栏互联网杂技

前端--理解 Promise 的工作原理

Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的...

3726
来自专栏章鱼的慢慢技术路

《算法图解》第二章笔记与课后练习

19410

扫码关注云+社区

领取腾讯云代金券