[python] 파이썬에서 상속의 요점은 무엇입니까?

다음과 같은 상황이 있다고 가정합니다.

#include <iostream>

class Animal {
public:
    virtual void speak() = 0;
};

class Dog : public Animal {
    void speak() { std::cout << "woff!" <<std::endl; }
};

class Cat : public Animal {
    void speak() { std::cout << "meow!" <<std::endl; }
};

void makeSpeak(Animal &a) {
    a.speak();
}

int main() {
    Dog d;
    Cat c;
    makeSpeak(d);
    makeSpeak(c);
}

보시다시피 makeSpeak는 일반적인 Animal 객체를 받아들이는 루틴입니다. 이 경우 Animal은 순수한 가상 메소드 만 포함하므로 Java 인터페이스와 매우 유사합니다. makeSpeak는 전달되는 동물의 본질을 알지 못합니다. “speak”신호를 보내고 Cat :: speak () 또는 Dog :: speak () 중 호출 할 메서드를 처리하기 위해 후기 바인딩을 남겨 둡니다. 이것은 makeSpeak에 관한 한 실제로 어떤 하위 클래스가 전달되는지에 대한 지식은 관련이 없음을 의미합니다.

하지만 파이썬은 어떻습니까? Python에서 동일한 경우에 대한 코드를 살펴 보겠습니다. 잠시 동안 C ++ 케이스와 최대한 비슷해 지려고 노력합니다.

class Animal(object):
    def speak(self):
        raise NotImplementedError()

class Dog(Animal):
    def speak(self):
        print "woff!"

class Cat(Animal):
    def speak(self):
        print "meow"

def makeSpeak(a):
    a.speak()

d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)

이제이 예에서 동일한 전략을 볼 수 있습니다. 상속을 사용하여 개와 고양이 모두 동물이라는 계층 적 개념을 활용합니다. 하지만 파이썬에서는이 계층이 필요하지 않습니다. 이것은 똑같이 잘 작동합니다.

class Dog:
    def speak(self):
        print "woff!"

class Cat:
    def speak(self):
        print "meow"

def makeSpeak(a):
    a.speak()

d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)

Python에서는 원하는 객체에 “speak”신호를 보낼 수 있습니다. 객체가 처리 할 수 ​​있으면 실행되고 그렇지 않으면 예외가 발생합니다. 두 코드 모두에 Airplane 클래스를 추가하고 makeSpeak에 Airplane 객체를 제출한다고 가정합니다. C ++의 경우 Airplane은 Animal의 파생 클래스가 아니기 때문에 컴파일되지 않습니다. Python의 경우 런타임에 예외가 발생하며 이는 예상되는 동작 일 수도 있습니다.

다른 쪽에서는 speak () 메서드를 사용하여 MouthOfTruth 클래스를 추가한다고 가정합니다. C ++의 경우 계층 구조를 리팩터링하거나 MouthOfTruth 객체를 허용하기 위해 다른 makeSpeak 메서드를 정의해야합니다. 또는 Java에서 동작을 CanSpeakIface로 추출하고 각각에 대한 인터페이스를 구현할 수 있습니다. 많은 해결책이 있습니다 …

제가 지적하고 싶은 것은 아직 파이썬에서 상속을 사용하는 단 하나의 이유를 찾지 못했다는 것입니다 (프레임 워크와 예외 트리를 제외하고는 대체 전략이 존재한다고 생각합니다). 다형성을 수행하기 위해 기본 파생 계층을 구현할 필요가 없습니다. 상속을 사용하여 구현을 재사용하려는 경우 포함 및 위임을 통해 동일한 작업을 수행 할 수 있으며 런타임에 변경할 수있는 추가 이점이 있으며 의도하지 않은 부작용 위험없이 포함 된 인터페이스를 명확하게 정의 할 수 있습니다.

그래서 결국 질문이 있습니다. 파이썬에서 상속의 요점은 무엇입니까?

