The reason for the difference is that functions implement the descriptor protocol, but your callable class does not. The descriptor protocol is part of the language specification.
When you look up an attribute on an instance or class it will check if the attribute on the class is a descriptor, i.e. if it has __get__
, __set__
, or __delete__
. If it's a descriptor then attribute-lookup (getting, setting, and deleting) will go through these methods. If you want to know more about how descriptors work, you can check the official Python documentation or other answers here on StackOverflow, for example "Understanding __get__
and __set__
and Python descriptors".
Functions have a __get__
and thus if you look them up, they return a bound method
. A bound method is a function where the instance is passed as first argument. I'm not sure this is part of the language specification (it probably is, but I couldn't find a reference).
So your bar
and wrapped_baz
functions are descriptors, but your Baz
class isn't. So the bar
(and the wrapped_baz
) function will be looked up as "bound method" where the instance is implicitly passed into the arguments when called. However the baz
instance is returned as-is, so there's no implicit argument when called.
Making your Baz
class more method-like
Depending on what you want, you can make your Baz
act like a method by implementing a __get__
:
import types
# ...
class Baz:
def __get__(self, instance, cls):
"""Makes Baz a descriptor and when looked up on an instance returns a
"bound baz" similar to normal methods."""
if instance is None:
return self
return types.MethodType(self, instance)
def __call__(*args):
print(list(map(type, args)))
# ...
Foo().baz() # [<class '__main__.Baz'>, <class '__main__.Foo'>]
Making the wrapped_baz
less method-like
Or if you don't want the Foo
(similar to your Baz
class) then just wrap the wrapped_baz
as staticmethod
:
# ...
class Baz:
def __call__(*args):
print(list(map(type, args)))
baz = Baz()
@staticmethod
def wrapped_baz(*args):
return baz(*args)
# ...
Foo().biz() # [<class '__main__.Baz'>]