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


83

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

#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). 나는 그것이 더 낫다고 주장하는 것이 아니라 단지 다른 방법입니다.

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


18
-1 : "위임 체인으로 동일한 결과를 얻을 수 있습니다." 사실이지만 상속보다 훨씬 더 고통 스럽습니다. 클래스 정의를 전혀 사용하지 않고도 복잡한 순수 함수를 많이 사용하여 동일한 결과를 얻을 수 있습니다. 상속보다 간단하지 않은 12 가지 방법으로 동일한 작업을 수행 할 수 있습니다.
S.Lott 2009-06-20

10
실제로 나는 "나는 더 나은 주장하고 있지 않다)"라고
스테파노 Borini

4
"나는 아직 파이썬에서 상속을 사용하는 단 하나의 이유를 찾지 못했다"... 확실히 "내 솔루션이 더 낫다"처럼 들린다.
S.Lott

10
이런 인상을 주 셨다면 죄송합니다. 내 게시물은 파이썬에서 상속 사용에 대한 실제 사례 이야기에 대한 긍정적 인 피드백을 얻기위한 것이 었는데, 오늘 현재로서는 찾을 수 없었습니다 (주로 모든 파이썬 프로그래밍에서 이것이 필요하지 않은 경우에 직면했기 때문에 나는 위에서 설명한 상황이었다).
Stefano Borini

2
실제 분류법은 객체 지향 예제의 기초가되는 경우는 거의 없습니다.
Apalala

답변:


81

당신은 런타임 덕-타이핑을 "우선적 인"상속으로 언급하고 있지만, 나는 상속이 객체 지향 디자인의 필수적인 부분 인 디자인 및 구현 접근 방식으로서 고유 한 장점을 가지고 있다고 믿습니다. 저의 겸손한 의견으로는, 실제로는 클래스, 함수 등없이 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).

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


3
첫 번째 경우, 그것은 비 파이썬적인 행동 대신 유형을 검사하고 있음을 의미합니다. 두 번째 경우에는 동의하며 기본적으로 "프레임 워크"접근 방식을 사용합니다. 인터페이스뿐만 아니라, 재정의를 위해 speak_twice의 구현을 재활용하고 있으며 파이썬을 고려할 때 상속없이 살 수 있습니다.
Stefano Borini

9
클래스와 함수와 같은 많은 것없이 살 수 있지만 문제는 코드를 훌륭하게 만드는 것입니다. 상속이 그렇다고 생각합니다.
Roee Adler

@Stefano Borini-매우 "규칙 기반"접근 방식을 취하는 것 같습니다. 하지만 오래된 진부한 표현은 사실입니다. :-)
Jason Baker

@Jason Baker-저는 규칙이 경험 (예 : 실수)에서 얻은 지혜를보고하기 때문에 규칙을 좋아하는 경향이 있지만, 그로 인해 방해받는 창의성은 좋아하지 않습니다. 그래서 나는 당신의 진술에 동의합니다.
스테파노 Borini

1
나는이 예제가 그렇게 명확하지 않다는 것을 알지 못합니다. 동물, 자동차 및 모양 예제는 정말 그 논쟁에 싫습니다. :) IMHO에게 중요한 유일한 것은 구현을 상속할지 여부입니다. 그렇다면 파이썬의 규칙은 실제로 java / C ++와 유사합니다. 차이점은 대부분 인터페이스에 대한 상속입니다. 이 경우 덕 타이핑이 종종 해결책이됩니다. 상속보다는 훨씬 더 그렇습니다.
David Cournapeau

12

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


11

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

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


8

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


3
선택적 재정의는 상속의 이유입니다. 모든 것을 재정의하려면 이상한 특수한 경우입니다.
S.Lott

1
누가 모든 것을 무시할까요? 모든 방법은 공공 및 가상처럼 당신은 파이썬 생각할 수
bashmohandes

1
@bashmohandes : 나는 모든 것을 재정의하지 않을 것입니다. 그러나 질문은 모든 것이 무시되는 퇴보적인 경우를 보여줍니다. 이 이상한 특별한 경우가 질문의 기초입니다. 정상적인 OO 디자인에서는 결코 발생하지 않기 때문에 질문은 무의미합니다.
S.Lott 2009-06-20

7

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

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

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

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


6

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

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

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


5

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

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

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


2

나는 상속에 많은 의미가 없다고 생각합니다.

