Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

override at runtime __setattr__

Tags:

python

I know that in Python it's possible to add at runtime a method to a class:

class Test:
    def __init__(self):
        self.a=5

test=Test()

import types
def foo(self):
    print self.a
test.foo = types.MethodType(foo, test)
test.foo() #prints 5

And I also know that it's possible to override the default setattr in the class definition:

class Test:
    def __init__(self):
        self.a=5
    def __setattr__(self,name,value):
        print "Possibility disabled for the sake of this test"

test=Test() #prints the message from the custom method called inside __init__

However, it seems it's not possible to override at runtime the setattr:

class Test:
    def __init__(self):
        self.a=5

test=Test()

import types
def __setattr__(self,name,value):
    print "Possibility disabled for the sake of this test"
test.__setattr__ = types.MethodType(__setattr__, test)
test.a=10 #does the assignment instead of calling the custom method

In both the two last cases, dir(test) reports also the method setattr. However, while in the first case it works correctly, in the second it doesn't. Note that I can also call it explicitly, and in that case it works. Seems like that, while the method has been defined, it has not been mapped correctly to override the default assigment method. Am I missing something?

I am using Python 2.7 by the way. The question is mostly academic, since it's probably not a good idea to do such a thing from the program design point of view, but still it deserves an answer - and despite I searched, I wasn't able to find it documented anywhere.

like image 990
Spock Avatar asked Nov 15 '12 23:11

Spock


2 Answers

It's documented in Data model, section "Class Instances":

Attribute assignments and deletions update the instance’s dictionary, never a class’s dictionary. If the class has a __setattr__() or __delattr__() method, this is called instead of updating the instance dictionary directly.

So no matter if old-style or new-style, those two checks are always made on the type, rather then the instance.

like image 103
lqc Avatar answered Nov 14 '22 19:11

lqc


See this section of the Python docs: Special method lookups for new-style classes

For new-style classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.

Follow the link for an elaboration of the rationale behind this. The basic idea as I understand it is that special methods that apply to both instance objects and type objects (such as __repr__) need to be called consistently, rather than sometimes needing an explicit argument and sometimes receiving an implicit argument. By always calling the method on the type of the object, we know to always pass an explicit argument - but the side-effect is that the instance dictionary is bypassed.

like image 2
Brian L Avatar answered Nov 14 '22 18:11

Brian L