Абстрактные атрибуты в Python - PullRequest
45 голосов
/ 29 апреля 2010

Какой самый короткий / самый элегантный способ реализовать следующий код Scala с абстрактным атрибутом в Python?

abstract class Controller {

    val path: String

}

Подкласс Controller применяется для определения «пути» компилятором Scala. Подкласс будет выглядеть так:

class MyController extends Controller {

    override val path = "/home"

}

Ответы [ 11 ]

60 голосов
/ 29 апреля 2010

Python имеет встроенное исключение для этого, хотя вы не встретите исключение до времени выполнения.

class Base(object):
    @property
    def path(self):
        raise NotImplementedError


class SubClass(Base):
    path = 'blah'
59 голосов
/ 27 января 2017

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

Неспособность объявить a или b в производном классе B вызовет TypeError, например:

TypeError: Не удалось создать экземпляр абстрактного класса B с помощью абстрактных методов a

Python 2.7

Для этого есть @ 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
10 голосов
/ 21 ноября 2018

Поскольку этот вопрос был задан изначально, 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, что делает применение менее строгим.

Это не вина оригинальных ответов. Со времени их публикации произошли серьезные изменения в синтаксисе абстрактного класса, что в данном случае позволяет более аккуратную и функциональную реализацию.

5 голосов
/ 03 августа 2016

Вы можете создать атрибут в абстрактном базовом классе abc.ABC со значением, таким как NotImplemented, так что если атрибут не будет переопределен, а затем использован, возникнет ошибка отображается во время выполнения.

Следующий код использует подсказку типа PEP 484 , чтобы помочь PyCharm корректно статически анализировать тип атрибута path.

import abc


class Controller(abc.ABC):
    path = NotImplemented  # type: str


class MyController(Controller):
    path = '/home'
3 голосов
/ 21 февраля 2019

В Python 3.6 + вы можете аннотировать атрибут абстрактного класса (или любую переменную) без указания значения для этого атрибута.

class Controller:
    path: str

class MyController(Controller):
    path = "/home"

Это делает для очень чистого кода, где очевидно, что атрибут является абстрактным. Код, который пытается получить доступ к атрибуту, если он не был перезаписан, вызовет AttributeError.

3 голосов
/ 11 сентября 2014

Ваш базовый класс может реализовать метод __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

Таким образом, ошибка возникает при создании экземпляра

3 голосов
/ 29 апреля 2010

Посмотрите на модуль abc (Abtract Base Class): http://docs.python.org/library/abc.html

Однако, на мой взгляд, самое простое и распространенное решение - вызвать исключение при создании экземпляра базового класса или при обращении к его свойству.

2 голосов
/ 23 ноября 2017

Реализация 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>
1 голос
/ 06 апреля 2019

Начиная с 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'
                )

Это приводит к ошибке при инициализации дочернего класса, если отсутствующая переменная класса не определена, поэтому вам не нужно ждать, пока не будет получена доступная переменная класса.

0 голосов
/ 03 августа 2018
class AbstractStuff:
    @property
    @abc.abstractmethod
    def some_property(self):
        pass

По состоянию на 3.3 abc.abstractproperty устарела, я думаю.

...