Можно ли создавать абстрактные классы в Python?


322

Как я могу сделать класс или метод абстрактным в Python?

Я пытался переопределить __new__() так:

class F:
    def __new__(cls):
        raise Exception("Unable to create an instance of abstract class %s" %cls)

но теперь, если я создам класс G который наследует от Fтак:

class G(F):
    pass

тогда я не могу создать экземпляр G, так как он вызывает __new__метод своего суперкласса .

Есть ли лучший способ определить абстрактный класс?


Да, вы можете создавать абстрактные классы в Python с помощью модуля abc (абстрактные базовые классы). Этот сайт поможет вам в этом: http://docs.python.org/2/library/abc.html
ORION

Ответы:


555

Используйте abcмодуль для создания абстрактных классов. Используйте abstractmethodдекоратор для объявления абстрактного метода и объявления абстрактного класса одним из трех способов, в зависимости от версии Python.

В Python 3.4 и выше вы можете наследовать от ABC. В более ранних версиях Python метакласс вашего класса нужно указывать как ABCMeta. Указание метакласса имеет разный синтаксис в Python 3 и Python 2. Ниже показаны три возможности:

# 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

Какой бы способ вы ни использовали, вы не сможете создать экземпляр абстрактного класса, который имеет абстрактные методы, но сможете создать экземпляр подкласса, который предоставляет конкретные определения этих методов:

>>> 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>

9
что делает @abstractmethod? Зачем тебе это? Если класс уже установлен как абстрактный, не должен ли компилятор / интерпретатор знать, что все методы взяты из рассматриваемого абстрактного класса?
Чарли Паркер

30
@CharlieParker - @abstractmethodделает так, что декорированная функция должна быть переопределена до того, как будет создан экземпляр класса. Из документов:A class that has a metaclass derived from ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden.
Фальшивое имя

6
@CharlieParker - По сути, он позволяет вам определять класс таким образом, чтобы подкласс должен реализовывать определенный набор методов для создания экземпляров вообще.
Фальшивое имя

13
Есть ли способ запретить пользователям создавать Abstract () без каких-либо методов @abstractmethod?
Джо

2
@Joe оказывается можно использовать @abstractmethodдля __init__метода , а также, см stackoverflow.com/q/44800659/547270
scrutari

108

Старый способ (pre- PEP 3119 ) сделать это просто raise NotImplementedErrorв абстрактном классе, когда вызывается абстрактный метод.

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): [...]

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

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


1
Цените простоту и эффективность этого подхода.
mohit6up

Ха-ха, я видел это везде в моем курсе OMSCS и понятия не имел, что это было :)
mLstudent33

18

Вот очень простой способ без использования модуля ABC.

В __init__методе класса, которым вы хотите быть абстрактным, вы можете проверить «тип» себя. Если тип self является базовым классом, то вызывающая сторона пытается создать экземпляр базового класса, поэтому выведите исключение. Вот простой пример:

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()

При запуске выдает:

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

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


11

Большинство предыдущих ответов были правильными, но вот ответ и пример для Python 3.7. Да, вы можете создать абстрактный класс и метод. Как напоминание, иногда класс должен определять метод, который логически принадлежит классу, но этот класс не может указать, как реализовать метод. Например, на следующих уроках «Родители и дети» они оба едят, но реализация будет отличаться для каждого, потому что дети и родители едят разные виды пищи, а количество раз, которое они едят, разное. Итак, подклассы метода переопределяют 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())

ВЫВОД:

moms ----------
eat solid food 3 times each day
infants ----------
Milk only 3 times or more each day

Возможно import abc, бесполезно.
Беньямин Джафари

Можем ли мы написать @abstractmethod для функции init ?
переменная

+1 Почему @abstractmethod def есть (самостоятельно)? Если этот класс является абстрактным и, следовательно, не предназначенным для реализации, почему вы переходите (само) к еде? Без него нормально работает
VMMF

7

Этот будет работать в 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

4

Как объяснено в других ответах, да, вы можете использовать абстрактные классы в Python, используя abcмодуль . Ниже я приведу конкретный пример , используя абстрактные @classmethod, @propertyи@abstractmethod ( с помощью Python 3.6 +). Для меня обычно легче начать с примеров, которые я могу легко скопировать и вставить; Я надеюсь, что этот ответ также полезен для других.

Давайте сначала создадим базовый класс с именем 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

Наш Baseкласс всегда будет иметь a from_dict classmethod, a property prop1(который только для чтения) и a property prop2(который также может быть установлен), а также вызываемую функцию do_stuff. Какой бы класс сейчас не был построен на основе Base, придется реализовать все это для методов / свойств. Обратите внимание, что для абстрактного метода требуются два декоратора - classmethodи абстрактныйproperty .

Теперь мы можем создать такой класс A:

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')

Все необходимые методы / свойства реализованы, и мы можем, конечно, также добавить дополнительные функции, которые не являются частью Base(здесь:) i_am_not_abstract.

Теперь мы можем сделать:

a1 = A('dummy', 10, 'stuff')
a2 = A.from_dict({'name': 'from_d', 'val1': 20, 'val2': 'stuff'})

a1.prop1
# prints 10

a1.prop2
# prints 'stuff'

При желании мы не можем установить prop1:

a.prop1 = 100

вернется

AttributeError: невозможно установить атрибут

Также наш from_dictметод работает отлично:

a2.prop1
# prints 20

Если мы теперь определили второй класс, Bкак это:

class B(Base):
    def __init__(self, name):
        self.name = name

    @property
    def prop1(self):
        return self.name

и попытался создать экземпляр объекта следующим образом:

b = B('iwillfail')

мы получим ошибку

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

список всех вещей, Baseкоторые мы не реализовали в B.


2

Также это работает и просто:

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()

2

Вы также можете использовать метод __new__ в своих интересах. Вы просто что-то забыли. Метод __new__ всегда возвращает новый объект, поэтому вы должны вернуть новый метод его суперкласса. Сделайте следующим образом.

class F:
    def __new__(cls):
        if cls is F:
            raise TypeError("Cannot create an instance of abstract class '{}'".format(cls.__name__))
        return super().__new__(cls)

При использовании нового метода вы должны возвращать объект, а не ключевое слово None. Это все, что вы пропустили.


2

Я считаю принятый ответ, а все остальные странными, поскольку они переходят selfв абстрактный класс. Абстрактный класс не создан, поэтому не может иметьself .

Так что попробуйте, это работает.

from abc import ABCMeta, abstractmethod


class Abstract(metaclass=ABCMeta):
    @staticmethod
    @abstractmethod
    def foo():
        """An abstract method. No need to write pass"""


class Derived(Abstract):
    def foo(self):
        print('Hooray!')


FOO = Derived()
FOO.foo()

1
Даже абв использование документации самостоятельно в своих примерах ссылки
Игорь Смирнов

2
 from abc import ABCMeta, abstractmethod

 #Abstract class and abstract method declaration
 class Jungle(metaclass=ABCMeta):
     #constructor with default values
     def __init__(self, name="Unknown"):
     self.visitorName = name

     def welcomeMessage(self):
         print("Hello %s , Welcome to the Jungle" % self.visitorName)

     # abstract method is compulsory to defined in child-class
     @abstractmethod
     def scarySound(self):
         pass

Хороший @Shivam Bharadwaj, я сделал то же самое
Мухаммед Ирфан

-3

В своем фрагменте кода вы также можете решить эту проблему, предоставив реализацию для __new__метода в подклассе, аналогично:

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

Но это взлом, и я советую вам против этого, если вы не знаете, что делаете. Почти во всех случаях я советую вам использовать abcмодуль, который предлагали другие до меня.

Кроме того, когда вы создаете новый (базовый) класс, сделать его подклассы object, например: class MyBaseClass(object):. Я не знаю, насколько это важно, но это помогает сохранить согласованность стилей в вашем коде


-4

Просто быстрое дополнение к ответу @ TimGilbert's old-school ... вы можете сделать так, чтобы метод init () вашего абстрактного базового класса выдавал исключение, и это предотвратило бы его создание, не так ли?

>>> 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! 

6
Это предотвратит использование подклассами любого общего кода инициализации, который логически должен присутствовать в их общем базовом классе.
Арпит Сингх,

Справедливо. Так много для старой школы.
Дэйв Уэйд-Стейн
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.