Code snippet: dualmethod descriptor

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Sat Jan 30 07:06:18 CET 2010


I came up with this descriptor a few years ago, but never used it for 
anything. I've just found an actual use for it in my own code, so I 
thought I'd make it public for anyone else who might have a use for it.

Use-case: if you have a class without an __init__ or __new__ method, then 
this may be of use to you. It enables the caller to call methods on 
either the class, or an instance, and the first argument passed the 
method will be the class or the instance respectively.

This differs from classmethods, which always passes the class, and 
staticmethods, which don't pass either.

The name "dualmethod" is my own.

Here's the implementation:

class dualmethod(object):
    """Descriptor implementing dualmethods (combination 
    class/instance method).

    Returns a method which takes either an instance or a class as 
    the first argument. When called on an instance, the instance is
    passed as the first argument. When called as a class, the class
    itself is passed instead.

    >>> class Example(object):
    ...     @dualmethod
    ...     def method(this):
    ...         if type(this) is type:
    ...             print("I am the class '%s'." % this.__name__)
    ...         else:
    ...             print("I am an instance of the class '%s'." %
    ...             this.__class__.__name__)
    ...
    >>> Example.method()
    I am the class 'Example'.
    >>> Example().method()
    I am an instance of the class 'Example'.

    """
    def __init__(self, func):
        self.func = func
    def __get__(self, obj, cls=None):
        if obj is None: obj = cls or type(obj)
        def newfunc(*args, **kwargs):
            return self.func(obj, *args, **kwargs)
        return newfunc


(Tested with Python 2.4, 2.5, 2.6, 3.0 and 3.1.)


My use-case is a class that has constants in class attributes, and 
methods which refer to those constants. Because they are constants, there 
is no reason to give each instance a separate reference to the attribute, 
hence the attribute is on the class:

class Example:
    ATTR = "something"
    @dualmethod
    def method(this):
        return this.ATTR + " else"

Normally I have no reason to instantiate the class: there is no __init__ 
or __new__ method. Normally I would use classmethod, but every now and 
again I want to change the behaviour by modifying ATTR, and using class-
methods would force me to subclass in order to make that change.

dualmethod lets me get the best of both worlds. If I'm using the default 
value of ATTR, I can just call the method on the class without bothering 
to instantiate it. But if I want to customise the behaviour of the 
method, rather than subclassing, I can instantiate and add an instance 
attribute:

>>> Example.method()
'something else'
>>> x = Example()
>>> x.ATTR = "nothing"
>>> x.method()
'nothing else'
>>> Example.method()  # default still works as expected
'something else'


without effecting the default behaviour.

I hope this is useful to you. You can use this without condition, 
although I would be grateful for credit.



-- 
Steven



More information about the Python-list mailing list