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

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

17 ответов

Решение

Использовать 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>

Старая школа (до 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 документация.

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

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

Irv

Большинство предыдущих ответов были правильными, но вот ответ и пример для Python 3.7. Да, вы можете создать абстрактный класс и метод. Как напоминание, иногда класс должен определять метод, который логически принадлежит классу, но этот класс не может указать, как реализовать метод. Например, на следующих уроках "Родители и дети" они оба едят, но реализация будет отличаться для каждого, потому что дети и родители едят разные виды пищи, а количество раз, которое они едят, разное. Итак, есть подклассы метода переопределяет AbstractClass.eat.

AbstractClass, есть метод

import abc
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 only3 times or more each day

Как объяснялось в других ответах, да, вы можете использовать абстрактные классы в 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_dictclassmethod, propertyprop1 (который только для чтения) и propertyprop2 (который также может быть установлен), а также функция под названием 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,

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

Вы можете создать абстрактный класс, расширив ABC , что означает «Абстрактные базовые классы» , и можете создать абстрактный метод с помощью @abstractmethod в абстрактном классе, как показано ниже:

      from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

И, чтобы использовать абстрактный класс, он должен быть расширен дочерним классом, а дочерний класс должен переопределить абстрактный метод абстрактного класса, как показано ниже:

      from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

class Cat(Animal): # Extends "Animal" abstract class
    def sound(self): # Overrides "sound()" abstract method
        print("Meow!!")

obj = Cat()
obj.sound()

Выход:

      Meow!!

И абстрактный метод может иметь код, а неpassи может вызываться дочерним классом, как показано ниже:

      from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        print("Wow!!") # Here

class Cat(Animal):
    def sound(self):
        super().sound() # Here
        
obj = Cat()
obj.sound()

Выход:

      Wow!!

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

      from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass
    
    def __init__(self): # Here
        self.name = "John" # Here
    
    x = "Hello" # Here
    
    def test1(self): # Here
        print("Test1")
    
    @classmethod # Here
    def test2(cls):
        print("Test2")
        
    @staticmethod # Here
    def test3():
        print("Test3")

class Cat(Animal):
    def sound(self):
        print(self.name) # Here
        print(super().x) # Here
        super().test1()  # Here
        super().test2()  # Here
        super().test3()  # Here

obj = Cat()
obj.sound()

Выход:

      John
Hello
Test1
Test2
Test3

И вы можете определить абстрактный класс и статические методы, а также абстрактный метод получения, установки и удаления в абстрактном классе, как показано ниже. * @abstractmethod должен быть самым внутренним декоратором, в противном случае возникает ошибка, и вы можете увидеть мой ответ , который больше объясняет об абстрактном методе получения, установки и удаления:

      from abc import ABC, abstractmethod

class Person(ABC):

    @classmethod
    @abstractmethod # The innermost decorator
    def test1(cls):
        pass
    
    @staticmethod
    @abstractmethod # The innermost decorator
    def test2():
        pass

    @property
    @abstractmethod # The innermost decorator
    def name(self):
        pass

    @name.setter
    @abstractmethod # The innermost decorator
    def name(self, name):
        pass

    @name.deleter
    @abstractmethod # The innermost decorator
    def name(self):
        pass

Затем вам нужно переопределить их в дочернем классе, как показано ниже:

      class Student(Person):
    
    def __init__(self, name):
        self._name = name
    
    @classmethod
    def test1(cls): # Overrides abstract class method
        print("Test1")
    
    @staticmethod
    def test2(): # Overrides abstract static method
        print("Test2")
    
    @property
    def name(self): # Overrides abstract getter
        return self._name
    
    @name.setter
    def name(self, name): # Overrides abstract setter
        self._name = name
    
    @name.deleter
    def name(self): # Overrides abstract deleter
        del self._name

Затем вы можете создать экземпляр дочернего класса и вызвать его, как показано ниже:

      obj = Student("John") # Instantiates "Student" class
obj.test1() # Class method
obj.test2() # Static method
print(obj.name) # Getter
obj.name = "Tom" # Setter
print(obj.name) # Getter
del obj.name # Deleter
print(hasattr(obj, "name"))

Выход:

      Test1
Test2
John 
Tom  
False

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

      from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

obj = Animal()

Возникает следующая ошибка:

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

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

      from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

class Cat(Animal):
    pass # Doesn't override "sound()" abstract method

obj = Cat() # Here

Возникает следующая ошибка:

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

И если вы определяете абстрактный метод в неабстрактном классе, который не расширяет ABC, абстрактный метод является обычным методом экземпляра, поэтому ошибок нет, даже если создается экземпляр неабстрактного класса и даже если дочерний класс не переопределяет абстрактный метод неабстрактного класса, как показано ниже:

      from abc import ABC, abstractmethod

class Animal: # Doesn't extend "ABC"
    @abstractmethod # Here
    def sound(self):
        print("Wow!!")

class Cat(Animal):
    pass # Doesn't override "sound()" abstract method

obj1 = Animal() # Here
obj1.sound()

obj2 = Cat() # Here
obj2.sound()

Выход:

      Wow!!
Wow!!

Кроме того, вы можете заменить класс расширения класса ниже:

      from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

# ↓↓↓ Here ↓↓↓

class Cat(Animal):
    def sound(self):
        print("Meow!!")

# ↑↑↑ Here ↑↑↑

print(issubclass(Cat, Animal))

С этим кодом, имеющим register() ниже:

      from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

# ↓↓↓ Here ↓↓↓

class Cat:
    def sound(self):
        print("Meow!!")
        
Animal.register(Cat)

# ↑↑↑ Here ↑↑↑

print(issubclass(Cat, Animal))

Затем оба приведенных выше кода выводят один и тот же результат ниже, показывая Catкласс является подклассом Animalкласс :

      True
 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

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

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

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

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

Поздно отвечать здесь, но чтобы ответить на другой вопрос «Как создавать абстрактные методы », который здесь указывает, я предлагаю следующее.

      # decorators.py
def abstract(f):
    def _decorator(*_):
        raise NotImplementedError(f"Method '{f.__name__}' is abstract")
    return _decorator


# yourclass.py
class Vehicle:
    def addGas():
       print("Gas added!")

    @abstract
    def getMake(): ...

    @abstract
    def getModel(): ...

Базовый класс класса Vehicle по-прежнему может быть создан для модульного тестирования (в отличие от ABC), и присутствует Pythonic-вызов исключения. О да, для удобства вы также получаете абстрактное имя метода в исключении.

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

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

Большинство ответов наследуют базовый класс для определения абстрактных методов. Но это не всегда полезно. Что, если вы хотите определить абстрактный метод во время выполнения?

Например, в java мы можем сделать это

      class UserClass { ...

BaseClass f = new BaseClass() {
    public void method() {
        system.out.println( "this is a test" )
    }
};

}

Итак, что делать, если нам нужно это реализовать, так что в этом случае

      class BaseClass:

    def __init__(self, func ):
        self.function = func

    def abstract_function(self ):
        if not self.function:
            raise NotImplementedError("function not implemented") 
        else:
            return self.function()

    def run(self ):
        self.abstract_function()

def func():
    print('this is a test')

bc = BaseClass( func )
bc.run()

должно сработать

Да, вы можете, используя . 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

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

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

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

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

Просто быстрое дополнение к ответу @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! 
Другие вопросы по тегам