python lambda结合列表推导式?

[图片] 可咋也没搞懂为啥子是这样
关注者
38
被浏览
25,600

11 个回答

一个列表,列表里的每一个元素是个lambda function,就是如此。

至于那个i,因为是个变量,在lambda求值时它是9,也就是说构造lambda的时候i是几并不重要,lambda里只是用了这么个引用;求值时i是多少它就是多少。

能理解下面这段代码为什么输出1而不是0吗:

i = 0
def foo():
    print(i)
i = 1
func()

如果能理解上面的,那么接下来这样:

data = range(10)

funcs = []
for i in data:
    l = lambda x: i * x
    funcs.append(l)
    
    def m(x):
        return i * x
    funcs.append(m)

i = 21
for func in funcs:
    print(func(2))

全都是42。

Python的闭包(lambda和function等)会保存变量的名字(i)和scoping(global)。当你调用的时候才会去找具体的对象。所以你给i赋不同的值,在那之后的调用都会去取新的object(21)。

In [1]: import dis

In [2]: i = 0

In [3]: def foo(x):
   ...:     return i * x
   ...:

In [4]: dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (i)
              3 LOAD_FAST                0 (x)
              6 BINARY_MULTIPLY
              7 RETURN_VALUE

In [5]: bar = lambda x: i * x

In [6]: dis.dis(bar)
  1           0 LOAD_GLOBAL              0 (i)
              3 LOAD_FAST                0 (x)
              6 BINARY_MULTIPLY
              7 RETURN_VALUE

重点在LOAD_GLOBAL. 调用的时候是去找global的i对应的object。

那么,怎么让Python保存object而不是保存名字和scoping呢?

Assignment.

创建一个local的变量,对其赋值,Python会记住那个时候的object,然后LOAD_GLOBAL变成LOAD_FAST,然后lambda被调用的时候就不会再去找global的i了。

In [7]: def baz(x, i=i):
   ...:     return i * x
   ...:

In [8]: dis.dis(baz)
  2           0 LOAD_FAST                1 (i)
              3 LOAD_FAST                0 (x)
              6 BINARY_MULTIPLY
              7 RETURN_VALUE

这上面i=i实际上创建了一个local的i,然后把global的i赋值给它。后面在return的时候就会用local的i。

除了LOAD_GLOBAL,还有啥LOAD_DEREF, LOAD_CLOSURE的。唉头疼,不懂,不说了。

OK,修改一下你的code:

data = range(10)

funcs = [lambda x, y=i: y * x for i in data]

i = 12345
for func in funcs:
    print(func(2))

不过这样一来func就变成接受两个参数的lambda了。。。

回到你的例子,为啥有这么奇怪的现象:

当任何一个lambda在被调用的时候,python会去找i当前对应的object,然后返回那个i*x;而不是你想像中的去找lambda定义的时候的那个object。

你要明白几件事情:

1. for i in data实际上等于

iterator = iter(data)
i = next(iterator)
# do something
i = next(iterator)
# do something
...
# raise StopIteration

2. Python的def、lambda等等都是statement,Python会执行一些特殊的code。这是为什么i=i能奏效的原因。

3. name和value(object)的区别

Ned Batchelder

这个talk你可以看一下

Facts and myths about Python names and values

4. scoping

python变量的scoping其实很直接:

4.1. Python只有三种scoping:global,module,class,function。

这是三种吗,会数数吗同学?

。。。module scoping其实就是global,所以实际上只有module,class,function三种scoping。

不像C++,加个左括号就是一层scoping。

4.2. 有assignment(等号=)的变量都是local,没有assignment的都不是local

除此之外,

python 2有一个global declaration可以指定某些变量是global的(即使有assignment也是global)

python 3有一个nonlocal declaration可以指定某些变量不是local的(即使有assignment也不是local)

多说一句,function,lambda的参数实际上有一个隐式的assignment。