Reputation: 4644
I had a container holding an object's methods, and possibly some descriptors. I wanted to test whether the descriptors had already been unwrapped or not by checking whether they had had a 'get' method. To my surprise, the __get__
method of a class-method returns an object which also has a __get__
method. Do you know when this behavior is useful? Does it have something to do with overriding class methods in a derived class?
import inspect
class K:
@classmethod
def cm(cls, a, b, c):
pass
def get_attr_info(attr):
try:
sig = inspect.signature(attr)
except:
sig = None
attr_info = [
('id ', id(attr),),
('type ', type(attr),),
('hasattr ', '__get__', hasattr(attr, '__get__'),),
('hasattr ', '__call__', hasattr(attr, '__call__'),),
('SIG: ', sig,)
]
return attr_info
get_label = lambda tpl: ' '.join([str(x) for x in tpl[0:-1]]).ljust(20)
kinst = K()
cm = object.__getattribute__(type(kinst), '__dict__')['cm']
try:
for idx in range(0, 5):
info = get_attr_info(cm)
print('\n' + '\n'.join([get_label(tpl) + str(tpl[-1]) for tpl in info]))
cm = cm.__get__(kinst, type(kinst))
except AttributeError:
print(idx)
The Output Is:
id 44545808
type <class 'classmethod'>
hasattr __get__ True
hasattr __call__ False
SIG: None
id 6437832
type <class 'method'>
hasattr __get__ True
hasattr __call__ True
SIG: (a, b, c)
id 6437832
type <class 'method'>
hasattr __get__ True
hasattr __call__ True
SIG: (a, b, c)
id 6437832
type <class 'method'>
hasattr __get__ True
hasattr __call__ True
SIG: (a, b, c)
id 6437832
type <class 'method'>
hasattr __get__ True
hasattr __call__ True
SIG: (a, b, c)
Upvotes: 0
Views: 132
Reputation: 365707
For the specific question of why bound methods are descriptors (it should be obvious why classmethod
objects are, right?), see user's answer.
For the general question of why non-data descriptors need to be allowed: it would be more painful to write them. After all, the most obvious way to write a non-data descriptor is often to just return a function. And functions have to be non-data descriptors, or methods wouldn't work, defeating the whole reason descriptors were added to the language.
For example, consider the "pure-Python classmethod
" example in the HOWTO:
class ClassMethod(object):
"Emulate PyClassMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
def newfunc(*args):
return self.f(klass, *args)
return newfunc
Or, more simply, consider staticmethod
, which even in the builtin implementation just returns the function itself when bound.
Of course you could also implement any of these by building an object with a custom __call__
method, and sometimes that's worth doing (e.g., you could implement classmethod
with partial
, or with something that acts like partial
, in which case there'd be no reason to add __get__
)—but when a function just works, why not use a function?
For the more abstract question of whether you could possibly find a use for this feature… Well, sure. Among the things you can do are:
__get__
, except for checking that either the second one is a type, or the second is None
and the first is a type…)None of these are things you're going to want to do very often, but they're also not things Python has any reason to prevent you from doing.
Upvotes: 0
Reputation: 280456
The specific behavior you're seeing used to make sense because of unbound method objects. Back in Python 2, if you did
class Foo(object):
def useful_method(self):
do_stuff()
class Bar(object):
useful_method = Foo.useful_method
Foo.useful_method
would evaluate to an unbound method object, something similar to but slightly different from a function. Unbound method objects needed a __get__
so Bar().useful_method
would auto-bind self
, just like if Foo.useful_method
had evaluated to a function instead of an unbound method during the definition of Bar
.
Unbound method objects and bound method objects were implemented with the same type, so bound methods shared the same __get__
method unbound methods had. For bound method objects, though, __get__
would just return the bound method object, as if no descriptor logic was involved.
Now that unbound methods no longer exist, the __get__
method of method objects is superfluous, but it hasn't been removed.
Upvotes: 1