¿Es posible hacer clases abstractas en Python?


321

¿Cómo puedo hacer un resumen de clase o método en Python?

Intenté redefinir __new__()así:

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

pero ahora si creo una clase Gque hereda de esta Fmanera:

class G(F):
    pass

entonces tampoco puedo crear instancias G, ya que llama al __new__método de su superclase.

¿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 lo ayudará con él: http://docs.python.org/2/library/abc.html
ORION

Respuestas:


554

Usa el abcmódulo para crear clases abstractas. Use el abstractmethoddecorador para declarar un resumen de método y declare un resumen de clase utilizando 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

De cualquier 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>

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

30
@CharlieParker - La @abstractmethodhace que sea para que la función decorado debe ser anulado antes de la clase se pueden crear instancias. 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

66
@CharlieParker: Básicamente, le permite definir una clase de una manera en que la subclase debe implementar un conjunto específico de métodos para crear instancias.
Nombre falso

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

2
@ Joe parece que puede utilizar @abstractmethodpara __init__el método, así, ver stackoverflow.com/q/44800659/547270
Scrutari

108

La forma de hacer esto de la vieja escuela (antes de PEP 3119 ) es simplemente raise NotImplementedErroren 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 el uso del abcmódulo. Todavía puede crear una instancia de la clase base abstracta en sí, 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 solo unos pocos métodos abstractos, este enfoque es un poco más fácil que tratar de leer la abcdocumentación.


1
Apreciamos la simplicidad y efectividad de este enfoque.
mohit6up

Jaja, vi esto en todas partes en mi curso OMSCS y no tenía idea de lo que era :)
mLstudent33

18

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 que genera 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.


11

La mayoría de las respuestas anteriores eran correctas, pero aquí está la respuesta y el ejemplo para Python 3.7. Sí, puedes crear una clase y 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 de 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 comida y la cantidad de veces que comen es diferente. Entonces, las subclases de 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())

SALIDA:

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

Quizás import abcsea ​​inútil.
Benyamin Jafari

¿Podemos escribir @abstractmethod en la función init también?
variable

+1 ¿Por qué @abstractmethod def eat (self)? Si esta clase es abstracta y, por lo tanto, no debe ser instanciada, ¿por qué pasas (auto) a comer? Funciona bien sin él
VMMF

7

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

4

Como se explica en las otras respuestas, sí, puede usar clases abstractas en Python usando el abcmódulo . A continuación voy a dar un ejemplo real utilizando abstracta @classmethod, @propertyy @abstractmethod(usando Python 3.6 o superior). Para mí, generalmente es más fácil comenzar con ejemplos que puedo copiar y pegar fácilmente; Espero que esta respuesta también sea útil para otros.

Primero creemos 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

Nuestra Baseclase siempre tendrá unfrom_dict classmethod , a property prop1(que es de solo lectura) y a property prop2(que también se puede configurar), así como una función llamada do_stuff. Cualquier clase en la que se cree ahora Basetendrá que implementar todo esto para métodos / propiedades. Tenga en cuenta que para que un método sea abstracto, se requieren dos decoradores, classmethody abstracto property.

Ahora podríamos crear una clase Acomo esta:

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 se implementan y, por supuesto, también podemos agregar funciones adicionales que no son 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 lo deseamos, no podemos establecer prop1:

a.prop1 = 100

volverá

AttributeError: no se puede establecer el atributo

También nuestro from_dictmétodo funciona bien:

a2.prop1
# prints 20

Si ahora definimos una segunda clase Bcomo esta:

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

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

e intenté instanciar un objeto como este:

b = B('iwillfail')

obtendremos un error

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

enumerando todas las cosas definidas en las Baseque no implementamos B.


2

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

2

También puede aprovechar el método __nuevo__ para su ventaja. Acabas de olvidar algo. El método __new__ siempre devuelve el nuevo objeto, por lo que debe devolver el nuevo método de su superclase. Haz lo siguiente.

class F:
    def __new__(cls):
        if cls is F:
            raise TypeError("Cannot create an instance of abstract class '{}'".format(cls.__name__))
        return super().__new__(cls)

Al usar el nuevo método, debe devolver el objeto, no la palabra clave None. Eso es todo lo que te perdiste.


2

Encuentro la respuesta aceptada, y todas las demás extrañas, ya que pasan selfa una clase abstracta. Una clase abstracta no se instancia, por lo que no puede tener unself .

Intenta esto, funciona.

from abc import ABCMeta, abstractmethod


class Abstract(metaclass=ABCMeta):
    @staticmethod
    @abstractmethod
    def foo():
        """An abstract method. No need to write pass"""


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


FOO = Derived()
FOO.foo()

1
Incluso la documentación de ABC usa self en su enlace de
igor Smirnov

2
 from abc import ABCMeta, abstractmethod

 #Abstract class and abstract method declaration
 class Jungle(metaclass=ABCMeta):
     #constructor with default values
     def __init__(self, name="Unknown"):
     self.visitorName = name

     def welcomeMessage(self):
         print("Hello %s , Welcome to the Jungle" % self.visitorName)

     # abstract method is compulsory to defined in child-class
     @abstractmethod
     def scarySound(self):
         pass

Agradable @Shivam Bharadwaj, lo hice de la misma manera
Muhammad Irfan

-3

En su fragmento de código, también puede resolver esto proporcionando una implementación para el __new__método en la subclase, de la misma manera:

def G(F):
    def __new__(cls):
        # do something here

Pero este es un truco y te aconsejo que no lo hagas, a menos que sepas lo que estás haciendo. Para casi todos los casos, le aconsejo que use el abcmódulo, que otros antes que yo han sugerido.

También cuando se crea una nueva clase (base), que sea subclase object, así: class MyBaseClass(object):. No sé si ya es tan significativo, pero ayuda a mantener la coherencia de estilo en su código


-4

Solo una adición rápida a la respuesta de la vieja escuela de @ TimGilbert ... puede hacer que el método init () de su clase base abstracta arroje una excepción y eso evitaría que se instanciara, ¿no?

>>> class Abstract(object):
...     def __init__(self):
...         raise NotImplementedError("You can't instantiate this class!")
...
>>> a = Abstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
NotImplementedError: You can't instantiate this class! 

66
Esto evitará que las subclases se beneficien con cualquier código de inicio común que lógicamente debería estar presente en su clase base común.
Arpit Singh

Lo suficientemente justo. Demasiado para la vieja escuela.
Dave Wade-Stein
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.