Wat is de kortste/meest elegante manier om de volgende Scala-code met een abstract attribuut in Python te implementeren?
abstract class Controller {
val path: String
}
Een subklasse van Controller
wordt afgedwongen om “pad” te definiëren door de Scala-compiler. Een subklasse ziet er als volgt uit:
class MyController extends Controller {
override val path = "/home"
}
Antwoord 1, autoriteit 100%
Python 3.3+
from abc import ABCMeta, abstractmethod
class A(metaclass=ABCMeta):
def __init__(self):
# ...
pass
@property
@abstractmethod
def a(self):
pass
@abstractmethod
def b(self):
pass
class B(A):
a = 1
def b(self):
pass
Het niet declareren van a
of B
in de afgeleide klasse B
zal een TypeError
veroorzaken, zoals:
TypeError
: kan abstracte klasseB
niet instantiëren met abstracte methodena
Python 2.7
Er is een @abstractproperty-decorateur hiervoor:
from abc import ABCMeta, abstractmethod, abstractproperty
class A:
__metaclass__ = ABCMeta
def __init__(self):
# ...
pass
@abstractproperty
def a(self):
pass
@abstractmethod
def b(self):
pass
class B(A):
a = 1
def b(self):
pass
Antwoord 2, autoriteit 84%
Python heeft hiervoor een ingebouwde uitzondering, maar u zult de uitzondering pas tijdens runtime tegenkomen.
class Base(object):
@property
def path(self):
raise NotImplementedError
class SubClass(Base):
path = 'blah'
Antwoord 3, autoriteit 49%
Sinds deze vraag oorspronkelijk werd gesteld, heeft Python de manier veranderd waarop abstracte klassen worden geïmplementeerd. Ik heb een iets andere benadering gebruikt met behulp van het abc.ABC-formalisme in python 3.6. Hier definieer ik de constante als een eigenschap die in elke subklasse moet worden gedefinieerd.
from abc import ABC, abstractmethod
class Base(ABC):
@property
@classmethod
@abstractmethod
def CONSTANT(cls):
return NotImplementedError
def print_constant(self):
print(type(self).CONSTANT)
class Derived(Base):
CONSTANT = 42
Dit dwingt de afgeleide klasse om de constante te definiëren, anders wordt er een TypeError
-uitzondering gegenereerd wanneer je probeert de subklasse te instantiëren. Als u de constante wilt gebruiken voor functionaliteit die in de abstracte klasse is geïmplementeerd, moet u de constante van de subklasse benaderen met type(self).CONSTANT
in plaats van alleen CONSTANT
, aangezien de waarde is niet gedefinieerd in de basisklasse.
Er zijn andere manieren om dit te implementeren, maar ik hou van deze syntaxis omdat het mij de meest duidelijke en voor de hand liggende lijkt voor de lezer.
De vorige antwoorden raakten allemaal nuttige punten, maar ik heb het gevoel dat het geaccepteerde antwoord de vraag niet direct beantwoordt, omdat
- De vraag vraagt om implementatie in een abstracte klasse, maar het geaccepteerde antwoord volgt niet het abstracte formalisme.
- De vraag is of de implementatie wordt afgedwongen. Ik zou willen beweren dat de handhaving in dit antwoord strenger is omdat het een runtime-fout veroorzaakt wanneer de subklasse wordt geïnstantieerd als
CONSTANT
niet is gedefinieerd. Met het geaccepteerde antwoord kan het object worden geïnstantieerd en wordt alleen een fout gegenereerd wanneerCONSTANT
wordt geopend, waardoor de handhaving minder strikt wordt.
Dit is niet in strijd met de oorspronkelijke antwoorden. Er zijn grote wijzigingen in de syntaxis van de abstracte klassen opgetreden sinds ze zijn gepost, wat in dit geval een nettere en functionelere implementatie mogelijk maakt.
Antwoord 4, autoriteit 31%
In Python 3.6+kun je een attribuut van een abstract annoteren class (of een andere variabele) zonder een waarde voor dat attribuut op te geven.
from abc import ABC
class Controller(ABC):
path: str
class MyController(Controller):
path = "/home"
Dit zorgt voor een zeer schone code waarbij het duidelijk is dat het attribuut abstract is. Code die probeert toegang te krijgen tot het attribuut wanneer if niet is overschreven, zal een AttributeError
opleveren.
Antwoord 5, autoriteit 16%
U kunt een kenmerk maken in de abc.ABCabstracte basisklasse met een waarde zoals NotImplemented
zodat als het kenmerk niet wordt overschreven en vervolgens wordt gebruikt, er tijdens runtime een fout wordt weergegeven.
De volgende code gebruikt een PEP 484hint om PyCharm te helpen ook het type van het path
-attribuut correct statisch analyseren.
from abc import ABC
class Controller(ABC):
path: str = NotImplemented
class MyController(Controller):
path = "/home"
Antwoord 6, autoriteit 14%
Voor Python 3.3+ is er een elegante oplossing
from abc import ABC, abstractmethod
class BaseController(ABC):
@property
@abstractmethod
def path(self) -> str:
...
class Controller(BaseController):
path = "/home"
# Instead of an elipsis, you can add a docstring for clarity
class AnotherBaseController(ABC):
@property
@abstractmethod
def path(self) -> str:
"""
:return: the url path of this controller
"""
Ondanks dat er al enkele goede antwoorden zijn gegeven, dacht ik dat dit antwoord toch enige waarde zou toevoegen. Deze aanpak heeft twee voordelen:
-
...
in de body van een abstracte methode heeft meer de voorkeur danpass
. In tegenstelling totpass
, impliceert...
geen bewerkingen, waarbijpass
alleen de afwezigheid van een daadwerkelijke implementatie betekent -
...
is meer aanbevolen dan het gooien vanNotImplementedError(...)
. Dit veroorzaakt automatisch een extreem uitgebreide fout als de implementatie van een abstract veld in een subklasse ontbreekt. Daarentegen verteltNotImplementedError
zelf niet waarom de implementatie ontbreekt. Bovendien vereist het handenarbeid om het daadwerkelijk op te tillen.
Antwoord 7, autoriteit 10%
Vanaf Python 3.6 kun je __init_subclass__
gebruiken om te controleren op de klassenvariabelen van de onderliggende klasse bij initialisatie:
from abc import ABC
class A(ABC):
@classmethod
def __init_subclass__(cls):
required_class_variables = [
'foo',
'bar',
]
for var in required_class_variables:
if not hasattr(cls, var):
raise NotImplementedError(
f'Class {cls} lacks required `{var}` class attribute'
)
Dit geeft een foutmelding bij het initialiseren van de onderliggende klasse, als de ontbrekende klassevariabele niet is gedefinieerd, zodat u niet hoeft te wachten tot de ontbrekende klassevariabele wordt geopend.
Antwoord 8, autoriteit 4%
Ik heb een klein @James-antwoord aangepast, zodat al die decorateurs niet zoveel plaats innemen. Als u meerdere van dergelijke abstracte eigenschappen moest definiëren, is dit handig:
from abc import ABC, abstractmethod
def abstractproperty(func):
return property(classmethod(abstractmethod(func)))
class Base(ABC):
@abstractproperty
def CONSTANT(cls): ...
def print_constant(self):
print(type(self).CONSTANT)
class Derived(Base):
CONSTANT = 42
class BadDerived(Base):
BAD_CONSTANT = 42
Derived() # -> Fine
BadDerived() # -> Error
Antwoord 9, autoriteit 4%
Python3.6-implementatie kan er als volgt uitzien:
In [20]: class X:
...: def __init_subclass__(cls):
...: if not hasattr(cls, 'required'):
...: raise NotImplementedError
In [21]: class Y(X):
...: required = 5
...:
In [22]: Y()
Out[22]: <__main__.Y at 0x7f08408c9a20>
Antwoord 10, autoriteit 4%
Uw basisklasse zou een __new__
-methode kunnen implementeren die controleert op class-attribuut:
class Controller(object):
def __new__(cls, *args, **kargs):
if not hasattr(cls,'path'):
raise NotImplementedError("'Controller' subclasses should have a 'path' attribute")
return object.__new__(cls)
class C1(Controller):
path = 42
class C2(Controller):
pass
c1 = C1()
# ok
c2 = C2()
# NotImplementedError: 'Controller' subclasses should have a 'path' attribute
Op deze manier neemt de fout toe bij het starten
Antwoord 11
Het antwoord van Bastien Léonard vermeldt de abstracte basisklasse-module en het antwoord van Brendan Abel gaat over niet-geïmplementeerde attributen die fouten veroorzaken. Om ervoor te zorgen dat de klasse niet buiten de module wordt geïmplementeerd, kunt u de basisnaam vooraf laten gaan door een onderstrepingsteken dat aangeeft dat het privé is voor de module (d.w.z. het wordt niet geïmporteerd).
d.w.z.
class _Controller(object):
path = '' # There are better ways to declare attributes - see other answers
class MyController(_Controller):
path = '/Home'
Antwoord 12
class AbstractStuff:
@property
@abc.abstractmethod
def some_property(self):
pass
Vanaf 3.3 is abc.abstractproperty
verouderd, denk ik.
Antwoord 13
Bekijk de abc (Abtract Base Class) module: http://docs. python.org/library/abc.html
Naar mijn mening is de eenvoudigste en meest gebruikelijke oplossing echter om een uitzondering te maken wanneer een instantie van de basisklasse wordt gemaakt of wanneer de eigenschap wordt geopend.