Why does setattr fail on a bound method

2019-01-11 10:47发布

In the following, setattr succeeds in the first invocation, but fails in the second, with:

AttributeError: 'method' object has no attribute 'i'

Why is this, and is there a way of setting an attribute on a method such that it will only exist on one instance, not for each instance of the class?

class c:

    def m(self):

        print(type(c.m))
        setattr(c.m, 'i', 0)

        print(type(self.m))
        setattr(self.m, 'i', 0)

Python 3.2.2

2条回答
不美不萌又怎样
2楼-- · 2019-01-11 11:34

Q: "Is there a way of setting an attribute on a method such that it will only exist on one instance, not for each instance of the class?"

A: Yes:

class c:

    def m(self):

        print(type(c.m))
        setattr(c.m, 'i', 0)

        print(type(self))
        setattr(self, 'i', 0)

The static variable on functions in the post you link to is not useful for methods. It sets an attribute on the function so that this attribute is available the next time the function is called, so you can make a counter or whatnot.

But methods have an object instance associated with them (self). Hence you have no need to set attributes on the method, as you simply can set it on the instance instead. That is in fact exactly what the instance is for.

The post you link to shows how to make a function with a static variable. I would say that in Python doing so would be misguided. Instead look at this answer: What is the Python equivalent of static variables inside a function?

That is the way to do it in Python in a way that is clear and easily understandable. You use a class and make it callable. Setting attributes on functions is possible and there are probably cases where it's a good idea, but in general it will just end up confusing people.

查看更多
老娘就宠你
3楼-- · 2019-01-11 11:45

The short answer: There is no way of adding custom attributes to bound methods.

The long answer follows.

In Python, there are function objects and method objects. When you define a class, the def statement creates a function object that lives within the class' namespace:

>>> class c:
...     def m(self):
...         pass
...
>>> c.m
<function m at 0x025FAE88>

Function objects have a special __dict__ attribute that can hold user-defined attributes:

>>> c.m.i = 0
>>> c.m.__dict__
{'i': 0}

Method objects are different beasts. They are tiny objects just holding a reference to the corresponding function object (__func__) and one to its host object (__self__):

>>> c().m
<bound method c.m of <__main__.c object at 0x025206D0>>
>>> c().m.__self__
<__main__.c object at 0x02625070>
>>> c().m.__func__
<function m at 0x025FAE88>
>>> c().m.__func__ is c.m
True

Method objects provide a special __getattr__ that forwards attribute access to the function object:

>>> c().m.i
0

This is also true for the __dict__ property:

>>> c().m.__dict__['a'] = 42
>>> c.m.a
42
>>> c().m.__dict__ is c.m.__dict__
True

Setting attributes follows the default rules, though, and since they don't have their own __dict__, there is no way to set arbitrary attributes.

This is similar to user-defined classes defining __slots__ and no __dict__ slot, when trying to set a non-existing slot raises an AttributeError (see the docs on __slots__ for more information):

>>> class c:
...     __slots__ = ('a', 'b')
...
>>> x = c()
>>> x.a = 1
>>> x.b = 2
>>> x.c = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'c' object has no attribute 'c'
查看更多
登录 后发表回答