In Python (2 and 3) we can assign attributes to function:
>>> 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!!'
And that's cool.
But why cannot we change foo.bar
at a later time? For example, in the constructor, like so:
>>> 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 claims there is no bar
even though it printed it fine on just the previous line.
Same error happens if we try to change it directly on the 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'
What's happening here, i.e. why are we seeing this behaviour?
Is there a way to set attributes on a function after class creation?
(I'm aware of multiple alternatives, but I'm explicitly wondering about attributes on methods here, or possibly a broader issue.)
Motivation: Django makes use of the possibility to set attributes on methods, e.g:
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
Setting foo.bar
inside the class body works because foo
is the actual foo
function. However, when you do
self.foo.bar = 456
self.foo
isn't that function. self.foo
is an instance method object, created on demand when you access it. You can't set attributes on it for several reasons:
foo
function, then assigning to a.foo.bar
has an unexpected effect on b.foo.bar
, contrary to all the usual expectations about attribute assignment.self.foo
instance method object, they won't show up the next time you access self.foo
, because you'll get a new instance method object next time.self.foo
instance method object and you change the rules so self.foo
is always the same object, then that massively bloats every object in Python to store a bunch of instance method objects you almost never need.self.__dict__
, what about objects that don't have a __dict__
? Also, you'd need to come up with some sort of name mangling rule, or store non-string keys in self.__dict__
, both of which have their own problems.If you want to set attributes on the foo
function after the class definition is done, you can do that with A.__dict__['foo'].bar = 456
. (I've used A.__dict__
to bypass the issue of whether A.foo
is the function or an unbound method object, which depends on your Python version. If A
inherits foo
, you'll have to either deal with that issue or access the dict of the class it inherits foo
from.)