装饰器,迭代器与生成器

闭包

定义一个函数,在函数的内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包.

def line_conf(a, b):
    def line(x):
        return a * x + b

    return line


line1 = line_conf(1, 5)
line2 = line_conf(3, 5)

print(line1(2))
print(line2(3))

如上所示,函数line与变量a,b构成了闭包.在创建闭包的过程中,通过lin_conf的参数进行取值,在一定程度上提高了代码的可复用性.

但是由于外包引用了外部函数的局部变量,使得外部函数的的局部变量没有及时的释放,消耗了内存.

装饰器

函数的调用

def foo():
    print("foo")


foo() # 表示执行foo 函数
foo # 表示函数
# =======================================
def too():
    print("too")


too = lambda x: x + 1
too() # 执行lambda 表达式

函数名只是一个变量,只是指向了定义的函数,所以才能通过函数名()的方式进行调用,如果函数名=XXX被修改了,那么当在执行函数名()的时候,调用的就不是之前的函数.

装饰器的作用

一般而言,写代码的过程中应当遵循开放封闭的原则,即,在编程的过程当中,已经实现的功能代码不允许被修改,但可以被扩展.

在python中通常使用装饰器完成代码功能的扩展,一般用于以下场景:

  • 引入日志
  • 函数执行时间统计
  • 执行函数前的预备处理
  • 执行函数后的清理功能
  • 权限校验
  • 缓存

装饰器示例

装饰器格式如下:

def wrapper(func):
    def inner(*args, **kwargs):
        return func(*args, **kwargs)

    return inner

带参数的装饰器

from time import ctime, sleep


def timefun_arg(pre="hello"):
    def timefun(func):
        def wrapped_func():
            print("%s called at %s %s" % (func.__name__, ctime(), pre))
            return func()

        return wrapped_func

    return timefun


@timefun_arg("itcast")
def foo():
    print("I am foo")
    
# 装饰过程如下
# 1. 调用timefun_arg("itcast")
# 2. 将步骤1得到的返回值,即time_fun返回, 然后time_fun(foo)
# 3. 将time_fun(foo)的结果返回,即wrapped_func
# 4. 让foo = wrapped_fun,即foo现在指向wrapped_func

foo()

# output 
# foo called at Wed Sep 23 13:57:27 2020 itcast
# I am foo
# foo called at Wed Sep 23 13:57:29 2020 itcast
# I am foo

迭代器

迭代

迭代是访问集合元素的一种方式,迭代器是一个可以记住遍历的位置的对象,迭代器对象是从集合的第一个元素开始访问,知道所有的元素被访问结束,迭代器只能往前不能往回退.

可迭代对象

可以通过for...in...这类语句迭代读取一条数据供我们使用的对象,称之为可迭代对象iterable

可通过isinstance()判断一个对象是否是Iterable对象.

from collections import Iterable

isinstance([],Iterable)
Out[12]: True
    
isinstance((),Iterable)
Out[13]: True
    
isinstance({},Iterable)
Out[14]: True
    
isinstance(str,Iterable)
Out[15]: False
    
isinstance('abcd',Iterable)
Out[1]: True
    
isinstance(100,Iterable)
Out[17]: False

可迭代对象的本质

可被迭代要满足的要求就叫做可迭代协议,内部实现了__iter__方法.

可迭代对象通过内部实现了一个__iter__方法向我们提供一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器以此获取对象中的每一个数据.

即,一个具备了__iter__方法的对象,就是一个可迭代对象.

from collections import Iterable


class MyClass(object):
    def __init__(self):
        self.container = []

    def __iter__(self):
        #
        pass

    def __add__(self, item):
        self.container.append(item)


myclass = MyClass()
print(isinstance(myclass, Iterable))

>>> True

iter()next()函数

list,tuple都是可迭代对象,我们可以通过iter()方法获取这个可迭代对象的迭代器,然后可以对获取到的迭代器不断使用next()函数获取下一条数据.

li = ['aa','bb','cc','dd']
li_iter = iter(li)

next(li_iter)
Out[20]: 'aa'
    
next(li_iter)
Out[21]: 'bb'
    
next(li_iter)
Out[22]: 'cc'
    
next(li_iter)
Out[23]: 'dd'
    
next(li_iter)
Traceback (most recent call last):
  File "<ipython-input-24-ab6a83f394a1>", line 1, in <module>
StopIteration

当我们他已经迭代万最后一个数据之后,再次调用next()函数会抛出一个异常,来告诉我们所有的数据已经迭代完成,不能在执行next()函数了.

迭代器

可以使用isinstance()判断一个对象是否是迭代器Iterator对象.

from collections import Iterator

isinstance([],Iterator)
Out[3]: False
    
isinstance(iter([]),Iterator)
Out[4]: True

