[Python-3000] The case for unbound methods?
Anthony Tolle
artomegus at gmail.com
Fri Mar 7 04:43:53 CET 2008
Exhibit A, wraptest.py (demonstrates using a wrapper to insert an
extra argument into method calls):
------------------------------------------------------------
import sys
if sys.version_info[0] < 3:
_self_attr = 'im_self'
else:
_self_attr = '__self__'
class methodwrapper(object):
def __init__(self, descriptor, callable=None):
self.descriptor = descriptor
self.callable = callable
def __get__(self, obj, type=None):
if self.callable is not None:
return self
return methodwrapper(self.descriptor,
self.descriptor.__get__(obj, type))
def __call__(self, *args, **kwargs):
if self.callable is None:
raise TypeError('wrapper called before __get__')
try:
obj = getattr(self.callable, _self_attr)
except AttributeError:
# must be a function
return self.callable.__call__('inserted',
*args,
**kwargs)
if obj is None:
# must be an unbound method
if not args:
raise TypeError('instance argument missing')
return self.callable.__call__(args[0],
'inserted',
*args[1:],
**kwargs)
# must be a bound method
return self.callable.__call__('inserted',
*args,
**kwargs)
if __name__ == '__main__':
class A(object):
@methodwrapper
@staticmethod
def s(inserted):
return inserted
@methodwrapper
@classmethod
def c(cls, inserted):
return inserted
@methodwrapper
def i(self, inserted):
return inserted
a = A()
assert a.s() == 'inserted' # instance binding - static method
assert a.c() == 'inserted' # instance binding - class method
assert a.i() == 'inserted' # instance binding - instance method
assert A.s() == 'inserted' # class binding - static method
assert A.c() == 'inserted' # class binding - class method
assert A.i(a) == 'inserted' # class binding - instance method
------------------------------------------------------------
Exhibit B:
Run wraptest.py in Python 2.5, and all assertions pass.
However, run it in Python 3.0, and witness:
Traceback (most recent call last):
File "wraptest.py", line 64, in <module>
assert A.i(a) == 'inserted' # class binding - instance method
AssertionError
------------------------------------------------------------
Summary:
There is a subtle difference between an unbound method and a function.
Generally, one can assume that the underlying function of an unbound
method will expect an instance as the first argument, but this is not
the case for plain functions. Here's why:
1) The staticmethod descriptor always returns a function, to which no
arguments are passed implicitly.
2) The classmethod descriptor always returns a bound method, which
implicitly passes an instance (the class) as the first argument to the
underlying function.
3) That leaves regular instance methods. In version 2.5, the
descriptor will return either a bound method (in which the instance
argument is passed implicitly), or an unbound method (in which an
instance argument must be passed explicitly), depending on the binding
used: instance binding or class binding, respectively. Either way,
the underlying function will expect an instance as the first argument.
Here is a table of combinations for Python 2.5, using wraptest.py as
the template:
a.s -> staticmethod descriptor -> function (no im_self attribute)
a.c -> classmethod descriptor -> bound method (im_self = A)
a.i -> function descriptor -> bound method (im_self = a)
A.s -> staticmethod descriptor -> function (no im_self attribute)
A.c -> classmethod descriptor -> bound method (im_self = A)
A.i -> function descriptor -> unbound method (im_self = None)
If you are creating a custom descriptor that needs to wrap static
methods, class methods, and instance methods, one can determine the
difference between instance binding and class binding for instance
methods by whether the descriptor returns a bound method or an unbound
method.
However, in 3.0, unbound methods have been done away with, and the
situation is as follows:
a.s -> staticmethod descriptor -> function (no __self__ attribute)
a.c -> classmethod descriptor -> bound method (__self__ = A)
a.i -> function descriptor -> bound method (__self__ = a)
A.s -> staticmethod descriptor -> function (no __self__ attribute)
A.c -> classmethod descriptor -> bound method (__self__ = A)
A.i -> function descriptor -> **function!** (no __self__ attribute)
As such, if the wrapper receives a function from the descriptor, how
does it know if it is a static method, which doesn't need an instance
argument, or an instance method with class binding, which does?
OK, I suppose that that the code *could* check if the descriptor is an
instance of staticmethod or classmethod. However, this is slower than
the duck typing used in the example code, assuming that a majority of
methods are plain instance methods. And, by using duck typing, the
class can wrap other descriptors that might mimic classmethod or
staticmethod. The code above could even be extended to do just that,
by mimicking the behavior of the underlying descriptor (essentially
masking its presence). That way, several wrappers could be chained
together, without having to check the type of the descriptor.
Another solution would be to create two separate wrappers: one for
static methods, and one for class methods and instance methods.
However, this seems clumsy, since it isn't even necessary in 2.5.
Have I made a case for the existence of unbound methods? I don't
know. I'll be the first to admit that I may have missed something
vitally important in my analysis. Perhaps there is a new way of doing
things in 3.0 to which I should strive. Or, perhaps I am too hung up
on creating a "universal" method wrapper.
Anyway, those are my thoughts on the subject. Thanks for your time,
Anthony Tolle
More information about the Python-3000
mailing list