Cómo crear propiedades abstractas en clases abstractas de python

6 minutos de lectura

avatar de usuario
Boris Gorelik

En el siguiente código, creo una clase abstracta base Base. Quiero todas las clases que heredan de Base para proporcionar el name propiedad, así que hice de esta propiedad una @abstractmethod.

Luego creé una subclase de Basellamó Base_1, que está destinado a proporcionar alguna funcionalidad, pero sigue siendo abstracto. No hay name propiedad en Base_1, pero, sin embargo, Python instala un objeto de esa clase sin error. ¿Cómo se crean propiedades abstractas?

from abc import ABCMeta, abstractmethod

class Base(object):
    __metaclass__ = ABCMeta
    def __init__(self, strDirConfig):
        self.strDirConfig = strDirConfig
    
    @abstractmethod
    def _doStuff(self, signals):
        pass
    
    @property    
    @abstractmethod
    def name(self):
        # this property will be supplied by the inheriting classes
        # individually
        pass
    

class Base_1(Base):
    __metaclass__ = ABCMeta
    # this class does not provide the name property, should raise an error
    def __init__(self, strDirConfig):
        super(Base_1, self).__init__(strDirConfig)
    
    def _doStuff(self, signals):
        print 'Base_1 does stuff'
        

class C(Base_1):
    @property
    def name(self):
        return 'class C'
    
        
if __name__ == '__main__':
    b1 = Base_1('abc')  

  • Gotcha: si olvidas usar decorador @property en class C, name volverá a un método.

    – Kevinarpe

    2 de noviembre de 2014 a las 5:35

avatar de usuario
Jaime

Ya que Pitón 3.3 se solucionó un error que significaba que property() decorador ahora se identifica correctamente como abstracto cuando se aplica a un método abstracto.

Nota: el orden importa, tienes que usar @property arriba @abstractmethod

Pitón 3.3+: (documentos de Python):

from abc import ABC, abstractmethod

class C(ABC):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...

Pitón 2: (documentos de Python)

from abc import ABC, abstractproperty

class C(ABC):
    @abstractproperty
    def my_abstract_property(self):
        ...

  • @James ¿Cómo hacerlo compatible con Python 2 y también?

    – himanshu219

    12 de julio de 2019 a las 6:52

  • @James en realidad quise decir para ambos, pero no importa, publiqué una respuesta basada en su solución

    – himanshu219

    12 de julio de 2019 a las 11:55


  • no creo que python verifique que la implementación realmente tenga un decorador @property, solo verifica que un método con el nombre my_abstract_property es creado.

    – ierdna

    9 de agosto de 2021 a las 16:33

  • @James, ¿funciona esto con functools.cached_property?

    – lmiguelvargasf

    20 de marzo a las 18:10

  • no lo entiendo OP está preguntando acerca de tener una propiedad de una clase base llamada name, que debe ser implementado por todas las clases secundarias, pero las respuestas aquí implementan funciones abstractas. Incluso el abc Los documentos muestran solo funciones abstractas. ¿No hay una manera de hacer que las propiedades/variables de clase sean abstractas de manera que las clases secundarias tengan que implementarlas? lo necesito para tener id variable que todas las clases secundarias deben implementar.

    – Navegación

    7 de mayo a las 11:19

avatar de usuario
código

Hasta que Pitón 3.3no puedes anidar @abstractmethod y @property.

Usar @abstractproperty para crear propiedades abstractas (documentos).

from abc import ABCMeta, abstractmethod, abstractproperty

class Base(object):
    # ...
    @abstractproperty
    def name(self):
        pass

El código ahora genera la excepción correcta:

Traceback (most recent call last):
  File "foo.py", line 36, in 
    b1 = Base_1('abc')  
TypeError: Can't instantiate abstract class Base_1 with abstract methods name

  • en realidad, esta respuesta es incorrecta para las pitones más jóvenes: desde 3.3, @abstractproperty está en desuso a favor de una combinación como OP.

    – oveja voladora

    9 de noviembre de 2012 a las 20:18

  • De los documentos 3.3: docs.python.org/3/library/abc.html#abc.abstractproperty

    – código de copia

    12 de noviembre de 2012 a las 8:40

  • @abstractproperty en los documentos de Python 2

    – Cerebro de Boltzmann

    24/10/2017 a las 19:50

  • así que hasta la 3.3, solo raise NotImplementedError

    – Sławomir Lenart

    17 oct 2018 a las 14:59

  • ¿Podemos heredar del objeto y seguir usando las anotaciones ABC? ¿Funcionará como se muestra en el ejemplo?

    – santhosh kumar

    23 de julio de 2020 a las 9:07

