5

I'm having trouble assigning the assignment operator.

I have successfully overloaded __setattr__. But after the object is initialized, I want __setattr__ to do something else, so I try assigning it to be another function, __setattr2__.

Code:

class C(object):

    def __init__(self):
        self.x = 0
        self.__setattr__ = self.__setattr2__

    def __setattr__(self, name, value):
        print "first, setting", name
        object.__setattr__(self, name, value)

    def __setattr2__(self, name, value):
        print "second, setting", name
        object.__setattr__(self, name, value)

c = C()
c.x = 1

What I get:

first, setting x
first, setting __setattr__
first, setting x

What I want/expect:

first, setting x
first, setting __setattr__
second, setting x

2 Answers 2

8

From the docs:

Special method lookup 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. That behaviour is the reason why the following code raises an exception (unlike the equivalent example with old-style classes):

>>> class C(object):
...     pass
...
>>> c = C()
>>> c.__len__ = lambda: 5
>>> len(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'C' has no len()
Sign up to request clarification or add additional context in comments.

4 Comments

+1. I knew the behaviour existed but couldn't find the relevant reference in the docs.
Great answer as to why I can't do it. The only thing I don't understand is that first I tried it with old-style classes (assigning by self.__dict__[name] = value), and it also didn't work.
@chappy: old-style as in class C: rather than class C(object):?
Yes, that too. (in addition to using __dict__ for assignment)
3

Why not use a flag to indicate that __init__ is still in progress?

class C(object):
    def __init__(self):
        # Use the superclass's __setattr__ because we've overridden our own.
        super(C, self).__setattr__('initialising', True)
        self.x = 0
        # the very last thing we do in __init__ is indicate that it's finished
        super(C, self).__setattr__('initialising', False)

    def __setattr__(self, name, value):
        if self.initialising:
            print "during __init__, setting", name
            # I happen to like super() rather than explicitly naming the superclass
            super(C, self).__setattr__(name, value)
        else:
            print "after __init__, setting", name
            super(C, self).__setattr__(name, value)

2 Comments

Really nice workaround, thank you. The fact that I have to use a workaround, though, makes me suspect that I should solve my more general problem a different way. But in the mean time I'll use this!
@chappy: I totally agree with "I should solve my more general problem a different way". I always feel squeamish rummaging around in __dict__. In my opinion, whenever one feels the need for __setattr__, one can generally get the job done with finer-grained descriptors or properties (or just a general change of strategy). Your original idea looks like a workaround too!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.