왜 파이썬에서 추상 기본 클래스를 사용합니까?


213

나는 파이썬에서 오리 타이핑의 오래된 방법에 익숙하기 때문에 ABC (추상 기본 클래스)의 필요성을 이해하지 못합니다. 도움 을 사용하는 방법에 좋다.

나는 PEP 의 이론적 근거를 읽으려고 노력했지만 내 머리 위로 넘어 갔다. 가변 시퀀스 컨테이너를 찾고 있다면를 확인 __setitem__하거나 사용하려고 시도 할 가능성이 높습니다 ( EAFP ). ABC를 사용 하는 숫자 모듈에 대한 실제 사용을 보지 못했지만 그것이 가장 이해해야합니다.

누구든지 나에게 근거를 설명 할 수 있습니까?

답변:


162

짧은 버전

ABC는 클라이언트와 구현 된 클래스간에 더 높은 수준의 의미 계약을 제공합니다.

긴 버전

클래스와 호출자간에 계약이 있습니다. 수업은 특정 일을하고 특정 속성을 갖도록 약속합니다.

계약에는 다른 수준이 있습니다.

매우 낮은 수준에서 계약에는 메소드 이름 또는 매개 변수 수가 포함될 수 있습니다.

정적으로 유형이 지정된 언어에서는 해당 계약이 실제로 컴파일러에 의해 시행됩니다. Python에서는 EAFP 또는 유형 검사를 사용 하여 알 수없는 객체가이 예상 계약을 충족하는지 확인할 수 있습니다 .

그러나 계약에는 더 높은 수준의 의미 론적 약속이 있습니다.

예를 들어, __str__()메서드 가 있으면 개체의 문자열 표현을 반환해야합니다. 그것은 , 개체의 모든 내용을 삭제 트랜잭션을 커밋하고 프린터에서 빈 페이지를 침 ...하지만 파이썬 매뉴얼에 설명이 무엇을해야하는지에 대한 공통의 이해가있다.

시맨틱 계약이 매뉴얼에 설명되어있는 특별한 경우입니다. print()방법은 어떻게해야 합니까? 프린터에 물체를 써야합니까, 아니면 화면에 다른 물체를 써야합니까? 그것은 달려 있습니다-전체 계약을 이해하려면 주석을 읽어야합니다. print()메소드가 존재 하는지 단순히 확인하는 클라이언트 코드 는 계약의 일부를 확인했습니다. 메소드 호출이 가능하지만 호출의 상위 레벨 의미에 대한 동의는 없습니다.

ACC (Abstract Base Class) 정의는 클래스 구현 자와 호출자 간의 계약을 생성하는 방법입니다. 메서드 이름의 목록 일뿐 아니라 해당 메서드의 기능에 대한 공통된 이해입니다. 이 ABC로부터 상속받은 경우, print()메소드 의 의미를 포함하여 주석에 설명 된 모든 규칙을 준수 할 것을 약속합니다 .

파이썬의 오리 타이핑은 정적 타이핑보다 유연성이 많은 장점이 있지만 모든 문제를 해결하지는 못합니다. ABC는 자유 형식의 Python과 정적으로 유형이 지정된 언어의 속박과 훈련 사이의 중간 솔루션을 제공합니다.


12
나는 당신이 거기에 요점이 있다고 생각하지만, 나는 당신을 따를 수 없습니다. 따라서 계약 측면에서 구현 __contains__하는 클래스와 상속되는 클래스 의 차이점은 무엇 collections.Container입니까? 귀하의 예에서, 파이썬에서는 항상에 대한 공유 이해가 __str__있었습니다. 구현 __str__은 일부 ABC에서 상속 한 다음 구현하는 것과 동일한 약속을합니다 __str__. 두 경우 모두 계약을 위반할 수 있습니다. 정적 타이핑에 사용되는 것과 같은 입증 가능한 의미는 없습니다.
Muhammad Alkarouri

15
collections.Container는을 포함 \_\_contains\_\_하며 사전 정의 된 규칙 만 의미 하는 축퇴 된 경우 입니다. ABC를 사용하는 것만으로는 많은 가치가 추가되지 않습니다. (예를 들어) Set상속 을 허용하기 위해 추가 된 것으로 의심 됩니다. 당신이 도착할 때 Set, 갑자기 ABC에 속하는 것은 상당한 의미를 가지고 있습니다. 아이템은 컬렉션에 두 번 속할 수 없습니다. 메소드의 존재로 감지 할 수 없습니다.
Oddthinking

