Torilla
Torilla

Reputation: 403

Python class method also instance method

I have a class that in principle carries all the information about it in its class body. When instantiated, it receives additional information that together with the class attributes forms a regular instance. My problem now lies in the fact that I need to implement a method which should be called as class method when it is called from a class object but should be called as regular instance method when called from an instance:

e.g. something like


class MyClass(object):
    attribs = 1, 2, 3

    def myMethod(self, args):
        if isclass(self):
            "do class stuff"
        else:
            "do instance stuff"


MyClass.myMethod(2) #should now be called as a class method, e.g. I would normally do @classmethod

MyClass().myMethod(2) #should now be called as instance method

Of course I could declare it as staticmethod and pass either the instance or the class object explicitly, but that seems rather unpythonic and also user unfriendly.

Upvotes: 1

Views: 202

Answers (2)

MisterMiyagi
MisterMiyagi

Reputation: 50076

You can define a decorator that works like a regular method when called on an instance, or class method when called on a class. This requires a descriptor:

from functools import partial

class anymethod:
    """Transform a method into both a regular and class method"""
    def __init__(self, call):
        self.__wrapped__ = call

    def __get__(self, instance, owner):
        if instance is None:  # called on class
            return partial(self.__wrapped__, owner)
        else:                 # called on instance
            return partial(self.__wrapped__, instance)


class Foo:
    @anymethod
    def bar(first):
        print(first)

Foo.bar()    # <class '__main__.Foo'>
Foo().bar()  # <__main__.Foo object at 0x106f86610>

Note that this behaviour will not be obvious to most programmers. Only use it if you really need it.

Upvotes: 3

Jonathon Reinhart
Jonathon Reinhart

Reputation: 137398

If the methods are to behave differently, you could simply change which one is exposed by that name at initialization time:

class MyCrazyClass:
    @classmethod
    def magicmeth(cls):
        print("I'm a class")

    def _magicmeth(self):
        print("I'm an instance")

    def __init__(self):
        self.magicmeth = self._magicmeth

Upvotes: 3

Related Questions