16

I've seen this quite often:

def __get__(self, instance, owner=None):

Why do some people use the default value of None for the the owner parameter?

This is even done in the Python docs:

descr.__get__(self, obj, type=None) --> value
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Markus Meskanen
  • 19,939
  • 18
  • 80
  • 119
  • Somewhat related question: http://stackoverflow.com/q/8719585 – Bas Swinckels Jul 25 '15 at 13:15
  • @BasSwinckels Indeed, but unfortunately that provides no information on my question. – Markus Meskanen Jul 25 '15 at 13:21
  • 1
    Not related, but the third parameter is not the "owner". That implies that owner is the object that the descriptor is located on. Rather it is the type that the descriptor was invoked through. That is, if the descriptor was accessed via a subclass then the third parameter will be the subclass and not the parent class (which "owns" the descriptor). – Dunes Aug 01 '15 at 08:32
  • @Dunes I guess people just don't like using `type` as a parameter name. I've always thought of it as the class that "owns" the instance (instead of the descriptor), thus the owner. – Markus Meskanen Aug 01 '15 at 08:34
  • Ah, that reasoning makes more sense. Still find it a bit weird to think of a class owning an instance though. Personally, I use `objtype`. No ambiguity, and no name hiding. – Dunes Aug 01 '15 at 08:41

3 Answers3

9

Because the owner can easily be derived from the instance, the second argument is optional. Only when there is no instance to derive an owner from, is the owner argument needed.

This is described in the proposal that introduced descriptors, PEP 252 - Making Types Look More Like Classes:

__get__: a function callable with one or two arguments that retrieves the attribute value from an object. This is also referred to as a "binding" operation, because it may return a "bound method" object in the case of method descriptors. The first argument, X, is the object from which the attribute must be retrieved or to which it must be bound. When X is None, the optional second argument, T, should be meta-object and the binding operation may return an unbound method restricted to instances of T.

(Bold emphasis mine).

Binding, from day one, was meant to be applicable to the instance alone, with the type being optional. Methods don't need it, for example, since they can be bound to the instance alone:

>>> class Foo: pass
... 
>>> def bar(self): return self
... 
>>> foo = Foo()
>>> foo.bar = bar.__get__(foo)  # look ma! no class!
>>> foo.bar
<bound method Foo.bar of <__main__.Foo object at 0x10a0c2710>>
>>> foo.bar()
<__main__.Foo object at 0x10a0c2710>

Besides, the second argument can easily be derived from the first argument; witness a classmethod still binding to the class even though we did not pass one in:

>>> classmethod(bar).__get__(foo)
<bound method type.bar of <class '__main__.Foo'>>
>>> classmethod(bar).__get__(foo)()
<class '__main__.Foo'>

The only reason the argument is there in the first place is to support binding to class, e.g. when there is no instance to bind to. The class method again; binding to None as the instance won't work, it only works if we actually pass in the class:

