我在尝试实现动态排序时遇到了一个有趣的问题。 给出以下代码:
>>> l = []
>>> for i in range(2):
>>> def f():
>>> return f.v
>>> f.v = i
>>> l.append(f)
您必须注意如何使用l
中的函数:
>>> l[0]()
1
>>> l[1]()
1
>>> [h() for h in l]
[1, 1]
>>> [f() for f in l]
[0, 1]
>>> f = l[0]
>>> f()
0
>>> k = l[1]
>>> k()
0
>>> f = l[1]
>>> k()
1
>>> del f
>>> k()
NameError: global name 'f' is not defined
该功能的行为取决于当前f
的内容。
我应该怎么做才能避免这个问题?如何设置一个不依赖于函数名称的函数属性?
更新
阅读你的评论和答案,这是我的实际问题。
我有一些数据需要根据用户输入进行排序(所以我不提前知道排序标准)。用户可以选择应用连续排序的数据部分,这些排序可以是升序或降序。
所以我的第一次尝试是循环用户输入,为每个条件定义一个函数,将此函数存储在列表中,然后将此列表用于sorted
的键,如下所示:{{ 1}}。为了避免将条件乘以函数本身,我在函数定义之前计算了一些所需的值,并将它们绑定到函数(具有不同预先计算值的不同函数)。
在调试时,我知道函数属性不是解决方案,所以我确实用key=lambda x: [f(x) for f in functions]
方法编写了一个类。
答案 0 :(得分:4)
问题是由于return f.v
加载了全局 f
,而不是你想要的那个。 1 你可以看到这通过反汇编代码:
>>> dis.dis(l[0])
3 0 LOAD_GLOBAL 0 (f)
3 LOAD_ATTR 1 (v)
6 RETURN_VALUE
在填充l
的循环之后,f
是对最后创建的闭包的引用,如下所示:
>>> l
[<function f at 0x02594170>, <function f at 0x02594130>]
>>> f
<function f at 0x02594130>
因此,当您致电l[0]()
时,它仍会加载指向最后创建的函数的f
,并返回1.当您通过{{1}重新定义f
时},然后全局f = l[0]
现在指向第一个函数。
你似乎想要的是一个具有状态的函数,它实际上是一个类。因此,您可以这样做:
f
虽然首先解释一下您的实际问题可能是一个好主意,因为可能有更好的解决方案。
1 :为什么不加载全局class MyFunction:
def __init__(self, v):
self.v = v
def __call__(self):
return self.v
l = [MyFunction(i) for i in range(2)]
l[0]() # 0
l[1]() # 1
而不是当前实例,你可能会问?
回想一下,当你创建一个类时,你需要传递一个f
参数,如下所示:
self
# ...
def my_method(self):
return self.value
实际上是对对象当前实例的引用。这就是Python知道加载属性self
的位置的方式。它知道它必须查看value
引用的实例。所以当你这样做时:
self
a.value = 1
a.my_method()
现在是对self
的引用。
所以当你这样做时:
a
Python无法知道def f():
return f.v
实际上是什么。它不是参数,因此必须从其他地方加载它。在您的情况下,它是从全局变量加载的
因此,当您执行f
时,执行为f.v = i
的实例设置属性v
时,无法知道您所指的是哪个实例在你的功能体内。
答案 1 :(得分:1)
请注意你在这里做的事情:
def f():
return f.v
是不创建一个函数,它返回自己的 v
属性。无论f
对象的v
属性是什么,它都会返回。所以它必然取决于f
的价值。并非v
属性&#34;取决于功能的名称&#34;。它与函数的名称完全没有任何关系。
稍后,当你做
时>>> f = l[0]
>>> k = l[1]
>>> k()
0
您所做的是将k
绑定到l[1]
处的函数。当你打电话时,你当然会得到f.v
,因为这就是这个功能所做的事情。
但请注意:
>>> k.v
1
>>> [h.v for h in l]
[0, 1]
因此,函数是一个对象,就像大多数对象一样,它可以分配给它的属性(可以使用点表示法或getattr()
函数访问,或者检查对象)字典等)。但是,函数并非旨在从其自己的代码中访问自己的属性。为此,你想使用一个类(由@VincentSavard演示)。
在您的特定情况下,您看起来的效果并不真正需要&#34;属性&#34;本身;你显然在寻找closure。你可以使用类来实现闭包,但是重量较轻的方法是嵌套函数(@TomKarzes演示了一种形式;你也可以使用命名的内部函数而不是lambda
)。
答案 2 :(得分:0)
试试这个:
l = []
for i in range(2):
def f(n):
return lambda: n
l.append(f(i))
这不使用属性,但会为i
的每个值创建一个闭包。 n
返回后,f
的值将被锁定。这是一些示例输出:
>>> [f() for f in l]
[0, 1]
答案 3 :(得分:0)
正如其他人所说,return f.v
在当前范围内查找f
名称,该名称等于最后定义的函数。
要解决此问题,您可以模拟功能:
>>> class Function(object):
... def __init__(self, return_value):
... self.return_value = return_value
... def __call__(self):
... return self.return_value
...
>>> l = []
>>> for i in range(2):
... l.append(Function(i))
...
>>> l[0]()
>>> 0
>>> l[1]()
>>> 1