편집 : 매우 흥미로운 답변에 감사드립니다. 실제로 코드 재사용에 사용할 수 있지만 구현을 재사용 할 때는 항상주의해야합니다. 일반적으로 저는 매우 얕은 상속 트리를 수행하거나 트리를 전혀 사용하지 않는 경향이 있으며, 기능이 공통적이면 공통 모듈 루틴으로 리팩토링 한 다음 각 객체에서 호출합니다. 단일 변경점을 갖는 이점이 있습니다 (예 : Dog, Cat, Moose 등을 추가하는 대신 상속의 기본 이점 인 Animal에 추가합니다). 위임 체인 (예 : JavaScript). 나는 그것이 더 낫다고 주장하는 것이 아니라 단지 다른 방법입니다.

나는 또한 이와 관련하여 유사한 게시물 을 발견 했습니다 .



답변

당신은 런타임 덕-타이핑을 “우선적 인”상속으로 언급하고 있지만, 나는 상속이 객체 지향 디자인의 필수적인 부분 인 디자인 및 구현 접근 방식으로서 고유 한 장점을 가지고 있다고 믿습니다. 저의 겸손한 의견으로는, 실제로는 클래스, 함수 등없이 Python을 코딩 할 수 있기 때문에 다른 방법으로 무언가를 달성 할 수 있는지 여부에 대한 질문은 그다지 적절하지 않습니다. 그러나 문제는 코드가 얼마나 잘 설계되고 견고하며 가독성이 있는지에 대한 것입니다.

제 생각에는 상속이 올바른 접근 방식 인 두 가지 예를들 수 있습니다. 더 많은 것이 있다고 확신합니다.

첫째, 현명하게 코드를 작성한다면 makeSpeak 함수는 입력이 실제로 Animal인지 확인하고 “말할 수 있음”뿐 아니라 가장 우아한 방법은 상속을 사용하는 것입니다. 다시 말하지만, 다른 방법으로도 할 수 있지만 이것이 상속을 통한 객체 지향 디자인의 아름다움입니다. 코드는 입력이 “동물”인지 “진짜”확인합니다.

둘째, 명확하게 더 간단한 것은 객체 지향 디자인의 또 다른 필수적인 부분 인 캡슐화입니다. 이는 조상이 데이터 멤버 및 / 또는 비추 상 메서드를 가질 때 관련이 있습니다. 조상이 then-abstract 함수를 호출하는 함수 (speak_twice)를 가지고있는 다음과 같은 어리석은 예를 보겠습니다.

class Animal(object):
    def speak(self):
        raise NotImplementedError()

    def speak_twice(self):
        self.speak()
        self.speak()

class Dog(Animal):
    def speak(self):
        print "woff!"

class Cat(Animal):
    def speak(self):
        print "meow"

"speak_twice"중요한 기능 이라고 가정하면 Dog와 Cat 모두에서 코드화하고 싶지 않으며이 예제를 추정 할 수 있습니다. 물론, 오리 유형의 객체를 받아들이는 Python 독립 실행 형 함수를 구현하고, 말하기 기능이 있는지 확인하고 두 번 호출 할 수 있지만, 이는 우아하지 않고 포인트 번호 1을 놓친 것입니다 (동물인지 확인). 더 나쁜 것은 캡슐화 예제를 강화하기 위해 하위 클래스의 멤버 함수가 사용하려는 경우 "speak_twice"어떨까요?

조상 클래스에 데이터 멤버가있는 경우, 예를 들어 "number_of_legs"조상에서 비추 상 메서드에 의해 사용 "print_number_of_legs"되지만 하위 클래스의 생성자에서 시작됩니다 (예 : Dog은 4로 초기화하고 Snake는 초기화합니다). 0).

다시 말하지만, 더 많은 예제가 있다고 확신하지만 기본적으로 견고한 객체 지향 설계를 기반으로하는 모든 (충분히 큰) 소프트웨어에는 상속이 필요합니다.


답변

Python의 상속은 코드 재사용에 관한 것입니다. 공통 기능을 기본 클래스로 분해하고 파생 클래스에서 다른 기능을 구현합니다.


답변

Python의 상속은 다른 무엇보다 편리합니다. 클래스에 “기본 동작”을 제공하는 데 가장 적합하다는 것을 알았습니다.

