class 创建后在方法上设置属性会引发“'instancemethod' 对象没有属性”,但属性显然存在

Setting attributes on a method after class creation raises "'instancemethod' object has no attribute" but the attribiute is clearly there

在Python(2和3)中我们可以给函数赋属性:

>>> class A(object):
...     def foo(self):
...         """ This is obviously just an example """
...         return "FOO{}!!".format(self.foo.bar)
...     foo.bar = 123
...
>>> a = A()
>>> a.foo()
'FOO123!!'

这很酷。

但为什么我们以后不能更改 foo.bar?例如,在构造函数中,像这样:

>>> class A(object):
...     def __init__(self, *args, **kwargs):
...         super(A, self).__init__(*args, **kwargs)
...         print(self.foo.bar)
...         self.foo.bar = 456  # KABOOM!
...     def foo(self):
...         """ This is obviously just an example """
...         return "FOO{}!!".format(self.foo.bar)
...     foo.bar = 123
...
>>> a = A()
123
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __init__
AttributeError: 'instancemethod' object has no attribute 'bar'

Python 声称没有 bar 即使它在前一行打印得很好。

如果我们尝试直接在 class 上更改它,则会发生同样的错误:

>>> A.foo.bar
123
>>> A.foo.bar = 345  # KABOOM!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'instancemethod' object has no attribute 'bar'

这里发生了什么,即为什么我们会看到这种行为?

有没有办法在 class 创建后设置函数的属性?

(我知道有多种选择,但我很想知道这里方法的属性,或者可能是更广泛的问题。)


动机:Django 利用了在方法上设置属性的可能性,例如:

class MyModelAdmin(ModelAdmin):
    ...

    def custom_admin_column(self, obj):
        return obj.something()
    custom_admin_column.admin_order_field ='relation__field__span'
    custom_admin_column.allow_tags = True

在 class 主体内设置 foo.bar 有效,因为 foo 是实际的 foo 函数。然而,当你这样做时

self.foo.bar = 456

self.foo 不是那个函数。 self.foo 是一个 实例方法对象 ,在您访问它时按需创建。由于多种原因,您无法为其设置属性:

  1. 如果这些属性存储在 foo 函数中,那么分配给 a.foo.bar 会对 b.foo.bar 产生意想不到的影响,这与对属性分配的所有通常期望相反。
  2. 如果这些属性存储在 self.foo 实例方法对象上,它们将不会在您下次访问 self.foo 时显示,因为下次您将获得一个新的实例方法对象.
  3. 如果这些属性存储在 self.foo 实例方法对象上,并且您更改规则使 self.foo 始终是同一个对象,那么 Python 中的每个对象都会大量膨胀到存储一堆你几乎不需要的实例方法对象。
  4. 如果这些属性存储在 self.__dict__ 中,那么没有 __dict__ 的对象呢?此外,您需要提出某种名称修改规则,或将非字符串键存储在 self.__dict__ 中,这两者都有各自的问题。

如果你想在 class 定义完成后在 foo 函数上设置属性,你可以使用 A.__dict__['foo'].bar = 456 来实现。 (我用 A.__dict__ 来绕过 A.foo 是函数还是未绑定方法对象的问题,这取决于你的 Python 版本。如果 A 继承 foo,您将不得不处理该问题或访问它从 foo 继承的 class 的字典。)