If you want _PrintingArguments to bind the same way as a plain function, this is actually possible, you just need to implement the descriptor protocol yourself to match the way built-in functions behave. Conveniently, Python provides types.MethodType, which can be used to create a bound method from any callable, given an instance to bind to, so we use that to implement our descriptor's __get__:
import types
class _PrintingArguments:
# __init__ and __call__ unchanged
def __get__(self, instance, owner):
if instance is None:
return self # Accessed from class, return unchanged
return types.MethodType(self, instance) # Accessed from instance, bind to instance
This works as you expect on Python 3 (Try it online!). On Python 2 it's even simpler (because unbound methods exist, so the call to types.MethodType can be made unconditionally):
import types
class _PrintingArguments(object): # Explicit inheritance from object needed for new-style class on Py2
# __init__ and __call__ unchanged
def __get__(self, instance, owner):
return types.MethodType(self, instance, owner) # Also pass owner
Try it online!
For slightly better performance (on Python 2 only), you could instead do:
class _PrintingArguments(object): # Explicit inheritance from object needed for new-style class on Py2
# __init__ and __call__ unchanged
# Defined outside class, immediately after dedent
_PrintingArguments.__get__ = types.MethodType(types.MethodType, None, _PrintingArguments)
which moves the implementation of __get__ to the C layer by making an unbound method out of types.MethodType itself, removing byte code interpreter overhead from each call.