Ответ 1
Для этого у Python есть встроенное исключение, хотя вы не столкнетесь с этим исключением до выполнения.
class Base(object):
@property
def path(self):
raise NotImplementedError
class SubClass(Base):
path = 'blah'
Каков самый короткий/самый элегантный способ реализовать следующий Scala код с абстрактным атрибутом в Python?
abstract class Controller {
val path: String
}
Подкласс Controller
применяется для определения "пути" компилятором Scala. Подкласс будет выглядеть следующим образом:
class MyController extends Controller {
override val path = "/home"
}
Для этого у Python есть встроенное исключение, хотя вы не столкнетесь с этим исключением до выполнения.
class Base(object):
@property
def path(self):
raise NotImplementedError
class SubClass(Base):
path = 'blah'
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
Неспособность объявить a
или b
в производном классе B
вызовет TypeError
такую как:
TypeError
: Не удается создать экземпляр абстрактного классаB
с помощью абстрактных методовa
Для этого есть декоратор @abstractproperty:
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
Поскольку этот вопрос был задан изначально, python изменил способ реализации абстрактных классов. Я использовал немного другой подход с использованием формализма abc.ABC в Python 3.6. Здесь я определяю константу как свойство, которое должно быть определено в каждом подклассе.
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
Это заставляет производный класс определять константу, иначе возникнет исключение TypeError
, когда вы попытаетесь создать экземпляр подкласса. Если вы хотите использовать константу для любой функциональности, реализованной в абстрактном классе, вы должны получить доступ к константе подкласса по type(self).CONSTANT
вместо просто CONSTANT
, поскольку значение в базовом классе не определено.
Есть и другие способы реализации этого, но мне нравится этот синтаксис, так как он кажется мне наиболее простым и очевидным для читателя.
Все предыдущие ответы касались полезных моментов, но я чувствую, что принятый ответ не дает прямого ответа на вопрос, потому что
CONSTANT
не определен. Принятый ответ позволяет создать экземпляр объекта и выдает ошибку только при обращении к CONSTANT
, делая исполнение менее строгим.Это не вина оригинальных ответов. Со времени их публикации произошли серьезные изменения в синтаксисе абстрактного класса, что в данном случае позволяет более аккуратную и функциональную реализацию.
Вы можете создать атрибут в абстрактном базовом классе abc.ABC со значением, например NotImplemented
чтобы, если атрибут не был переопределен, а затем использован, во время выполнения отображалась ошибка.
В следующем коде используется подсказка типа PEP 484, чтобы помочь PyCharm правильно статически анализировать тип атрибута path
.
import abc
class Controller(abc.ABC):
path = NotImplemented # type: str
class MyController(Controller):
path = '/home'
В Python 3. 6+ вы можете аннотировать атрибут абстрактного класса (или любую переменную) без указания значения для этого атрибута.
class Controller:
path: str
class MyController(Controller):
path = "/home"
Это делает для очень чистого кода, где очевидно, что атрибут является абстрактным. Код, который пытается получить доступ к атрибуту, если он не был перезаписан, вызовет AttributeError
.
Ваш базовый класс может реализовать метод __new__
, который проверяет атрибут класса:
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,*args,**kargs)
class C1(Controller):
path = 42
class C2(Controller):
pass
c1 = C1()
# ok
c2 = C2()
# NotImplementedError: 'Controller' subclasses should have a 'path' attribute
Таким образом возникает ошибка при создании экземпляра
Посмотрите модуль abc (Abtract Base Class): http://docs.python.org/library/abc.html
Однако, на мой взгляд, самым простым и наиболее распространенным решением является повышение исключения, когда создается экземпляр базового класса или когда его свойство доступно.
Реализация Python3.6 может выглядеть так:
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>
Начиная с Python 3.6 вы можете использовать __init_subclass__
для проверки переменных класса дочернего класса при инициализации:
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'
)
Это приводит к ошибке при инициализации дочернего класса, если отсутствующая переменная класса не определена, поэтому вам не нужно ждать, пока не будет получена доступная переменная класса.
Для Python 3.3+ есть элегантное решение
from abc import ABC, abstractmethod
class BaseController(ABC):
@property
@abstractmethod
def path(self) -> str:
...
class Controller(BaseController):
path = "/home"
Чем он отличается от других ответов?
...
в теле абстрактного метода предпочтительнее, чем pass
. В отличие от pass
, ...
подразумевает никаких операций, где pass
означает только отсутствие фактической реализации
...
более рекомендуется, чем бросать NotImplementedError(...)
. Это автоматически выдает чрезвычайно подробную ошибку, если в подклассе отсутствует реализация абстрактного поля. Напротив, сам TG48 не говорит, почему реализация отсутствует. Более того, для его поднятия требуется ручной труд.
Bastien Léonard отвечает на абстрактный модуль базового класса, а ответ Брендана Абеля отвечает на нереализованные атрибуты, вызывающие ошибки. Чтобы гарантировать, что класс не реализован вне модуля, вы можете префикс базового имени с подчеркиванием, которое обозначает его как закрытое для модуля (т.е. Оно не импортируется).
то есть.
class _Controller(object):
path = '' # There are better ways to declare attributes - see other answers
class MyController(_Controller):
path = '/Home'
class AbstractStuff:
@property
@abc.abstractmethod
def some_property(self):
pass
По состоянию на 3.3 abc.abstractproperty
устарела, я думаю.