실제 시스템에서 상속을 사용할 때마다 의존성 웹이 엉 키게했기 때문에 화상을 입었거나 시간이 지나면 그것 없이는 훨씬 나아질 수 있다는 것을 깨달았습니다. 이제는 가능한 한 피합니다. 나는 단순히 그것을 사용하지 않습니다.

class Repeat:
    "Send a message more than once"
    def __init__(repeat, times, do):
        repeat.times = times
        repeat.do = do

    def __call__(repeat):
        for i in xrange(repeat.times):
             repeat.do()

class Speak:
    def __init__(speak, animal):
        """
        Check that the animal can speak.

        If not we can do something about it (e.g. ignore it).
        """
        speak.__call__ = animal.speak

    def twice(speak):
        Repeat(2, speak)()

class Dog:
     def speak(dog):
         print "Woof"

class Cat:
     def speak(cat):
         print "Meow"

>>> felix = Cat()
>>> Speak(felix)()
Meow

>>> fido = Dog()
>>> speak = Speak(fido)
>>> speak()
Woof

>>> speak.twice()
Woof

>>> speak_twice = Repeat(2, Speak(felix))
>>> speak_twice()
Meow
Meow

James Gosling은 기자 회견에서 "다시 돌아가서 Java를 다르게 수행 할 수 있다면 무엇을 생략 하시겠습니까?"라는 질문을 받았습니다. 그의 반응은 "수업"이었고 웃음이 있었다. 그러나 그는 진지하게 문제가되는 것은 클래스가 아니라 상속이라고 설명했다.

나는 일종의 약물 의존이라고 생각합니다. 그것은 당신에게 기분 좋은 빠른 해결책을 제공하지만 결국에는 당신을 엉망으로 만듭니다. 즉, 코드를 재사용하는 편리한 방법이지만 자식과 부모 클래스 사이에 비정상적인 결합을 강제합니다. 부모를 변경하면 자녀가 깨질 수 있습니다. 자식은 특정 기능에 대해 부모에 의존하며 해당 기능을 변경할 수 없습니다. 따라서 자식이 제공하는 기능도 부모와 연결되어 있습니다. 둘 다만 가질 수 있습니다.

더 나은 방법은 생성시에 구성되는 다른 객체의 기능을 사용하여 인터페이스를 구현하는 인터페이스에 대해 하나의 클라이언트 대면 클래스를 제공하는 것입니다. 적절하게 설계된 인터페이스를 통해이 작업을 수행하면 모든 결합이 제거 될 수 있으며 고도로 구성 가능한 API를 제공합니다 (이것은 새로운 것이 아닙니다. 대부분의 프로그래머는 이미이 작업을 수행하고 충분하지 않습니다). 구현 클래스는 단순히 기능을 노출해서는 안됩니다. 그렇지 않으면 클라이언트는 구성된 클래스를 직접 사용해야합니다 . 해당 기능을 결합하여 새로운 작업을 수행 해야합니다 .

상속 캠프에서 순수한 위임 구현은 단순히 위임 '체인'을 통해 값을 전달하는 많은 '접착'메서드를 필요로하기 때문에 어려움을 겪고 있다는 주장이 있습니다. 그러나 이것은 단순히 위임을 사용하여 상속과 유사한 디자인을 재창조하는 것입니다. 상속 기반 설계에 너무 오랫동안 노출 된 프로그래머는이 함정에 빠지는 데 특히 취약합니다. 깨닫지 못한 채 상속을 사용하여 무언가를 구현 한 다음 위임으로 변환하는 방법을 생각하기 때문입니다.

위의 코드와 같은 문제를 적절하게 분리하는 데에는 글루 메서드가 필요하지 않습니다. 각 단계가 실제로 값을 추가하는 것이므로 실제로 는 '글루'메서드가 아닙니다 (가치가 추가되지 않으면 디자인에 결함이 있음).

요약하면 다음과 같습니다.

  • 재사용 가능한 코드의 경우 각 클래스는 한 가지만 수행해야합니다 (그리고 잘 수행해야합니다).

  • 상속은 부모 클래스와 섞여 있기 때문에 두 가지 이상의 작업을 수행하는 클래스를 만듭니다.

  • 따라서 상속을 사용하면 재사용하기 어려운 클래스가 만들어집니다.


1

Python 및 거의 모든 다른 언어에서 상속을 피할 수 있습니다. 하지만 코드 재사용과 코드 단순화에 관한 것입니다.

의미 론적 트릭 일 뿐이지 만 클래스와 기본 클래스를 빌드 한 후에는 객체로 할 수 있는지 확인하기 위해 무엇을 할 수 있는지 알 필요도 없습니다.

