20

What are some general rules of thumb for choosing which of these to implement in a given class, in a given situation?

I have read the docs, and so understand the difference between them. Rather, I am looking for guidance on how to best integrate their usage into my workflow by being better able to notice more subtle opportunities to use them, and which to use when. That kind of thing. The methods in question are (to my knowledge):

## fallback
__getattr__
__setattr__
__delattr__

## full control
__getattribute__
##(no __setattribute__ ? What's the deal there?)

## (the descriptor protocol)
__get__
__set__
__delete__
Ethan Bierlein
  • 3,353
  • 4
  • 28
  • 42
Inversus
  • 3,125
  • 4
  • 32
  • 37
  • 2
    To the close-voter: IMX, learner questions like "how do I decide between X and Y?" or "what is the difference between X and Y?" are really two questions - "what is the intended purpose of X?" and "what is the intended purpose of Y?" Perhaps it would be better to ask them separately, but neither is, in the current case, "too broad" IMO, and there is at least *some* connection to be made between the different magic methods here. – Karl Knechtel Sep 12 '14 at 12:55

2 Answers2

17

__setattribute__ does not exist because __setattr__ is always called. __getattr__ is only called for f.x if the attribute lookup fails via the normal channel (which is provided by __getattribute__, so that function is similarly always called).

The descriptor protocol is slightly orthogonal to the others. Given

class Foo(object):
    def __init__(self):
        self.x = 5

f = Foo()

The following are true:

  • f.x will invoke f.__getattribute__('x') if __getattribute__ is defined.
  • f.x will not invoke f.__getattr__('x') if __getattr__ is defined.
  • f.y will invoke f.__getattr__('y') if __getattr__ is defined, or else f.__getattribute__('y') if __getattribute__ is defined.

The descriptor is invoked by an attribute, rather than for an attribute. That is:

class MyDescriptor(object):
    def __get__(...):
        pass
    def __set__(...):
        pass

class Foo(object):
    x = MyDescriptor()

f = Foo()

Now, f.x would cause type(f).__dict__['x'].__get__ to be called, and f.x = 3 would call type(f).__dict__['x'].__set__(3).

That is, Foo.__getattr__ and Foo.__getattribute__ would be used to find what f.x references; once you have that, f.x produces the result of type(f.x).__get__() if defined, and f.x = y invokes f.x.__set__(y) if defined.

(The above calls to __get__ and __set__ are only approximately correct, since I've left out the details of what arguments __get__ and __set__ actually receive, but this should be enough to explain the difference between __get__ and __getattr[ibute]__.)

Put yet another way, if MyDescriptor did not define __get__, then f.x would simply return the instance of MyDescriptor.

Brand0R
  • 1,413
  • 11
  • 17
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Oh.. I see now. Thanks for making those connections clearer. Is there any way to make `x = 3` (with qualifying the object the method is attached to?) – Inversus Sep 12 '14 at 13:07
  • But, now that I've played with it a bit. I think __getattribute__ is exactly what I need. My use case is when it's in a collection anyway, so I think this will work. Thanks. – Inversus Sep 14 '14 at 02:48
  • And reread your very good answer, and that edit helped. I can, in fact do what I'm looking to. (Have the syntax of not having to qualify the object) by defining `MyDescriptor`, setting it to `x`, and then doing the assignment `x = 3`. Sweet!. Thanks. – Inversus Sep 14 '14 at 03:09
  • It would seem that it's not possible to create a descriptor and use it at the ground level? (in an interpreter session) – Inversus Sep 14 '14 at 04:39
  • Right; the descriptor protocol is only invoked when accessing an attribute of a class instance. – chepner Sep 15 '14 at 12:43
14

For __getattr__ vs __getattribute__, see for example Difference between __getattr__ vs __getattribute__ .

__get__ is not really related. I'm going to quote from the official documentation for the descriptor protocol here:

The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses. If the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined.

The purpose of __get__ is to control what happens once a.x is found through that "lookup chain" (for example, to create a method object instead of returning a plain function found via type(a).__dict__['x']); the purpose of __getattr__ and __getattribute__ is to alter the lookup chain itself.

There is no __setattribute__ because there is only one way to actually set an attribute of an object, under the hood. You might want to cause other things to happen "magically" when an attribute is set; but __setattr__ covers that. __setattribute__ couldn't possibly provide any functionality that __setattr__ doesn't already.

However - the best answer, in the overwhelming majority of cases, is to not even think of using any of these. First look to higher-level abstractions, such as propertys, classmethods, and staticmethods. If you think you need specialized tools like this, and can't figure it out for yourself, there's a pretty good chance you're wrong in your thinking; but regardless, it's better to post a more specific question in that case.

Community
  • 1
  • 1
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153