¿Es posible hacer clases abstractas en Python?


¿Cómo puedo hacer abstracta una 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 G que hereda de F así:

class G(F):
    pass

Entonces tampoco puedo instanciar G, ya que llama al método __new__ de su superclase.

¿Hay una mejor manera de definir una clase abstracta?

Author: Martin Thoma, 2012-11-30

10 answers

Utilice el abc módulo para crear clases abstractas. Utilice el abstractmethod decorador para declarar un método abstracto, y declarar un resumen de clase usando una de tres maneras, 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 son se muestra 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

Cualquiera que sea la forma que use, no podrá instanciar una clase abstracta que tenga métodos abstractos, pero podrá instanciar 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>
 366
Author: alexvassel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-16 09:48:51

La forma antigua (pre-PEP 3119) de hacer esto es solo 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 módulo abc. 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 vadear a través de la documentación abc.

 66
Author: Tim Gilbert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-30 16:30:24

Este funcionará 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
 8
Author: Rajkumar SR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-07 05:59:21

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

En el método __init__ de la clase que desea ser una clase abstracta, puede verificar el "tipo" de self. 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 plantea una excepción. He aquí un ejemplo sencillo:

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 instanciar una subclase que hereda de una clase base, pero no puede instanciar la clase base directamente.

Irv

 5
Author: Irv Kalb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-16 05:37:16

La mayoría de las respuestas anteriores eran correctas, pero aquí está la respuesta y el ejemplo para Python 3.7. Sí, puede crear clase y método abstractos. Al igual que un 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 diferentes tipos de alimentos y el número de veces que comen es diferente. Por lo tanto, el método eat en subclases anula AbstractClass.comer.

AbstractClass, eat metho

import abc
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 only3 times or more each day
 2
Author: CPU 100,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-10 05:01:32

Sí, puede crear clases abstractas en python con el módulo abc (abstract base classes).

Este sitio le ayudará con esto: http://docs.python.org/2/library/abc.html

 1
Author: ORION,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-30 13:32:12

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()
 1
Author: Giovanni Angeli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-12 14:00:15

Sí se puede, utilizando el . abc - Módulo de Clases Base abstractas, aquí hay un ejemplo:

Python3. 6

# importing abstract base classes module
import abc

class GetterSetter(abc.ABC):
    '''
    ABSTRACT BASE CLASSES:

    -  An abstract base class is a kind of 'model' for other classes to be defined.
        - It is not designed to construct instances, but can be subclassed by regular classes

    - Abstract classes can define interface, or methods that must be implemented by its subclasses.

    '''


    # Abstract classes are not designed to be instantiated, only to be subclassed

    #  decorator for abstract class
    @abc.abstractmethod
    def set_val(self, input):
        """set the value in the instance"""
        return

    @abc.abstractmethod
    def get_val(self):
        """Get and return a value from the instance..."""
        return

# Inheriting from the above abstract class
class MyClass(GetterSetter):

    # methods overriding in the GetterSetter
    def set_val(self, input):
        self.val = input

    def get_val(self):
        return self.val


# Instantiate
x = MyClass()
print(x) # prints the instance <__main__.MyClass object at 0x10218ee48>
x = GetterSetter() #throws error, abstract classes can't be instantiated

Python2.x

import abc

class GetterSetter(object):
    # meta class is used to define other classes
    __metaclass__ = abc.ABCMeta


    #  decorator for abstract class
    @abc.abstractmethod
    def set_val(self, input):
        """set the value in the instance"""
        return

    @abc.abstractmethod
    def get_val(self):
        """Get and return a value from the instance..."""
        return

# Inheriting from the above abstract class
class MyClass(GetterSetter):

    # methods overriding in the GetterSetter
    def set_val(self, input):
        self.val = input

    def get_val(self):
        return self.val


# Instantiate
x = GetterSetter()
print(x)
x = GetterSetter() #throws error, abstract classes can't be instantiated
 0
Author: rzskhr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-04 15:36:25

En su fragmento de código, también podría resolver esto proporcionando una implementación para el método __new__ en la subclase, de la misma manera:

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

Pero esto es un truco y te aconsejo que no lo hagas, a menos que sepas lo que estás haciendo. Para casi todos los casos les aconsejo usar el módulo abc, que otros antes que yo han sugerido.

También cuando creas una nueva clase (base), hazla subclase object, así: class MyBaseClass(object):. Ya no se si es tan significativo, pero ayuda mantenga la consistencia de estilo en su código

 -1
Author: NlightNFotis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-30 15:05:37

Solo una adición rápida a la respuesta de la vieja escuela de @TimGilbert...puedes hacer que el método init() de tu clase base abstracta lance 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! 
 -1
Author: Dave Wade-Stein,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-07 23:10:04