Example A:
Requirement:
[1, 2, 3] --> [2, 4, 6]
Solution:
def maps(a):
return [i*2 for i in a]
1-1 列表生成式
>>> a = [1,2,3]
>>> a
[1, 2, 3]
>>>
>>> [i*2 for i in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# for i in range(10) 这是一个循环,循环 10 次,i*2 中的 i 就是 for 循环中的 i
# 把 for 循环中的临时变量 i 赋值到前面的 i*2 的 i 中,得到的结果,就是列表中的元素
# 这个列表 和 列表 a 的区别是:列表 a 是写死的,下面的 i*2 是可以进行操作的
1-1-1
# [i*2 for i in range(10)] 相当于
>>> a = []
>>> for i in range(10):
... a.append(i*2)
...
>>> a
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# 相当于至少 3 行代码; [i*2 for i in range(10)] 就叫 列表生成式
列表生成式的主要作用就是使代码更简洁
1-1-2
当然列表生成式也可以传函数,比如
>>> [func(i) for i in range(10)]
注:
# 在 python2 上 range(10) 数据已经准备好了
# 在 python3 上 range(10) 是没有准备好的
Example B:
Given a string of digits, you should replace any digit below 5 with ‘0‘ and any digit 5 and above with ‘1‘.
Return the resulting string.
Solution A:
def fake_bin(x):
return ‘‘.join[‘0‘ if int(a) < 5 else ‘1‘ for a in x]
Solution B:
def fake_bin(x):
return ‘‘.join(‘0‘ if int(a) < 5 else ‘1‘ for a in x)
# 为什么Solution A 是[],而Solution B 中是 ()?
2-1 生成器
通过列表生成式,我们可以直接创理一个列表。但是,受到内存限制,列表容量肯定是有限的。
如果仅需要访问前面几个元素,后面绝大多数元素占用的空间都白白浪费了。
所以, 如果列表元素可以按照某种算法推算出来,是否可以在循环的过程中不断推算出后续的元素呢?
这样就不必创建完整的list,从而节省大量的空间。
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator, 有很多种方法。
第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator;
# 比如希望在循环的时候,循环到 列表的 第 4 个元素时,这个元素才刚生成;
# 这样,就不用把所有数据都提前准备好了,就可以节省空间
2-1-1
>>> [i*2 for i in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> (i*2 for i in range(10))
<generator object <genexpr> at 0x0000027B0E6C3BC8>
# 这就变成了一个生成器,对这个数据进行循环时,每循环一次就按此规律乘 2
2-1-2
>>> b = (i*2 for i in range(10))
>>> for i in b:
... print(i)
0
2
4
……
18
>>>
# 这里的数据量比较少,所以也许无法看出差异;
# 但如果数据量达到100个以上,这种方法可以马上返回数据,这时,如果继续用以往的形式,就会感觉到慢了
2-1-3
>>> c = ( i*2 for i in range(100000000))
>>> c
<generator object <genexpr> at 0x0000027B0E6F72C8>
>>>
# 事实上,这里并没有生成数据,只是返回了一个地址,除非访问这个地址,才会生成数据
# 如果不访问,这个数据就不会存在;
# 列表则是把所有数据都准备好的;
# 而生成器则是准备了一个算法,算法写好了一个规则,调用时,才会产生数据
2-1-4
# 现在想访问 c 的第 1000 个数据,前面的 999 个数据要被循环到,才能够调用;
# 否则无法直接一下调用第 1000 个
>>> c[1000]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: ‘generator‘ object is not subscriptable
# 所以它不支持列表的那种切片;既然生成器不支持切片,那生成器能够支持什么呢?
# 除了用 for 循环一个一个的取,还支持别的方式吗?
# 没有,只能用 for 循环这一种方式获取
Example C:
Create a function that takes a number as an argument and returns a grade based on that number.
Score Grade
Anything greater than 1 or less than 0.6 "F"
0.9 or greater "A"
0.8 or greater "B"
0.7 or greater "C"
0.6 or greater "D"
Sample Tests:
grader(0) should be "F"
grader(1.1) should be "F"
grader(0.9) should be "A"
grader(0.8) should be "B"
grader(0.7) should be "C"
grader(0.6) should be "D"
Solution:
GRADE = ((0.9, ‘A‘), (0.8, ‘B‘), (0.7, ‘C‘), (0.6, ‘D‘))
def grader(score):
return next(v for k, v in GRADE if score >= k) if 1 >= score >= 0.6 else ‘F‘
# next 是什么意思呢?
2-1-6
# 如果只访问 2-1-3 中的前两个数据,应该怎么做呢?
# 生成器,有一个方法,叫做 __next__() 方法
>>> c = ( i*2 for i in range(100000000))
>>> for i in c:
... print(i)
...
0
2
4
……
2210
2212
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
KeyboardInterrupt
>>> c.__next__()
2214
>>> c.__next__()
2216
>>> c.__next__()
2218
>>> c.__next__()
2220
>>>
# 现在要回去取 2210,是不可以的,回去取是不行的;而且也没有任何相应的方法让你回到上一个
# 生成器只记住当前位置,它不知道前面,也不知道后面;前面用完了,就没了
# 所以既不能往前走,也不能往后退;只能一点一点的往后走
1 生成器 只有在调用时才会生成相应的数据
2 只记录当前位置
3 只有一个 __next__()方法。(在 python2.7 中是 next())
# 所以,我们创建了一个generator后,基本上永远不会调用next(),
# 而是通过for循环来迭代它,并且不需要关心StopIteration的错误:
# generator非常强大。
# 如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1,1,2,3,5,8,13,21,34,...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
def fib(max):
n,a,b = 0,0,1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return ‘done‘
注意,赋值语句:a, b = b, a + b
相当于:
t = (b, a + b) # t 是一个 tuple
a = t[0]
b = t[1]
# 仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,
# 可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似 generator
# 也就是说,上面的函数和generator仅一步之遥。
# 要把 fib 函数变成 generator,只需要把 print(b) 改为 yield b 就可以了:
def fib(max) :
n,a,b=0,0,1
while n<max:
#print(b)
yield b
a,b=b,a+b
n += 1
return‘done‘
3-1
def fib(max):
n,a,b = 0,0,1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return ‘done‘
fib(10) # 10 就表示从 1 开始,生成 10 个 斐波那契数列
--->
1
1
2
3
5
8
13
21
34
55
3-1-2
>>> a,b = 1,2
>>> a
1
>>> b
2
>>> t = (b,a+b)
>>> t
(2, 3)
>>>
3-2
# 现在把这个 fib 函数,变成生成器
def fib(max):
n,a,b = 0,0,1
while n < max:
# print(b)
yield b
a, b = b, a + b
n = n + 1
return ‘done‘
print(fib(100))
--->
<generator object fib at 0x0000018C3144A7C8>
# 就变成生成器了
3-2-1
def fib(max):
n,a,b = 0,0,1
while n < max:
# print(b)
yield b
a, b = b, a + b
n = n + 1
# return ‘done‘
f = fib(100)
print(f.__next__())
print(f.__next__())
print(f.__next__())
--->
1
1
2
# 之前一个函数一旦调用,就需要等待执行结束,才可以继续执行后面的内容
# 现在用生成器,就可以在 函数没有完全执行完的过程中 “出出进进”
3-2-2
def fib(max):
n,a,b = 0,0,1
while n < max:
# print(b)
yield b
a, b = b, a + b
n = n + 1
# return ‘done‘
f = fib(100)
print(f.__next__())
print("===1===")
print(f.__next__())
print("===2===")
print(f.__next__())
print("====start loop")
for i in f:
print(i)
--->
1
===1===
1
===2===
2
====start loop
3
5
8
…… ……
# 这样就不需要等函数执行完毕了,而是可以让函数停在一个地方;
# 可以中断函数去做别的事情,然后在回来执行函数
3-3
def fib(max):
n,a,b = 0,0,1
while n < max:
# print(b)
yield b
a, b = b, a + b
n = n + 1
return ‘done‘
f = fib(10)
print(f.__next__())
print("=======")
print(f.__next__())
print("====start loop")
for i in f:
print(i)
--->
1
=======
1
====start loop
2
3
5
8
13
21
34
55
# 可以看出,done 并没有被打印;使用 for 循环,done 就不会被打印
3-3-1
# 那么,如果不用 for 循环呢?这里,有 10 组数,打印 11 个,让它报错,会发生什么呢?
def fib(max):
n,a,b = 0,0,1
while n < max:
# print(b)
yield b
a, b = b, a + b
n = n + 1
return ‘done‘
f = fib(10)
print(f.__next__())
print("=======")
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
--->
1
=======
1
2
3
5
8
13
21
34
55
Traceback (most recent call last):
File "c:/Users/Majesty/Desktop/2.py", line 22, in <module>
print(f.__next__())
StopIteration: done
# 抛出了 一个包含 done 的异常
3-3-2
# 用for循环调用generator时, 发现拿不到generator的return语句的返回值。
# 如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
def fib(max):
n,a,b = 0,0,1
while n < max:
# print(b)
yield b
a, b = b, a + b
n = n + 1
return ‘done‘
g=fib(6)
while True:
try:
x=next(g)
print(‘g:‘, x)
except StopIteration as e:
print(‘Generator returnvalue:‘, e.value)
break
--->
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator returnvalue: done
# 这就是 return 在生成器中的作用;只要有 yield 它就是一个生成器,就不叫函数了
# 单线程就是串行执行
# 通过生成器可以实现并行效果
def fib(max):
n,a,b = 0,0,1
while n < max:
# print(b)
yield b
a, b = b, a + b
n = n + 1
return ‘done‘
# 想把谁返回到外面,就把 yield 放到哪
# 只要执行到 yield b,程序就会中断,函数中断后,就会回到外面
# 回到外面后,通过 next(g) 就又回到了函数里
# 所以 yield 是保存了 函数的中断状态,返回当前状态的值
g=fib(6)
while True:
try:
x=next(g) # next()也是一个内置方法,和 __next__()是一样的
print(‘g:‘, x)
except StopIteration as e:
print(‘Generator returnvalue:‘, e.value)
break
4-1
生成器还可通过 yield 实现在单线程的情况下实现并发运算的效果
import time
def consumer(name) :
print("%s 准备吃包子啦!" %name)
while True:
baozi = yield
# 这里 yield 没有返回值,没有返回值,结果就是空
print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
def producer(name) :
c=consumer(‘A‘)
c2=consumer(‘B‘)
c.__next__()
c2.__next__()
print("老子开始准备做包子啦!")
for i in range(10) :
time.sleep(1)
print("做了2个包子!")
c.send(i)
c2.send(i)
producer("Aether")
# 这是一个典型的生产者,消费者模型
4-1-2
import time
def consumer(name):
print("%s 准备吃包子啦!" %name)
while True:
baozi = yield
print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
c = consumer("Nyx") # 这样就变成一个生成器了
c.__next__()
--->
Nyx 准备吃包子啦!
4-1-3
import time
def consumer(name):
print("%s 准备吃包子啦!" %name)
while True:
baozi = yield
print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
c = consumer("Nyx") # 这样就变成一个生成器了
c.__next__()
c.__next__()
--->
Nyx 准备吃包子啦!
包子[None]来了,被[Nyx]吃了!
# None 代表空,可是没有包子吃个毛线? 所以,可以这样
1-1-4
import time
def consumer(name):
print("%s 准备吃包子啦!" %name)
while True:
baozi = yield
print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
c = consumer("Nyx") # 这样就变成一个生成器了
c.__next__()
b1 = "韭菜馅"
c.send(b1)
# 把 b1 发送过去,通过 send 函数,包子就被传进去了;传进去后,被 yield 接收到了
# 接收到后,将值付给了 baozi
# yield 的作用是保存当前状态,然后返回,返回后调用 next,返回 yield;但是 调用 next 并不会传值
# 但是 send 可以传值;
# next 和 send 的区别:1. next 只是再次调用 yield,但是不会传值; 2. send 调用 yield 的同时给 yield 传值
--->
Nyx 准备吃包子啦!
包子[韭菜馅]来了,被[Nyx]吃了!
4-2-1
# 现在要边做边吃,看一个并行的效果
import time
def consumer(name):
print("%s 准备吃包子啦!" %name)
while True:
baozi = yield
print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
c = consumer("Nyx") # 这样就变成一个生成器了
c.__next__()
b1 = "韭菜馅"
c.send(b1)
def producer(name): # producer 就是一个生产包子的
c = consumer(‘A‘) # c 和 c2 相当于 两个消费者
c2 = consumer(‘B‘)
c.__next__()
c2.__next__()
print("老子开始准备做包子啦!")
for i in range(4):
time.sleep(1)
print("做了2个包子!") # 没一秒钟做两个包子
c.send(i)
c2.send(i) # 分别把 包子 送给 c 和 c2 两个消费者
producer("Aether")
--->
Nyx 准备吃包子啦!
包子[韭菜馅]来了,被[Nyx]吃了!
A 准备吃包子啦!
B 准备吃包子啦!
老子开始准备做包子啦!
做了2个包子!
包子[0]来了,被[A]吃了!
包子[0]来了,被[B]吃了!
做了2个包子!
包子[1]来了,被[A]吃了!
包子[1]来了,被[B]吃了!
做了2个包子!
包子[2]来了,被[A]吃了!
包子[2]来了,被[B]吃了!
做了2个包子!
包子[3]来了,被[A]吃了!
包子[3]来了,被[B]吃了!
# 这就是单线程下的并行效果;虽然还是依次执行的,但可以在不同角色之间来回切换;看起来就好像是并行的
# 这个就是 异步 IO 的雏形,nginx的效率高,就因为它是异步,底层的原理用的就是这个
# nginx在单线程下,承载的并发量,比多线程还要快;本质上就是通过这样的效果实现的
这种情况,不把它叫做 yield,把它叫做 协程;协程是比线程更小的单位;这个可以理解为是最简单的协程
原文:https://blog.51cto.com/u_15149862/2775633