簡體   English   中英

列表理解中的Lambdas在調用時返回lambda

[英]Lambdas from a list comprehension are returning a lambda when called

我試圖在test.py的列表上迭代lambda func,我想得到lambda的調用結果,而不是函數對象本身。 但是,以下輸出真的讓我很困惑。

------test.py---------
#!/bin/env python
#coding: utf-8

a = [lambda: i for i in range(5)]
for i in a:
    print i()

--------output---------
<function <lambda> at 0x7f489e542e60>
<function <lambda> at 0x7f489e542ed8>
<function <lambda> at 0x7f489e542f50>
<function <lambda> at 0x7f489e54a050>
<function <lambda> at 0x7f489e54a0c8>

我將調用結果打印到t時修改了變量名,如下所示,一切順利。 我想知道這是怎么回事。

--------test.py(update)--------
a = [lambda: i for i in range(5)]
for t in a:
    print t()

-----------output-------------
4
4
4
4
4

在Python 2中,列表理解“泄漏”變量到外部范圍:

>>> [i for i in xrange(3)]
[0, 1, 2]
>>> i
2

請注意,Python 3上的行為是不同的:

>>> [i for i in range(3)]
[0, 1, 2]
>>> i
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'i' is not defined

當你定義lambda時,它綁定到變量i ,而不是它的第二個例子顯示的當前值。 現在,當您為i分配新值時,lambda將返回當前值:

>>> a = [lambda: i for i in range(5)]
>>> a[0]()
4
>>> i = 'foobar'
>>> a[0]()
'foobar'

由於循環中i的值是lambda本身,因此您將獲得它作為返回值:

>>> i = a[0]
>>> i()
<function <lambda> at 0x01D689F0>
>>> i()()()()
<function <lambda> at 0x01D689F0>

更新 :Python 2.7上的示例:

Python 2.7.6 (default, Jun 22 2015, 17:58:13) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [lambda: i for i in range(5)]
>>> for i in a:
...     print i()
... 
<function <lambda> at 0x7f1eae7f15f0>
<function <lambda> at 0x7f1eae7f1668>
<function <lambda> at 0x7f1eae7f16e0>
<function <lambda> at 0x7f1eae7f1758>
<function <lambda> at 0x7f1eae7f17d0>

在Python 3.4上也是如此:

Python 3.4.3 (default, Oct 14 2015, 20:28:29) 
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [lambda: i for i in range(5)]
>>> for i in a:
...     print(i())
... 
4
4
4
4
4

有關列表理解的變量范圍變化的詳細信息,請參閱2010年的 Guido 博客文章

我們還在Python 3中進行了另一項更改,以改進列表推導和生成器表達式之間的等效性。 在Python 2中,列表推導將循環控制變量“泄漏”到周圍的范圍中:

x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'

但是,在Python 3中,我們決定使用與生成器表達式相同的實現策略來修復列表推導的“臟小秘密”。 因此,在Python 3中,上面的例子(修改后使用print(x):-)將打印'before',證明列表理解中的'x'暫時陰影但不覆蓋周圍的'x'范圍。

Python中的閉包是后期綁定的 ,這意味着列表中的每個lambda函數只會在調用時評估變量i ,而不是在定義時。 這就是為什么所有函數返回的值相同,即最后一個值ì (也就是4)。

為避免這種情況,一種技術是將i的值綁定到本地命名參數:

>>> a = [lambda i=i: i for i in range(5)]
>>> for t in a:
...   print t()
... 
0
1
2
3
4

另一種選擇是創建一個部分函數並將i的當前值綁定為其參數:

>>> from functools import partial
>>> a = [partial(lambda x: x, i) for i in range(5)]
>>> for t in a:
...   print t()
... 
0
1
2
3
4

編輯:對不起,最初誤讀了這個問題,因為這類問題經常與后期綁定有關(感謝@soon評論)。

這種行為的第二個原因是列表理解的變量在Python2中泄漏,正如其他人已經解釋過的那樣。 當使用i作為for循環中的迭代變量時,每個函數都會打印i的當前值(由於上述原因),這只是函數本身。 當使用不同的名稱(例如t )時,函數打印i的最后一個值,就像在列表推導循環中那樣,它是4。

lambda: i是一個匿名函數,沒有返回i的參數。 因此,您將生成一個匿名函數列表,您可以稍后(在第二個示例中)綁定到名稱t並使用()調用。 請注意,您可以對非匿名函數執行相同的操作:

>>> def f():
...   return 42
... 
>>> name = f
>>> name
<function f at 0x7fed4d70fb90>
>>> name()
42

@plamut剛剛回答了問題中隱含的其他部分,所以我不會。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM