@mixedmethod mixes class- and instance- method into one.

Gribouillis 1 Tallied Votes 405 Views Share

This snippet defines a decorator @mixedmethod similar to @classmethod, which allows the method to access the calling instance when it exists. The decorated functions have two implicit arguments self, cls, the former having a None value when there is no instance in the call.
Using python's descriptor protocol, the implementation of @mixedmethod needs no more than 5 lines of code !

#!/usr/bin/env python
# -*-coding: utf8-*-
# Compatibility: python 2 and py3k
# Title: mixedmethod.py
# Author: Gribouillis
# Created: 2012-01-13 12:10:17.722112 (isoformat date)
# License: Public Domain
# Use this code freely.

from __future__ import print_function
from functools import partial
import sys

"""This module implements a mixedmethod() decorator for class definitions.
"""

version_info = (0, 1)
version = ".".join(map(str, version_info))


class mixedmethod(object):
    """This decorator mutates a function defined in a class into a 'mixed' class and instance method.
    
    Usage:
    
        class Spam:
        
            @mixedmethod
            def egg(self, cls, *args, **kwargs):
                if self is None:
                    pass # executed if egg was called as a class method (eg. Spam.egg())
                else:
                    pass # executed if egg was called as an instance method (eg. instance.egg())

    The decorated methods need 2 implicit arguments: self and cls, the former being None when
    there is no instance in the call. This follows the same rule as __get__ methods in python's
    descriptor protocol.
    """
    def __init__(self, func):
        self.func = func
    def __get__(self, instance, cls):
        return partial(self.func, instance, cls)

if __name__ == '__main__':
    
    class Spam(object):
        
        @mixedmethod
        def ham(self, cls, *args):
            if self is None:
                print("Spam.ham() was called as a class method with {0}.".format((self, cls)+ args))
            else:
                print("Spam.ham() was called as an instance method with {0}.".format((self, cls) + args))
            
        def __repr__(self):
            return '<Spam instance>'

    egg = Spam()
    egg.ham(5)
    Spam.ham(5)

    """ my output -->
Spam.ham() was called as an instance method with (<Spam instance>, <class '__main__.Spam'>, 5).
Spam.ham() was called as a class method with (None, <class '__main__.Spam'>, 5).
    """
TrustyTony 888 pyMod Team Colleague Featured Poster

It is quite unclear which is the 'five lines'. You could reduce this terrible code bloat ;) by using the short function exception from PEP8 and put __init__ and __get__ definitions in single line also the example could use tertiary if handling the one word difference in printed strings.

Cheers, and thanks for sharing (remind my of wonderfull story in http://www.linuxjournal.com/article/3882, unfortunately the code example's indention is messed up).

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.