• python-函数进阶


                     python-函数进阶

    1,名称空间

    又名name space, 顾名思义就是存放名字的地方,存什么名字呢?举例说明,若变量x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方

    名称空间共3种,分别如下

    • locals: 是函数内的名称空间,包括局部变量和形参
    • globals: 全局变量,函数定义所在模块的名字空间
    • builtins: 内置模块的名字空间,通过dir(_builtins_)查看..
    >>> globals()
    {'__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>,<br> '__builtins__': <module 'builtins' (built-in)>,
    'x': 1, '__name__': '__main__', '__spec__': None}
    >>>
     
    >>> locals()
    {'__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, <br>'__builtins__': <module 'builtins' (built-in)>,
    'x': 1, '__name__': '__main__', '__spec__': None}
    >>>
     
    >>> dir(__builtins__)
    ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', <br>'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError',<br> 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError',
    'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', <br>'FileExistsError', 'FileNotFoundError',
    'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning'<br>, 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError',
    'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotADirectoryError',<br> 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError',
    'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'ReferenceError', <br>'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopIteration', 'SyntaxError', <br>'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError',
    'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', <br>'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', <br>'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError',
     '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', <br>'__name__', '__package__', '__spec__',
     'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', <br>'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr',<br> 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', <br>'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash',
    'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', <br>'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next',
    'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr',<br> 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted',
    'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
    View Code

    2,作用域的查找顺序

    不同变量的作用域不同就是由这个变量所在的命名空间决定的

    作用域即范围

    全局范围:全局存活,全局有效

    局部范围:临时存活,局部有效

    查看作用域方法 globals(), locals()

    作用域的查找顺序

    LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__

    • locals 是函数内的名字空间,包括局部变量和形参
    • enclosing 外部嵌套函数的名字空间
    • globals 全局变量,函数定义所在模块的名字空间
    • builtins 内置模块的名字空间

    3,for本质

      优点(和其它循环(while)相比的):比whlle等循环功能强大,不仅遍历的对象种类多,而且比普通循环效率更高(自动把遍历对象生成迭代器)

      定义:遍历可迭代对象(string,list,dict等),如果是遍历可迭代对象,for会自动把in后面的可迭代对象转换成迭代器,把所有元素依次加载到内存,遍历完成后自动处理异常

    1 for i in t:  # t被for转换成t.__iter__()
    2     print(i)

      等效于

      

     1 t = [1, 2, 3, 4, 5].__iter__()  # 可以通过__iter__()方法或iter()内建函数把Iterable类型变成Iterator对象。
     2 # 循环:
     3 while True:
     4     try:
     5         # 获得下一个值:
     6         i = t.next()
     7         print(i)
     8     except StopIteration:
     9         # 遇到StopIteration就退出循环
    10         break

    4,闭包

      内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。

      说明:闭包指的是内层函数,之所以叫闭包,闭是因为在外层函数内,包是因为和外层函数的变量绑定在一起。

    1.闭包的定义  # 结构:两个def,两个变量,返回内存地址
    2.考虑三部:
        1,定义的函数是否是闭包
        2,看他怎么用(执行外层函数赋给一个变量,后面执行这个变量会导致这个这个不会销毁 )
        3,产生bug,内层波函数修改外层函数变量,
    闭包定义
     1 def outer():
     2     n = 10
     3  
     4     def inner():
     5         print("inner:", n)
     6     return inner
     7  
     8 val = outer()
     9 print(val)
    10 val()
    11  
    12 # 执行结果<br><function outer.<locals>.inner at 0x0033A390>
    13 inner: 10

    闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域

    保存上次的运行环境(外层函数的的生命周期结束之后,外层函数的变量不被销毁)

     1 def outer(name):
     2     count = [0]
     3  
     4     def inner():
     5         count[0] += 1
     6         print('Hello,', name, ', ', str(count[0]) + ' access!')
     7         print(count)
     8  
     9     return inner
    10  
    11  
    12 hello = outer('hy')
    13 hello()
    14 hello()
    15 hello()
    16  
    17 ###
    18 Hello, hy ,  1 access!
    19 [1]
    20 Hello, hy ,  2 access!
    21 [2]
    22 Hello, hy ,  3 access!
    23 [3]

      这里面调用outer的时候就产生了一个闭包——inner,并且该闭包持有自由变量——count,因此这也意味着,当函数outer的生命周期结束之后,count这个变量依然存在,因为它被闭包引用了,所以不会被回收。
    另外再说一点,闭包并不是Python中特有的概念,所有把函数做为一等公民的语言均有闭包的概念。不过像Java这样以class为一等公民的语言中也可以使用闭包,只是它得用类或接口来实现。

    闭包思考:

    1.闭包似优化了变量,原来需要类对象完成的工作,闭包也可以完成
    2.由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存

     自由变量

    1,在闭包中,被调用并改动,引发bug的那个值,是自由变量。

    2,在装饰器中,被传进来的,但是未引发bug的那个值,是自由变量。(如装饰器的局部变量被嵌套函数调用并修改,从而引发bug,这个局部变量就是自由变量,从意义上来讲,这个局部变量被引用并改动,导致其不被释放,就会使局部变量升华成类似全局变量)。

    比如闭包中这个被传进去的name,被闭包调用并且没有改变,所以就是自由变量。装饰器里面,count被引用并修改了,形成了bug,导致count不被销毁,升华成类似全局变量,它也是自由变量。

     5,装饰器 

        装饰器 = 高阶函数 + 函数嵌套 + 闭包

    装饰器的定义:特殊的闭包
        1.自由变量是函数
        2.外层函数执行后赋给的变量名和使用装饰器的函数名相同,所以可以不改变用法。
        3.函数传到闭包中,函数并没有被修改,因此不会产生BUG)
    装饰器定义

    1,高阶函数

      变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

        编写高阶函数,就是让函数的参数能够接收别的函数。

        只需满足以下任意一个条件,即是高阶函数

          接受一个或多个函数作为输入

          return 返回另外一个函数本身

     1 # 接受一个或多个函数作为输入
     2 def add(x, y, f):
     3     return f(x)+f(y)
     4 val = add(5, 6, abs)  # 参数可以接受函数
     5 print(val)
     6   
     7 # return返回另外一个函数本身
     8 def calc(x, y):
     9     return x+y
    10 def add2():
    11     return calc  # return可以返回另一个函数
    12 val2 = add2()
    13 print(val2(3, 6))

     2,嵌套函数

      用于先准备一个函数,即外层函数执行完后,内层函数仍可以使用外层函数的变量,装饰器应用了嵌套函数。

     1 def line_conf(a, b):
     2     def line(x):  # 参数其实传到了这里来了
     3         return a * x + b
     4  
     5     return line
     6  
     7 line1 = line_conf(1, 1)
     8 line2 = line_conf(4, 5)
     9 print(line1(5))
    10 print(line2(5))<br><br>###<br>6<br>25

      通俗一点:执行的时候,line1=line_conf,带入函数,返回的是line,所以此时line1=line,变量a,b已经传入进去了,后面执行line1(5)的时候,其实就是执行第二行的line(x)。

      这个例子中,函数line与环境变量a,b构 成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个环境变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,利用闭包,使开始赋予的值没有销毁,闭包也具有提高代码可复用性的作用。缺点消耗了内存,没有释放出来。

      如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。利用闭包,我们实际上创建了泛函。line函数定义一种广泛意义的函数。这个函数的一些方面已经确定(必须是直线),但另一些方面(比如a和b参数待定)。随后,我们根据line_conf传递来的参数,通过闭包的形式,将最终函数确定下来。

    嵌套函数和闭包的联系:

      你可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的的表达式(通常是函数)。

    既然嵌套函数是一个闭包,就意味着一个嵌套函数可以”继承“容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。

    可以总结如下:

    • 内部函数只可以在外部函数中访问。
    • 内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。

    由于内部函数形成了闭包,因此你可以调用外部函数并为外部函数和内部函数指定参数

    3,装饰器

      装饰函数

      定义:装饰器是特殊的闭包(1,自由变量是函数。2,外层函数执行后赋给的变量名和使用装饰器的函数名相同,所以可以不改变用法。3,函数传到闭包中,函数并没有被修改,因此不会产生BUG)

      作用:不改动原函数、不改变原函数调用方式的前提下,扩展函数功能,遵循了开放封闭原则

    需求一:为源码函数拓展功能,且不改变调用方式

     # _*_ encoding:utf-8 _*_
     def login(func):
         print("passer user vertification...")
         func()
     
     
     def tv():
         print("Welcome [%s] to home page")
     
     tv = login(tv)  # tv的值为None
     tv()  # 这步出错:TypeError: 'NoneType' object is not callable
     
     # 输出
    passer user vertification...
    File "D:/PythonProject/0313/str.py", line 14, in <module> tv()
    TypeError: 'NoneType' object is not callable
    
    问题:
    1.调用方法改变了
    2.调用端还没有调用 tv(),扩展的功能就先执行了
    v1
    def login(func):
        print("passer user vertification...")
        return func
    
    @login  #@login等效于tv = login(tv)
    def tv():
        print("Welcome [%s] to tv page")
    
    tv()
    
    # 输出
    # passer user vertification...
    # Welcome [%s] to tv page
    #
    # 问题:
    # 调用端还没有调用 tv(),扩展的功能就执行执行
    v2
    def login(func):
        print("passer user vertification...")
        return func
    
    @login  #@login等效于tv = login(tv)
    def tv():
        print("Welcome [%s] to tv page")
    
    tv()
    
    # 输出
    # passer user vertification...
    # Welcome [%s] to tv page
    #
    # 问题:
    # 调用端还没有调用 tv(),扩展的功能就执行执行
    v2.1
    def login(func):
         def inner():
             print("passed user vertification...")
             func()
         return inner
     
     def home():
         print("Welcome [%s] to home page" % name)
     # @login # tv=login(tv)
     def tv():
         print("Welcome [%s] to tv page")
     def movie():
         print("Welcome [%s] to home movie" % name)
     
     tv=login(tv)
     tv()
    v3.1
    def outer(func):  
        def inner(args):  
            print("passed user vertification...")
            func(args)
    
        return inner
    
    @outer 
    #  这个@outer相当于将outer下面的函数当outer的参数执行,也即,tv = outer(tv)
    def tv(name):
        print("Welcome [%s] to tv page" % name)
    
    tv('hy')
    
    ###
    passed user vertification...
    Welcome [hy] to tv page
    v3.2


    需求二:需求一 + 传递参数 + 返回值

    def outer(func):  
        def inner(args):  
            print("passed user vertification...")
            func(args)
    
        return inner
    
    @outer 
    #  这个@outer相当于将outer下面的函数当outer的参数执行,也即,tv = outer(tv)
    def tv(name):
        print("Welcome [%s] to tv page" % name)
    
    tv('hy')
    
    ###
    passed user vertification...
    Welcome [hy] to tv page
    func无返回值+普通参数
    def outer(func):  # func=tv,函数的地址其实传到了这里
        def inner(args):  # 参数其实传到了这里(记)
            print("passed user vertification...")
            func(args)  # tv(name)
    
        return inner
    
    @outer  # == tv=outer(tv)/inner1=outer(tv)
            #  tv=inner,tv()=inner()
            #  这个@outer相当于将outer下面的函数当outer的参数执行,也即,tv = outer(tv)
    def tv(name):  # 传过去tv函数的地址
        print("Welcome [%s] to tv page" % name)
    
    tv('hy')  # inner('hy')
    #  第一次@outer:tv函数还在,inner==tv;第二次:tv('hy')==inner('hy')
    
    # 第一次执行
    # @outer → inner1=outer(tv) #这个tv是函数
    #    def inner(args):  #参数其实传到了这里(记)
    #        print("passed user vertification...")
    #       func(args) # tv(name)
    # 第二次执行
    # tv('hy') → inner1('hy')
    
    # 执行结果
    passed user vertification...
    Welcome [hy] to tv page
    
    #-------------------------  对比 ---------------
    
    
    def line_conf(a, b):
        def line(x):  # line变量
            return a * x + b
    
        return line  # 没有执行,指向函数的地址
    
    line1 = line_conf(1, 1)  # 正常来说,函数调用结束,内部所有变量销毁,但是由于闭包会储存在内存中,利用这个bug,可以执行后面的。
    #    def line(x):
    #        return a*x + b
    
    print(line1(5))  # 正常来说,a,b在这步没有了(但是有)
    
    #执行结果######
    # 6
    # 25
    
    func无返回值 + 普通参数 理解
    func无返回值+普通参数+理解
    def outer(func):  # func=tv
        def inner(*args, **kwargs):  # 参数其实传到了这里
            print("passed user vertification...")
            addfunc = func(*args, **kwargs)
            print("passed user logout...")
            return addfunc
        return inner
    
    
    @outer  # tv=outer(tv),tv=inner,tv()=inner()
    def tv(name):
        print("Welcome [%s] to tv page" % name)
        return 6
    
    print(tv('hy'))
    ---------------------------------------------------------------
    关于参数
    'hy'
    为什么传到了inner,因为第二次tv('hy') = inner1(arg)
    
    总结:
    1.装饰器的原理是闭包;
    2.闭包的作用是第一次执行后,函数tv还在inner中
    func无返回值 + 普通参数 + 新函数
    def outer(func):  # func=tv
        def inner(*args, **kwargs):  # 参数其实传到了这里
            print("passed user vertification...")
            func(*args, **kwargs)
        return inner
    
    
    @outer  # tv=outer(tv),tv=inner,tv()=inner()
    def tv(name):
        print("Welcome [%s] to tv page" % name)
    
    tv('hy')
    #######
    passed user vertification...
    Welcome [hy] to tv page
    func无返回值 + 动态参数
     1 def outer(func):  # func=tv
     2     def inner(args):  # 参数其实传到了这里
     3         print("passed user vertification...")
     4         return func(args)
     5     return inner
     6 
     7 
     8 @outer  # tv=outer(tv),tv=inner,tv()=inner()
     9 def tv(name):
    10     print("Welcome [%s] to tv page" % name)
    11     return 6
    12 
    13 print tv('hy')
    14 
    15 ########
    16 passed user vertification...
    17 Welcome [hy] to tv page
    18 6
    func有返回值 + 普通参数
    def outer(func):  # func=tv
        def inner(*args, **kwargs):  # 参数其实传到了这里
            print("passed user vertification...")
            return func(*args, **kwargs)
        return inner
    
    
    @outer  # tv=outer(tv),tv=inner,tv()=inner()
    def tv(name):
        print("Welcome [%s] to tv page" % name)
        return 6
    
    print tv('hy')
    
    ########
    passed user vertification...
    Welcome [hy] to tv page
    func有返回值 + 动态参数

    需求三:需求二 + 含参装饰器

    def wrapper(*wrapperargs):
        def outer(func):  # func=tv
            def inner(*args, **kwargs):  # 参数其实传到了这里
                print("passed user vertification...")
                print '[%s] looks like very [%s]' % (''.join(args), ''.join(wrapperargs))
                return func(*args, **kwargs)
            return inner
        return outer
    
    
    @wrapper('shuai')  # tv=outer(tv),tv=inner,tv()=inner()
    def tv(name):
        print("Welcome [%s] to tv page" % name)
        return 6
    
    print tv('hy')
    
    ########
    passed user vertification...
    [hy] looks like very [shuai]
    Welcome [hy] to tv page
    6
    func有返回值+func动态参数+装饰器动态参数
    def Before():
        print('before')
    
    
    def After():
        print('after')
    
    
    def wrapper(before_func, after_func):
        def outer(func):  # func=tv
            def inner(*args, **kwargs):  # 参数其实传到了这里
                print("passed user vertification...")
                before_func()
                after_func()
                return func(*args, **kwargs)
    
            return inner
    
        return outer
    
    
    @wrapper(Before, After)
    def tv(name):
        print("Welcome [%s] to tv page" % name)
        return 6
    
    
    print(tv('hy'))
    
    #######
    passed user vertification...
    before
    after
    Welcome [hy] to tv page
    
    func有返回值+func动态参数+装饰器参数(函数作为参数)
    func有返回值+func动态参数+装饰器参数(函数作为参数)

    需求四:多层装饰器

    def outer(func):  
        def inner(*args,**kwargs):  
            print('log')  
            r=func(*args,**kwargs)  
            print('after')  
            return r  
        return inner  
      
    def outer2(func):  
        def inner(*args,**kwargs):  
            if LOGIN_INFO['is_login']:  
                r=func()  
                return r  
            else:  
                print('please login')  
        return inner  
      
    #如果套两层装饰器,就是双层装饰器了,当然也有三层,四层,道理类似  
    #这里大家可能有疑惑,python在解释有 “@+函数”这种格式的语法时,会自动从里向外解读,再从外向内执行,  
    #也就是最里层的原函数被逐层装饰直到最外层,对应例子里,python先把f2(原函数)发给outer2(里层装饰器),被装饰后的outer2的inner再  
    #被outer(外层装饰器)装饰,最终返回的是outer的inner函数体。  
    @outer  
    @outer2  
    def f2(a,v):  
        print('F2')  
    #当然有人问主函数的调用为啥这样写呢,这个会在模块对于的blog中介绍  
      
    if __name__ == '__main__':  
        f2
    随意组合上面几种类型装饰器

    4,类装饰器

      装饰函数

     再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

    class Outer(object):
        def __init__(self, func):
            self._func = func
    
        def __call__(self):
            self._func()
    
    
    @Outer  # tv=Outer(tv)
    def tv():
        print("Welcome  to tv page")
    
    tv()
    
    ###
    Welcome  to tv page
    无参数 + 无返回值

    6,生成式

     1,列表生成器(推导式)

      python一种独特的语法,相当于语法糖的存在,可以帮你在某些场合写出比较精简炫酷的代码,带没有它,也不会有太多的影响。

      语法糖指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

      需求:把a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]里面的每个元素+1

     1 # sb版本
     2 >>> b = []
     3 >>> for i in a:b.append(i+1)
     4 >>> a = b
     5   
     6 # 普通版本
     7 a = [1,3,4,6,7,7,8,9,11]
     8   
     9 for index,i in enumerate(a):
    10     a[index] +=1
    11 print(a)
    12   
    13 # 文艺版本
    14 >>> a = map(lambda x:x+1,a)
    15 >>> a
    16 <map object at 0x02861810>
    17 >>> list(a)
    18 [2, 3, 4, 5, 6, 7, 8, 9]

    装逼版本(列表生成式版本)

    1 >>> a = [i+1 for i in range(10)]
    2 >>> a
    3 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]<br><br>>>>a = [i for i in range(5) if i % 2 == 0 and not i == 2]<br>>>>print(a)<br>[0,4] 

    加入三元运算式

    1 # 三元运算加入
    2 a = list(range(10))
    3 c = [i if 8 < 5 else i+1 for i in a]  <br># 每次都会先执行for i in a,然后再循环前面的部分,每循环一次,前面都会执行一次,挨个得出相应的值并存放
    4 # a可以循环字典,元组,甚至字符串<br>print(c)
    5  
    6 # 执行结果
    7 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    # 看下面代码回答输出的结果是什么?为什么?
    
    result = [lambda x: x + i for i in range(10)]
    print(result[0](10))
      
    
    这是一个结合了变量作用域、列表推导式和匿名函数的题目,较为复杂,比较考验Python基础知识掌握程度。有同学可能会回答10,其实答案是19,并且result[0~9](10)的结果都是19。
    
    这是因为函数具有调用时才查找变量的特性。在你没调用它之前,它不会保存也不关心它内部变量的具体值。只有等到你调用它的时候,它才逐一去找这些变量的具体值。这里的result[0]被调用的时候,变量i已经循环完毕,变成9了,而不是想象中的动态0-9值。
    
    那如果不想要这样的结果,想要i是循环的值怎么办?不要直接引用上层变量,把变量直接传进来。
    
    result = [lambda x, i=i: x + i for i in range(10)]
    print(result[0](10))
    面试真题 

    2,生成器

      作用:

      1.逐步生成序列,不用像列表一样初始化时就要开辟所有的空间(相当于python2.x的xrange

      2.模拟并发:协程(Python实现协程最简单的方法,就是使用yield)

      定义:如果函数中包含yield语法,那这个函数就会变成生成器,这个函数调用时返回一个迭代器,生成器属于迭代器

      通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

      所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

      生成器也可以理解为(迭代器对象返回的是函数;特殊的函数;特殊的迭代器(遍历无序数据结构),而特殊在迭代器对象是一个函数,不是列表,字符串(数字)的集合对象。)

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

    1 >>> L = [x * x for x in range(10)]
    2 >>> L
    3 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    4 >>> g = (x * x for x in range(10))
    5 >>> g
    6 <generator object <genexpr> at 0x1022ef630>

        L是一个list,g是一个generator,如果要一个一个打印出来,使用next()函数获得generator的下一个返回值。 

      特性:1,取一次创建一次。2,只能往前走,不能回退。

    1 >>> next(g)
    2 0
    3 >>> next(g)
    4 1
    5 >>> next(g)
    6 4
    7 >>> next(g)
    8 9

    使用while执行完以后也会报错。

    a = list(range(5))
    while True:
        next(a)
    
    # 执行结果:正常的执行完以后还是会报错。

    生成器循环最好使用for来进行。

      使用next,while生产完后会报错,但是用for循环不会报错,而generator也是可迭代对象:

    1 >>> g = (x * x for x in range(4))
    2 >>> for n in g:
    3 ...     print(n)
    4 ...
    5 0
    6 1
    7 4
    8 9

     斐波那契数列:除第一个和第二个数外,任意一个数都可由前面两个数相加得到。

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

    1 def fib(max):
    2     n, a, b = 0, 0, 1
    3     while n < max:
    4         print(b)
    5         a, b = b, a + b # 相当于 t = a + b, a = b, b = t
    6         n = n + 1
    7     return 'done'<br>fib(3)

     执行结果:1,1,2,3  

       利用yield改为生成器

     1 def fib(max):
     2     n, a, b = 0, 0, 1
     3     while n < max:
     4         print('before yield')
     5         yield b  # 把函数的执行过程冻结在这一步,并且把b的值返回给外面的next()
     6         print(b)
     7         a, b = b, a+b
     8         n = n+1
     9     return "done"
    10 f = fib(15)  # turn function into a generator
    11 next(f)  # fitst time call next()
    12 next(f)
    13 next(f)
    14 next(f)
    15 next(f)
    16  
    17 # 执行结果
    18 before yield
    19 1
    20 before yield
    21 1
    22 before yield
    23 2
    24 before yield
    25 3
    26 before yield

     在函数执行中,有些数据想返回到外部程序里,可以使用yield

       里边有yield,函数名一加括号,内部代码根本不执行,只是生成一个生成器对象,

     3,生成器调用方法

      在python2中,range = list, xrange = 生成器

      在python3中,range = 生成器, xrange 没有

     4,函数生成器

      支持更复杂的步骤,可以使用函数生成器。

     1 def range2(n):
     2     conut = 0
     3     while conut < n:
     4         print('count', count)
     5         count += 1
     6         yield count
     7 new_range = range2(10)
     8 r1 = next(new_range)
     9 print(r1)
    10 r2 = next(new_range)  # 相等于 r2 = new_range.__next__
    11 print(r2)

     yield vs return

        return 返回并中止函数

        yield 返回 数据,并冻结当前的执行过程

        next 唤醒冻结的函数执行过程,继续执行,直到遇到下一个yield

      函数有了yield之后

        函数名加()就变得到了一个生成器。

        return在生成器里,代表生成器的中止,直接报错。

     5,生成器send方法

      sent作用:

        唤醒并继续执行

        发送一个信息到生成器内部

     1 def range2(n):
     2     conut = 0
     3     while conut < n:
     4         print('count', count)
     5         count += 1
     6         sign = yield count     
     7         if sign == 'stop':       
     8           print('----sign',sign)
     9 break
    10 new_range = range2(3) 
    11 n1= next(new_range) 
    12 
    13 new_range.send('stop')
    14 # 执行结果 
    15 count 0 
    16 ----sign stop 
    17 count 1        

    6,协程函数

      python中yield浅析

      利用yield将函数变成一个generater(生成器),整个函数变成一个generater对象,函数返回一个iterable对象(迭代值)

      当一个函数在执行过程中被阻塞时,就用yield挂起,然后执行另一个函数。当阻塞结束后,可以用next()或者send()唤醒。相比多线程,协程的好处是它在一个线程内执行,避免线程之间切换带来的额外开销,而且多线程中使用共享资源,往往需要加锁,而协程不需要,因为代码的执行顺序是你完全可以预见的,不存在多个线程同时写某个共享变量而导致出错的情况。

     1 #如果在一个函数内部yield的使用方式是表达式形式的话,如x=yield,那么该函数成为协程函数
     2 def eater(name):
     3     print('%s start to eat food' % name)
     4     food_list = []
     5     while True:
     6         food = yield food_list
     7         print('%s get %s ,to start eat' % (name, food))
     8         food_list.append(food)
     9  
    10         print('done')
    11  
    12 e = eater('钢蛋')
    13 # print(e)
    14  
    15 print(next(e))
    16  
    17 print(e.send('包子'))
    18 print(e.send('韭菜馅包子'))
    19 print(e.send('大蒜包子'))
    20  
    21 ###
    22 钢蛋 start to eat food
    23 []
    24 钢蛋 get 包子 ,to start eat
    25 done
    26 ['包子']
    27 钢蛋 get 韭菜馅包子 ,to start eat
    28 done
    29 ['包子', '韭菜馅包子']
    30 钢蛋 get 大蒜包子 ,to start eat
    31 done
    32 ['包子', '韭菜馅包子', '大蒜包子']

    yield的表达式形式:

     1 yield的表达式形式:
     2     food=yield
     3      
     4 def eater(name):
     5     print('%s start to eat' %name)
     6     while True:
     7         food=yield
     8         print('%s eat %s' %(name,food))
     9          
    10 e=eater('钢蛋')
    11      
    12 #e.send与next(e)的区别
    13 #1.如果函数内yield是表达式形式,那么必须先next(e)
    14 #2.二者的共同之处是都可以让函数在上次暂停的位置继续运行,不一样的地方在于<br>#  send在触发下一次代码的执行时,会顺便给yield传一个值

    7,生成器总结

      生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。

      生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。

    生成器的特点:

    1. 节约内存
    2. 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的
     1 from collections import Iterator
     2 #生成器就是一个函数,这个函数内包含有yield这个关键字
     3 def test():
     4     print('one')
     5     yield 1 #return 1
     6 
     7 
     8 g=test()
     9 print(g)
    10 print(isinstance(g,Iterator))
    11 
    12 ###
    13 <generator object test at 0x000001E80F8F0780>
    14 True
    15 
    16 ---------------------------------------------------------------------
    17 from collections import Iterator
    18 #生成器就是一个函数,这个函数内包含有yield这个关键字
    19 def test():
    20     print('one')
    21     yield 1 #return 1
    22 
    23 
    24 g=test()
    25 print(g)
    26 print(isinstance(g,Iterator))
    27 
    28 print(next(g))
    29 
    30 ###
    31 <generator object test at 0x0000023F1F5E0780>
    32 True
    33 one  #调用next方法时,生成器才执行
    34 1
    35 --------------------------------------------------------------
    36 def countdown(n):
    37     print('start coutdown')
    38     while n > 0:
    39         yield n #1
    40         n-=1
    41     print('done')
    42 
    43 g=countdown(5)
    44 # print(g)
    45 
    46 # print(next(g))
    47 # print(next(g))
    48 # print(next(g))
    49 # print(next(g))
    50 # print(next(g))
    51 # print(next(g))
    52 
    53 # for i in g: #iter(g)
    54 #     print(i)
    55 
    56 # while True:
    57 #     try:
    58 #         print(next(g))
    59 #     except StopIteration:
    60 #         break
    61 
    62 ------------------------------------------
    63 >>> def createGenerator() :
    64 ...    mylist = range(3)
    65 ...    for i in mylist :
    66 ...        yield i*i
    67 ...
    68 >>> mygenerator = createGenerator() # create a generator
    69 >>> print(mygenerator) # mygenerator is an object!
    70 <generator object createGenerator at 0xb7555c34>
    71 >>> for i in mygenerator:
    72 ...     print(i)
    73 0
    74 1
    75 4
    示例

    8,迭代式

      迭代器是一个可以记住遍历的位置的对象。

      迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

      迭代器有两个基本的方法:iter() 和 next()

      迭代器就类似一个循环,迭代一次,就是相当于循环一次。

      可以直接作用于for循环的数据类型:

    • 一类是集合数据类型,如listtupledictsetstr等;
    • 一类是generator(生成器),包括生成器和带yield的 generator function。

     1,可迭代对象

       直接作用于for循环的对象统称为可迭代对象:Iterable

     1 可以使用isinstance()判断一个对象是否是Iterable对象:
     2 >>> from collections import Iterable
     3 >>> isinstance([], Iterable)
     4 True
     5 >>> isinstance({}, Iterable)
     6 True
     7 >>> isinstance('abc', Iterable)
     8 True
     9 >>> isinstance((x for x in range(10)), Iterable)
    10 True
    11 >>> isinstance(100, Iterable)
    12 False

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

     2,迭代器:是一种数据流

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

     1 可以使用isinstance()判断一个对象是否是Iterator对象:
     2   
     3 >>> from collections import Iterator
     4 >>> isinstance((x for x in range(10)), Iterator)
     5 True
     6 >>> isinstance([], Iterator)
     7 False
     8 >>> isinstance({}, Iterator)
     9 False
    10 >>> isinstance('abc', Iterator)
    11 False

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

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

    1 >>> isinstance(iter([]), Iterator)
    2 True
    3 >>> isinstance(iter('abc'), Iterator)
    4 True

    3,小结

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

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

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

      Python3的for循环本质上就是通过不断调用next()函数实现的,例如:

    1 for x in [1, 2, 3, 4, 5]:
    2     pass

    实际上完全等价于:

     1 # 首先获得Iterator对象:
     2 it = iter([1, 2, 3, 4, 5])
     3 # 循环:
     4 while True:
     5     try:
     6         # 获得下一个值:
     7         x = next(it)
     8     except StopIteration:
     9         # 遇到StopIteration就退出循环
    10         break

    9,函数中的一些大坑

     ①函数的默认函数一定要是不变对象(immutable)

    • str
    • int
    • float
    • tuple
    • 数值型(number)

      看下面一个例子:

    1 def foo(bar=[]):
    2     bar.append('a')
    3     return bar
    4 print(foo())#['a']
    5 print(foo())#['a','a']
    6 print(foo())#['a','a','a']

    乍一眼一看,每次调用foo(),变量bar都应该重置为[]啊,为什么上一次的结果会进行保留呢?
    从Python文档中可以找到这样一句话

    Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. For example, the following function accumulates the arguments passed to it on subsequent calls:

    翻译过来就是:重要警告:默认值只计算一次。当默认值是可变对象(如列表,字典或大多数类的实例)时,这会有所不同。例如,以下函数会累积在后续调用中传递给它的参数。

      这个原因是由于默认参数只计算一次,因为list 是可变数据类型,函数每次调用时,L 是同一个对象的引用。就相当于全局变量一般了

    def foo(bar=None):
        if bar is None:
            bar=[]
        bar.append('a')
    print(foo())#['a']
    print(foo())#['a']
    print(foo())#['a']

    记住,默认参数一定要是不可变类型

  • 相关阅读:
    决策树算法小结
    低配置电脑播放 flash 视频时 占 cpu 资源过高的解决方法
    ts tp 高清播放软件 Elecard MPEG Player 6.0.130827
    KBS2 SBS MBC 高清播放地址 + mplayer 播放 录制
    MPlayer-ww 增加边看边剪切功能
    MPlayer 增加边看边剪切功能
    -fomit-frame-pointer 编译选项在gcc 4.8.2版本中的汇编代码研究
    ffplay mini 媒体播放器
    libavcodec/dxva2.h:40:5: error: unknown type name 'IDirectXVideoDecoder'
    ARGB32 to YUV12 利用 SDL1.2 SDL_ttf 在视频表面输出文本
  • 原文地址:https://www.cnblogs.com/herosyuan/p/10043238.html
Copyright © 2020-2023  润新知