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
posted @ 2018-01-21 12:18  Love_always_online  阅读(186)  评论(0编辑  收藏  举报