I'd like to do something like this:
class X:
@classmethod
def id(cls):
return cls.__name__
def id(self):
return self.__class__.__name__
And now call id()
for either the class or an instance of it:
>>> X.id()
'X'
>>> X().id()
'X'
Obviously this exact code doesn't work, but is there a similar way to make it work?
Or any other workarounds to get such behavior without too much "hacky" stuff?
Class and instance methods live in the same namespace and you cannot reuse names like that; the last definition of id
will win in that case.
The class method will continue to work on instances however, there is no need to create a separate instance method; just use:
class X:
@classmethod
def id(cls):
return cls.__name__
because the method continues to be bound to the class:
>>> class X:
... @classmethod
... def id(cls):
... return cls.__name__
...
>>> X.id()
'X'
>>> X().id()
'X'
This is explicitly documented:
It can be called either on the class (such as C.f()
) or on an instance (such as C().f()
). The instance is ignored except for its class.
No idea what's your actual use case is, but you can do something like this using a descriptor:
class Desc(object):
def __get__(self, ins, typ):
if ins is None:
print 'Called by a class.'
return lambda : typ.__name__
else:
print 'Called by an instance.'
return lambda : ins.__class__.__name__
class X(object):
id = Desc()
x = X()
print x.id()
print X.id()
Output:
Called by an instance.
X
Called by a class.
X
It can be done, quite succinctly, by binding the instance-bound version of your method explicitly to the instance (rather than to the class). Python will invoke the instance attribute found in Class().__dict__
when Class().foo()
is called (because it searches the instance's __dict__
before the class'), and the class-bound method found in Class.__dict__
when Class.foo()
is called.
This has a number of potential use-cases, though whether they are anti-patterns is open for debate:
class Test:
def __init__(self):
self.check = self.__check
@staticmethod
def check():
print('Called as class')
def __check(self):
print('Called as instance, probably')
>>> Test.check()
Called as class
>>> Test().check()
Called as instance, probably
Or... let's say we want to be able to abuse stuff like map()
:
class Str(str):
def __init__(self, *args):
self.split = self.__split
@staticmethod
def split(sep=None, maxsplit=-1):
return lambda string: string.split(sep, maxsplit)
def __split(self, sep=None, maxsplit=-1):
return super().split(sep, maxsplit)
>>> s = Str('w-o-w')
>>> s.split('-')
['w', 'o', 'w']
>>> Str.split('-')(s)
['w', 'o', 'w']
>>> list(map(Str.split('-'), [s]*3))
[['w', 'o', 'w'], ['w', 'o', 'w'], ['w', 'o', 'w']]
In your example, you could simply delete the second method entirely, since both the staticmethod and the class method do the same thing.
If you wanted them to do different things:
class X:
def id(self=None):
if self is None:
# It's being called as a static method
else:
# It's being called as an instance method
"types" provides something quite interesting since Python 3.4: DynamicClassAttribute
It is not doing 100% of what you had in mind, but it seems to be closely related, and you might need to tweak a bit my metaclass but, rougly, you can have this;
from types import DynamicClassAttribute
class XMeta(type):
def __getattr__(self, value):
if value == 'id':
return XMeta.id # You may want to change a bit that line.
@property
def id(self):
return "Class {}".format(self.__name__)
That would define your class attribute. For the instance attribute:
class X(metaclass=XMeta):
@DynamicClassAttribute
def id(self):
return "Instance {}".format(self.__class__.__name__)
It might be a bit overkill especially if you want to stay away from metaclasses. It's a trick I'd like to explore on my side, so I just wanted to share this hidden jewel, in case you can polish it and make it shine!
>>> X().id
'Instance X'
>>> X.id
'Class X'
Voila...