[Python-3000] The case for unbound methods?
Anthony Tolle
artomegus at gmail.com
Sat Mar 8 04:47:48 CET 2008
On Thu, Mar 6, 2008 at 11:59 PM, Guido van Rossum <guido at python.org> wrote:
> Would you mind giving an "executive summary" of your argument that
> doesn't require scanning 40 lines of code?
>
Let me put it this way: if unbound methods are gone for good, then I
think it would nice to develop some guidance on checking the signature
of callable objects, to enable decorators to play nice with each
other--especially if they intend to modify the argument list.
For example, let's say there are two wrappers around two different
method types, like so (this is a short pseudo-code example, I
promise):
class A(object):
@wrapper2
@wrapper1
@staticmethod
def sm(*args):
...
@wrapper2
@wrapper1
def im(self, *args):
...
Let's say the purpose of both wrapper1 and wrapper2 is to insert a new
argument into the argument list. How should they do this for each
combination of method and binding type?
It would work as follows (in these examples, self.callable is what was
returned by the __get__ method of the wrapped descriptor):
For a static method:
return self.callable(newarg, *args, **kwargs)
For an instance method with instance binding (i.e. a bound method):
return self.callable(newarg, *args, **kwargs)
For an instance method with class binding (i.e. an unbound method):
return self.callable(args[0], newarg, args[1:], **kwargs)
Notice the change? This is necessary to maintain consistency for
handling instance binding and class binding. With instance binding
(i.e. a bound method), the instance argument is passed implicitly--as
the first argument--by the wrapped callable. Thus, the wrapper must
simulate this for class binding, because the instance argument must be
passed explicitly.
So how does wrapper1 know whether it is wrapping a static method, a
bound method, or an unbound method? Well, one way it could do this is
to examine the type of the descriptor it is wrapping. If it sees that
it is an instance of staticmethod, then it can process it as such.
However, wrapper2 doesn't have this luxury, since all it knows is that
it is wrapping an instance of wrapper1.
The other way is to examine the callable returned by the __get__
method of the wrapped descriptor. This is where things get
interesting.
In Python 2.5, this was extremely easy: If the callable has no
im_self attribute, then it is a static function. If the callable has
an im_self attribute, and im_self is None, then it is an unbound
method. If im_self is not None, then it is a bound method. Simple.
But there's more: because the attribute signature is so simple, it can
easily be emulated by wrapper1's __get__ function, and thus wrapper2
would be able to make the same determination. Plus, wrapper1 could
even copy the im_func attribute, so it would be possible to follow the
chain of wrappers all the way back to the original function.
But Python 3.0 breaks the pattern, because unbound methods no longer
exist. When examining the callable returned by __get__, the signature
for a static method and an "unbound" method is the same (both plain
functions). Now the wrapper will not be able to determine how to
modify the arguments.
How about this as a compromise?
1) In Python 3.0, all functions will have a read-only __self__
attribute that is set to None (so that it looks like an unbound
method, signature wise).
2) Bound methods will continue to behave as before; as a callable
object with a __self__ attribute referencing the instance object, and
a __func__ attribute referencing the original function object.
3) If a function is decorated with @staticmethod, then its __get__
method will return a callable object that wraps the function. The
callable object will have no __self__ attribute, but it will have a
__func__ attribute referencing the original function.
4) As a convention, a callable that wraps another callable will copy
the __self__ attribute (or lack thereof), and the __func__ attribute.
Thus, the signature will be preserved up the chain.
Thus, the signature for a callable can be tested as follows:
if hasattr(obj, '__self__'):
if obj.__self__ is None:
..."unbound" method...
else:
....bound method...
else:
....static method....
Note that class methods always fall into the category of a bound
method, whether or not instance binding is used. If there is a need
to differentiate between an instance method and a class method, then
the __self__ object can be examined to see if it a class.
More information about the Python-3000
mailing list