Python动态函数属性

时间:2016-02-10 14:43:52

标签: python

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

>>> 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]方法编写了一个类。

4 个答案:

答案 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