Why does __get__ take an owner while __set__ and __delete__ do not?
Asked Answered
T

2

3

From the Python data model documentation:

object.__get__(self, instance, owner=None)

Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access). The optional owner argument is the owner class, while instance is the instance that the attribute was accessed through, or None when the attribute is accessed through the owner.

This method should return the computed attribute value or raise an AttributeError exception.

PEP 252 specifies that __get__() is callable with one or two arguments. Python’s own built-in descriptors support this specification; however, it is likely that some third-party tools have descriptors that require both arguments. Python’s own __getattribute__() implementation always passes in both arguments whether they are required or not.

object.__set__(self, instance, value)

Called to set the attribute on an instance instance of the owner class to a new value, value.

Note, adding __set__() or __delete__() changes the kind of descriptor to a “data descriptor”. See Invoking Descriptors for more details.

object.__delete__(self, instance)

Called to delete the attribute on an instance instance of the owner class.

Why does __get__ take an owner while __set__ and __delete__ do not?

Does it mean that when a descriptor supplies both __get__ and __set__,

  • we can get an attribute no matter whether it belongs to an instance of the owner class or to the owner class,
  • we can set and delete an attribute when it belongs to an instance of the owner class but not when it belongs to the owner class?

My question is actually part of this one.

Tevet answered 6/7, 2017 at 0:6 Comment(0)
S
7

owner mostly exists for getting the attribute on the class itself, rather than an instance. When you're retrieving the attribute on an instance, the owner argument is redundant, since it's just type(instance).

__set__ doesn't apply to setting the attribute on the class itself, so it has no use for owner.

Sarver answered 6/7, 2017 at 0:9 Comment(3)
Thanks. My question is actually part of stackoverflow.com/questions/44937897/…Tevet
Thanks. Why "__set__ doesn't apply to setting the attribute on the class itself", while __get__ applies to getting the attribute on either a class or an instance of a class?Tevet
@Tim: That's how the language was designed. They probably didn't have enough of a use case for making __set__ apply to the class. If you want to see the actual rationale, you'd have to search the Python mailing lists.Sarver
U
0

Let us consider an attribute access on an originating object which ends up finding a descriptor on an owning class and calls its __get__, __set__ or __delete__ method.

  • For __get__, the information on whether the originating object is an instance or subclass of the owning class is necessary, because classmethod add a subclass of the owning class as first argument before forwarding a call, and because Python 2 unbound methods check that the first argument is an instance of the owning class before forwarding a call (the second reason is now only historical since Python 3 replaced unbound methods with plain functions). So __get__ needs two pieces of information: the originating object to retrieve the attribute value from, and whether the originating object is an instance or subclass of the owning class.
  • For __set__ and __delete__, the information on whether the originating object is an instance or subclass of the owning class is not necessary, because the methods are only called when the originating object is an instance of the owning class, since if they were also called when the originating object is a subclass of the owning class it would be impossible to change a faulty descriptor as class dictionaries are read-only types.MappingProxyType. So __set__ needs two piece of information: the originating object on which to set the attribute value, and the attribute value. And __delete__ needs one piece of information: the originating object from which to delete the attribute value.

A straightforward way to provide this information would have been to design the descriptor API with the following parameters:

  • an origin object parameter for providing the originating object (so for __get__, __set__, and __delete__);
  • an isinstance Boolean parameter for providing whether the originating object is an instance or subclass of the owning class (so only for __get__);
  • a value object parameter for providing the attribute value (so only for __set__).

However Guido van Rossum adopted a different but equivalent design for the descriptor API:

  • an instance object parameter for providing the originating object when it is an instance of the owning class (so for __get__, __set__, and __delete__), and whether the originating object is an instance or subclass of the owning class by using None for the latter (so only for __get__);
  • an owner type parameter for providing the originating object when it is a subclass of the owning class (so only for __get__);
  • a value object parameter for providing the attribute value (so only for __set__).

He specified the design of the descriptor API in PEP 252: Making Types Look More Like Classes, published on 19 April 2001:

Specification of the attribute descriptor API Attribute descriptors

Attribute descriptors may have the following attributes. In the examples, x is an object, C is x.__class__, x.meth() is a method, and x.ivar is a data attribute or instance variable. All attributes are optional -- a specific attribute may or may not be present on a given descriptor. An absent attribute means that the corresponding information is not available or the corresponding functionality is not implemented.

  • __name__: the attribute name. Because of aliasing and renaming, the attribute may (additionally or exclusively) be known under a different name, but this is the name under which it was born. Example: C.meth.__name__ == 'meth'.
  • __doc__: the attribute's documentation string. This may be None.
  • __objclass__: the class that declared this attribute. The descriptor only applies to objects that are instances of this class (this includes instances of its subclasses). Example: C.meth.__objclass__ is C.
  • __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. When both X and T are specified, X should be an instance of T. Exactly what is returned by the binding operation depends on the semantics of the descriptor; for example, static methods and class methods (see below) ignore the instance and bind to the type instead.
  • __set__(): a function of two arguments that sets the attribute value on the object. If the attribute is read-only, this method may raise a TypeError or AttributeError exception (both are allowed, because both are historically found for undefined or unsettable attributes). Example: C.ivar.set(x, y) ~~ x.ivar = y.

I am indebted to Martijn Pieters for the arguments used in this answer (cf. our discussion in comments of this post).

Udell answered 24/3, 2021 at 23:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.