Animal을 하위 분류 한 Dog 인 d가 있다고 가정 해 보겠습니다.

command = raw_input("What do you want the dog to do?")
if command in dir(d): getattr(d,command)()

사용자가 입력 한 내용을 사용할 수있는 경우 코드는 적절한 메서드를 실행합니다.

이것을 사용하여 원하는 포유류 / 파충류 / 새 하이브리드 괴물의 조합을 만들 수 있으며 이제 'Bark!'라고 말할 수 있습니다. 날아 다니면서 갈래 혀를 내밀면 제대로 처리됩니다! 재미있게 보내세요!


1

또 다른 작은 점은 op의 세 번째 예제입니다. isinstance ()를 호출 할 수 없습니다. 예를 들어 세 번째 예제를 다른 개체에 전달하고 "Animal"을 입력하면 호출에 대해 말합니다. 그렇게하지 않으면 개 유형, 고양이 유형 등을 확인해야합니다. 지연 바인딩으로 인해 인스턴스 검사가 실제로 "Pythonic"인지 확실하지 않습니다. 하지만 치즈 버거가 말을하지 않기 때문에 AnimalControl이 치즈 버거 유형을 트럭에 던지려고하지 않는 방식을 구현해야합니다.

class AnimalControl(object):
    def __init__(self):
        self._animalsInTruck=[]

    def catachAnimal(self,animal):
        if isinstance(animal,Animal):
            animal.speak()  #It's upset so it speak's/maybe it should be makesNoise
            if not self._animalsInTruck.count <=10:
                self._animalsInTruck.append(animal) #It's then put in the truck.
            else:
                #make note of location, catch you later...
        else:
            return animal #It's not an Animal() type / maybe return False/0/"message"

0

Python의 클래스는 기본적으로 함수와 데이터를 그룹화하는 방법 일뿐입니다. C ++의 클래스와는 다릅니다.

나는 주로 수퍼 클래스의 메서드를 재정의하는 데 사용되는 상속을 보았습니다. 예를 들어, 아마도 더 파이썬적인 상속 사용은 ..

from world.animals import Dog

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

물론 고양이는 개 유형은 아니지만 Dog완벽하게 작동하는 이 (타사) 클래스가 있습니다. , speak재정의하려는 메서드를 제외 하면 전체 클래스를 다시 구현할 수 있습니다. 다시 말하지만 while Cat은 유형이 Dog아니지만 고양이는 많은 속성을 상속합니다.

메서드 또는 속성을 재정의하는 훨씬 더 나은 (실용적인) 예는 urllib에 대한 사용자 에이전트를 변경하는 방법입니다. 기본적으로 하위 클래스를 urllib.FancyURLopener만들고 버전 속성을 변경합니다 ( 문서에서 ).

import urllib

class AppURLopener(urllib.FancyURLopener):
    version = "App/1.7"

urllib._urlopener = AppURLopener()

예외가 사용되는 또 다른 방법은 상속이보다 "적절한"방식으로 사용될 때 예외에 대한 것입니다.

class AnimalError(Exception):
    pass

class AnimalBrokenLegError(AnimalError):
    pass

class AnimalSickError(AnimalError):
    pass

.. 그런 다음 AnimalError그로부터 상속받은 모든 예외를 포착하거나 다음과 같은 특정 예외를 포착 할 수 있습니다.AnimalBrokenLegError


6
나는 ... 당신의 첫 번째 예에서 약간 혼란 스럽습니다. 마지막으로 확인한 바에 따르면 고양이는 개가 아니기 때문에 어떤 관계를 보여 주려는 지 잘 모르겠습니다. :-)
Ben Blank

1
당신은 Liskov 원칙을 엉망으로 만들고 있습니다. 고양이는 개가 아닙니다. 이 경우 사용하는 것은 괜찮지 만 Dog 클래스가 변경되어 예를 들어 고양이에게는 무의미한 "Lead"필드를 가져 오면 어떻게 될까요?
Dmitry Risenberg

1
동물베이스 클래스가 없다면, 당신의 대안은 전체를 다시 구현하는 것입니다. 저는 이것이 최선의 방법이라고 말하는 것이 아닙니다 (동물베이스 클래스가 있다면 그것을 사용하십시오), 작동하고 일반적으로 사용됩니다 ( 내가 추가 한 예제에 따라 urllib의 사용자 에이전트를 변경하는 권장 방법입니다.)
dbr
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.