[Python-3000] Bound and unbound methods

Josiah Carlson jcarlson at uci.edu
Sun Aug 13 19:58:33 CEST 2006


Talin <talin at acm.org> wrote:
> 
> One of the items in PEP 3100 is getting rid of unbound methods. I want 
> to explore a heretical notion, which is getting rid of bound methods as 
> well.
> 
> Now, to be honest, I rather like bound methods. I like being able to 
> capture a method call, store it in a variable, and call it later.
> 
> However, I also realize that requiring every access to a class variable 
> to instantiate a new method object is expensive, to say the least.

Well, it's up-front vs. at-access.  For instances whose methods are
generally used rarely, the up-front cost of instantiating every method
is high in comparison (unless there are a relatively large number of
method accesses), and technically infinite if applied to all objects.
Why?

I have a class foo, I instantiate foo, now all of foo's methods get
instantiated.  Ahh, but foo's methods are also instances of function. It
doesn't really have any new methods on foo's methods, but they do have
attributes that are instances, so we will need to instantiate all of the
methods' attributes' methods, and recursively, to infinity.  The
non-creation of instantiated methods for objects is a lazy-evaluation
technique to prevent infinite recursion, in general.

On the other hand, it may make sense to offer a metaclass and/or
decorator that signals that a single method instance should be created
for particular methods up-front, rather than at-access to those methods.
But what kind of difference could we expect?  42%/28% improvement for
class methods/object methods in 2.4 respectively, and 45%/26%
improvement in 2.5 beta .  This does not include actually calling the
methods.


> Now, one remaining problem to be solved is whether or not to pass 'self' 
> as an argument to the resulting callable. I suppose that could be 
> handled by inspecting the attributes of the callable and adding the 
> extra 'self' argument at the last minute if its not a static method. I 
> suspect such tests would be relatively fast, much less than the time 
> needed to instantiate and initialize a new method object.

I think that a change that required calls of the form
obj.instancemethod(obj, ...) are non-starters.  


I'm -1 for instantiating all methods (for the infinite cost reasons),
and -1 for int, long, list, tuple, dict, float (method access is
generally limited for these objects).  I'm +0 for offering a suitable
metaclass and/or decorator, but believe it would be better suited for
the Python cookbook, as performance improvements when function calls are
taken into consideration is significantly less.

 - Josiah

[1]

Timings for accessing instance methods

Python 2.4.3 (#69, Mar 29 2006, 17:35:34) [MSC v.1310 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import time
>>>
>>> def test(n):
...     _time = time
...
...     class foo:
...         def bar(self):
...             pass
...     xr = xrange(n)
...     x = foo()
...     t = time.time()
...     for i in xr:
...         x.bar
...     print 'class method', time.time()-t
...
...     x.bar = x.bar
...     t = time.time()
...     for i in xr:
...         x.bar
...     print 'instantiated class method', time.time()-t
...
...     class foo(object):
...         def bar(self):
...             pass
...
...     x = foo()
...     t = time.time()
...     for i in xr:
...         x.bar
...     print 'object method', time.time()-t
...
...     x.bar = x.bar
...     t = time.time()
...     for i in xr:
...         x.bar
...     print 'instantiated object method', time.time()-t
...
...     class foo(object):
...         __slots__ = 'bar'
...         def __init__(self):
...             self.bar = self._bar
...         def _bar(self):
...             pass
...
...     x = foo()
...     t = time.time()
...     for i in xr:
...         x.bar
...     print 'instantiated object __slot__ method', time.time()-t
...
>>> test(5000000)
class method 1.96799993515
instantiated class method 1.14100003242
object method 1.71900010109
instantiated object method 1.23399996758
instantiated object __slot__ method 1.26600003242
>>>

Python 2.5b2 (r25b2:50512, Jul 11 2006, 10:16:14) [MSC v.1310 32 bit (Intel)] on
 win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import time
>>>
>>> def test(n):
...     _time = time
...
...     class foo:
...         def bar(self):
...             pass
...     xr = xrange(n)
...     x = foo()
...     t = time.time()
...     for i in xr:
...         x.bar
...     print 'class method', time.time()-t
...
...     x.bar = x.bar
...     t = time.time()
...     for i in xr:
...         x.bar
...     print 'instantiated class method', time.time()-t
...
...     class foo(object):
...         def bar(self):
...             pass
...
...     x = foo()
...     t = time.time()
...     for i in xr:
...         x.bar
...     print 'object method', time.time()-t
...
...     x.bar = x.bar
...     t = time.time()
...     for i in xr:
...         x.bar
...     print 'instantiated object method', time.time()-t
...
...     class foo(object):
...         __slots__ = 'bar'
...         def __init__(self):
...             self.bar = self._bar
...         def _bar(self):
...             pass
...
...     x = foo()
...     t = time.time()
...     for i in xr:
...         x.bar
...     print 'instantiated object __slot__ method', time.time()-t
...
>>> test(5000000)
class method 1.98500013351
instantiated class method 1.09299993515
object method 1.67199993134
instantiated object method 1.23500013351
instantiated object __slot__ method 1.23399996758
>>>



More information about the Python-3000 mailing list