• Python 基础 -2.4 函数进阶,名称空间,闭包,高阶函数,递归,匿名函数,生产式,生成器,迭代器


    名称空间

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

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

    • locals: 是函数内的名称空间,包括局部变量和形参
    • globals: 全局变量
    • builtins: 内置模块的名字空间

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

    作用域即范围

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

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

    作用域查找顺序

    level = 'L0'
    n = 22
    
    
    def func():
        level = 'L1'
        n = 33
        print(locals())
    
        def outer():
            n = 44
            level = 'L2'
            print(locals(),n)
    
            def inner():
                level = 'L3'
                print(locals(),n) #此外打印的n是多少?
            inner()
        outer()
    
    
    func()

    问题:在inner()里的打印的n的值是多少?

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

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

    闭包

    关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。

    def outer():
        name = 'alex'
    
        def inner():
            print("在inner里打印外层函数的变量",name)
    
        return inner
    
    
    f = outer() 
    
    f()

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

    匿名函数

    匿名函数就是不需要显式的指定函数名

    #这段代码
    
    def calc(x,y):
        return x**y
    
    print(calc(2,5))
    
    #换成匿名函数
    
    calc = lambda x,y:x**y
    print(calc(2,5))

    你也许会说,用上这个东西没感觉有毛方便呀, 。。。。呵呵,如果是这么用,确实没毛线改进,不过匿名函数主要是和其它函数搭配使用的呢,如下

    res = map(lambda x:x**2,[1,5,7,4,8])
    for i in res:
        print(i)

    输出

    1
    25
    49
    16
    64

    高阶函数

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

    def add(x,y,f):
        return f(x) + f(y)
    
    
    res = add(3,-6,abs)
    print(res)

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

    • 接受一个或多个函数作为输入
    • return 返回另外一个函数

    递归

    在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

    def calc(n):
        print(n)
        if int(n/2) ==0:
            return n
        return calc(int(n/2))
    
    calc(10)

    输出

    10
    5
    2
    1

    来看实现过程,我改了下代码

    def calc(n):
        v = int(n/2)
        print(v)
        if v > 0:
            calc(v)
        print(n)
    
    calc(10)

    输出

    5
    2
    1
    0
    1
    2
    5
    10
     

    列表生成式

    现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求你把列表里的每个值加1,

    In [56]: a = [i+1 for i in range(10)]
    
    In [57]: a
    Out[57]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    这样的写法就叫做列表生成式

    生成器

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

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

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

    In [58]: L = [x * x for x in range(10)]
    
    In [59]: L
    Out[59]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    In [60]: g = (x * x for x in range(10))
    
    In [61]: g
    Out[61]: <generator object <genexpr> at 0x7f0dc1fc5eb0>

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

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

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

    In [62]: next(g)
    Out[62]: 0
    
    In [63]: next(g)
    Out[63]: 1
    
    In [64]: next(g)
    Out[64]: 4
    
    In [65]: next(g)
    Out[65]: 9
    
    In [66]: next(g)
    Out[66]: 16
    
    In [67]: next(g)
    Out[67]: 25
    
    In [68]: next(g)
    Out[68]: 36
    
    In [69]: next(g)
    Out[69]: 49
    
    In [70]: next(g)
    Out[70]: 64
    
    In [71]: next(g)
    Out[71]: 81
    
    In [72]: next(g)
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-72-5f315c5de15b> in <module>()
    ----> 1 next(g)
    
    StopIteration: 

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

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

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

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

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

    迭代器

    我们已经知道,可以直接作用于for循环的数据类型有以下几种:

    一类是集合数据类型,如listtupledictsetstr等;

    一类是generator,包括生成器和带yield的generator function。

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

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

    In [90]: from collections import Iterable
    
    In [91]: isinstance([], Iterable)
    Out[91]: True
    
    In [92]: isinstance({}, Iterable)
    Out[92]: True
    
    In [93]: isinstance('abc', Iterable)
    Out[93]: True
    
    In [94]: isinstance((x for x in range(10)), Iterable)
    Out[94]: True
    
    In [95]: isinstance(100, Iterable)
    Out[95]: False

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

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

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

    In [97]: isinstance((x for x in range(10)), Iterator)
    Out[97]: True
    
    In [98]: isinstance([], Iterator)
    Out[98]: False
    
    In [99]: isinstance({}, Iterator)
    Out[99]: False
    
    In [100]: isinstance('abc', Iterator)
    Out[100]: False

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

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

    In [101]: isinstance(iter([]), Iterator)
    Out[101]: True
    
    In [102]: isinstance(iter('abc'), Iterator)
    Out[102]: True

    你可能会问,为什么listdictstr等数据类型不是Iterator

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

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

    小结

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

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

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

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

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

    实际上完全等价于:

    # 首先获得Iterator对象:
    it = iter([1, 2, 3, 4, 5])
    # 循环:
    while True:
        try:
            # 获得下一个值:
            x = next(it)
        except StopIteration:
            # 遇到StopIteration就退出循环
            break
  • 相关阅读:
    GFS.BigTable.MapReduce谷歌论文学习笔记
    Android图表
    JAVA内存管理
    关于Ajax工作原理
    走进AngularJs(二) ng模板中常用指令的使用方式
    走进AngularJs(一)angular基本概念的认识与实战
    使用CSS3 制作一个material-design 风格登录界面
    一分钟搞定AlloyTouch图片轮播
    PHP+JQUEY+AJAX实现分页
    全面的Seo面试题
  • 原文地址:https://www.cnblogs.com/caimengzhi/p/8323837.html
Copyright © 2020-2023  润新知