我在尝试实现动态排序时遇到了一个有趣的问题。 给定以下代码:
>>> 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
的键,如下所示:key=lambda x: [f(x) for f in functions]
。为了避免将条件乘以函数本身,我在函数定义之前计算了一些需要的值并将它们绑定(bind)到函数(不同的函数具有不同的预计算值)。
在调试时,我了解到函数属性不是这里的解决方案,所以我确实用 __call__
方法编写了一个类。
最佳答案
问题是由于 return f.v
加载了 global 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。当你重新定义f
通过执行 f = l[0]
,然后全局 f
现在指向第一个函数。
您似乎想要的是一个具有状态的函数,它实际上是一个类。因此,您可以这样做:
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
虽然最好先解释您的实际问题,因为可能会有更好的解决方案。
1:您可能会问,为什么它不加载全局 f
而不是当前实例?
回想一下,当您创建一个类时,您需要传递一个 self
参数,如下所示:
# ...
def my_method(self):
return self.value
self
实际上是对对象当前实例的引用。这就是 Python 知道在哪里加载属性 value
的方式。它知道它必须查看 self
引用的实例。所以当你这样做时:
a.value = 1
a.my_method()
self
现在是对 a
的引用。
所以当你这样做的时候:
def f():
return f.v
Python 无法知道 f
实际是什么。它不是参数,所以它必须从其他地方加载它。在您的情况下,它是从全局变量加载的。
因此,当您执行 f.v = i
时,当您执行 为 f
的实例设置属性 v
时,无法知道您在函数主体中指的是哪个实例。
关于Python动态函数属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35318231/