Basado en la respuesta de James anterior

def compatibleabstractproperty(func):

    if sys.version_info > (3, 3):             
        return property(abstractmethod(func))
    else:
        return abstractproperty(func)

y usarlo como decorador

@compatibleabstractproperty
def env(self):
    raise NotImplementedError()

Utilizando el @property decorador en la clase abstracta (como se recomienda en la respuesta de James) funciona si desea que los atributos de nivel de instancia requeridos también usen el decorador de propiedades.

Si no desea utilizar el decorador de propiedades, puede utilizar super(). Terminé usando algo como el __post_init__() de las clases de datos y obtiene la funcionalidad deseada para los atributos de nivel de instancia:

import abc
from typing import List

class Abstract(abc.ABC):
    """An ABC with required attributes.

    Attributes:
        attr0
        attr1 
    """

    @abc.abstractmethod
    def __init__(self):
        """Forces you to implement __init__ in 'Concrete'. 
        Make sure to call __post_init__() from inside 'Concrete'."""

    def __post_init__(self):
        self._has_required_attributes()
        # You can also type check here if you want.

    def _has_required_attributes(self):
        req_attrs: List[str] = ['attr0', 'attr1']
        for attr in req_attrs:
            if not hasattr(self, attr):
                raise AttributeError(f"Missing attribute: '{attr}'")

class Concrete(Abstract):

    def __init__(self, attr0, attr1):
        self.attr0 = attr0
        self.attr1 = attr1
        self.attr2 = "some value" # not required
        super().__post_init__() # Enforces the attribute requirement.

avatar de usuario
Gers

En pitón 3.6+, también puede anotar una variable sin proporcionar un valor predeterminado. Creo que esta es una forma más concisa de hacerlo abstracto.

class Base():
    name: str
    
    def print_name(self):
        print(self.name)  # will raise an Attribute error at runtime if `name` isn't defined in subclass

class Base_1(Base):
    name = "base one"

también se puede usar para forzarlo a inicializar la variable en el __new__ o __init__ métodos

Como otro ejemplo, el siguiente código fallará cuando intente inicializar el Base_1 clase

    class Base():
        name: str

        def __init__(self):
            self.print_name()

    class Base_1(Base):
        _nemo = "base one"
    
    b = Base_1() 

AttributeError: 'Base_1' object has no attribute 'name'

  • No funcionó para mí. Pitón 3.9.6. ¿Quizás te perdiste algo?

    – Navegación

    7 de mayo a las 11:15

  • bueno, algo que podría no quedar claro en mi respuesta es que solo obtendrá el error cuando intente acceder al atributo faltante. para que puedas comprobarlo fácilmente en el __init__ método por ejemplo. Actualizaré mi respuesta para demostrar eso. (por cierto, lo probé con Python 3.8.6)

    – Gers

    8 de mayo a las 13:44


  • Tal vez esta respuesta sea útil, pero no es lo que pregunta la pregunta OP. Está definiendo un atributo, no una propiedad.

    – Colin D. Bennett

    28 de junio a las 17:32

  • No funcionó para mí. Pitón 3.9.6. ¿Quizás te perdiste algo?

    – Navegación

    7 de mayo a las 11:15

  • bueno, algo que podría no quedar claro en mi respuesta es que solo obtendrá el error cuando intente acceder al atributo faltante. para que puedas comprobarlo fácilmente en el __init__ método por ejemplo. Actualizaré mi respuesta para demostrar eso. (por cierto, lo probé con Python 3.8.6)

    – Gers

    8 de mayo a las 13:44


  • Tal vez esta respuesta sea útil, pero no es lo que pregunta la pregunta OP. Está definiendo un atributo, no una propiedad.

    – Colin D. Bennett

    28 de junio a las 17:32

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad