¿Es posible hacer clases abstractas?

10 minutos de lectura

¿Cómo puedo hacer que una clase o método sea abstracto en Python?

Traté de redefinir __new__() al igual que:

class F:
    def __new__(cls):
        raise Exception("Unable to create an instance of abstract class %s" %cls)

pero ahora si creo una clase G que hereda de F al igual que:

class G(F):
    pass

entonces no puedo instanciar G ya sea, ya que llama a su superclase __new__ método.

¿Hay una mejor manera de definir una clase abstracta?

  • Sí, puede crear clases abstractas en python con el módulo abc (clases base abstractas). Este sitio te ayudará con eso: http://docs.python.org/2/library/abc.html

    – ORIÓN

    30 de noviembre de 2012 a las 13:32

avatar de usuario de alexvassel
alexvassel

Utilizar el abc módulo para crear clases abstractas. Utilizar el abstractmethod decorador para declarar un método abstracto y declarar una clase abstracta usando una de tres formas, dependiendo de su versión de Python.

En Python 3.4 y superior, puede heredar de ABC. En versiones anteriores de Python, debe especificar la metaclase de su clase como ABCMeta. Especificar la metaclase tiene una sintaxis diferente en Python 3 y Python 2. Las tres posibilidades se muestran a continuación:

# Python 3.4+
from abc import ABC, abstractmethod
class Abstract(ABC):
    @abstractmethod
    def foo(self):
        pass
