大家先看两个两个例子,比如使用filter
过滤出偶数
l = [1,2,3]
lf1 = filter(lambda x: x%2==0, l)
l = [4,5,6]
lf2 = filter(lambda x: x%2==0, l)
print(list(lf1)) # 结果 >>> [2]
print(list(lf2)) # 结果 >>> [4, 6]
比如计算平方
l = [1,2,3]
lf1 = (i**2 for i in l)
l = [4,5,6]
lf2 = (i**2 for i in l)
print(list(lf1)) # 结果 >>> [1, 4, 9]
print(list(lf2)) # 结果 >>> [16, 25, 36]
重点来了,如果我用这个切片,取出前两个元素,大家觉得结果是什么?
l = 'ABC'
l_f1 = (l[i] for i in range(2))
l = 'DEF'
l_f2 = (l[i] for i in range(2))
print(list(l_f1))
print(list(l_f2))
居然都是
['D', 'E']
['D', 'E']
这种情况下,传递给生成器(...)
的变量 l
居然不是内存地址,而是一个符号。这太奇怪了吧,想不通。
1
siluni 2022-10-20 13:46:06 +08:00 3
应该不是传了符号,而是 generator 本身是 lazy 的,在调用 list(l_f1)的时候才真正执行了运算,这时 l 已经指向第二个值了
|
2
lambdaq 2022-10-20 13:47:48 +08:00 1
l_f1 = 和 l_f2 = 后面如果是「小括号」是惰性求值的,你不 print 它就不计算。改成「方括号」就没问题了。
基础姿势。 |
3
stein42 2022-10-20 13:48:25 +08:00 1
生成器里面第一个表达式是延迟求值的。
相当于: map(lambda i: l[i], range(2)) 每次计算会去读 l 的值。 函数式编程需要避免赋值这些有副作用的操作。 |
4
TongDu OP @siluni 我最开始也是那么理解的,但是前两个例子可以排除这样的情况。我想到了给函数传参,传入的应该就是内存地址。对于`(y[i] for i in x)`这样的生成器,x 应该是按照函数外传参进去的(第一第二个例子可佐证),而 y 不是传参进去的,而是属于在局部 local 域里面直接获取外部的变量。
|
5
deplivesb 2022-10-20 13:56:04 +08:00 3
|
6
clino 2022-10-20 13:56:25 +08:00
你先入为主了,这两种选择明显是取变量比取地址更自然
|
7
siluni 2022-10-20 13:56:42 +08:00
@TongDu 前两个例子是 iterable 的循环,但 range()生成的是 generator 。iterable 的值是一直在内存里的,generator 是调用 next()的时候才会计算的
|
8
apake 2022-10-20 14:17:08 +08:00 1
并不是 惰性求值的问题, 两个例子都是 惰性求值. 楼主的意思是 generator 的执行环境 并不像 函数那样 封装了一个 闭包环境.
|
9
whoami9894 2022-10-20 14:17:23 +08:00 7
(i**2 for i in l):
l 是创建生成器时闭包捕获的,i ** 2 是 f.next() 时计算的 *** (l[i] for i in range(2)): 闭包没有捕获任何变量,计算 l[i] 时从外部作用域获取 l *** 两个表达式对应的 opcode ,注意看 LOAD opcode ``` 2 8 LOAD_NAME 1 (list) 10 LOAD_CONST 1 (<code object <genexpr> at 0x10256d3a0, file "<dis>", line 2>) 12 LOAD_CONST 2 ('<genexpr>') 14 MAKE_FUNCTION 0 16 LOAD_NAME 0 (l) 18 GET_ITER 20 CALL_FUNCTION 1 22 CALL_FUNCTION 1 24 POP_TOP 3 26 LOAD_NAME 1 (list) 28 LOAD_CONST 3 (<code object <genexpr> at 0x10256d450, file "<dis>", line 3>) 30 LOAD_CONST 2 ('<genexpr>') 32 MAKE_FUNCTION 0 34 LOAD_NAME 2 (range) 36 LOAD_CONST 4 (2) 38 CALL_FUNCTION 1 40 GET_ITER 42 CALL_FUNCTION 1 44 CALL_FUNCTION 1 46 POP_TOP 48 LOAD_CONST 5 (None) 50 RETURN_VALUE Disassembly of <code object <genexpr> at 0x10256d3a0, file "<dis>", line 2>: 2 0 LOAD_FAST 0 (.0) >> 2 FOR_ITER 14 (to 18) 4 STORE_FAST 1 (i) 6 LOAD_FAST 1 (i) 8 LOAD_CONST 0 (2) 10 BINARY_POWER 12 YIELD_VALUE 14 POP_TOP 16 JUMP_ABSOLUTE 2 >> 18 LOAD_CONST 1 (None) 20 RETURN_VALUE Disassembly of <code object <genexpr> at 0x10256d450, file "<dis>", line 3>: 3 0 LOAD_FAST 0 (.0) >> 2 FOR_ITER 14 (to 18) 4 STORE_FAST 1 (i) 6 LOAD_GLOBAL 0 (l) 8 LOAD_FAST 1 (i) 10 BINARY_SUBSCR 12 YIELD_VALUE 14 POP_TOP 16 JUMP_ABSOLUTE 2 >> 18 LOAD_CONST 0 (None) 20 RETURN_VALUE ``` |
10
MRlaopeng 2022-10-20 14:25:32 +08:00
因为 range(2) 生成的 [0,1]
|
11
amlee 2022-10-20 15:18:23 +08:00 5
生成器表达式中的 for 子句是立即计算的,这时候有闭包环境。
但是生成器表达式的小括号一开头的那个表达式是惰性计算的。 #这时候闭包的 i 变量存的是 l 中的值,表达式 i**2 是惰性计算的,放入 list()的时候才计算 lf1 = (i**2 for i in l) #这时候闭包的 i 变量存的是 [0, 1] 中的值,表达式 l[i] 是惰性计算的,放入 list()的时候才计算 lf2 = (l[i] for i in range(2)) 所以,在执行 print(list(lf2)) 之前,l 被重新赋值了,就会出现 op 说的情况。 这就是函数式编程为什么强调 immutable |
12
westoy 2022-10-20 15:32:56 +08:00
>>> l = 'ABC'
>>> l_f1 = (lambda l=l: (l[i] for i in range(2)))() >>> l = 'DEF' >>> l_f2 = (lambda l=l: (l[i] for i in range(2)))() >>> print(list(l_f2)) ['D', 'E'] >>> print(list(l_f1)) ['A', 'B'] |
15
milkpuff 2022-10-20 18:28:30 +08:00
>>> (c[i] for i in range(5))
<generator object <genexpr> at 0x000002A859CD4820> >>> c Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'c' is not defined 当没有变量 c 时,也能正确创建 generator |
16
nyxsonsleep 2022-10-20 22:02:57 +08:00 1
@crab 生成器特性
|
17
jurassic2long 2022-10-21 10:50:24 +08:00
这重点不在生成器,而是在:
i for i in l 与 l[i] for i in range(3) 的区别 |
18
brucmao 2022-10-21 11:22:16 +08:00
@westoy 看不明白 lambda l=l ,这里默认参数为啥这样传
``` l = "ABC" l_f1 = (lambda l: (l[i] for i in range(2)))(l) l = "DEF" l_f2 = (lambda l: (l[i] for i in range(2)))(l) print(list(l_f2)) print(list(l_f1)) ``` |
20
zlstone 2022-10-21 12:03:32 +08:00
python 不存在取地址,底层一直传的就是 c struct 本身,只是因为 struct 里面存的是一个地址,所以看起来像是取地址的行为。
第一个示例传的就是两个不同的 list ,所以 filter 出来的东西是不一样的,如果这么写,这两个结果就是相同的 ``` l = [1,2,3] lf1 = filter(lambda x: x%2==0, l) l.append(4) lf2 = filter(lambda x: x%2==0, l) print(list(lf1)) # 结果 >>> [2, 4] print(list(lf2)) # 结果 >>> [2, 4] ``` |