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

Как я могу создать реферат класса или метода в 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__.

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

Ответ 1

Используйте модуль 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>

Ответ 2

Старая школа (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.

Ответ 3

Здесь очень простой способ без необходимости иметь дело с модулем 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

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

Irv

Ответ 4

Большинство предыдущих ответов были правильными, но вот ответ и пример для 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

Ответ 5

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

Ответ 6

Да, вы можете создавать абстрактные классы в python с помощью модуля abc (абстрактных базовых классов).

Этот сайт поможет вам в этом: http://docs.python.org/2/library/abc.html

Ответ 7

Да, вы можете, используя . abc - абстрактный модуль базовых классов, вот пример:

Python3.6

# importing abstract base classes module
import abc

class GetterSetter(abc.ABC):
    '''
    ABSTRACT BASE CLASSES:

    -  An abstract base class is a kind of 'model' for other classes to be defined.
        - It is not designed to construct instances, but can be subclassed by regular classes

    - Abstract classes can define interface, or methods that must be implemented by its subclasses.

    '''


    # Abstract classes are not designed to be instantiated, only to be subclassed

    #  decorator for abstract class
    @abc.abstractmethod
    def set_val(self, input):
        """set the value in the instance"""
        return

    @abc.abstractmethod
    def get_val(self):
        """Get and return a value from the instance..."""
        return

# Inheriting from the above abstract class
class MyClass(GetterSetter):

    # methods overriding in the GetterSetter
    def set_val(self, input):
        self.val = input

    def get_val(self):
        return self.val


# Instantiate
x = MyClass()
print(x) # prints the instance <__main__.MyClass object at 0x10218ee48>
x = GetterSetter() #throws error, abstract classes can't be instantiated

Python2.x

import abc

class GetterSetter(object):
    # meta class is used to define other classes
    __metaclass__ = abc.ABCMeta


    #  decorator for abstract class
    @abc.abstractmethod
    def set_val(self, input):
        """set the value in the instance"""
        return

    @abc.abstractmethod
    def get_val(self):
        """Get and return a value from the instance..."""
        return

# Inheriting from the above abstract class
class MyClass(GetterSetter):

    # methods overriding in the GetterSetter
    def set_val(self, input):
        self.val = input

    def get_val(self):
        return self.val


# Instantiate
x = GetterSetter()
print(x)
x = GetterSetter() #throws error, abstract classes can't be instantiated

Ответ 8

Как объяснялось в других ответах, да, вы можете использовать абстрактные классы в 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 всегда будет также иметь from_dict classmethod, property prop1 (который только для чтения) и 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.

Ответ 9

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

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

Ответ 10

Вы также можете использовать метод __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. Это все, что вы пропустили.

Ответ 11

Я считаю принятый ответ и все остальные странными, поскольку они передают 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()

Ответ 12

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

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

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

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

Ответ 13

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

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