Чи можна робити абстрактні класи на Python?


321

Як можна зробити конспект класу чи методу в 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

Відповіді:


554

Використовуйте 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
що робить @ab абстрактmethod? Навіщо вам це потрібно? Якщо клас вже встановлений як абстрактний, чи не повинен компілятор / інтерпретатор знати, що всі методи походять із відповідного абстрактного класу?
Чарлі Паркер

29
@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 () без будь-яких методів @ab абстрактmethod?
Джо

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

108

Старий шкільний (до- 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. Якщо тип 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, марно.
Бенямін Джафарі

Чи можемо ми також написати @ab абстрактmethod для функції init ?
змінна

+1 Чому @ab абстрактmethod def eat (self)? Якщо цей клас абстрактний і, отже, не мається на увазі, чому ви передаєте (само) їсти? Добре працює без нього
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
Навіть abc документація використовує self у своїх прикладах посилання
igor Smirnov

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 ... ви можете змусити метод 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
Це запобіжить вигоді підкласів з будь-яким загальним кодом init, який логічно повинен бути присутнім у їхньому загальному базовому класі.
Арпіт Сінгх

Справедливо. Стільки для старої школи.
Дейв Вейд-Штейн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.