函数进阶,装饰器,生成器,迭代器

名称空间

定义:假如有 x = 1 , 1是存放到内存的,那 x 存哪里的呢?名称空间是存放名字x与1绑定关系的地方。

名称空间分3种:

  • locals:是函数内的名称空间,包括局部变量和形参。通过locals()可以打印当前名称空间的局部变量,如果在函数里就是函数里的局部变量。
  • globals:全局变量,函数定义所在模块的名字空间。通过globals()可以打印所有的全局变量,无论是在函数内还是函数外。
  • builtins:内置模块的名字空间。可通过dir(__builtins__)打印所有的内置方法

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

作用域即范围:

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

作用域查找顺序:

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

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

闭包

首先看例子:

def func():
    name = "Alex"

    def inner():
        print("在inner里打印name:", name)

    return inner

f = func()
f()
View Code

输出:

在inner里打印name: Alex

以上例子可以看出,嵌套函数中,直接执行func函数,里面的inner函数并没有执行,而是func函数将inner函数对象返回给了外部,给到了f ,这时候 f 实际上就是inner函数,f 加上括号就相当于直接执行inner函数。这样使得嵌套在函数内的子函数在外部也可被调用执行,调用的时候依然能够获取inner函数所在作用域的变量以及参数。

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

闭包的意义:

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

定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).

闭包详解引用:

https://www.cnblogs.com/JohnABC/p/4076855.html

装饰器

装饰器就是在不修改原来程序的情况下给程序添加新的功能。

  • 软件开发中的一个原则:开放-封闭原则
  • 开放:已实现的功能代码块不应该被修改
  • 封闭:对现有功能的扩展开放

装饰器练习题

 1 '''
 2 一:编写3个函数,每个函数执行的时间是不一样的,
 3 提示:可以使用time.sleep(2),让程序sleep 2s或更多,
 4 
 5 二:编写装饰器,为每个函数加上统计运行时间的功能
 6 提示:在函数开始执行时加上start=time.time()就可纪录当前执行的时间戳,函数执行结束后在time.time() - start就可以拿到执行所用时间
 7 
 8 三:编写装饰器,为函数加上认证的功能,即要求认证成功后才能执行函数
 9 
10 四:编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码
11 提示:从文件中读出字符串形式的字典,可以用eval('{"name":"egon","password":"123"}')转成字典格式
12 '''
13 
14 import time
15 
16 
17 def runtime(func):
18     def inner():
19         start_time = time.time()
20         func()
21         end_time = time.time()
22         run_time = end_time - start_time
23         print("run time:", run_time)
24 
25     return inner
26 
27 
28 def get_account_from_file():
29     f = open("account2.txt", mode="r", encoding="utf-8")
30     data = eval(f.read())
31     f.close()
32     return data
33 
34 
35 login_status = False
36 
37 
38 def login(func):
39     def inner(*args, **kwargs):
40         global login_status
41         if login_status == False:
42             username = input("username:>")
43             password = input("password:>")
44             account_dict = get_account_from_file()
45             if username == account_dict["name"] and password == account_dict["password"]:
46                 print("登录成功")
47                 login_status = True
48             else:
49                 print("用户名或密码错误.")
50 
51         if login_status:
52             print("已通过验证..")
53             func(*args, **kwargs)
54 
55     return inner
56 
57 
58 @login
59 @runtime
60 def func1():
61     print("func1 running...")
62     time.sleep(2)
63 
64 
65 @login
66 @runtime
67 def func2():
68     print("func2 running...")
69     time.sleep(3)
70 
71 
72 @login
73 @runtime
74 def func3():
75     print("func3 running...")
76     time.sleep(1)
77 
78 
79 func1()
80 func2()
81 func3()

列表生成式

有一个需求:有一个列表,要对列表里的每个元素加1,如何做,有以下几种

# 版本一:
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = []
for i in a:
    b.append(i + 1)
print(b)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 版本二:
c = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for index, i in enumerate(c):
    c[index] = i + 1
print(c)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 版本三:
d = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
d = list(map(lambda x: x + 1, d))
print(d)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 版本四:列表生成式
e = [i + 1 for i in range(10)]
print(e)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
f = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
f = [i if i < 5 else i * i for i in f]    # 列表生成式
print(f)  # 输出:[0, 1, 2, 3, 4, 25, 36, 49, 64, 81]  

生成器

通过前面的列表生成式,我们可以很容易的创建一个列表,但是假如我要创建100万的数据,那么这100万的数据就会全部在内存中,而内存是有限的,不可能让你无限制的存,而且我只需要访问前面的一部分数据,后面的其实根本不会使用,还以这样的方式创建就对资源消耗太大了。

如果列表里的元素可以按照某种算法推算出来,那我们就可以循环的推算出后面的元素的值,就可以不用完整的创建整个列表从而大大节省资源消耗,这种一边循环一边计算的机制,就称为生成器(generator)

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

