[Python-3000] The case for unbound methods?
Guido van Rossum
guido at python.org
Fri Mar 7 05:59:54 CET 2008
Would you mind giving an "executive summary" of your argument that
doesn't require scanning 40 lines of code?
On Thu, Mar 6, 2008 at 7:43 PM, Anthony Tolle <artomegus at gmail.com> wrote:
> 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
> _______________________________________________
> Python-3000 mailing list
> Python-3000 at python.org
> http://mail.python.org/mailman/listinfo/python-3000
> Unsubscribe: http://mail.python.org/mailman/options/python-3000/guido%40python.org
>
--
--Guido van Rossum (home page: http://www.python.org/~guido/)
More information about the Python-3000
mailing list