[Python-3000] Bound and unbound methods (original) (raw)

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.

[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