파이썬에서 추상 클래스를 만들 수 있습니까?


321

파이썬에서 클래스 또는 메소드 추상화를 어떻게 만들 수 있습니까?

나는 __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__메소드를 호출하기 때문에 인스턴스화 할 수 없습니다 .

추상 클래스를 정의하는 더 좋은 방법이 있습니까?


예, abc (abstract base classes) 모듈을 사용하여 파이썬에서 추상 클래스를 만들 수 있습니다. 이 사이트는 당신을 도울 것입니다 : 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
@abstractmethod는 무엇을합니까? 왜 필요한가요? 클래스가 이미 abstract로 설정되어 있으면 컴파일러 / 해석이 모든 메소드가 해당 추상 클래스의 것임을 알 수 없어야합니까?
Charlie Parker

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
사용자가 @abstractmethod 메소드없이 Abstract ()를 작성하지 못하게하는 방법이 있습니까?
Joe

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 과정의 어느 곳에서나 이것을 보았고 그것이 무엇인지 전혀
알지 못했습니다

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 의 답변과 예는 다음과 같습니다 . 예, 추상 클래스와 메서드를 만들 수 있습니다. 리마인더처럼 클래스는 논리적으로 클래스에 속하는 메소드를 정의해야하지만 해당 클래스는 메소드 구현 방법을 지정할 수 없습니다. 예를 들어, 아래의 부모와 아기 수업에서 둘 다 먹는 것이지만, 아기와 부모가 다른 종류의 음식을 먹고 먹는 횟수가 다르기 때문에 구현 방식이 각각 다릅니다. 따라서 eat 메소드 서브 클래스는 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쓸모가 없습니다.
Benyamin Jafari

초기화 함수에 @abstractmethod를 쓸 수 있습니까 ?
변수

+1 왜 @abstractmethod def eat (self)? 이 클래스가 추상적이어서 인스턴스화를 의미하지 않는다면 왜 (자기) 음식을 먹어야합니까? 그것은없이 잘 작동
VMMF

7

이것은 파이썬 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

다른 답변에서 설명한 것처럼 그렇습니다 . abc모듈을 사용하여 Python에서 추상 클래스를 사용할 수 있습니다 . 나는 추상적 사용하여 실제 예를 들어 줄 아래 @classmethod, @property그리고 @abstractmethod(3.6 Python을 사용). 나를 위해 일반적으로 쉽게 복사하여 붙여 넣을 수있는 예제로 시작하는 것이 더 쉽습니다. 이 답변이 다른 사람들에게도 유용하기를 바랍니다.

먼저 다음과 같은 기본 클래스를 만듭니다 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, a 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 : 추상 메소드 do_stuff, from_dict, prop2로 추상 클래스 B를 인스턴스화 할 수 없습니다

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, 나는 같은 방식으로
무함마드 Irfan

-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
이렇게하면 서브 클래스가 공통 기본 클래스에 논리적으로 존재해야하는 공통 초기화 코드를 사용하지 못하게됩니다 .
Arpit Singh

그럴 수 있지. 옛날 학교에 너무 많은.
Dave Wade-Stein
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.