我在尝试实现动态排序时遇到了一个有趣的问题。给出以下代码:
>>> l = []
>>> for i in range(2):
>>> def f():
>>> return f.v
>>> f.v = i
>>> l.append(f)
Run Code Online (Sandbox Code Playgroud)
您必须注意如何使用以下功能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
Run Code Online (Sandbox Code Playgroud)
函数的行为取决于f
当前的行为。
我应该怎么做才能避免这个问题?如何设置不依赖于函数名称的函数属性?
更新资料
阅读您的评论和答案,这是我的实际问题。
我有一些要根据用户输入进行排序的数据(因此我事先不知道排序标准)。用户可以选择对数据的哪一部分应用连续排序,这些排序可以升序或降序。
因此,我的第一个尝试是遍历用户输入,为每个条件定义一个函数,将此函数存储在列表中,然后将此列表用作sorted
的键,如下所示:key=lambda x: [f(x) for f in functions]
。为了避免将条件乘以函数本身,我在函数定义之前计算了一些需要的值,并将它们绑定到函数(具有不同预先计算值的不同函数)。
调试时,我知道函数属性不是这里的解决方案,因此我确实编写了带有__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
Run Code Online (Sandbox Code Playgroud)
在填充的循环之后l
,f
是对最后创建的闭包的引用,如下所示:
>>> l
[<function f at 0x02594170>, <function f at 0x02594130>]
>>> f
<function f at 0x02594130>
Run Code Online (Sandbox Code Playgroud)
因此,当您调用时l[0]()
,它仍然会加载f
指向最后创建的函数的函数,并返回1。当您f
通过do进行重新定义时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
Run Code Online (Sandbox Code Playgroud)
尽管最好先解决您的实际问题,但这可能是一个更好的解决方案。
1:f
您可能会问,为什么它不加载全局实例而不是当前实例?
回想一下,当您创建一个类时,您需要传递一个self
参数,如下所示:
# ...
def my_method(self):
return self.value
Run Code Online (Sandbox Code Playgroud)
self
实际上是对您对象的当前实例的引用。这就是Python知道将属性加载到哪里的方式value
。它知道必须研究引用的实例self
。因此,当您这样做时:
a.value = 1
a.my_method()
Run Code Online (Sandbox Code Playgroud)
self
现在是对的引用a
。
因此,当您这样做时:
def f():
return f.v
Run Code Online (Sandbox Code Playgroud)
Python无法知道f
实际上是什么。它不是参数,因此必须从其他位置加载。在您的情况下,它是从全局变量加载的。
因此,当您执行时f.v = i
,尽管您确实v
为的实例设置了属性f
,但无法知道函数主体中要引用的实例。
归档时间: |
|
查看次数: |
284 次 |
最近记录: |