Python动态函数属性

paw*_*moy 4 python

我在尝试实现动态排序时遇到了一个有趣的问题。给出以下代码:

>>> 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__方法的类。

Vin*_*ard 5

问题是由于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)

在填充的循环之后lf是对最后创建的闭包的引用,如下所示:

>>> 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)

尽管最好先解决您的实际问题,但这可能是一个更好的解决方案。

1f您可能会问,为什么它不加载全局实例而不是当前实例?

回想一下,当您创建一个类时,您需要传递一个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,但无法知道函数主体中要引用的实例。