Python三大器:迭代器、生成器和装饰器

Python三大器:迭代器、生成器和装饰器

大家好!我是栗子鑫,今天主要分享一下笔者在准备秋招面试的过程中遇到的问题:Python三大器。正文如下,希望对你们有所帮助。



01


迭代器



迭代和可迭代对象

在介绍迭代器之前介绍一下迭代和可迭代对象的概念:

迭代:通常从一个对象中依次取出数据,这个过程叫做遍历,也称为迭代(重复执行某一段代码块,并将每一次迭代得到的结果作为下一次迭代的初始值)。

可迭代对象:是指该对象可以被用于for循环,例如:集合,列表,元组,字典,字符串,迭代器等。


那么在代码层面如何定义一个可迭代对象呢?

在python中如果一个对象实现了__iter__方法,我们就称之为可迭代对象。例如,可以查看list,set等源码,其内部均实现了__iter__方法,实现方式和对象的初始化方法一样。

class MyIterable:
def __init__(self):
pass
def __iter__(self):
return self
  • 如果一个对象未实现__iter__方法,对其使用for则会抛出TypeError: ‘xxx’ object is not iterable
  • Iterable实现了__iter__方法

💡tips:如何判断一个对象是不是可迭代对象

isinstance(MyIterable(),Iterable) # True

什么是迭代器?

迭代器:对可迭代对象进行迭代的方式或容器,并且需要记录当前迭代进行到的位置。对于python可迭代对象不仅仅实现__iter__方法,还需要实现__next__方法,来获取下一个值。迭代器一定是可迭代对象,可迭代对象不一定是迭代器。同样迭代器拥有迭代对象的特点。如果可迭代对象遍历完后继续调用next(),则会抛出:StopIteration异常。

如何实现一个迭代器?

#手动实现一个range函数
from typing import Iterable
class MyIterable2range:

def __init__(self,start,end):
if start is None:
self.start = 0
self.start = start
self.end = end

def __iter__(self):
return self

def __next__(self):
if self.start < self.end:
#迭代当前值
val = self.start
self.start +=1
return val
else:
return StopIteration
if __name__ == '__main__':
myrange = MyIterable2range(0,3)
print(next(myrange)) #0
print(myrange.__next__()) #1
print(next(myrange)) #2
print(next(myrange))#<class 'StopIteration'>
print(isinstance(myrange,Iterable)) # True

在python range函数中开发者已经处理了StopIteration异常,所以不会抛出异常,上述代码中实现了一个range函数,可以看出迭代器的优缺点:

优点:迭代器对象表示的是一个数据流,可以在需要时才去调用next来获取一个值;因而本身在内存中始终只保留一个值,对于内存占用小可以存放无限数据流。于其他容器需要一次将所有元素都存放进内存,如:列表、集合、字典…等

缺点:无法获取存放的元素长度,除非取完计数。同时取值不灵活,只能向后取值,next()永远返回的是下一个值;无法取出指定值(如字典的key,或列表的下标),而且迭代器对象的生命周期是一次性的,元素被迭代完则生命周期结束。



02


生成器



yield关键字

在介绍生成器先介绍一下yield关键字:yield关键字在某种意义上可以看做return。普通的return是在程序中返回某个值,返回之后程序就不再往下运行了。

yieldreturn不同的是,yield会返回一个生成器,而return不会。yield可以返回值,但是不会结束函数的执行,如果函数后面还有代码,同样是可以执行的。

举个🌰

def s_yield():
print("starting...")
while True:
res = yield 4
print("res结果:",res)
g = s_yield()
print(next(g))
print("-----------")
print(next(g))
-------------结果----------
starting...
4
-----------
res结果: None
4
  1. 程序开始执行以后,因为s_yield函数中有yield关键字,所以s_yield函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)

  2. 直到调用next方法,s_yield函数正式开始执行,先执行s_yield函数中的print方法,然后进入while循环

  3. 程序遇到yield关键字,然后把yield想想成return ,return 了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return 出的结果)是执行print(next(g))的结果,

  4. 程序执行print("-----------")

  5. 又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res结果:None,

  6. 程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return 出的4.

写到这里读者应该能明白yield关键字的作用了吧!

对于生成器:在python中使用了 yield的函数被称为生成器(generator)。同时生成器对象也是迭代器对象,所以他有迭代器的特性;例如支持for循环、next()方法…等。