由上可知,迭代器是用来记录每次迭代访问的位置,在我们对迭代器对象使用next()方法的时候,迭代器会返回他所记录的位置的下一个位置的数据,实际上,在我们使用next()方法的饿时候,调用的就是迭代器对象的__next__方法,因此我们如果想构建一个迭代器,就要实现他的__next__方法.

但是Python要求迭代器本身也是可迭代的,所以我们还要为他实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身就是一个迭代器,所以__iter__方法返回自身就可以.


class MyClass(object):
    def __init__(self):
        self.container = []

    def __iter__(self):
        # 构建返回的迭代器对象
        myiterator = MyIterator(self)
        return myiterator

    def add(self, item):
        self.container.append(item)

        

class MyIterator(object):
    """自定义的可供可迭代对象使用的一个迭代器"""

    def __init__(self, mylist):
        self.mylist = mylist
        # 用来记录当前访问位置
        self.current = 0

    def __iter__(self):
        # 返回自身
        return self

    def __next__(self):
        # 构建迭代器自身的next()方法
        if self.current < len(self.mylist.container):
            item = self.mylist.container[self.current]
            self.current += 1
            return item
        else:
            raise StopIteration


if __name__ == '__main__':
    my_list = MyClass()
    my_list.add(1)
    my_list.add(2)
    my_list.add(3)
    my_list.add(4)
    my_list.add(5)

    for item in my_list:
        print(item)

for循环的本质

for item in Iterable循环的本质就是先通过iter()方法获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断地调用next()方法,来获取下一个值,并将它赋值给item,当遇到StopIteration的时候循环结束

迭代器的应用场景

迭代器最核心的功能就是通过next()函数的调用来返回下一个数据值,可见下例:

class FibIterator(object):
    """斐波那契数列迭代器"""
    def __init__(self, n):
        """
        :param n: 指明生成数列前n个数 
        """
        self.n = n
        # 记录当前生成数列中的第几个数
        self.current = 0
        # 记录前前一个数的位置
        self.num1 = 0
        # 记录前一个数的位置
        self.num2 = 1

    def __next__(self):
        """被next()方法调用"""
        if self.current < self.n:
            num = self.num1
            self.num1, self.num2 = self.num2, self.num1 + self.num2
            self.current += 1
            return num
        else:
            raise StopIteration

    def __iter__(self):
        """迭代器的__iter__返回自身即可"""
        return self


if __name__ == '__main__':
    fib = FibIterator(10)
    for num in fib:
        print(num, end=" ")

接收可迭代对象的容器

除了for循环之外,listtuple都可以接收可迭代对象

li = list(FibIterator(10))
print(li)

tup = tuple(FibIterator(10))
print((tup))

生成器

在迭代器中,我们每次获得数据(next())时都按照特定的规律进行生成.但是在我们实现一个迭代器的时候,关于当前迭代的状态需要我们自己进行记录,进而才能依据当前状态生成下一个数据.为了纪录当前的状态,并配合next()函数进行使用,我们采用更为便捷的生成器语法,即generator.

因此,生成器是一种特殊的装饰器语法

创建生成器的语法

把列表生成式的[]修改为()

li = [ x**2 for x in range(10) ]
li
Out[6]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
   

gi = ( x**2 for x in range(10))
list(gi)
Out[8]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

如上,li是一个列表,gi是一个生成器,对于生成器可以使用for,next()函数或者list()等方法进行使用.

使用函数实现

使用函数实现复杂的生成器功能的时候,我们可以使用yield代替return,此时的的函数就是一个生成器.

也可以认为,只要在函数中存在关键字yield,那么这个函数就不再是一个函数而是一个生成器.

def fib(n):
    current = 0
    num1, num2 = 0, 1
    while current < n:
        num = num1
        num1, num2 = num2, num1 + num2
        current += 1
        yield num
    return 'done'


if __name__ == '__main__':
    fi = fib(10)
    print(list(fi))
    
>>> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
yield关键字的作用
  • 保存当前的运行状态,然后暂停执行,即将生成器(函数)挂起,
  • yield关键字后面的表达式的值作为返回值返回,此处视线的作用相当于return
  • 再次使用next()函数让生成器从断点处开始执行,即唤醒生成器函数

使用send唤醒

除了使用next()函数可以将生成器唤醒,还可以使用send()函数来唤醒执行.

使用send()函数的时候,可以在唤醒函数的同时想断点处传入一个附加数据

def gen():
   ...:   i = 0
   ...:   while i<5:
   ...:       temp = yield i
   ...:       print(temp)
   ...:       i+=1
   ...:   
    
    
f = gen()

next(f)
Out[26]: 0
    
next(f)
None
Out[27]: 1
    
f.send('hahahddd')
hahahddd
Out[28]: 2
    
f.send('hahahddd')
hahahddd
Out[29]: 3
    
next(f)
None
Out[30]: 4
    
next(f)
None
posted @ 2020-09-23 17:34  郁文  阅读(215)  评论(0)    收藏  举报