>>> L=[i*i for i in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>>
>>>
>>> g=(i*i for i in range(10))
>>> g
<generator object <genexpr> at 0x0000022BD464E150>
>>>

如何获取generator里的元素呢?通过next()方法来获取。

说明:

生成器生成的只是方法,并不执行。

next()只能往前走,不能往回退。意思就是已经通过next()获取的元素不能够再次获取。 

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

一步一步的nexg(g)很麻烦,我们可以通过for循环来获取元素值,还不会报StopIteration异常

g = (i * i for i in range(10))
for j in g:
    print(j)

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

比如斐波那契数列(Fibonacci),除第一个和第二个数外,任意一个数都是由前两个数相加得到,用列表生成式写不出来,但是用函数打印出来很容易:

1 def fib(max):
2     n, a, b = 0, 0, 1
3     while n < max:
4         print(b)
5         a, b = b, a + b
6         n = n + 1
7     return 'done'
8 
9 fib(10)

注意:

a, b = b, a + b

相当于:

t = a + b

a = b

b = t

说到这里我们还没有用到生成器,回头想一下生成器的实现逻辑,再来看看这个斐波那契数列的程序,fib函数实际上是定义了斐波那契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这个逻辑和生成器的逻辑是非常相似的。

上面的函数里生成器 generator就是一步之遥,要把fib函数变成generator,只需要把print(b)n 改为 yield b 就可以了:

 1 def fib(max):
 2     n, a, b = 0, 0, 1
 3     while n < max:
 4         # print(b)
 5         yield b  # 改为 yield 就变成了generator,遇到yield程序就停在这里,等到下一次next()时,程序继续从这里开始运行,然后又等待下一次next(),循环往复,知道报出StopIteration异常
 6         a, b = b, a + b
 7         n = n + 1
 8     return 'done'
 9 
10 
11 g = fib(10)
12 print(g)  # 输出 <generator object fib at 0x000001F14DE340F8>  说明函数变成了generator
13 print(next(g))  # next(g)  出generator里的元素
14 print(next(g))
15 print(next(g))
16 print(next(g))
17 print(next(g))
18 print("其他程序在这个地方执行一下")  # 在next的过程中依然可以执行其他的程序
19 print(next(g))  # 还可以继续执行之前的next
20 print(next(g))
21 print(next(g))
22 print(next(g))
23 print(next(g))
View Code

输出结果

<generator object fib at 0x000001F14DE340F8>
1
1
2
3
5
其他程序在这个地方执行一下
8
13
21
34
55

关于执行流程:

函数是顺序执行,遇到 return 语句或者最后一行函数语句就返回。

变成generator的函数,在每次调用 next() 的时候执行,遇到yield语句返回,再次被next()调用时,从上次返回的yield语句处继续执行。

yield后面加上a,就表示返回a,类似于 return a ,但yield不会退出,return会退出。

关于如何拿到generator里的return返回值:

如果我们再加一个next(g) 这时候就要报StopIteration异常了,因为已经没有值可以拿了,但是函数最后有一个   return  "done"  的返回值,我们是可以看到的。

 1 Traceback (most recent call last):
 2 <generator object fib at 0x000001D4FA4540F8>
 3   File "D:/PycharmProjects/python_fullstack_middle/第二模块:函数编程/第1章·函数、装饰器、迭代器、内置方法/函数/生成器.py", line 41, in <module>
 4 1
 5 1
 6 2
 7 3
 8 5
 9 其他程序在这个地方执行一下
10 8
11 13
12 21
13 34
14 55
15     print(next(g))
16 StopIteration: done
View Code

如果使用 for 循环则拿不到generator里面的 return 返回值。如果要拿到返回值,必须要捕获StopIteration异常,返回值在StopIteration的value中。

g = fib(10)
while True:
    try:
        x = next(g)
        print("g:", x)
    except StopIteration as e:
        print("Genrator return value:", e.value)
        break

输出

g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
g: 13
g: 21
g: 34
g: 55
Genrator return value: done

关于捕获异常,后面还会详细讲解。

再来看看生成器的调用方法:

g = (i for i in range(10))
print(next(g))
print(next(g))
print(next(g))

print("---------------")
for i in g:
    print(i)

输出

0
1
2
---------------
3
4
5
6
7
8
9

next()之后,之前的值就没有了,所以看到for循环的时候是从3开始的

for循环其实就是每循环一次就next一次

range其实也是一个生成器

关于range:

在python2里:
range(100000000000000) 会直接生成这么多个数据

在python3里:
range(100000000000000) 直接生成一个公式,根本就没有创建,在python2里也有同样的 不过叫 xrange(100000000000000) 和py3的range是一样的。

python2:
  range = list
  xrange = 生成器
python3
  range = 生成器
  xrange 没有  

函数写生成器:

生成器的创建方式:

  1. 列表生成式

  2. 函数

区别:

  列表生成式最后只能写一个三元运算,更复杂的情况没办法办到。

  所以要用到函数

 1 def range2(n):
 2     count = 0
 3     while count < n:
 4         print("count", count)
 5         count += 1
 6         yield count
 7 
 8 
 9 new_range = range2(10)
10 
11 r1 = next(new_range)
12 print(r1)
13 r2 = next(new_range)
14 print(r2)
15 r3 = next(new_range)
16 print(r3)
View Code

输出:

count 0    # 由print打印的
1          # 由yield返回的
count 1
2
count 2
3

 yield vs return:

return:返回并终止function

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

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

 生成器send方法:

在函数里已经有了yield后,再写return是不会返回return值的,并且会报StopIteration异常。

函数有了yield之后:

1. 调用时函数名加 ()  就便得到了一个生成器 

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

send作用:

1. 唤醒并继续执行

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

 1 def range2(n):
 2     count = 0
 3     while count < n:
 4         print("count", count)
 5         count += 1
 6         sign = yield count
 7         print("收到来自send的消息::", sign)
 8         if sign == "stop":
 9             print("我要停止程序运行了,bye bye")
10             break  # break后就执行return了,return后会报错,需要自己捕获
11     return 333
12 
13 
14 new_range = range2(3)
15 n1 = next(new_range)
16 new_range.send("stop")
View Code

输出:

count 0
Traceback (most recent call last):
收到来自send的消息:: stop
我要停止程序运行了,bye bye
  File "D:/PycharmProjects/python_fullstack_middle/第二模块:函数编程/第1章·函数、装饰器、迭代器、内置方法/函数/生成器send.py", line 16, in <module>
    new_range.send("stop")
StopIteration: 333

send()里面不写值的话,默认是发送一个None的。

next()默认也是发了一个None的。  

next(iterator, default=None)   如果后面的参数给了值的话,但next完所有元素的时候也不会报StopIteration异常。

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

 1 import time
 2 
 3 
 4 def consumer(name):
 5     print("%s 准备吃包子啦!" % name)
 6     while True:
 7         baozi = yield
 8         print("包子[%s]来了,被[%s]吃了!" % (baozi, name))
 9 
10 
11 def producer(name):
12     c = consumer('A')
13     c2 = consumer('B')
14     c.__next__()
15     c2.__next__()
16     print("老子开始准备做包子啦!")
17     for i in range(10):
18         time.sleep(1)
19         print("做了%s个包子!" % (i))
20         c.send(i)
21         c2.send(i)
22 
23 
24 producer("alex")
View Code

输出:

 1 A 准备吃包子啦!
 2 B 准备吃包子啦!
 3 老子开始准备做包子啦!
 4 做了0个包子!
 5 包子[0]来了,被[A]吃了!
 6 包子[0]来了,被[B]吃了!
 7 做了1个包子!
 8 包子[1]来了,被[A]吃了!
 9 包子[1]来了,被[B]吃了!
10 做了2个包子!
11 包子[2]来了,被[A]吃了!
12 包子[2]来了,被[B]吃了!
13 做了3个包子!
14 包子[3]来了,被[A]吃了!
15 包子[3]来了,被[B]吃了!
16 做了4个包子!
17 包子[4]来了,被[A]吃了!
18 包子[4]来了,被[B]吃了!
19 做了5个包子!
20 包子[5]来了,被[A]吃了!
21 包子[5]来了,被[B]吃了!
22 做了6个包子!
23 包子[6]来了,被[A]吃了!
24 包子[6]来了,被[B]吃了!
25 做了7个包子!
26 包子[7]来了,被[A]吃了!
27 包子[7]来了,被[B]吃了!
28 做了8个包子!
29 包子[8]来了,被[A]吃了!
30 包子[8]来了,被[B]吃了!
31 做了9个包子!
32 包子[9]来了,被[A]吃了!
33 包子[9]来了,被[B]吃了!
View Code

迭代器

可以理解为 迭代器=循环 ,迭代一次,循环一次。

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

  • 一类是集合数据类型,如 list、tuple、dict、set、str等;
  • 一类是generator,包括生成器和带 yield 的generator function

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

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

 1 >>> from collections import  Iterable
 2 >>> isinstance([],Iterable)
 3 True
 4 >>> isinstance({},Iterable)
 5 True
 6 >>> isinstance("abc",Iterable)
 7 True
 8 >>> isinstance((x for x in range(10)),Iterable)
 9 True
10 >>> isinstance(100,Iterable)
11 False
12 >>>
View Code

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

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

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

 1 >>> from collections import  Iterator
 2 >>> isinstance((x for x in range(10)),Iterator)
 3 True
 4 >>> isinstance([],Iterator)
 5 False
 6 >>> isinstance({},Iterator)
 7 False
 8 >>> isinstance("abc",Iterator)
 9 False
10 >>>
View Code

生成器都是 Itrator 对象。

list 、dict、str 是 Iterable,但不是 Iterator。

可以把 list、dict、str 等 Iterable 变成 Iterator ,使用 iter() 函数:

>>> li = [1,2,3,4,5,7]
>>> isinstance(li,Iterable)
True
>>> isinstance(li,Iterator)
False
>>> li2=iter(li)
>>> isinstance(li2,Iterator)
True  

为什么 list、dict、str 等数据类型不是 Iterator ?

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

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

小结:

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

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

集合数据类型如 list、dict、str等时Iterable但不是Iterator,不过可以通过iter() 函数变成 Itrator对象。

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

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

完全等价于:  

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

  

原文地址:https://www.cnblogs.com/joneylulu/p/10211961.html