• python基础之迭代器、生成器、装饰器


    一、列表生成式

    a = [0,1,2,3,4,5,6,7,8,9]
    b = []
    for i in a:
        b.append(i+1)
    print(b)
    a = b
    print(a)
    ----------------------------------------------
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    a = [1,3,4,5,4,5,6,7,8,11]
    for index,i in enumerate(a):
        a[index] += 1
    print(a)
    ------------------------------------------
    [2, 4, 5, 6, 5, 6, 7, 8, 9, 12]
    a = [1,2,3,4,5,6,7,8,9,10]
    a = map(lambda x:x+1, a)
    print(a)
    for i in a:
        print(i)
    -----------------------------------------------------
    <map object at 0x000000C5343A32B0>
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    a = [i+1 for i in range(10)]
    print(a)
    --------------------------------------------
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    二、生成器

    列表解析式(列表推导式)生成器generator

    通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

    要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

    In [1]: l = [x*x for x in range(10)]
    In [2]: l
    Out[2]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    In [3]: g = (x*x for x in range(10))
    In [4]: g
    Out[4]: <generator object <genexpr> at 0x0000003E8157CE58>

    创建Lg的区别仅在于最外层的[]()L是一个list,而g是一个generator。

    我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

    如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:

    In [5]: next(g)
    Out[5]: 0
    In [6]: next(g)
    Out[6]: 1
    In [7]: next(g)
    Out[7]: 4
    In [8]: next(g)
    Out[8]: 9

    生成器保存的是算法,每次调用 next(g) ,就计算出 g 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。

    当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

    In [9]: g = (x * x for x in range(10))
    In [10]: for n in g:
        ...:     print(n)
        ...: 
    0
    1
    4
    9
    16
    25
    36
    49
    64
    81

    所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

    generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

    比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

    1, 1, 2, 3, 5, 8, 13, 21, 34, ...

    斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            print(b)
            a, b = b, a + b #相当于t = (b, a + b);a = t[0];b = t[1](t是一个tuple)
            n = n + 1
        return 'done'
    
    fib(10)
    ----------------------------------------------------------------------
    1
    1
    2
    3
    5
    8
    13
    21
    34
    55

    fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

    也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            # print(b)
            yield b
            a, b = b, a + b #相当于t = (b, a + b);a = t[0];b = t[1](t是一个元组)
            n = n + 1
        return 'done'
    
    print(fib(10))
    ---------------------------------------------------
    <generator object fib at 0x0000001082BB7A20>

    这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。

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

    例子:通过yield实现在单线程的情况下并发运算的效果

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 # Author: Renyz
     4 #_*_coding:utf-8_*_
     5 import time
     6 def consumers(name):
     7     print("%s 准备吃包子啦!" %name)
     8     while True:
     9        baozi = yield
    10        print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
    11 
    12 def producer():
    13     c1 = consumers('')
    14     c2 = consumers('')
    15     c1.__next__()
    16     c2.__next__()
    17     print("开始做包子啦!")
    18     for i in range(10):
    19         time.sleep(1)
    20         print("做了2个包子!")
    21         c1.send(i)
    22         c2.send(i)
    23 
    24 producer()
    25 # 通过生成器实现协程并行运算
    View Code

    三、迭代器

    迭代是一个重复的过程,即每一次重复为一次迭代,并且每次迭代的结果都是下一次迭代的初始值

    可以直接作用于for循环的数据类型有以下几种:
    一类是集合数据类型,如list、tuple、dict、set、str等;
    一类是generator,包括生成器和带yield的generator function。
    这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
    可以使用isinstance()判断一个对象是否是Iterable对象:

    In [1]: from collections import Iterable
    In [2]: isinstance([],Iterable)
    Out[2]: True
    In [3]: isinstance({},Iterable)
    Out[3]: True
    In [4]: isinstance((),Iterable)
    Out[4]: True
    In [5]: isinstance('abc',Iterable)
    Out[5]: True
    In [7]: isinstance((x for x in range(10)),Iterable)
    Out[7]: True
    In [8]: isinstance(123,Iterable)
    Out[8]: False

    而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

    *可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

    可以使用isinstance()判断一个对象是否是Iterator对象:

    In [9]: from collections import Iterator
    In [10]: isinstance((x for x in range(10)),Iterator)
    Out[10]: True
    In [11]: isinstance(123,Iterator)
    Out[11]: False
    In [12]: isinstance('abc',Iterator)
    Out[12]: False
    In [13]: isinstance((),Iterator)
    Out[13]: False
    In [14]: isinstance([],Iterator)
    Out[14]: False
    In [15]: isinstance({},Iterator)
    Out[15]: False

    生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

    listdictstrIterable变成Iterator可以使用iter()函数:

    In [16]: isinstance(iter({}),Iterator)
    Out[16]: True
    In [17]: isinstance(iter([]),Iterator)
    Out[17]: True
    In [18]: isinstance(iter('abc'),Iterator)
    Out[18]: True

    为什么listdictstr等数据类型不是Iterator

    这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

    Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

    小结

    凡是可作用于for循环的对象都是Iterable类型;

    凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

    集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

    Python的for循环本质上就是通过不断调用next()函数实现的

    #1、为何要有迭代器?
    对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器
    
    #2、什么是可迭代对象?
    可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,如下:
    'world'.__iter__
    (4,5,6).__iter__
    [1,2,3].__iter__
    {'a':1}.__iter__
    {'a','b'}.__iter__
    open('a.txt').__iter__
    #3、什么是迭代器对象?
    可迭代对象执行obj.__iter__()得到的结果就是迭代器对象
    而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象
    
    #4、文件类型是迭代器对象
    open('a.txt').__iter__()
    open('a.txt').__next__()
    
    #5、总结:
    迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象

    四、装饰器

      写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展即:
      对扩展是开放的,而对修改是封闭的。
      因此,开放封闭原则主要体现在两个方面:
      对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
      对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类或者函数内部进行任何修改。
    装饰器就是:
      在不修改源代码和调用方式的基础上给其增加新的功能,多个装饰器可以装饰在同一个函数上

    1、无参装饰器

    原函数
    import
    time def bar(): time.sleep(1) print('这是bar函数') bar() ---------------------------------------------- 这是bar函数
    # 无参装饰器
    import time
    def timer(func):
        def deco():
            start_time = time.time()
            res = func()    #func()=bar()
            end_time = time.time()
            print('cost', end_time-start_time)
            return res
        return deco
    @timer  #bar=deco |func=bar
    def bar():
        time.sleep(1)
        print('这是bar函数')
    # bar = timer(bar)  #bar=deco |func=bar
    bar()

    2、有参装饰器

    # 有参装饰器
    import time
    def timer(func):
        def deco(*args,**kwargs):
            start_time = time.time()
            res = func(*args,**kwargs)
            end_time = time.time()
            print('cost', end_time-start_time)
            return res
        return deco
    @timer
    def bar(a, b):
        time.sleep(1)
        print('这是bar')
        print(a)
        print(b)
    bar(1,2)

    3、带参数的装饰器

    # 带参数的装饰器
    def default_engine(engine=None):
        def auth(func):
            def deco(*args, **kwargs):
                user = input('user:')
                password = input('password:')
                if engine == 'mysql':
                    if user == 'root' and password == 'root':
                        res = func(*args, **kwargs)
                        return res
                    else:
                        print('用户名或密码错误')
                else:
                    print('没有这个引擎')
            return deco
        return auth
    @default_engine(engine='mysql')
    def index():
        print('welcome to home page')
    # res = default_engine(engine='mysql')
    # index = res(index)
    index()

    五、闭包

    1、闭包定义

    (1)闭包函数必须有内嵌函数;

    (2)内嵌函数需要引用该嵌套函数上一级namespace中的变量;

    (3)闭包函数必须返回内嵌函数;

    通过这三点,就可以创建一个闭包;python装饰器就是使用了闭包

    闭包的好处:使代码变得简洁

    在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,并且把里面的函数返回,我们把这种情况叫闭包

    def test(num):  #定义一个函数
        def test_in(num2):  #又定义一个函数
            print(num + num2)
        return test_in  #返回test_in里面的引用/就是闭包的结果
    res = test(22)  #22表示给test函数赋值,num=22
    res(11) #11是参数num2的实参;加括号表示运行

    例子:

    def line_conf(x,y):
        def line(n):
            return x*n+y
        return line
    l1 = line_conf(3,8)
    print(l1(2))
  • 相关阅读:
    小米手机miui8.5连接电脑
    js数组map方法
    wxui入门
    动画函数封装
    系列属性(offset、scroll、client)
    定时器( setInterval和 setTimeout)
    BOM(浏览器对象模型)
    事件(绑定、解绑、冒泡)
    元素(element)创建
    节点(node)操作
  • 原文地址:https://www.cnblogs.com/renyz/p/11546822.html
Copyright © 2020-2023  润新知