Python三 —— Python迭代器、生成器、装饰器

3. Python迭代器、生成器、装饰器

可迭代对象、迭代器和生成器的区别和联系

画图表示如下:

在这里插入图片描述

迭代器和可迭代对象:

迭代器都是一个可迭代对象,且所有的Iterable(迭代对象)都可以通过内置的iter()转变为Iterator(迭代器)

生成器和迭代器:

联系: 所有的生成器都是迭代器,有yield的是生成器,因为yield可以是生成器表达式,也可以是生成器函数。

区别: 迭代器用于从集合中取出元素,生成器用于凭空生成元素。

斐波那契数列示例

函数方法
def fab(max):
    n,a,b = 0,0,1
    L = []
    while n < max:
        L.append(b)
        a,b = b,a+b
        n += 1
    return L

Iterator方法

为了节省内存,和处于未知输出的考虑,使用迭代器来改善代码。

class fab(object):
    '''
    Iterator to produce Fibonacci
    '''
    def __init__(self,max):
        self.max = max
        self.n = 0
        self.a = 0
        self.b = 1
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.n < self.max:
            r = self.b
            self.a,self.b = self.b,self.a + self.b
            self.n += 1
            return r
        raise StopIteration('Done')

迭代器什么都好,就是写起来不简洁。所以用 yield 来改写第三版。

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

使用下面来输出

for a in fab(8):
    print(a)

看起来很简洁,而且有了迭代器的特性。

可迭代对象 (Iterable Object)

可迭代对象(iterable object),简单来理解就是可以使用for循环来遍历的对象。比如常见的list、set和dict

可以用以下方法来测试对象是否可迭代:

>>> from collections import Iterable
>>> isinstance('abc', Iterable)     # str是否可迭代
True
>>> isinstance([1,2,3], Iterable)   # list是否可迭代
True
>>> isinstance(123, Iterable)       # 整数是否可迭代
False

迭代器

概念

其实当我们对所有的可迭代对象调用dir()方法时,会发现其实他们都定义了__iter__方法。这样就可以通过iter(object)来返回一个迭代器。

>>> x = [1, 2, 3]
>>> y = iter(x)
>>> type(x)
<class 'list'>
>>> type(y)
<class 'list_iterator'>

可以看到,调用iter()后,变成了一个list_iterator的对象。会发现增加__next__方法。所有实现了__iter____next__两个方法的对象,都是迭代器。

迭代器是带状态的对象,它会记录当前迭代所在的位置,以便下次迭代的时候获取正确的元素。

__iter__方法返回迭代器自身

__next__方法返回容器中的下一个值,如果容器中,没有更多元素了,则抛出StopIteration异常

>>> x = [1, 2, 3]
>>> y = iter(x)
>>> next(y)
1
>>> next(y)
2
>>> next(y)
3
>>> next(y)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

判断对象是否是迭代器

和判断是否是可迭代对象差不多,只需要将Iterable换为Iterator,即:from collections import Iterator,然后调用isinstance方法。

python中 for循环的本质

Python中for循环本质上就是通过不断调用next()函数实现的,举个例子,下面的代码:

x = [1, 2, 3]
for elem in x:
    ...

实际上执行的是:

在这里插入图片描述

也就是先将可迭代对象转换为Iterator,再去迭代。

这样迭代器只有在调用next()时,才会实际计算到下一个值,起到节省内存的作用。

常见迭代器

itertools库提供了很多常见迭代器的使用:

计数器
>>> from itertools import count     # 计数器
>>> counter = count(start=13)
>>> next(counter)
13
>>> next(counter)
14

无限循环列表
>>> from itertools import cycle
>>> colors = cycle(['red', 'white', 'blue'])
>>> next(colors)
'red'
>>> next(colors)
'white'
>>> next(colors)
'blue'
>>> next(colors)
'red'

生成器 (generator)

概念

Python汇总,一边循环,一边计算的机制,称之为生成器: generator

常见生成器

生成器列表

要创建一个 generator ,最简单的方法是改造列表生成式