3
예, Set보다 나은 예 라고 생각 print()합니다. 의미가 모호하고 이름만으로도 파악할 수없는 메서드 이름을 찾으려고했기 때문에 이름과 Python 설명서만으로 올바른 작업을 수행 할 수 있는지 확신 할 수 없었습니다.
Oddthinking

3
? Set대신에 예를 사용 하여 답변을 다시 쓸 가능성 이 print있습니까? Set@Oddthinking, 많은 의미가 있습니다.
Ehtesh Choudhury

1
: 나는이 문서가 아주 잘 설명 생각 dbader.org/blog/abstract-base-classes-in-python
szabgab

228

@Oddthinking의 대답은 틀린 것은 아니지만 파이썬이 오리 타이핑 세계에서 ABC를 가지고 있는 실제 적이고 실용적인 이유를 놓친 것 같습니다 .

추상적 인 방법은 깔끔하지만 내 의견으로는 오리 타이핑으로 아직 다루지 않은 유스 케이스를 실제로 채우지는 않습니다. 추상 기본 클래스의 실제 성능은 의 동작을 사용자 정의 할 수있는 방식에 있습니다isinstanceissubclass . ( __subclasshook__기본적으로 파이썬 __instancecheck____subclasscheck__ 훅 위에 친숙한 API입니다 .) 커스텀 타입에서 작동하도록 내장 구문을 적용하는 것은 파이썬 철학의 많은 부분입니다.

파이썬의 소스 코드는 모범적입니다. 표준 라이브러리에서 작성 하는 방법 은 다음과 같습니다collections.Container (작성시).

