zoukankan      html  css  js  c++  java
  • 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']

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

  • 相关阅读:
    XAML学习笔记之Layout(五)——ViewBox
    XAML学习笔记——Layout(三)
    XAML学习笔记——Layout(二)
    XAML学习笔记——Layout(一)
    从0开始搭建SQL Server 2012 AlwaysOn 第三篇(安装数据,配置AlwaysOn)
    从0开始搭建SQL Server 2012 AlwaysOn 第二篇(配置故障转移集群)
    从0开始搭建SQL Server 2012 AlwaysOn 第一篇(AD域与DNS)
    Sql Server 2012 事务复制遇到的问题及解决方式
    Sql Server 2008R2升级 Sql Server 2012 问题
    第一次ACM
  • 原文地址:https://www.cnblogs.com/herosyuan/p/10043238.html
Copyright © 2011-2022 走看看