49

I want to override my Python class's __getattribute__ and __setattr__ methods. My use case is the usual one: I have a few special names that I want to handle, and I want the default behavior for anything else. For __getattribute__, it seems that I can request the default behavior simply by raising AttributeError. However, how can I achieve the same in __setattr__? Here is a trivial example, implementing a class with immutable fields "A", "B", and "C".

class ABCImmutable(SomeSuperclass):
    def __getattribute__(self, name):
        if name in ("A", "B", "C"):
            return "Immutable value of %s" % name
        else:
            # This should trigger the default behavior for any other
            # attribute name.
            raise AttributeError()

    def __setattr__(self, name, value):
        if name in ("A", "B", "C"):
            raise AttributeError("%s is an immutable attribute.")
        else:
            # How do I request the default behavior?
            ???

What goes in place of the question marks? With old-style classes, the answer was apparently self.__dict__[name] = value, but documentation indicates that this is wrong for new-style classes.

Ryan C. Thompson
  • 40,856
  • 28
  • 97
  • 159
  • 1
    "documentation indicates that this is wrong for new-style classes"...and didn't it indicate what was right for new-style classes? – Gerrat Aug 12 '11 at 15:19
  • Why aren't you just implementing your named fields as set-only properties? – Katriel Aug 12 '11 at 16:04
  • 2
    The immutability was just a trivial example use case for __setattr__. My actual use is a bit more complicated. My class inherits from dict, but in addition, certain special keys (determined at runtime) are accessible `object.key` instead of `object['key']`. I could probably add them as properties using runtime reflection or something, but it's easier to use `__getattr__` and `__setattr__`, and performance isn't particularly critical. – Ryan C. Thompson Aug 12 '11 at 19:06
  • 3
    By the way the reason why self.__dict__[name] = value wouldn't work is because you need to get the attribute self.__dict__ to do this, and thus cause infinite recursions. – trevorKirkby May 21 '14 at 04:54

3 Answers3

53

It's

super(ABCImmutable, self).__setattr__(name, value)

in Python 2, or

super().__setattr__(name, value)

in Python 3.

Also, raising AttributeError is not how you fall back to the default behavior for __getattribute__. You fall back to the default with

return super(ABCImmutable, self).__getattribute__(name)

on Python 2 or

return super().__getattribute__(name)

on Python 3.

Raising AttributeError skips the default handling and goes to __getattr__, or just produces an AttributeError in the calling code if there's no __getattr__.

See the documentation on Customizing Attribute Access.

user2357112
  • 260,549
  • 28
  • 431
  • 505
Hank Gay
  • 70,339
  • 36
  • 160
  • 222
  • 6
    Close, but you don't need the second "self". You'd call it like this: super(ABCImmutable, self).__setattr__(name, value) Otherwise you'll get the "expected 2 arguments, but got three" exception. – Dave Oct 27 '11 at 19:46
6

SomeSuperclass.__setattr__(self, name, value) ?

Jeannot
  • 1,165
  • 7
  • 18
  • Don't I need a call to `super` somewhere? At least in the general case? – Ryan C. Thompson Aug 12 '11 at 15:18
  • @Ryan Thompson I'm pretty sure you can use `super()` in place of `SomeSuperclass` but I'm trying to track down whether that is specific to Python 3. – Hank Gay Aug 12 '11 at 15:24
  • @Ryan Thompson Looking at the [Python 2 examples for Raymond Hettinger's "Python's super() considered super!" post](http://code.activestate.com/recipes/577721-how-to-use-super-effectively-python-27-version/), it looks like it would be `super(ABCImmutable, self).__setattr__(self, name, value)` in Python 2. I'll update my answer with what I think should be the correct invocation. – Hank Gay Aug 12 '11 at 15:26
  • 3
    you can also use super: `super(ABCImmutable, self).__setattr__(name, value)` in python 2.x or `super().__setattr__(name, value)` in 3.x. Maybe you want to have a look at this: http://rhettinger.wordpress.com/2011/05/26/super-considered-super/ – Jeannot Aug 12 '11 at 15:27
  • This answer most closely follows the [Python docs](https://docs.python.org/3.7/reference/datamodel.html#customizing-attribute-access). The techniques (`SomeSuperClass...` or `super...`) seem equivalent to me, but if you use `super` do take note of the args passed in as shown by @Jeannot and @Dave's [comment](https://stackoverflow.com/questions/7042152/how-do-i-properly-override-setattr-and-getattribute-on-new-style-classes#comment9675936_7042247). – Matt P Feb 27 '19 at 03:50
1

The following seems to do the trick:

class ABCImmutable:
    def __getattribute__(self, name:str):
        if name in ("A", "B", "C"):
            return "Immutable value of %s" % name
        else:
            return super(ABCImmutable, self).__getattribute__(name)

    def __setattr__(self, name:str, value):
        if name in ("A", "B", "C"):
            msg = "%s is an immutable attribute." % name
            # msg = f"{name} is an immutable attribute."
            # msg = "{} is an immutable attribute.".format(name)
            raise AttributeError(msg)
        else:
            super().__setattr__(name, value)
Toothpick Anemone
  • 4,290
  • 2
  • 20
  • 42