# Python 3.0+
from abc import ABCMeta, abstractmethod
class Abstract(metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass
# Python 2
from abc import ABCMeta, abstractmethod
class Abstract:
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

Independientemente de la forma que utilice, no podrá crear una instancia de una clase abstracta que tenga métodos abstractos, pero podrá crear una instancia de una subclase que proporcione definiciones concretas de esos métodos:

>>> Abstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Abstract with abstract methods foo
>>> class StillAbstract(Abstract):
...     pass
... 
>>> StillAbstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class StillAbstract with abstract methods foo
>>> class Concrete(Abstract):
...     def foo(self):
...         print('Hello, World')
... 
>>> Concrete()
<__main__.Concrete object at 0x7fc935d28898>

  • ¿Qué hace @abstractmethod? ¿Por qué lo necesitas? Si la clase ya está establecida como abstracta, ¿no debería saber el compilador/interpretador que todos los métodos son de la clase abstracta en cuestión?

    –Charlie Parker

    17 de marzo de 2014 a las 3:30

  • @CharlieParker – El @abstractmethod hace que la función decorada deber anularse antes de que se pueda crear una instancia de la clase. De los documentos: A class that has a metaclass derived from ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden.

    – Nombre falso

    18 de abril de 2014 a las 6:48

  • @CharlieParker: básicamente, le permite definir una clase de una manera en la que la subclase deber implementar un conjunto específico de métodos para crear instancias.

    – Nombre falso

    18 de abril de 2014 a las 6:49

  • ¿Hay alguna manera de prohibir a los usuarios crear Abstract() sin ningún método @abstractmethod?

    – José

    2 de diciembre de 2014 a las 7:45

  • ¿Por qué no puedo usar solo @abstractmethod sin que ABC? Se siente poco pitónico. Cuando escribo método abstracto significa que ya es una clase base abstracta. ¿O extraño algo?

    – hans

    7 dic 2019 a las 23:51


Avatar de usuario de Tim Gilbert
tim gilberto

La vieja escuela (pre-PEP 3119) manera de hacer esto es simplemente para raise NotImplementedError en la clase abstracta cuando se llama a un método abstracto.

class Abstract(object):
    def foo(self):
        raise NotImplementedError('subclasses must override foo()!')

class Derived(Abstract):
    def foo(self):
        print 'Hooray!'

>>> d = Derived()
>>> d.foo()
Hooray!
>>> a = Abstract()
>>> a.foo()
Traceback (most recent call last): [...]

Esto no tiene las mismas buenas propiedades que usar el abc el módulo lo hace. Todavía puede crear una instancia de la clase base abstracta en sí misma, y ​​no encontrará su error hasta que llame al método abstracto en tiempo de ejecución.

Pero si se trata de un pequeño conjunto de clases simples, tal vez con unos pocos métodos abstractos, este enfoque es un poco más fácil que tratar de abrirse paso entre las abc documentación.

  • Aprecie la simplicidad y la eficacia de este enfoque.

    – mohit6up

    29 mayo 2019 a las 21:13

  • Jaja, vi esto en todas partes en mi curso OMSCS y no tenía idea de qué era 🙂

    – mlestudiante33

    5 de junio de 2020 a las 7:40

  • Sin embargo, no es realmente útil. Porque es posible que desee implementar algún comportamiento común dentro Abstract#foo. Debería estar prohibido llamarlo directamente, pero aún debería ser posible llamarlo con super().

    –Eric Duminil

    7 julio 2021 a las 6:40

Avatar de usuario de Irv Kalb
irv kalb

Aquí hay una manera muy fácil sin tener que lidiar con el módulo ABC.

En el __init__ método de la clase que desea que sea una clase abstracta, puede verificar el “tipo” de sí mismo. Si el tipo de self es la clase base, entonces la persona que llama está tratando de crear una instancia de la clase base, por lo tanto, genere una excepción. Aquí hay un ejemplo simple:

class Base():
    def __init__(self):
        if type(self) is Base:
            raise Exception('Base is an abstract class and cannot be instantiated directly')
        # Any initialization code
        print('In the __init__  method of the Base class')

class Sub(Base):
    def __init__(self):
        print('In the __init__ method of the Sub class before calling __init__ of the Base class')
        super().__init__()
        print('In the __init__ method of the Sub class after calling __init__ of the Base class')

subObj = Sub()
baseObj = Base()

Cuando se ejecuta, produce:

In the __init__ method of the Sub class before calling __init__ of the Base class
In the __init__  method of the Base class
In the __init__ method of the Sub class after calling __init__ of the Base class
Traceback (most recent call last):
  File "/Users/irvkalb/Desktop/Demo files/Abstract.py", line 16, in <module>
    baseObj = Base()
  File "/Users/irvkalb/Desktop/Demo files/Abstract.py", line 4, in __init__
    raise Exception('Base is an abstract class and cannot be instantiated directly')
Exception: Base is an abstract class and cannot be instantiated directly

Esto muestra que puede crear una instancia de una subclase que hereda de una clase base, pero no puede crear una instancia de la clase base directamente.

avatar de usuario de grepit
grepit

La mayoría de las respuestas anteriores fueron correctas, pero aquí está la respuesta y el ejemplo para Pitón 3.7. Sí, puede crear una clase y un método abstractos. Solo como recordatorio, a veces una clase debe definir un método que lógicamente pertenece a una clase, pero esa clase no puede especificar cómo implementar el método. Por ejemplo, en las siguientes clases para padres y bebés, ambos comen, pero la implementación será diferente para cada uno porque los bebés y los padres comen un tipo diferente de alimentos y la cantidad de veces que comen es diferente. Por lo tanto, las subclases del método eat anulan AbstractClass.eat.

from abc import ABC, abstractmethod

class AbstractClass(ABC):

    def __init__(self, value):
        self.value = value
        super().__init__()

    @abstractmethod
    def eat(self):
        pass

class Parents(AbstractClass):
    def eat(self):
        return "eat solid food "+ str(self.value) + " times each day"

class Babies(AbstractClass):
    def eat(self):
        return "Milk only "+ str(self.value) + " times or more each day"

food = 3    
mom = Parents(food)
print("moms ----------")
print(mom.eat())

infant = Babies(food)
print("infants ----------")
print(infant.eat())

PRODUCCIÓN:

moms ----------
eat solid food 3 times each day
infants ----------
Milk only 3 times or more each day

Avatar de usuario de Cleb
clave

Como se explica en las otras respuestas, sí, puedes usar clases abstractas en Python usando el abc módulo. A continuación doy un ejemplo real usando abstract @classmethod, @property y @abstractmethod (usando Python 3.6+). Para mí suele ser más fácil empezar con ejemplos que puedo copiar y pegar fácilmente; Espero que esta respuesta también sea útil para otros.

Primero vamos a crear una clase base llamada Base:

from abc import ABC, abstractmethod

class Base(ABC):

    @classmethod
    @abstractmethod
    def from_dict(cls, d):
        pass
    
    @property
    @abstractmethod
    def prop1(self):
        pass

    @property
    @abstractmethod
    def prop2(self):
        pass

    @prop2.setter
    @abstractmethod
    def prop2(self, val):
        pass

    @abstractmethod
    def do_stuff(self):
        pass

Nuestro Base la clase siempre tendrá un from_dict classmethoda property prop1 (que es de sólo lectura) y un property prop2 (que también se puede configurar), así como una función llamada do_stuff. Cualquiera que sea la clase que ahora se construye en base a Base tendrá que implementar todos estos cuatro métodos/propiedades. Tenga en cuenta que para que un método sea abstracto, se requieren dos decoradores: classmethod y abstracto property.

Ahora podríamos crear una clase. A como esto:

class A(Base):
    def __init__(self, name, val1, val2):
        self.name = name
        self.__val1 = val1
        self._val2 = val2

    @classmethod
    def from_dict(cls, d):
        name = d['name']
        val1 = d['val1']
        val2 = d['val2']

        return cls(name, val1, val2)

    @property
    def prop1(self):
        return self.__val1

    @property
    def prop2(self):
        return self._val2

    @prop2.setter
    def prop2(self, value):
        self._val2 = value

    def do_stuff(self):
        print('juhu!')

    def i_am_not_abstract(self):
        print('I can be customized')

Todos los métodos/propiedades requeridos están implementados y, por supuesto, también podemos agregar funciones adicionales que no forman parte de Base (aquí: i_am_not_abstract).

Ahora podemos hacer:

a1 = A('dummy', 10, 'stuff')
a2 = A.from_dict({'name': 'from_d', 'val1': 20, 'val2': 'stuff'})

a1.prop1
# prints 10

a1.prop2
# prints 'stuff'

Como se desee, no podemos establecer prop1:

a.prop1 = 100

regresará

AttributeError: no se puede establecer el atributo

También nuestro from_dict el método funciona bien:

a2.prop1
# prints 20

Si ahora definimos una segunda clase B como esto:

class B(Base):
    def __init__(self, name):
        self.name = name

    @property
    def prop1(self):
        return self.name

y traté de crear una instancia de un objeto como este:

b = B('iwillfail')

obtendremos un error

TypeError: no se puede crear una instancia de clase B abstracta con métodos abstractos do_stuff, from_dict, prop2

enumerando todas las cosas definidas en Base que no implementamos en B.

  • Tenga en cuenta que hay una anotación @abstactclassmethod que puede usar en lugar de dos separadas

    – La pesca es vida

    23 de febrero a las 8:40

  • @La pesca es vida: Según la documentación @abstractclassmethod está en desuso a partir de la versión 3.3, si lo entiendo correctamente.

    – Clebo

    1 de marzo a las 8:57

  • tienes razón. Gracias por la pista.

    – La pesca es vida

    1 de marzo a las 9:14

Avatar de usuario de Pipo
pipo

Este estará trabajando en python 3

from abc import ABCMeta, abstractmethod

class Abstract(metaclass=ABCMeta):

    @abstractmethod
    def foo(self):
        pass

Abstract()
>>> TypeError: Can not instantiate abstract class Abstract with abstract methods foo

  • Tenga en cuenta que hay una anotación @abstactclassmethod que puede usar en lugar de dos separadas

    – La pesca es vida

    23 de febrero a las 8:40

  • @La pesca es vida: Según la documentación @abstractclassmethod está en desuso a partir de la versión 3.3, si lo entiendo correctamente.

    – Clebo

    1 de marzo a las 8:57

  • tienes razón. Gracias por la pista.

    – La pesca es vida

    1 de marzo a las 9:14

Avatar de usuario de Giovanni Angeli
giovanni angeli

También esto funciona y es simple:

class A_abstract(object):

    def __init__(self):
        # quite simple, old-school way.
        if self.__class__.__name__ == "A_abstract": 
            raise NotImplementedError("You can't instantiate this abstract class. Derive it, please.")

class B(A_abstract):

        pass

b = B()

# here an exception is raised:
a = A_abstract()

¿Ha sido útil esta solución?