>>> classmethod(bar).__get__(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __get__(None, None) is invalid
>>> classmethod(bar).__get__(None, Foo)
<bound method type.bar of <class '__main__.Foo'>>
Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I was under the impression that the `owner` argument would always be the ancestor class in whose dict the descriptor object is actually found, but upon further experimentation and a look at the source, it seems to just be `type(X)`. Unlike how I thought, we really can always deduce the owner from the instance. – user2357112 Aug 07 '15 at 01:37
  • 1
    @user2357112: that would rather defeat the purpose of `@classmethod`, which promises to bind the method to the current class when subclassed, not the class it was defined on. – Martijn Pieters Aug 07 '15 at 07:39
  • "Only when there is no instance to derive an owner from, is the owner argument needed." Even in this case the `owner` argument is actually not needed. The OP in [this post](https://stackoverflow.com/q/63380778/2326961) made an insightful suggestion: the same functionality could be achieved with a single argument `instance` always bound to the originating object, regardless of whether the originating object is a class instance or a class (i.e. a metaclass instance). If that information is really needed, one could just check using `isinstance(instance, type)`. – Géry Ogam Mar 28 '21 at 17:33
  • 1
    @Maggyero that would make it impossible to use descriptors *defined on a metaclass*. Note that `type` itself is also a metaclass so you can’t use `issubclass(instance, type)` as a fallback (yes, `type(type) is type` is True, and so are `issubclass(type, type)` *and* `isinstance(type, type)`). – Martijn Pieters Mar 29 '21 at 06:44
  • Yes, I realized it this morning: for a descriptor attached to a metaclass, the test `issubclass(instance, type)` will not allow the distinction between an attribute lookup from the metaclass and from an instance of the metaclass *if the instance is itself a metaclass* (however if the instance is a class, the test will work). So as you said, the `owner` parameter is absolutely necessary for a descriptor on a metaclass. And it is a shame that only the `__get__` method has it, not the `__set__` and `__delete__` methods. Do you know why Guido omitted `owner` for `__set__` and `__delete__`? – Géry Ogam Mar 29 '21 at 14:31
  • 1
    @Maggyero: 2 reasons: `__get__` needed to be special-cased to support `classmethod`, basically, *and* it would make it impossible to delete a faulty descriptor object from a type (e.g. `descriptor.__delete__(None, cls)` throwing an exception), as `classobject.__dict__` is a _read-only proxy_. You can always use a descriptor on the metaclass if you must hook into setting or deleting attributes on a class, and the use case is rare enough. – Martijn Pieters Mar 30 '21 at 11:33
  • Thanks, that is a *very* compelling argument. Is it your own interpretation, or you have a reference to a message in the Python mailing lists or elsewhere? You are the only one who found the true answer to [this question](https://stackoverflow.com/q/44937813/2326961) about the `owner` omission for `__set__` and `__delete__` so I think it really deserves to be posted. – Géry Ogam Mar 30 '21 at 15:14
  • 1
    @Maggyero: I don't, it is my own interpretation. Another reason `__get__` was special-cased: Python 2 had unbound and bound methods; unbound methods only accept `self` if `isinstance(self, owner)` is true, and that relationship needed preserving from old to new-style classes. – Martijn Pieters Apr 02 '21 at 10:12
  • Exact! To sum up, the knowledge of whether the originating object is an owner *instance* or *subclass* is necessary for `__get__`, because `classmethod` add an implicit owner subclass first argument before forwarding a call, and Python 2 unbound methods check that the first argument is an owner instance before forwarding a call. And it is not necessary for `__set__` and `__delete__`, because they are only called when the originating object is an owner instance, since if they were also called when the originating object is an owner subclass it would be impossible to change a faulty descriptor. – Géry Ogam Apr 02 '21 at 18:35
  • This week I have been studying the reference documentation on `__slots__` and I have these two questions [here](https://stackoverflow.com/q/66972862/2326961) and [here](https://stackoverflow.com/q/67009407/2326961) that did not find an answer yet. Can I request your Python expertise? – Géry Ogam Apr 09 '21 at 08:59
5

This is the standard way to do it; all Python built-in descriptors I've seen do it, including functions, properties, staticmethods, etc. I know of no case in the descriptor protocol where __get__ will be called without the owner argument, but if you want to call __get__ manually, it can be useful not to have to pass an owner. The owner argument usually doesn't do much.

As an example, you might want a cleaner way to give individual objects new methods. The following decorator cleans up the syntax and lets the methods have access to self:

def method_of(instance):
    def method_adder(function):
        setattr(instance, function.__name__, function.__get__(instance))
        return function
    return method_adder

@method_of(a)
def foo(self, arg1, arg2):
    stuff()

Now a has a foo method. We manually used the __get__ method of the foo function to create a bound method object like any other, except that since this method isn't associated with a class, we didn't pass __get__ a class. Pretty much the only difference is that when you print the method object, you see ?.foo instead of SomeClassName.foo.

Community
  • 1
  • 1
user2357112
  • 260,549
  • 28
  • 431
  • 505
  • If this is the only reason behind the default value of `owner=None`, I think that it's quite useless. These seem to be extremely rare scenarios, and I could just write `function.__get__(instance, None)` instead. I've accepted your answer, after coming into conclusion that there's no **really** good reason to use `owner=None` instead of just `owner`. – Markus Meskanen Aug 01 '15 at 11:00
2

Because that's how the descriptor protocol is specified:

descr.__get__(self, obj, type=None) --> value

cf https://docs.python.org/2/howto/descriptor.html#descriptor-protocol

The type argument allows access to the class on which the descriptor is looked up when it's looked up on a class instead of an instance. Since you can get the class from the instance, it's somehow redundant when the descriptor is looked up on an instance, so it has been made optional to allow the less verbose desc.__get__(obj) call (instead of desc.__get__(obj, type(obj))).

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • 3
    I don't think this still explains the reasoning, why would anyone do `desc.__get__(obj)`? http://pastebin.com/YwNWXR7c it seems like the owner gets passed in anyways, even when using through an instance. – Markus Meskanen Jul 25 '15 at 17:43