>>> [x*x for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

>>> (x * x for x in range(10))
<generator object <genexpr> at 0x03804630>

生成器函数

通过def定义,然后通过yield来支持迭代器协议,所以比迭代器写起来看着简单

>>>def spam():
       yield"first"
       yield"second"
       yield"third"
>>> spam
<function spam at 0x011F32B0>
>>> gen
<generator object spam at 0x01220B20>
>>> gen.next()
'first'
>>> gen.next()
'second'
>>> gen.next()
'third'

一般情况下,生成器函数通过for来使用,这样不用关心StopIteration的异常。

>>>for x in spam():
       print x
        
first
second
third

进行函数调用的时候,返回一个生成器对象。在使用next()调用的时候,遇到yield就返回,记录此时函数调用的位置,下次调用next()时,从断点处开始。

装饰器 (Decorator)

装饰器(Decorator)是Python中最吸引人的特征,装饰器本质上是一个函数,它可以让已有的函数不作任何改动的情况下,增加功能

因此装饰器非常适合有切面需求的场景,比如权限校验,日志记录和性能测试等等。比如像要执行某个函数前,记录日志或者记录时间来统计性能,又不想改动这个函数,就可以通过装饰器来实现。

不用装饰器,我们需要在函数执行前插入日志,如下:

def foo():
    print('i am foo')
    
def foo():
    print('foo is running')
    print('i am foo')

虽然这样写,满足了需求,但是修改了原有代码,如果其他函数也需要插入日志的话,就需要修改所有的代码,不能复用代码,可以这么写

def use_logg(func):
    logging.warn("%s is running" % func.__name__)
    func()

def bar():
    print('i am bar')

use_log(bar)    #将函数作为参数传入

这样写的确可以复用插入的日志,缺点就是显示的封装原来的函数,我们希望透明的做这件事。用装饰器来写,如下:

bar = use_log(bar)def use_log(func):
    def wrapper(*args,**kwargs):
        logging.warn('%s is running' % func.__name___)
        return func(*args,**kwargs)
    return wrapper

def bar():
    print('I am bar')
    
bar = use_log(bar)
bar()

use_log() 就是装饰器,它把真正我们想要执行的函数 bar() 封装在里面,返回一个封装了加入代码的新函数,看起来就像是 bar() 被装饰了一样。

这个例子中的切面就是函数进入的时候,在这个时候,我们插入了一句记录日志的代码。这样写还是不够透明,通过@语法糖来起到 bar = use_log(bar) 的作用。

bar = use_log(bar)def use_log(func):
    def wrapper(*args,**kwargs):
        logging.warn('%s is running' % func.__name___)
        return func(*args,**kwargs)
    return wrapper

@use_log
def bar():
    print('I am bar')
    
@use_log
def haha():
    print('I am haha')
    
bar()
haha()

这样看起来就很简洁,而且代码很容易复用。可以看成是一种智能的高级封装。

装饰器也是可以带参数的,这为装饰器提供了更大的灵活性

def use_log(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            return func(*args)
        return wrapper

    return decorator

@use_log(level="warn")
def foo(name='foo'):
    print("i am %s" % name)

foo()

实际上是对装饰器的一个函数封装,并返回一个装饰器。这里涉及到作用域的概念,可以把它看成一个带参数的闭包。

当使用 @use_log(level='warn') 时,会将 level 的值传给装饰器的环境中。它的效果相当于 use_log(level='warn')(foo) ,也就是一个三层的调用。

这里有一个美中不足,decorator 不会改变装饰的函数的功能,但会悄悄的改变一个 __name__ 的属性(还有其他一些元信息),因为 __name__ 是跟着函数命名走的。可以用 @functools.wraps(func) 来让装饰器仍然使用 func 的名字。比如

import functools

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

functools.wraps 也是一个装饰器,它将原函数的元信息拷贝到装饰器环境中,从而不会被所替换的新函数覆盖掉。

有了装饰器,我们就可以剥离出大量与函数功能本身无关的代码,增加了代码的重用性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

L☆★

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值