As described here:
https://docs.python.org/3/reference/datamodel.html#object.__get__
The two arguments ('self' excluded) passed to the __get__ method are the object and a class through which the attribute was accessed, respectively. Isn't the second argument redundant? Furthermore, why is there a need to make a distinction between object and class access when 'classes' are also objects?
So, to me it looks like there are two possibilities:
It looks to me like the same functionality could be achieved if only one argument was used (for example instance) which will always hold the originating object, regardless of whether it is a "class" or not. If that information is really needed, one could just check using isinstance(instance, type)
.
So, why the need for both arguments?
The reason they are separate comes from the original prose in PEP 252
__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.
in other words, the two arguments allow for differentiation between an "unbound" descriptor (one called upon the class) and a "bound" descriptor (one called upon the instance). one example of where you see this often but don't really think about it is classmethod
(which uses the owner
parameter and ignores the instance
parameter).
If you're always using "bound" descriptors, you're right the owner
is a bit redundant since instance
should be an instance of that type.
Perhaps easier to see is a classmethod
descriptor implemented in pure python:
class MyClassmethod(object):
def __init__(self, func):
self._func = func
def __get__(self, instance, owner = None):
# instance is ignored, `owner` is bound to the first arg
return self._func.__get__(owner)
class C:
@MyClassmethod
def func(cls, x):
print(cls)
print(x)
C.func(1)
C().func(2)
OUTPUT = '''\
$ python3 t.py
<class '__main__.C'>
1
<class '__main__.C'>
2
'''
or consider this (somewhat incomplete) cached_class_property
:
class cached_class_property:
def __init__(self, fget):
self.fget = fget
def __get__(self, obj, owner):
val = self.fget(owner)
setattr(owner, self.fget.__name__, val)
return val
class C:
@cached_class_property
def f(self):
print('calculating...')
return 42
print(C.f)
print(C().f)
OUTPUT = '''\
$ python3 t.py
calculating...
42
42
'''
note that since python3, "unbound" and "bound" methods aren't really a concept any more, but the api persists at the descriptor level -- notably functions on classes no longer validate that the type of the instance matches the owner:
class C:
def d(self):
print(self)
class D:
pass
C().d()
C.d(D())
OUTPUT = '''\
$ python3 t.py
<__main__.C object at 0x7f09576d3040>
<__main__.D object at 0x7f09576d3040>
$ python2 t.py
<__main__.C instance at 0x7efe2c8a7910>
Traceback (most recent call last):
File "t2.py", line 9, in <module>
C.d(D())
TypeError: unbound method d() must be called with C instance as first argument (got D instance instead)
'''
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With