He estado tratando de ver si se puede crear una propiedad de clase abstracta mezclando los tres decoradores (en Python 3.9.6, si eso importa), y noté un comportamiento extraño.
Considere el siguiente código:
from abc import ABC, abstractmethod class Foo(ABC): @classmethod @property @abstractmethod def x(cls): print(cls) return None class Bar(Foo): @classmethod @property def x(cls): print("this is executed") return super().xEsto produce
this is executed <class '__main__.Bar'> Esto significa que, de alguna manera, Bar.x termina siendo llamado.
PyCharm me advierte que la Property 'self' cannot be deleted . Si invierto el orden de @classmethod y @property , no se llama a Bar.x , pero sigo recibiendo la misma advertencia, y también otra: This decorator will not receive a callable it may expect; the built-in decorator returns a special object (esto también aparece cada vez que coloco @property encima de @classmethod ).
Eliminar cualquiera de los tres decoradores (con los cambios apropiados: agregar () al eliminar @property o cambiar cls a self al eliminar @classmethod ) también evita que se llame a Bar.x
Supongo que todo esto significa que probablemente sea una mala idea mezclar directamente esos decoradores (como se indica en la discusión sobre las propiedades de clase en otros hilos aquí).
Sin embargo, tengo curiosidad: ¿qué está pasando aquí? ¿Por qué se llama Bar.x?
Puede intentar generar una excepción en Bar.x De esta manera se puede ver donde se llama.
Debería llevarlo a abc.py en la biblioteca estándar, específicamente a la línea _abc_init(cls) . Esta función está implementada en C. Una de las primeras cosas que hace es llamar a compute_abstract_methods(self) verificar todos los métodos abstractos que la clase ha heredado para ver si están implementados. Esto significa obtener Bar.x que invoca el captador de propiedades.
Esto parece un error en la lógica que verifica los métodos abstractos heredados.
Un objeto en una clase dict se considera abstracto si recuperar su atributo __isabstractmethod__ produce True . Cuando Bar crea una subclase de Foo , Python necesita determinar si Bar anula el Foo.x abstracto y, de ser así, si la anulación es en sí misma abstracta. Debería hacer esto buscando en el MRO una entrada 'x' en un dictado de clase, de modo que pueda examinar __isabstractmethod__ en los descriptores directamente sin invocar el protocolo del descriptor, pero en su lugar, realiza un acceso de atributo Bar.x simple .
El acceso al atributo Bar.x invoca la propiedad de clase. También devuelve None en lugar de la propiedad abstract, y None no es abstracto, por lo que Python se confunde acerca de si Bar.x es abstracto. Python termina pensando que Bar.x es abstracto debido a una verificación diferente, pero si cambia un poco el ejemplo:
>>> from abc import ABC, abstractmethod >>> >>> class Foo(ABC): ... @classmethod ... @property ... @abstractmethod ... def x(cls): ... print(cls) ... return None ... >>> class Bar(Foo): pass ... <class '__main__.Bar'> >>> Bar() <__main__.Bar object at 0x7f46eca8ab80> Python termina pensando que Bar es una clase concreta, aunque el ejemplo modificado no anula x en absoluto.