파이썬의 클래스 메커니즘은 최소한의 새로운 구문과 의미구조로 클래스를 추가합니다. C++과 모듈라-3의 클래스 메커니즘이 혼합되었습니다. 모듈에서 그런 것처럼, 클래스도 정의와 사용자 사이에 절대적인 장벽을 놓지 않습니다. 그러나 정의를 깨지 않을 것이라는 사용자의 친절함에 약간 의존합니다. 그렇지만 클래스의 가장 중요한 특징들을 최대한 확보하였습니다: 클래스 상속 메커니즘으로 여러 바탕 클래스가 허용되며, 파생된 클래스는 그의 바탕 클래스의 메쏘드를 오버라이드할 수 있고, 메쏘드는 바탕 클래스의 메쏘드를 같은 이름을 가지고 호출할 수 있습니다. 객체에 얼마든지 사적인 데이터를 담을 수 있습니다.
C++ 전문용어로, 모든 클래스 멤버는 (데이터 멤버를 포함하여) 공개되어(public) 있으며 모든 멤버 함수는 가상적(virtual)입니다. 특별한 구성자나 파괴자는 없습니다. 모듈라-3에서처럼 객체의 메쏘드로부터 그의 멤버를 참조하는 지름길은 없습니다: 메쏘드 함수는 그 객체를 나타내는 명시적인 첫 인자로 선언됩니다. 이 인자는 호출 때 묵시적으로 제공됩니다. 스몰토크에서처럼 클래스 그 자체는 객체입니다. 물론, 그 단어를 넓은 의미로 사용하면 말입니다: 파이썬에서 모든 데이터 유형은 객체입니다. 객체는 반입과 이름바꾸기에 대하여 의미구조를 제공합니다. C++ 그리고 모듈라-3와는 다르게, 내장 유형을 바탕 클래스로 사용하여 사용자가 확장할 수 있습니다. 또한, C++과는 같고 모듈라-3와는 다르게, 대부분의 특별한 구문을 가진 내장 연산자(산술 연산자, 첨자 등등)를 클래스 실체에 대하여 재정의할 수 있습니다.
클래스에 관하여 언급할 때 일반적으로 용인된 전문용어가 없으면, 스몰토크와 C++ 용어를 사용하겠습니다. (모듈라-3 용어를 사용할 때는 그의 객체-지향적 의미구조가 C++보다 파이썬에 더 가깝기 때문입니다. 그러나 독자 중에 그에 관하여 들어 본 분은 별로 없으리라고 생각합니다.)
객체는 독자적이며, (여러 영역에서) 여러 이름이 같은 객체에 묶일 수 있습니다. 이것을 다른 언어에서는 별칭(aliasing)이라고 부릅니다. 별칭은 보통 파이썬을 처음 접하면 이해가 되지 않습니다. 변경불능 기본 유형(숫자, 문자열, 터플)을 다룰 때는 무시해도 안전합니다. 그렇지만, 별칭은 변경불능 객체에 연관된 파이썬 코드의 의미구조에 (의도적으로!) 영향을 미칩니다. 예를 들면 리스트, 사전, (파일, 창, 등등) 프로그램 밖에 있는 객체들을 표현하는 대부분의 유형들 말입니다. 별칭은 보통 프로그램의 장점으로 사용됩니다. 왜냐하면 별칭은 어떤 관점에서 마치 포인터처럼 행동하기 때문입니다. 예를 들어, 객체를 건네는 것은 비용이 싸게 먹힙니다. 그 구현이 오직 포인터만 건네면 되기 때문입니다; 함수가 인자로 건넨 객체를 수정하면, 호출자는 그 변화를 볼 수 있습니다 -- 이 덕분에 파스칼에서처럼 두 가지 다른 인자를 건네는 메커니즘이 필요 없습니다.
클래스를 소개하기 전에 먼저 파이썬의 영역 규칙에 관하여 몇가지 말씀을 드려야겠습니다. 클래스 정의는 이름 공간을 멋지게 이용합니다. 무슨 일이 일어나는지 완전히 이해하려면 영역과 이름공간이 작동하는 법을 알 필요가 있습니다. 게다가 이 주제를 이해하면 고급 파이썬 프로그래머로 성장하는데 도움이 됩니다.
몇가지 정의와 함께 시작하겠습니다.
이름공간(namespace)은 이름과 객체를 짝지워 놓은 곳입니다. 현재 대부분의 이름공간은 파이썬의 사전으로 구현되어 있습니다. 그러나 보통은 (수행성능 때문이 아니라면) 눈에 띄지 않습니다. 그리고 앞으로 바뀔 가능성도 있습니다. 이름공간의 예를 들자면: 내장 이름 집합 (abs()같은 함수, 그리고 내장 예외 이름); 모듈의 전역 이름; 함수 요청에서의 지역 이름. 어떤 면에서 한 객체의 속성 집합도 역시 이름공간을 형성합니다. 이름공간에 관하여 알아야 할 중요한 일은 다른 이름공간에 있는 이름 사이에 절대로 관련이 없다는 것입니다; 예를 들면, 두 가지 다른 모듈에 아무 혼란 없이 ``maximize''라는 함수를 정의할 수 있습니다 -- 모듈의 사용자는 함수 앞에 반드시 그 모듈 이름을 접두사로 붙여야 합니다.
그런데 여기에서 점이 따르는 이름에 대하여 속성(attribute)이라는 단어를 사용했습니다 -- 예를 들어, z.real이라는 표현식에서, real은 z라는 객체의 속성입니다. 엄밀히 말해, 모듈에서 이름을 참조하는 것은 속성 참조입니다: modname.funcname라는 표현식에서, modname은 모듈 객체이고 funcname은 그의 속성입니다. 이 경우 우연하게도 모듈의 속성과 그 모듈에 정의된 전역 이름 사이가 눈에 보이는 그대로 짝지워져 있습니다: 그들은 같은 이름공간을 공유합니다!
9.1
속성은 읽기 전용이거나 쓸 수도 있습니다. 후자의 경우 속성에 할당하는 것도 가능합니다. 모듈 속성은 쓸 수 있습니다: "modname.the_answer = 42"와 같이 쓸 수 있습니다. 쓰기가능 속성은 del 서술문으로 지울 수도 있습니다. 예를 들어, "del modname.the_answer"은 modname이라고 이름 붙은 객체로부터 the_answer 속성을 삭제합니다.
이름 공간은 순간순간 만들어지고 사라집니다. 내장 이름이 담긴 이름공간은 파이썬이 기동할 때 만들어지고 삭제되지 않습니다. 모듈에 대한 전역 이름공간은 모듈 정의를 읽는 순간에 만들어집니다; 보통, 모듈 이름공간도 역시 파이썬이 끝날 때까지 살아있습니다. 스크립트 파일에서 읽거나 상호대화적으로 최상위 수준의 호출로 실행된 서술문들은 __main__이라는 모듈의 일부로 간주됩니다. 그래서 자신만의 전역 이름공간을 가집니다. (내장 이름은 실제로 모듈 안에도 삽니다; 이를 __builtin__이라고 부릅니다.)
함수를 위한 지역 이름공간은 함수가 호출될 때 만들어지고 그 함수가 실행을 마치거나 또는 처리하지 못하는 예외를 일으킬 때 삭제됩니다. (실제로 무슨 일이 일어나는지 기술하려 하지 말고 잊어버리는 편이 더 좋은 방법입니다.) 물론, 재귀적으로 요청할 때마다 독자적으로 지역 이름공간을 가집니다.
영역(scope)이란 파이썬 프로그램에서 이름공간에 직접적으로 접근가능한 텍스트 구역입니다. 여기에서 ``직접적으로 접근가능한''이란 이름을 (점없이) 무자격으로 참조하더라도 그 이름공간에서 이름을 찾을 수 있다는 뜻입니다.
영역은 정적으로 결정되지만 동적으로 사용됩니다. 실행중에는 언제나 적어도 세가지 영역이 내포되며 그의 이름공간에 직접적으로 접근할 수 있습니다: 지역 이름이 담긴 안쪽 영역이 제일 먼저 검색되고; 다음 바로 바깥의 둘러싼 함수 영역이 검색되고; 다음으로 중간 영역이 검색되는데 여기에는 현재 모듈의 전역 이름들이 담겨 있습니다; (가장 나중에 검색되는) 가장 바깥 영역은 내장 이름이 담긴 이름공간입니다.
이름이 전역으로 선언되어 있다면, 모든 참조와 할당은 그 모듈의 전역 이름을 담고 있는 중간 영역으로 직접적으로 들어갑니다. 그렇지 않으면, 제일 안쪽 영역 바깥에서 발견되는 모든 변수는 읽기-전용입니다 (그런 변수에 쓰려고 시도하면 그냥 새로운 지역 변수가 그 내부 영역에 생성되고, 바깥쪽에 이름이 똑 같은 변수는 그냥 그대로 둡니다).
보통, 지역 영역은 (텍스트 상의) 현재 함수의 지역 이름을 참조합니다. 함수 바깥의 지역 영역은 전역 공간과 같은 이름공간을 참조합니다: 즉 모듈의 이름공간을 참조합니다. 클래스를 정의하면 지역 영역에 또 하나의 이름공간이 배정됩니다.
영역이 텍스트적으로 결정된다는 사실을 깨닫는 것이 중요합니다: 모듈에 정의된 함수의 전역 영역은 그 모듈의 이름공간입니다. 그 함수가 어디에서 또는 어떤 별칭으로 호출되었는지에 상관이 없습니다. 반면에 이름에 대한 실제 검색은 동적으로 실행시간에 이루어집니다 -- 그렇지만, 언어 정의는 ``컴파일'' 시간에 정적 이름 결정을 향하여 진화하고 있습니다. 그래서 동적 이름 결정에 의지하지 마세요! (사실, 지역 변수는 이미 정적으로 결정됩니다.)
파이썬의 특별한 습관은 할당이 언제나 가장 안쪽 영역으로 들어간다는 것입니다. 할당은 데이터를 복사하지 않습니다 -- 단지 이름을 객체에 묶을 뿐입니다. 이는 삭제에도 똑 같이 적용됩니다: "del x" 서술문은 지역 이름영역이 참조하는 이름공간으로부터 x의 바인딩을 제거합니다. 사실, 새로운 이름을 도입하는 모든 연산은 지역 영역을 사용합니다: 특히, 반입(import) 서술문과 함수 정의는 모듈이름이나 함수 이름을 지역 영역에 묶습니다. (global 서술문을 사용하면 특정 변수가 전역 공간에 살고 있다고 지시할 수 있습니다.)
클래스에 약간 새로운 구문과 세 가지 새로운 유형 그리고 몇가지 새로운 의미구조가 도입되었습니다.
가장 단순한 형태로 클래스를 정의하면 다음과 같습니다:
class ClassName:
<statement-1>
.
.
.
<statement-N>
클래스 정의는 함수 정의(def 서술문)와 마찬가지로 먼저 실행되어야 효과를 발휘합니다 (의도적으로 클래스 정의를 함수 안이나 if 서술문의 한 가지에 둘 수도 있습니다.)
실제로, 클래스 정의 안에서 서술문은 보통 함수 정의이지만, 다른 서술문도 가능하며 유용한 경우가 많습니다 -- 나중에 다시 이 주제로 돌아오겠습니다. 클래스 안에서 함수 정의는 보통 특별한 형태의 인자 리스트가 있습니다. 이는 메쏘드에 대한 호출관행으로 굳어져 있는데 -- 이 역시 나중에 설명합니다.
클래스 정의에 들어가면, 새로운 이름공간이 만들어지고 지역 영역으로 사용됩니다 -- 그리하여, 지역 변수에 대한 할당은 모두 이 새로운 이름공간으로 들어갑니다. 특히, 함수 정의는 그 새로운 함수의 이름을 여기에 묶습니다.
클래스 정의를 정상적으로 지나면 (끝나면), 클래스 객체(class object)가 생성됩니다. 이 클래스 객체는 기본적으로 클래스 정의가 만든 이름공간의 내용을 둘러싼 포장지입니다; 다음 섹션에서 클래스에 관하여 더 자세히 배우겠습니다. 원래의 지역 영역(클래스 정의에 들어가기 바로 전의 영역)은 회복되고, 그 클래스 객체는 여기 클래스 정의 헤더에서 주어진 클래스 이름에 (예제에서는 ClassName에) 묶입니다.
클래스 객체는 두 가지 종류의 연산을 지원합니다: 하나는 속성 참조이고 다른 하나는 실체화입니다.
파이썬에서 속성 참조(Attribute references)는 표준 구문을 사용합니다: obj.name이라는 형태로 말입니다. 클래스 객체가 생성될 때 클래스의 이름공간 안에 있는 이름은 모두 유효한 속성 이름입니다. 그래서, 클래스 정의가 다음과 같다면:
class MyClass:
"간단한 예제 클래스"
i = 12345
def f(self):
return 'hello world'
MyClass.i와 MyClass.f는 유효한 속성 참조로서 각각 정수와 함수 객체를 돌려줍니다. 클래스 속성도 할당될 수 있습니다. 그래서 할당으로 MyClass.i의 값을 바꿀 수 있습니다. __doc__ 역시 유효한 속성으로서 이 클래스에 속한 문서화문자열을 돌려줍니다: "간단한 예제 클래스"를 돌려줍니다.
클래스 실체화(instantiation)는 함수 표기법을 사용합니다. 마치 클래스 객체가 매개변수 없는 함수인 것처럼 보입니다. 이 거짓 함수는 그 클래스의 새로운 실체를 돌려줍니다. (위의 클래스를 가정하고) 예를 들어, 다음은:
x = MyClass()
클래스의 새로운 실체(instance)를 만들어서 이 객체를 지역 변수 x에 할당합니다.
실체화 연산으로 (클래스 객체를 ``호출하면'') 빈 객체가 생성됩니다. 많은 클래스들은 특정한 초기화 상태로 맞춤재단된 실체를 가지고 객체를 만들기 좋아합니다. 그러므로 클래스에는 다음과 같이 __init__()라는 이름의 특별한 메쏘드가 정의될 수 있습니다:
def __init__(self):
self.data = []
클래스에 __init__() 메쏘드가 정의되면, 클래스 실체화는 새로-만들어진 클래스 실체에 대하여 자동으로 __init__()을 요청합니다. 그래서 이 예제에서 새로 초기화된 실체는 다음과 같이 얻을 수 있습니다:
x = MyClass()
물론, __init__() 메쏘드는 더 유연하게도 인자를 많이 가질 수 있습니다. 그 경우, 클래스 실체화 연산자에 주어지는 인자들은 __init__()에 건네집니다. 예를 들어, 다음과 같습니다.
>>> class Complex: ... def __init__(self, realpart, imagpart): ... self.r = realpart ... self.i = imagpart ... >>> x = Complex(3.0, -4.5) >>> x.r, x.i (3.0, -4.5)
이제 실체 객체로 무엇을 할 수 있을까요? 실체 객체가 이해하는 유일한 연산은 속성 참조입니다. 두 가지 종류의 유효한 속성 이름이 있습니다. 데이터 속성과 메쏘드가 그것입니다.
데이터 속성(data attributes)는 스몰토크의 ``실체 변수''에 상응하고, C++의 ``데이터 멤버(data members)''에 상응합니다. 데이터 속성은 선언될 필요가 없습니다; 지역 변수처럼, 처음으로 할당될 때 탄생합니다. 예를 들어, x가 위에서 만든 MyClass의 실체라면, 아무 흔적도 남기지 않고 다음 코드 조각은 값 16을 인쇄합니다:
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print x.counter
del x.counter
다른 종류의 실체 속성 참조는 메쏘드(method)입니다. 메쏘드는 객체에 ``속한'' 함수입니다. (파이썬에서, 메쏘드라는 용어는 클래스 실체에 국한되지는 않습니다: 다른 객체 유형도 역시 메쏘드를 가질 수 있습니다. 예를 들어, 리스트 객체는 이른바 append, insert, remove, sort, 등등의 메쏘드를 가질 수 있습니다. 그렇지만, 다음 연구에서는 메쏘드라는 용어를 단독으로 사용하겠습니다. 명시적으로 언급하지 않는 한, 클래스 실체 객체의 메쏘드를 의미하는 것으로 말입니다.)
실체 객체의 메쏘드 이름이 유효한지는 그의 클래스에 달려 있습니다. 정의상, 함수 객체인 클래스의 모든 속성들은 자신의 실체에 상응하는 메쏘드를 정의합니다. 그래서 우리의 예에서 x.f는 유효한 메쏘드 참조입니다. 왜냐하면 MyClass.f는 함수이기 때문입니다. 그러나 MyClass.i는 함수가 아니기 때문에 x.i는 함수가 아닙니다. 그러나 x.f는 MyClass.f와 같지 않습니다 -- 함수 객체가 아니라, 메쏘드 객체(method object)입니다.
보통 메쏘드는 묶이면 바로 호출됩니다:
x.f()
MyClass 예제에서는 문자열 'hello world'를 돌려줍니다. 그렇지만, 메쏘드를 바로 호출할 필요는 없습니다: x.f는 메쏘드 객체이며, 저장해 둘 수 있고 나중에 호출할 수 있습니다. 예를 들어:
xf = x.f
while True:
print xf()
끝없이 "hello world"를 인쇄합니다.
메쏘드가 호출되면 정확하게 무슨 일이 일어날까요? 위를 잘 보면 x.f()가 인자 없이 호출되었음을 눈치채셨을 것입니다. f 함수 정의에서 인자를 지정했음에도 불구하고 말입니다.
인자에 무슨 일이 일어난 걸까요? 분명히 파이썬은 인자를 요구하는 함수가 인자 없이 호출되면 예외를 일으킵니다. -- 인자가 실제로는 전혀 사용되지 않더라도 말입니다...
실제로, 해답을 짐작하실 수 있을 것입니다: 메쏘드에 대하여 특별한 일은 그 객체가 첫 인자로 함수에 건네진다는 것입니다. 예제에서는 x.f() 호출이 정확하게 MyClass.f(x)와 동등합니다. 일반적으로 n개의 인자 리스트를 가지고 메쏘드를 호출하는 것은 다음과 같이 그에 상응하는 함수를 호출하는 것과 동등합니다. 즉, 메쏘드의 객체를 그 첫 인자 앞에 삽입함으로서 생성된 인자 리스트를 가지고 호출하는 것과 동등합니다.
메쏘드의 작동 방식이 아직도 이해되지 않으면, 그 구현을 한 번 보면 아마도 문제를 이해하실 수 있을 것입니다. 데이터 속성이 아닌 실체 속성이 참조되면, 그의 클래스가 검색됩니다. 그 이름이 함수 객체인 유효한 클래스 속성을 가리킨다면, 추상 객체에서 함께 발견된 실체 객체와 함수 객체 (를 가리키는 포인터)를 꾸려 넣어 메쏘드 객체를 생성합니다: 이것이 메쏘드 객체입니다. 메쏘드 객체가 인자 리스트로 호출되면 다시 풀어서, 실체 객체와 원래 인자로부터 새로운 인자 리스트를 구성합니다. 이 새로운 인자 리스트로 함수 객체를 호출합니다.
데이터 속성은 같은 이름으로 메쏘드 속성을 오버라이드 합니다; 우연하게 이름 충돌이 일어나는 것을 방지하려면 충돌의 기회를 최소화하는 관례를 따르는 것이 현명합니다. 큰 프로그램에서는 찾기가-몹시-어려운 버그를 야기할 수 있습니다. 가능한 관례로는 메쏘드 이름의 첫글자를 대문자로 바꾸고, 데이터 속성 이름 앞에 작은 유일 문자(어쩌면 그냥 밑줄 문자 하나)를 붙이거나 또는 데이터 속성에는 명사를 사용하고 메쏘드에는 동사를 사용하는 것이 있습니다.
데이터 속성은 객체의 일반 사용자 (``"클라이언트'') 뿐만 아니라 메쏘드에서도 참조할 수 있습니다. 다시 말해, 클래스는 순수한 추상 데이터 유형을 구현하는데 사용할 수 없습니다. 실제로, 파이썬에서는 어떤 것도 데이터를 감추지 못합니다 -- 모든 것은 관례에 기반합니다. (반면에 파이썬 구현을 C로 작성하면, 완벽하게 구현 상세를 감출 수 있고 필요하면 객체에 접근하는 것을 통제할 수 있습니다; C로 작성된 파이썬 확장은 이 특징을 이용할 수 있습니다.)
클라이언트는 데이터 속성을 조심스럽게 사용해야 합니다 -- 클라이언트는 메쏘드가 데이터 속성에 도장을 찍어가며 유지관리하는 불변값(invariants)을 망쳐 놓을 수도 있습니다. 클라이언트가 자신만의 데이터 속성을 실체 객체에 추가할지도 모르니 주의하세요. 이름 충돌이 일어나지 않는 한, 메쏘드의 유효성에 영향을 미치지 않고 말입니다 -- 역시, 여기에서 이름짓기 관례가 수 많은 두통거리를 절약해 줍니다.
메쏘드 안으로부터 데이터 속성(또는 다른 메쏘드)을 참조하는 지름길은 없습니다. 이 덕분에 실제로 메쏘드의 가독성이 증가합니다: 메쏘드를 통해서 보면 실체 변수와 지역 변수를 혼동할 틈이 없습니다.
종종, 메쏘드에 건네는 첫 인자는 self라고 부릅니다. 이것은 그저 관례일 뿐 아무것도 아닙니다: self라는 이름은 파이썬에서 특별한 의미가 없습니다. (그렇지만, 관례를 따르지 않으면 다른 프로그래머가 코드를 읽기가 더 어려워질 수 있으니 주의하세요. 또한 클래스 브라우저 프로그램을 그런 관례에 의거하여 작성할 수도 있습니다.)
어떤 함수 객체도 클래스 속성으로서 그 클래스의 실체를 위한 메쏘드로 정의됩니다. 함수 정의는 클래스 정의 안에 텍스트적으로 둘러싸일 필요가 없습니다: 함수 객체를 클래스 안의 지역 변수에 할당해도 좋습니다. 예를 들어:
# 클래스에 바깥에 정의된 함수
def f1(self, x, y):
return min(x, x+y)
class C:
f = f1
def g(self):
return 'hello world'
h = g
이제 f와 g 그리고 h는 모두 함수 객체를 가리키는 C 클래스의 속성입니다. 결론적으로 그들은 모두 C 실체의 메쏘드 입니다 -- h는 정확하게 g와 동등합니다.
주의하세요. 이런 관행은 보통 프로그램을 읽는 사람을 혼란스럽게 만드는데 기여할 뿐입니다.
메쏘드는 self 인자의 메쏘드 속성을 사용하여 다른 메쏘드를 호출할 수 있습니다:
class Bag:
def __init__(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)
메쏘드는 보통의 함수와 똑 같은 방식으로 전역 변수를 참조할 수 있습니다. 메쏘드에 연관된 전역 영역은 그 클래스가 정의된 모듈입니다. (클래스 그 자체는 절대로 전역 영역으로 사용되지 않습니다!) 메쏘드에서 전역 데이터를 사용해야할 좋은 이유를 발견하기는 어렵지만, 전역 영역을 합법적으로 사용하는 방법도 많습니다: 한가지 예를 들면, 전역 영역 안에 반입된 함수와 모듈은 메쏘드가 사용할 수 있으며, 뿐만 아니라 그 안에 정의된 함수와 클래스도 사용할 수 있습니다. 보통, 메쏘드를 담은 클래스는 그 자체로 이 전역 영역에 정의됩니다. 다음 섹션에서는 왜 메쏘드가 자신만의 클래스를 참조하고 싶어 하는지 좋은 이유를 알아 보겠습니다.
물론, 상속을 지원하지 않는다면 언어 특징에 ``클래스''라는 이름을 붙일 가치가 없을 것입니다. 파생된 클래스 정의 구문은 다음과 같이 보입니다:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
BaseClassName 이름은 파생된 클래스 정의가 담긴 영역에서 정의되어야 합니다. 바탕 클래스 이름 대신에, 다른 임의의 표현식도 허용됩니다. 예를 들어, 다음은 바탕 클래스가 또다른 모듈에 정의되어 있을 경우 유용합니다:
class DerivedClassName(modname.BaseClassName):
파생된 클래스 정의를 실행하면 바탕 클래스와 같이 똑 같이 처리됩니다. 클래스 객체가 구성되면, 바탕 클래스가 기억됩니다. 이것은 속성 참조를 결정하는데 사용됩니다: 요청된 속성이 그 클래스에 없으면, 검색은 계속해서 바탕 클래스를 들여다 봅니다. 이 규칙은 바탕 클래스 자체가 다른 어떤 클래스로부터 파생되었다면 재귀적으로 적용됩니다..
파생된 클래스의 실체화에 특별한 것은 없습니다: DerivedClassName()는 클래스의 새로운 실체를 만듭니다. 메쏘드 참조는 다음과 같이 결정됩니다: 상응하는 클래스 속성이 검색되고, 필요하면 바탕 클래스의 사슬을 따라 내려 갑니다. 그리고 메쏘드 참조는 이 검색이 함수 객체를 양보하면 유효합니다.
파생된 클래스는 바탕 클래스의 메쏘드를 오버라이드할 수 있습니다. 메쏘드는 같은 객체의 다른 메쏘드를 호출할 때 특권이 없기 때문에, 바탕 클래스의 메쏘드가 같은 바탕 클래스에 정의된 또다른 메쏘드를 호출하면, 결과적으로 그를 오버라이드한 파생된 클래스의 메쏘드를 호출할 가능성이 있습니다. (C++ 프로그래머를 위하여: 파이썬에서 모든 메쏘드는 그 효과상 가상적(virtual)입니다.)
파생된 클래스에서 오버라이딩 메쏘드는 단순히 같은 이름의 바탕 클래스 메쏘드를 교체하는 대신에 실제로 확장하고 싶을 수 있습니다. 바탕 클래스 메쏘드를 직접 호출하는 간단한 방법이 있습니다: 그냥 "BaseClassName.methodname(self, arguments)"로 호출하면 됩니다. 이는 종종 클라이언트에도 유용합니다. (이것은 바탕 클래스가 전역 공간에서 직접적으로 정의되거나 반입될 경우에만 작동함에 주의하세요.)
파이썬은 제한된 형태로 다중 상속도 지원합니다. 여러 바탕 클래스를 가진 클래스 정의는 다음과 같이 보입니다:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
구형-스타일의 클래스에 대하여 유일한 규칙은 깊이-우선 검색 그리고 왼쪽에서-오른쪽으로 검색됩니다. 그리하여 DerivedClassName에 속성이 발견되지 않으면, Base1에서 검색된 다음, (재귀적으로) Base1의 바탕 클래스에서 검색됩니다. 거기에서 발견되지 않을 경우에만, Base2에서 검색됩니다. 등등.
(어떤 사람들에게는 너비 우선 검색이 -- Base1 바탕 클래스보다 먼저 Base2와 Base3를 검색하는 것이 -- 더 자연스러워 보입니다. 그렇지만, 이렇게 하려면 Base1의 특정 속성이 실제로 Base1에 정의되어 있는지 아니면 그의 바탕 클래스 중의 하나에 정의되어 있는지 알 필요가 있습니다. Base2 속성과 이름 충돌이 있는지 그 결과를 알아 보기 전에 먼저 말입니다. Base1에서 직접 속성과 상속받은 속성 사이에 깊이-우선 규칙은 아무 차이가 없습니다.)
새로운-스타일의 클래스에 대하여, 메쏘드 이름결정 순서는 동적으로 바뀌어서 super()를 협력적으로 호출하도록 지원합니다. 이 방법은 다른 다중-상속 언어에서는 호출-다음-메쏘드 규칙으로 알려져 있으며 단일-상속 언어에서 발견되는 수퍼 호출보다 더 강력합니다.
새로운-스타일의 클래스에서는 동적 순서가 필요합니다. 모든 다중 상속은 하나 이상의 다이아몬드 관계를 보여주기 때문입니다. (여기에서는 최하위 클래스로부터 여러 경로를 통하여 하나 적어도 부모 클래스 중의 하나에 접근할 수 있습니다). 예를 들어, 새로운-스타일의 클래스는 모두 object로부터 상속을 받습니다. 그래서 다중 상속의 경우는 object에 도달하는데 여러 경로를 제공합니다. 바탕 클래스에 한 번 이상 접근하는 것을 막기 위해, 동적 알고리즘은 검색 순서를 선형화하여 각 클래스에 지정된 왼쪽에서-오른쪽 순서를 유지합니다. 그리하여 각 부모는 오직 한 번만 호출됩니다. 즉 모노토닉(monotonic) 합니다. (즉 클래스는 그의 부모의 우선 순위에 영향을 주지 않고 하부클래스화 될 수 있다는 뜻입니다). 결론을 내리자면, 이런 특성들 덕분에 다중 상속을 가진 신뢰성 있고 확장 가능한 클래스를 디자인할 수 있습니다. 더 자세한 정보는 http://www.python.org/download/releases/2.3/mro/를 참조하세요.
클래스의-사적 식별자 지원에는 제한이 있습니다. (적어도 앞에 이끄는 밑줄 문자가 두개 오고 기껏해야 뒤에 밑줄문자 하나가 따르는) __spam 형태의 식별자는 모두 텍스트에서 _classname__spam 형태로 교체됩니다. 여기에서 classname은 이끄는 밑줄문자(들)을 걷어낸 현재 클래스 이름입니다. 이런 이름조작은 식별자의 구문적 위치와 상관없이 이루어집니다. 그래서 클래스의-사적 실체 변수와 클래스 변수, 메쏘드, 전역영역에 저장된 변수, 심지어 실체에 저장된 변수까지도 정의하는데 사용할 수 있습니다. 다른 클래스의 실체가 이 클래스에 접근하지 못하도록 말입니다. 조작된 이름이 255 문자를 넘으면 잘라내기가 일어날 수도 있습니다. 클래스 바깥에서는, 즉 클래스 이름이 밑줄 문자로만 구성되어 있다면, 이름 조작이 일어나지 않습니다.
이름 조작은 의도가 클래스에게 ``사적'' 실체 변수와 메쏘드를 쉽게 정의하는 방법을 부여하는 것입니다. 파생 클래스에 정의된 실체 변수에 관하여 걱정할 필요없이, 또는 클래스 바깥의 코드에 의해서 실체 변수가 망가질까바 걱정할 필요없이 말입니다. 이름조작 규칙은 보통 사고를 방지하기 위해 설계되었음에 주목하세요; 집요한 사람이라면 여전히 사적이라고 간주되는 변수에 접근하거나 수정할 수 있습니다. 이것은 특별한 상황에서 유용할 수 있는데, 예를 들어 디버거 같은 경우가 있습니다. 그것이 바로 이 뒷구멍을 닫아 놓고 있지 않은 한 가지 이유이기도 합니다. (주의: 바탕 클래스와 이름이 같은 클래스를 파생시키면 가능하면 바탕 클래스의 사적 변수를 이용합니다.)
exec나 eval() 또는 execfile()에 건넨 코드는 요청 클래스의 클래스 이름이 현재 클래스라고 간주하지 않습니다; 이것은 global 서술문의 효과와 비슷합니다. 마찬가지로 그 효과는 함께 바이트-컴파일된 코드도 제한합니다. 똑 같은 제한이 getattr()과 setattr() 그리고 delattr()에 적용되며, 뿐만 아니라 __dict__를 직접 참조할 경우에도 적용됩니다.
종종 데이터 유형이 파스칼의 ``record''나 C의 ``struct''와 비슷하게, 이름 붙은 데이터 항목들을 함께 묶으면 좋은 경우가 있습니다. 빈 클래스를 정의하면 깔끔하게 이렇게 할 수 있습니다:
class Employee:
pass
john = Employee() # 빈 피고용인 레코드 생성
# 레코드의 필드 채우기
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
파이썬 코드가 특별한 추상 데이터 유형을 예상한다면 대신에 그 데이터 유형의 메쏘드를 흉내내는 클래스를 건넬 수 있습니다. 예를 들면, 파일 객체로부터 데이터를 구성하는 함수가 있다면, 다음과 같이 클래스를 정의하면 됩니다. 데이터를 파일 대신에 문자열 버퍼로부터 얻는 read() 메쏘드와 readline() 메쏘드로 클래스를 정의하여, 그것을 인자로 건네면 됩니다.
실체 메쏘드 객체에도 속성이 있습니다: m.im_self는 메쏘드 m을 가진 실체 객체입니다. 그리고m.im_func는 그 메쏘드에 상응하는 함수 객체입니다.
사용자-정의 예외도 클래스로 식별됩니다. 이 메커니즘을 사용하면 예외 계통도를 확장할 수 있습니다.
raise 서술문에 대하여 두 가지 새로운 (의미구조적으로) 유효한 형태가 있습니다:
raise Class, instance raise instance
첫 형태에서, 실체(instance)는 반드시 Class의 실체이거나 그로부터 파생된 클래스의 실체여야 합니다. 두번째 형태는 다음을 축약한 것입니다:
raise instance.__class__, instance
except 절에 있는 클래스는 같은 클래스이거나 자신이 바탕 클래스라면 예외가 호환됩니다. (그러나 다른 방식으로는 안됩니다 -- 파생 클래스를 나열한 except 절은 바탕 클래스와 호환되지 않습니다). 예를 들어, 다음 코드는 순서대로 B, C, D를 인쇄합니다:
class B:
pass
class C(B):
pass
class D(C):
pass
for c in [B, C, D]:
try:
raise c()
except D:
print "D"
except C:
print "C"
except B:
print "B"
만약 except 절이 반대로 되었다면 ("except B"가 먼저라면), B, B, B로 인쇄되었을 것입니다 -- 제일 처음 일치한 except 절이 촉발됩니다.
처리되지 못한 예외에 대하여 에러 메시지가 인쇄될 때, 그 예외의 이름이 인쇄된 다음, 쌍점과 공간 하나, 그리고 마지막으로 내장 함수 str()를 사용하여 문자열로 변환된 실체가 인쇄됩니다.
지금까지 대부분의 컨테이너 객체는 for 서술문을 사용하면 회돌이 할 수 있다는 것을 눈치 채셨을 것입니다:
for element in [1, 2, 3]:
print element
for element in (1, 2, 3):
print element
for key in {'one':1, 'two':2}:
print key
for char in "123":
print char
for line in open("myfile.txt"):
print line
이런 스타일로 접근하는 것은 명료하고 간결하며 편리합니다. 반복자의 사용 덕분에 파이썬이 널리 보급되고 통일되었습니다. 배경 뒤에서, for 서술문은 컨테이너 객체에 iter()를 호출합니다. 함수는 한 번에 하나씩 컨테이너의 요소에 접근하는 next() 메쏘드가 정의된 반복자 객체를 돌려줍니다. 원소가 더 이상 없으면, next()는 StopIteration 예외를 일으켜서 for 회돌이가 끝났음을 알립니다. 다음 에제는 그 작동 방식을 보여줍니다:
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> it.next()
'a'
>>> it.next()
'b'
>>> it.next()
'c'
>>> it.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
it.next()
StopIteration
반복자 프로토콜 뒤의 메커니즘을 보셨으므로, 클래스에 반복자 행위를 쉽게 덧붙일 수 있읍니다. __iter__() 메쏘드를 정의하여 next() 메쏘드를 가진 객체를 돌려주면 됩니다. 클래스에 next()가 정의되어 있으면, __iter__()는 self를 돌려줄 수 있습니다:
class Reverse:
"연속열을 반대로 순회하는 반복자"
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def next(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
>>> for char in Reverse('spam'):
... print char
...
m
a
p
s
발생자는 간단하고 강력한 도구입니다. 평범한 함수처럼 작성되지만 데이터를 돌려줄 때 yield 서술문을 사용합니다. next()가 호출될 때마다, 발생자는 자신이 떠났던 곳에서부터 다시 시작합니다 (모든 데이터 값을 기억하며 어느 서술문이 마지막으로 실행되었는지 모두 기억합니다). 예제를 보면 발생자는 너무나 쉽게 만들 수 있습니다:
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
>>> for char in reverse('golf'):
... print char
...
f
l
o
g
발생자로 할 수 있는 일은 무엇이든 앞 섹션에서 기술된대로 반복자에 근거하여 완수할 수도 있습니다. 발생자를 간결하게 만드는 요인은 __iter__() 메쏘드와 next() 메쏘드가 자동으로 생성되기 때문입니다.
또다른 주요 특징은 지역 변수와 실행 상태가 자동으로 호출 사이에 저장된다는 것입니다. 이 덕분에 함수를 더 쉽게 작성할 수 있고 훨씬 더 명료해 집니다. self.index와 self.data 같이 실체 변수를 사용하는 접근법보다 말입니다.
자동 메쏘드 생성 그리고 자동 프로그램 상태 저장과 더불어, 발생자가 끝나면, 자동으로 StopIteration 예외를 일으킵니다. 이런 특징들을 조합하면 보통의 함수를 작성하는 것보다 훨씬 노력을 덜 들이고도 반복자를 쉽게 만들 수 있습니다.
간단한 발생자는 표현식처럼 간결하게 코딩할 수 있습니다. 리스트 통합과 비슷한 구문을 사용하지만 각괄호가 아니라 반괄호를 사용합니다. 이런 표현식들은 둘러싼 함수가 발생자를 바로 사용되는 상황을 위해 디자인되었습니다. 발생자 표현식이 완벽한 발생자 정의보다 더 간결하지만 능력은 떨어집니다. 그리고 리스트 통합에 비해 메모리에 더 친화적인 경향이 있습니다.
예제:
>>> sum(i*i for i in range(10)) # 면적 총계 285 >>> xvec = [10, 20, 30] >>> yvec = [7, 5, 3] >>> sum(x*y for x,y in zip(xvec, yvec)) # 점 적 260 >>> from math import pi, sin >>> sine_table = dict((x, sin(x*pi/180)) for x in range(0, 91)) >>> unique_words = set(word for line in page for word in line.split()) >>> valedictorian = max((student.gpa, student.name) for student in graduates) >>> data = 'golf' >>> list(data[i] for i in range(len(data)-1,-1,-1)) ['f', 'l', 'o', 'g']