class Container(metaclass=ABCMeta):
    __slots__ = ()

    @abstractmethod
    def __contains__(self, x):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Container:
            if any("__contains__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

이 정의는 속성이있는 클래스는 직접 서브 클래스하지 않더라도 컨테이너의 서브 클래스로 간주 __subclasshook__된다고 말합니다 __contains__. 그래서 나는 이것을 쓸 수 있습니다 :

class ContainAllTheThings(object):
    def __contains__(self, item):
        return True

>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True

다시 말해, 올바른 인터페이스를 구현하면 하위 클래스입니다! ABC는 오리 타이핑의 정신에 충실하면서 파이썬에서 인터페이스를 정의하는 공식적인 방법을 제공합니다. 게다가, 이것은 공개 폐쇄 원칙 을 존중하는 방식으로 작동합니다 .

파이썬의 객체 모델은 좀 더 "전통적인"OO 시스템과 비슷하게 보입니다 (Java를 의미합니다). 우리는 yer 클래스, yer 객체, yer 메소드를 얻었습니다. 더 유연합니다. 마찬가지로, 파이썬의 추상 기본 클래스 개념은 Java 개발자가 인식 할 수 있지만 실제로는 매우 다른 목적으로 사용됩니다.

때로는 단일 항목이나 항목 모음에서 작동 할 수있는 다형성 함수를 작성 isinstance(x, collections.Iterable)하는 경우가 많으며 hasattr(x, '__iter__')동등한 try...except블록 보다 훨씬 읽기 쉽습니다. (파이썬을 모른다면,이 세 가지 중 코드의 의도를 가장 명확하게하는 것은 무엇입니까?)

즉, 나는 내 ABC를 거의 쓸 필요가 없으며 일반적으로 리팩토링을 통해 ABC의 필요성을 발견합니다. 많은 속성 검사를 수행하는 다형성 함수 또는 동일한 속성 검사를 수행하는 많은 함수가 보이면 그 냄새는 ABC가 추출되기를 기다리고 있음을 나타냅니다.

* 자바가 "전통적인"OO 시스템인지에 대한 논쟁없이


부록 : 추상 기본 클래스가 isinstance및 의 동작을 재정의 할 수는 issubclass있지만 여전히 가상 하위 클래스 의 MRO 에 들어 가지 않습니다 . 이것은 클라이언트에게 잠재적 인 함정입니다 . isinstance(x, MyABC) == True메소드가 정의 된 모든 객체가 아닙니다 MyABC.

class MyABC(metaclass=abc.ABCMeta):
    def abc_method(self):
        pass
    @classmethod
    def __subclasshook__(cls, C):
        return True

class C(object):
    pass

# typical client code
c = C()
if isinstance(c, MyABC):  # will be true
    c.abc_method()  # raises AttributeError

불행히도이 중 하나는 "그냥하지 말라"는 함정 (파이썬이 비교적 적은 수의 함정) : a __subclasshook__와 non-abstract 메소드로 ABC를 정의하지 마십시오 . 또한 __subclasshook__ABC가 정의한 일련의 추상 메소드와 일치하도록 정의를 작성해야 합니다.


21
"올바른 인터페이스를 구현하면 하위 클래스입니다." Oddthinking이 그것을 놓쳤는 지 모르겠다. 그러나 나는 확실히했다. FWIW isinstance(x, collections.Iterable)는 더 명확하며 Python을 알고 있습니다.
Muhammad Alkarouri

훌륭한 게시물. 감사합니다. 나는 "그냥하지 마라"트랩과 같은 부록은 일반적인 서브 클래스 상속을하는 것과 약간 비슷하지만 서브 클래스를 상속 받은 것으로부터 C삭제 (또는 복구를 넘어서게하는)하는 것이라고 생각한다 . 주요 차이점은 하위 클래스가 아닌 상속 계약을 망쳐 놓는 것이 슈퍼 클래스라는 것입니다. abc_method()MyABC
Michael Scott Cuthbert

Container.register(ContainAllTheThings)주어진 예가 작동 하기 위해 할 필요는 없습니까?
BoZenKhaa

1
@BoZenKhaa 답변의 코드가 작동합니다! 시도 해봐! 의 의미는 __subclasshook__"이 술어가 목적하는 서브 간주 만족하는 클래스 isinstanceissubclass, 검사 에 관계없이 그것이 ABC에 등록되었는지 여부관계없이 직접 서브 여부의 ". 대답에서 말했듯 이 올바른 인터페이스를 구현하면 하위 클래스입니다!
Benjamin Hodgson

1
이탤릭체에서 굵은 체로 서식을 변경해야합니다. "올바른 인터페이스를 구현하면 하위 클래스입니다!" 매우 간결한 설명. 감사합니다!
marti

108

ABC의 편리한 기능은 필요한 메소드 (및 속성)를 모두 구현하지 않으면 AttributeError실제로 누락 된 메소드를 사용하려고 할 때 훨씬 나중에 인스턴스화시 오류가 발생한다는 것 입니다.

from abc import ABCMeta, abstractmethod

# python2
class Base(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

# python3
class Base(object, metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

class Concrete(Base):
    def foo(self):
        pass

    # We forget to declare `bar`


c = Concrete()
# TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"

https://dbader.org/blog/abstract-base-classes-in-python의

편집 : python3 구문을 포함하려면 @PandasRocks 덕분에


5
예제와 링크는 모두 매우 유용했습니다. 감사!
nalyd88

자료는 별도의 파일에 정의되어있는 경우, 당신은 Base.Base로 상속 또는 '자료 가져 오기 자료에서'로 가져 오기 라인을 변경해야합니다
라이언 Tennill

Python3에서는 구문이 약간 다릅니다. 이 답변보기 : stackoverflow.com/questions/28688784/…
PandasRocks

7
C # 배경에서 비롯된 추상 클래스를 사용 하는 이유입니다. 기능을 제공하고 있지만 기능을 추가로 구현해야한다고 명시하고 있습니다. 다른 답변은이 시점을 놓친 것 같습니다.
Josh Noe

18

프로토콜에서 모든 메소드의 존재를 확인하지 않고도 또는 지원되지 않기 때문에 "적자"영역에서 예외를 트리거하지 않고 오브젝트가 주어진 프로토콜을 지원하는지 여부를 판별합니다.


6

추상 메서드는 부모 클래스에서 호출하는 메서드가 자식 클래스에 나타나야합니다. 아래는 추상을 호출하고 사용하는 특별한 방법입니다. python3으로 작성된 프로그램

정상적인 전화 방법

class Parent:
def methodone(self):
    raise NotImplemented()

def methodtwo(self):
    raise NotImplementedError()

class Son(Parent):
   def methodone(self):
       return 'methodone() is called'

c = Son()
c.methodone()

'methodone ()이 호출되었습니다'

c.methodtwo()

NotImplementedError

추상적 인 방법으로

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'

c = Son()

TypeError : 추상 메서드 methodtwo로 추상 클래스 Son을 인스턴스화 할 수 없습니다.

childclass에서 methodtwo가 호출되지 않았으므로 오류가 발생했습니다. 올바른 구현은 다음과 같습니다

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'
    def methodtwo(self):
        return 'methodtwo() is called'

c = Son()
c.methodone()

'methodone ()이 호출되었습니다'


1
감사. 위의 cerberos와 같은 점이 위의 대답이 아닙니까?
무하마드 알카로 우리
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.