¿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?
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 queABC
? 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
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 consuper()
.–Eric Duminil
7 julio 2021 a las 6:40
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.
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
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
classmethod
a 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
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
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()
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