生成器的作用

对象中的元素是按照某种算法推算出来的,在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间

生成器“种类”

1.列表式生成

# 列表生成式
list_1 = [x ** 2 for x in range(10)]
print(list_1) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

2.生成器

# 生成器
list_2 = (x**2 for x in range(10) )
print(list_2) # <generator object <genexpr> at 0x104c8fcf0>

两者区别在于 一个是[]一个是() 返回值一个是具体的值一个是生成器对象地址

生成器特点

  • 生成器只有在调用的时候才会生成相应的数据(列表式生成需要在运行前将所有的数据生成),极大的节省了空间
  • 生成式可以直接打印列表,生成器只能打印地址
  • 生成式可以通过下角标获取元素,生成器不行
  • 生成器可以通过__next()__函数获得生成器(generator)的下一个返回值(生成器也属于迭代器)

生成器的用途

举个🌰 –用生成器实现斐波拉契数

def fib(sum):
a, b, c = 0, 1, 0
while c < sum:
yield b # 代码执行到这里,会跳出这个函数,并将b的值返回到使用next的代码处
a, b = b, a + b
c += 1
p = fib(6)
print(next(p)) # 1
print(next(p)) # 1
print(next(p)) # 2
print(p.__next__()) # 3
print(next(p)) # 5
print(p.__next__()) # 8

generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

for n in fib(6):
print(n)
#结果和上文一样



03


装饰器



讲装饰器之前要先了解两个概念:

对象引用

对象引用:对象名仅仅只是个绑定内存地址的变量,下面举一个🌰说明

def fo():   # 函数名仅仅只是个绑定内存地址的变量       
print("i`m running")

# 这是调用
fo() # i`m running
# 这是对象引用,引用的是内存地址 func2和fo的地址相同
func2 = fo
print(func2 is fo) # True
# 通过引用进行调用
func2() # i`m running

闭包

闭包:闭包就是外部函数中定义一个内部函数,内部函数引用外部函数中的变量,外部函数的返回值是内部函数

def out_func():
a = 10

def inner_func(inner_x):
return a + inner_x

return inner_func
out = out_func()
print(out) # <function out_func.<locals>.inner_func at 0x7ff378af5c10> out_func返回的是inner_func的内存地址
print(out(inner_x=2)) # 12

闭包一般其传入的参数都为普通的数据对象,比如上文中的inner_x = 2

装饰器特点

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的应用有插入日志、增加计时逻辑来检测性能、加入事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括来说,装饰器的作用就是为已经存在的对象添加额外的功能。

装饰器用途

装饰器的定义很是抽象,这里举一个小🌰来介绍装饰器的用途:

#计算某个函数的运行时间
#在没有使用装饰器的解法
import time
def f():
print("f")

def func():
start = time.clock()#获取执行前的时间
print("执行操作")
end = time.clock()#获取执行前的时间
print("time used",end - start)
func()
# 功能看起来完成需求。但是如果我要看看另外一个函数的执行性能,计算时间的这些与函数本身功能无关的代码我还得写一遍 这个时候可以考虑到装饰器

def time_cost(func):
def wrapper():
start = time.clock()#获取执行前的时间
func()
end = time.clock()#获取执行前的时间
print("time used",end - start)
# 将包装后的函数返回
return wrapper
f = time_cost(f)
f()

代码中下半部分就是一个完整的装饰器,新增了一个time_cost函数其参数并不是不同的数据对象,而是某个函数的对象地址应用,同时该函数也是一个闭包函数返回内部函数wrapper的地址,这样可以让后time_cost函数可以像普通函数一样调用。

装饰器和闭包不同点在于:装饰器的入参是函数对象,闭包入参是普通数据对象

Tips: python语法糖支持更便捷的调用

#上述装饰器可以跟便捷调用

@time_cost
def f():
pass
#直接调用f 可以达到同样效果

Python内置的装饰器有三个,分别是staticmethod、classmethod和property,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。



04


总结



此,这篇关于python三大器:迭代器、生成器、装饰器的文章就介绍到这了,希望大家能够早日拿到心仪的offer。


作者    栗子鑫

编辑   一口栗子  

原文始发于微信公众号(六只栗子):Python三大器:迭代器、生成器和装饰器

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88655.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!