Reputation: 88711
I want to write some Python code that when called from the context of a type behaves like an @classmethod
, however when called on an instance of an object the first argument is behaves as normal, with self
referencing the current instance. I.e. I want to write:
class Foo(object):
@something # <- does this exist? How can I even write it?
def bar(self_or_class):
print(repr(self_or_class))
Foo.bar() # Should print: <class '__main__.Foo'>
inst = Foo()
inst.bar() # Should print: <__main__.Foo at 0x....>
Upvotes: 1
Views: 617
Reputation: 2220
I once wrote a code snippet in daniweb about this
from __future__ import print_function
from functools import partial
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)
Upvotes: 2
Reputation: 88711
After some research I managed to do this. The first thing I found is that you can actually implement @classmethod
in pure Python, as referenced in the documentation.
With that knowledge it's fairly simple to adapt the code to test if obj
exists:
class something(object):
'''
This works like @classmethod, except the first argument is either class or self if it's available
'''
def __init__(self, f):
self.f = f
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
def newfunc(*args, **kwargs):
return self.f(klass if obj is None else obj, *args, **kwargs)
return newfunc
All we have to do over and above the example in the documentation is check if obj
is None
and favour obj
over klass
if it is not.
Upvotes: 1