0

The official Python Programming FAQ advises the programmer to define the methods object.__getattr__(self, name) and object.__setattr__(self, name, value) under certain circumstances when implementing the OOP concept of delegation. However, I can't find good general advice on when defining these two methods is necessary, customary, or useful.


The Python Programming FAQ's example is the following code

class UpperOut:

    def __init__(self, outfile):
        self._outfile = outfile

    def write(self, s):
        self._outfile.write(s.upper())

    def __getattr__(self, name):
        return getattr(self._outfile, name)

"to implement[] a class that behaves like a file but converts all written data to uppercase". The accompanying text also contains a vague note telling the programmer to define __setattr__ in a careful way whenever attributes need to be set/modified (as opposed to just being read).

One question about this code is why their example doesn't use inheritance, which would presumably take care of what __getattr__ does. However, as one commenter kindly pointed out, file doesn't exist as a distinct type in Python 3, which may point us to an answer: Maybe they wanted to illustrate pure delegation (that is: delegation without inheritance) as one use case of overwriting __getattr__ and __setattr__. (If one uses plain inheritance, attributes are inherited by default and hence don't need to be accessed explicitly via calls to __getattr__ or __setattr__.)

For reference, a definition of "delegation" from Wikipedia is the following:

In object-oriented programming, delegation refers to evaluating a member [...] of one object [...] in the context of another [...] object [...]. Delegation can be done explicitly [...]; or implicitly, by the member lookup rules of the language [...]. Implicit delegation is the fundamental method for behavior reuse in prototype-based programming, corresponding to inheritance in class-based programming.

Even though the two concepts are often discussed together, delegation is not the same as inheritance:

The term "inheritance" is loosely used for both class-based and prototype-based programming, but in narrow use the term is reserved for class-based programming (one class inherits from another), with the corresponding technique in prototype-based programming being instead called delegation (one object delegates to another).

Note that delegation is uncommon in Python.


Note that this question on this site is about use cases of vanilla getattr(object, name) and setattr(object, name, value) and is not directly related.

Lover of Structure
  • 1,561
  • 3
  • 11
  • 27
  • 2
    When you want custom behavior when an attribute is set or fetched, or dynamically patch in more attributes. It's never necessary but it could make the object more convenient and easier to use correctly. – Ted Klein Bergman Feb 22 '23 at 10:56
  • In the example that you link to, using `__getattr__` means that the `UpperOut` class doesn't have to implement all the methods of `file`; the only one it implements is `write`, because - for that method - it wants to have different behaviour. All other methods (and attributes) are derived from the underlying `file` object. – Nick Feb 22 '23 at 10:56
  • @Nick Thanks. I was wondering why their example doesn't use inheritance; wouldn't that take care of what `__getattr__` does here? Maybe they wanted to illustrate "delegation without inheritance", but then that would be one use case of overwriting `__getattr__` and `__setattr__`. – Lover of Structure Feb 22 '23 at 11:07
  • 1
    In this particular case, yes, I think you are right, since otherwise `UpperOut` is intended to be just like `file`. There's more information on this [here](https://stackoverflow.com/questions/832536/when-to-use-delegation-instead-of-inheritance) and [here](https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance) – Nick Feb 22 '23 at 11:15
  • Awesome. Perhaps then "delegation without inheritance" is a possible class of answers to my question. – Lover of Structure Feb 22 '23 at 11:18
  • 2
    @Nick One nit: `file` doesn't exist as a distinct type in Python 3. `open` instead returns instances of various stream classes from the `io` module, depending on the arguments to `open`. (That's actually significant, as there is no longer *one* class to override in this case; calls are delegated to an *object*, no matter what type that object might have.) – chepner Feb 22 '23 at 13:31
  • @chepner Well, I did say "file object" in my first comment but then I guess I confused the issue with the second comment (I should have said "the file object" again or something similar). Thanks for clarifying. – Nick Feb 22 '23 at 22:23
  • related question (there doesn't seem to be much on this topic): https://stackoverflow.com/q/63338641/2057969 – Lover of Structure Feb 28 '23 at 03:49
  • 1
    Check out this answer I wrote a while back. The question was asking how to "choose whether an instance inherits from one class or another". That doesn't really fit with the inheritance paradigm, so I suggested using *delegation* instead: https://stackoverflow.com/questions/56746709/can-i-choose-the-parent-class-of-a-class-from-a-fixed-set-of-parent-classes-cond/56747123#56747123 – juanpa.arrivillaga Feb 28 '23 at 19:24
  • 1
    Here is another answer with an example of delegation, this time *without* `__getattr__`, but instead, using the descriptor protocol: https://stackoverflow.com/questions/59742625/dynamically-adding-builtin-methods-to-point-to-a-propertys-built-ins/59743116#59743116 And in the same question, here is another answer with yet another approach to delegation: https://stackoverflow.com/a/59742860/5014455 – juanpa.arrivillaga Feb 28 '23 at 19:26
  • 1
    As you have noted, inheritance is really a form of delegation. In class-based OOP languages, it is the first form that comes to mind. But if you look at other OOP flavors like *prototype-based OOP*, delegation is at the center of everything. One way to think about prototype-based OOP is that objects don't inherit from *types* they inherit from *other objects* (i.e. prototype). Javascript is one such language, although, pretty much everyone ignores the fundamental prototypal nature of the language and a class-based system has been grafted on to it. – juanpa.arrivillaga Feb 28 '23 at 19:36

1 Answers1

1

Just coincidentally I ran into an instance of this in the Python standard ctypes module not long ago, and it led me to ask a question: Safe use of ctypes.create_string_buffer?

Many of the functions in that module create objects that act as wrappers for simple C types, like create_string_buffer creates a wrapper for a simple char array. Since the char array isn't a Python type, you can't work with it directly. When for example you try to get the value of the .raw attribute, it returns a bytes object which is a copy of the internal array; this has to be done via a __getattr__ call. Similarly when you try to write to the .raw attribute you can't write to it directly, it must pass through a __setattr__ call.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622