Reputation: 99418
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, whileinstance
is the instance that the attribute was accessed through, orNone
when the attribute is accessed through theowner
.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__
,
My question is actually part of this one.
Upvotes: 3
Views: 859
Reputation: 13
I am going to borrow Marty Alchin's answer:
... the fact that only
__get__
can receive theowner
class, while the rest only receive the instance.
Descriptors are assigned to a class, not to an instance, and modifying the class would actually overwrite or delete the descriptor itself, rather than triggering its code. This is intentional. Otherwise, once a descriptor is set, it could never be removed or modified without modifying source code and restarting the program. That’s not preferable, so only the retrieval method (
__get__
) has access to the owner class.
An example to show what is explained above. The code also explains this portion of @Gery Ogam's answer:
import random
class Die:
def __init__(self, sides=6):
self.sides = sides
def __get__(self, instance, owner=None):
return int(random.random() * self.sides) + 1
def __set__(self, instance, value):
...
class Game:
d6 = Die()
# Assume:
# if `__set__` was designed to take an `owner` class,
# the code below will trigger the `__set__` method
# rather than call `__get__` to override
# the value of `d6` in the `Game.__dict__`.
Game.d6 = 3
print(Game.d6) # 3
The
owner
will always be set to the appropriate class, thoughinstance
may beNone
if the attribute was accessed from the class. This is what Django uses to throw an error if you try to access a manager from an object instead of a class, or a related model (ForeignKey) on a class instead of an object.
Upvotes: 0
Reputation: 8027
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.
__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.__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:
origin
object parameter for providing the originating object (so for __get__
, __set__
, and __delete__
);isinstance
Boolean parameter for providing whether the originating object is an instance or subclass of the owning class (so only for __get__
);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:
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__
);owner
type parameter for providing the originating object when it is a subclass of the owning class (so only for __get__
);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
isx.__class__
,x.meth()
is a method, andx.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 beNone
.__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. WhenX
isNone
, the optional second argument,T
, should be meta-object and the binding operation may return an unbound method restricted to instances ofT
. When bothX
andT
are specified,X
should be an instance ofT
. 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 aTypeError
orAttributeError
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).
Upvotes: 0
Reputation: 280465
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
.
Upvotes: 7