[Python-ideas] Weak-referencing/weak-proxying of (bound) methods

Jan Kaliszewski zuo at chopin.edu.pl
Mon Jun 11 01:16:29 CEST 2012


Hello,

Today, I encountered a surprising bug in my code which creates
some weakref.proxies to instance methods... The actual Python
behaviour related to the issue can be ilustrated with the
following example:

    >>> import weakref
    >>> class A:
    ...     def method(self): print(self)
    ...
    >>> A.method
    <function method at 0xb732926c>
    >>> a = A()
    >>> a.method
    <bound method A.method of <__main__.A object at 0xb7326bec>>
    >>> r = weakref.ref(a.method)  # creating a weak reference
    >>> r                          # ...but it appears to be dead
    <weakref at 0xb7327d9c; dead>
    >>> w = weakref.proxy(a.method)  # the same with a weak proxy
    >>> w
    <weakproxy at 0xb7327d74 to NoneType at 0x829f7d0>
    >>> w()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ReferenceError: weakly-referenced object no longer exists

This behaviour is perfectly correct -- but still surprising,
especially for people who know little about method creation
machinery, descriptors etc.

I think it would be nice to make this 'trap' less painful --
for example, by doing one or both of the following:

1. Describe and explain this behaviour in the weakref
module documentation.

2. Provide (in functools?) a type-and-decorator that do the
same what func_descr_get() does (transforms a function into
a method) *plus* caches the created method (e.g. at the
instance object).

A prototype implementation:

    class InstanceCachedMethod(object):

        def __init__(self, func):
            self.func = func
            (self.instance_attr_name
            ) = '__{0}_method_ref'.format(func.__name__)

        def __get__(self, instance, owner):
            if instance is None:
                return self.func
            try:
                return getattr(instance, self.instance_attr_name)
            except AttributeError:
                method = types.MethodType(self.func, instance)
                setattr(instance, self.instance_attr_name, method)
                return method
            
A simplified version that reuses the func.__name__ (works well
as long as func.__name__ is the actual instance attribute name...):

    class InstanceCachedMethod(object):

        def __init__(self, func):
            self.func = func

        def __get__(self, instance, owner):
            if instance is None:
                return self.func
            method = types.MethodType(self.func, instance)
            setattr(instance, self.func.__name__, method)
            return method 

Both versions work well with weakref.proxy()/ref() objects:

    >>> class B:
    ...     @InstanceCachedMethod
    ...     def method(self): print(self)
    ...
    >>> B.method
    <function method at 0xb7329d6c>
    >>> b = B()
    >>> b.method
    <bound method B.method of <__main__.B object at 0xb7206ccc>>
    >>> r = weakref.ref(b.method)
    >>> r
    <weakref at 0xb72c611c; to 'method' at 0xb736c40c (method)>
    >>> w = weakref.proxy(b.method)
    >>> w
    <weakproxy at 0xb7327e14 to method at 0xb736c40c>
    >>> w()
    <__main__.B object at 0xb7206ccc>

What do you think about it?

Cheers.
*j




More information about the Python-ideas mailing list