실제로 상속을 사용하는 것에 반대하는 Python 개발자 커뮤니티가 있습니다. 무엇을하든 과용하지 마십시오. 지나치게 복잡한 클래스 계층 구조를 갖는 것은 “자바 프로그래머”라는 레이블을 붙일 수있는 확실한 방법이며 당신은 그것을 가질 수 없습니다. 🙂


답변

파이썬에서 상속의 요점은 코드를 컴파일하는 것이 아니라 클래스를 다른 자식 클래스로 확장하고 기본 클래스의 논리를 재정의하는 상속의 실제 이유 때문이라고 생각합니다. 그러나 파이썬의 덕 타이핑은 “인터페이스”개념을 쓸모 없게 만듭니다. 클래스 구조를 제한하기 위해 인터페이스를 사용할 필요없이 호출 전에 메서드가 존재하는지 확인할 수 있기 때문입니다.


답변

그런 추상적 인 예를 가지고 의미 있고 구체적으로 대답하는 것은 매우 어렵다고 생각합니다.

단순화하기 위해 상속에는 인터페이스와 구현의 두 가지 유형이 있습니다. 구현을 상속해야하는 경우 파이썬은 C ++와 같이 정적으로 형식화 된 OO 언어와 크게 다르지 않습니다.

인터페이스의 상속은 내 경험에서 소프트웨어 디자인에 근본적인 결과와 함께 큰 차이가있는 곳입니다. Python과 같은 언어는이 경우 상속을 사용하도록 강요하지 않으며 나중에 잘못된 디자인 선택을 수정하기가 매우 어렵 기 때문에 대부분의 경우 상속을 피하는 것이 좋습니다. 이것은 좋은 OOP 책에서 제기 된 잘 알려진 요점입니다.

인터페이스에 대한 상속을 사용하는 것이 Python에서 권장되는 경우가 있습니다 (예 : 플러그인 등). 이러한 경우 Python 2.5 이하에는 “내장 된”우아한 접근 방식이 없으며 여러 큰 프레임 워크가 자체 솔루션을 설계했습니다. (zope, trac, twister). Python 2.6 이상에는 이를 해결하기위한 ABC 클래스가 있습니다.


답변

오리 타이핑이 무의미하게 만드는 것은 상속이 아니라 인터페이스입니다. 완전히 추상적 인 동물 클래스를 만들 때 선택한 것과 같은 인터페이스입니다.

후손이 사용할 실제 행동을 도입하는 동물 클래스를 사용했다면, 추가 행동을 도입 한 개와 고양이 클래스에는 두 클래스 모두에 대한 이유가있을 것입니다. 당신의 주장이 정확하다는 것은 자손 클래스에 실제 코드를 제공하지 않는 조상 클래스의 경우에만 있습니다.

파이썬은 어떤 객체의 능력을 직접 알 수 있고 그러한 능력은 클래스 정의를 넘어서 변경 가능하기 때문에, 어떤 메서드를 호출 할 수 있는지 프로그램에 “말”하기 위해 순수한 추상 인터페이스를 사용한다는 생각은 다소 무의미합니다. 그러나 그것은 상속의 유일한 지점이나 주요 지점이 아닙니다.


답변

C ++ / Java / etc에서 다형성은 상속으로 인해 발생합니다. 잘못 잊혀진 믿음을 버리고 역동적 인 언어가 당신에게 열려 있습니다.

본질적으로, 파이썬에는 “특정 메소드가 호출 가능하다는 이해”만큼의 인터페이스가 없습니다. 손으로 흔들리고 학문적으로 들리 죠? 이는 “speak”를 호출하기 때문에 객체에 “speak”메서드가 있어야한다고 분명히 예상한다는 것을 의미합니다. 간단 하죠? 이것은 클래스의 사용자가 인터페이스를 정의한다는 점에서 매우 Liskov-ian입니다. 이는 더 건강한 TDD로 이끄는 좋은 디자인 개념입니다.

그래서 남은 것은 다른 포스터가 공손하게 코드 공유 트릭을 말하지 않도록 관리 한 것입니다. 각 “하위”클래스에 동일한 동작을 작성할 수 있지만 이는 중복됩니다. 상속 계층 전체에서 변하지 않는 기능을 상속하거나 혼합하기가 더 쉽습니다. 일반적으로 더 작고 DRY-er 코드가 더 좋습니다.