Code snippet: dualmethod descriptor

S

Steven D'Aprano

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.
... @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__)
... 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:
'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.
 
G

Gabriel Genellina

En Sat, 30 Jan 2010 03:06:18 -0300, Steven D'Aprano
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.

Seems useful!
Perhaps a better place to post it would be
<http://code.activestate.com/recipes/langs/python/>, at least it's easier
to search.
 
A

Arnaud Delobelle

Steven D'Aprano said:
En Sat, 30 Jan 2010 03:06:18 -0300, Steven D'Aprano
class dualmethod(object):
[...]

Seems useful!
Perhaps a better place to post it would be
<http://code.activestate.com/recipes/langs/python/>, at least it's
easier to search.



Thank you for the encouragement. The recipe is now posted:

http://code.activestate.com/recipes/577030/

Oddly, in Python 3 you can achieve something a bit similar very simply:
.... def foo(self=None):
.... return "Instance" if self else "Class"
.... 'Instance'

It works in python 3 because unbound methods are plain functions.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,073
Latest member
DarinCeden

Latest Threads

Top