¿El mismo nombre para classmethod y instancemethod?

Me gustaría hacer algo como esto:

class X: @classmethod def id(cls): return cls.__name__ def id(self): return self.__class__.__name__ 

Y ahora llame a id() para la clase o una instancia de ella:

 >>> X.id() 'X' >>> X().id() 'X' 

Obviamente, este código exacto no funciona, pero ¿hay una manera similar de hacerlo funcionar? ¿O cualquier otra solución para obtener ese comportamiento sin demasiadas cosas “piratas”?

Los métodos de clase e instancia viven en el mismo espacio de nombres y no se pueden reutilizar nombres como ese; La última definición de id ganará en ese caso.

El método de clase continuará funcionando en instancias; sin embargo, no es necesario crear un método de instancia separado; Solo usa:

 class X: @classmethod def id(cls): return cls.__name__ 

Porque el método sigue estando vinculado a la clase:

 >>> class X: ... @classmethod ... def id(cls): ... return cls.__name__ ... >>> X.id() 'X' >>> X().id() 'X' 

Esto está documentado explícitamente:

Se puede llamar en la clase (como Cf() ) o en una instancia (como C().f() ). La instancia se ignora a excepción de su clase.

No tengo idea de cuál es su caso de uso real, pero puede hacer algo como esto usando un 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() 

Salida:

 Called by an instance. X Called by a class. X 

Se puede hacer, de manera bastante sucinta, vinculando la versión vinculada a la instancia de su método explícitamente a la instancia (en lugar de a la clase). Python invocará el atributo de instancia que se encuentra en Class().__dict__ cuando se Class().__dict__ Class().foo() (porque busca el __dict__ la instancia antes de la clase ‘), y el método vinculado a la clase que se encuentra en Class.__dict__ cuando Class.foo() se llama.

Esto tiene una serie de posibles casos de uso, aunque el hecho de que sean anti-patrones está abierto a 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 

O … digamos que queremos poder abusar de cosas como 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('wo-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']] 

“tipos” proporciona algo bastante interesante desde Python 3.4: DynamicClassAttribute

No está haciendo el 100% de lo que tenía en mente, pero parece estar estrechamente relacionado, y es posible que necesite modificar un poco mi metaclase pero, por el contrario, puede tener esto;

 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__) 

Eso definiría tu atributo de clase. Para el atributo de instancia:

 class X(metaclass=XMeta): @DynamicClassAttribute def id(self): return "Instance {}".format(self.__class__.__name__) 

Puede que sea un poco excesivo, especialmente si desea mantenerse alejado de las metaclases. Es un truco que me gustaría explorar de mi lado, así que solo quería compartir esta joya oculta, ¡en caso de que pueda pulirla y hacerla brillar!

 >>> X().id 'Instance X' >>> X.id 'Class X' 

Voila …

En su ejemplo, simplemente puede eliminar el segundo método por completo, ya que tanto el método estático como el método de clase hacen lo mismo.

Si quisieras que hicieran cosas diferentes:

 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