개발자/파이썬 Python

파이선 클래스 기초 1. 리얼파이선 18

지구빵집 2022. 3. 10. 09:27
반응형

 

 

 

9. 클래스

 

클래스는 데이터와 기능을 함께 묶는 방법을 제공합니다. 새 클래스를 만드는 것은 객체의 새 데이터 형을 만들어서, 그 형의 새 인스턴스를 만들 수 있도록 합니다. 각 클래스 인스턴스는 상태를 유지하기 위해 그 자신에게 첨부된 어트리뷰트를 가질 수 있습니다. 클래스 인스턴스는 상태를 바꾸기 위한 (클래스에 의해 정의된) 메서드도 가질 수 있습니다.

 

다른 프로그래밍 언어들과 비교할 때, 파이썬의 클래스 메커니즘은 최소한의 새로운 문법과 개념을 써서 클래스를 추가합니다. C++과 모듈라-3에서 발견되는 클래스 메커니즘을 혼합합니다. 파이썬 클래스는 객체 지향형 프로그래밍의 모든 표준 기능들을 제공합니다: 클래스 상속 메커니즘은 다중 베이스 클래스를 허락하고, 자식 클래스는 베이스 클래스나 클래스들의 어떤 메서드도 재정의할 수 있으며, 메서드는 같은 이름의 베이스 클래스의 메서드를 호출할 수 있습니다. 객체들은 임의의 종류의 데이터를 양적 제한 없이 가질 수 있습니다. 모듈과 마찬가지로, 클래스는 파이썬의 동적인 본성을 함께 나눕니다: 실행 시간에 만들어지고, 만들어진 후에도 더 수정될 수 있습니다.

 

C++ 용어로, 보통 클래스 멤버들은 (데이터 멤버를 포함해서) public (예외는 아래 비공개 변수를 보세요) 하고, 모든 멤버 함수들은 virtual 입니다. 모듈라-3처럼, 객체의 매소드에서 그 객체의 멤버를 참조하는 줄임 표현은 없습니다: 메서드 함수는 그 객체를 표현하는 명시적인 첫 번째 인자를 선언하는데, 함수 호출 때 묵시적으로 제공됩니다. 스몰토크처럼, 클래스 자신도 객체입니다. 이것이 임포팅과 이름 변경을 위한 개념을 제공합니다. C++ 나 모듈라-3 와는 달리, 내장형도 사용자가 확장하기 위해 베이스 클래스로 사용할 수 있습니다. 또한, C++처럼, 특별한 문법을 갖는 대부분의 내장 연산자들은 (산술 연산자, 서브 스크립팅, 등등) 클래스 인스턴스에 대해 새로 정의될 수 있습니다.

 

(클래스에 대해 보편적으로 받아들여지는 용어들이 없는 상태에서, 이따금 스몰토크나 C++ 용어들을 사용할 것입니다. C++ 보다 객체 지향적 개념들이 파이썬의 것과 더 가까우므로 모듈라-3 용어를 사용할 수도 있지만, 들어본 독자들이 별로 없을 것으로 예상합니다.)

 

9.1. 이름과 객체에 관한 한마디

 

객체는 개체성(individuality)을 갖고, 여러 개의 이름이 (여러 개의 스코프에서) 같은 객체에 연결될 수 있습니다. 이것은 다른 언어들에서는 에일리어싱(aliasing) 이라고 알려져 있습니다. 보통 파이썬을 처음 볼 때 이 점을 높이 평가하지는 않고, 불변 기본형들 (숫자, 문자열, 튜플)을 다루는 동안은 안전하게 무시할 수 있습니다. 하지만, 에일리어싱은 리스트, 딕셔너리나 그 밖의 다른 가변 객체들을 수반하는 파이썬 코드의 의미에 극적인 효과를 줄 수 있습니다. 이것은 보통 프로그램에 혜택이 되는데, 에일리어스는 어떤 면에서 포인터처럼 동작하기 때문입니다. 예를 들어, 구현이 포인터만 전달하기 때문에, 객체를 전달하는 비용이 적게 듭니다; 그리고 함수가 인자로 전달된 객체를 수정하면, 호출자는 그 변경을 보게 됩니다 — 이것은 파스칼에서 사용되는 두 가지 서로 다른 인자 전달 메커니즘의 필요를 제거합니다.

 

9.2. 파이썬 스코프와 이름 공간

 

클래스를 소개하기 전에, 파이썬의 스코프 규칙에 대해 몇 가지 말할 것이 있습니다. 클래스 정의는 이름 공간으로 깔끔한 요령을 부리고, 여러분은 무엇이 일어나는지 완전히 이해하기 위해 스코프와 이름 공간이 어떻게 동작하는지 알 필요가 있습니다. 덧붙여 말하자면, 이 주제에 대한 지식은 모든 고급 파이썬 프로그래머에게 쓸모가 있습니다.

 

몇 가지 정의로 시작합시다.

 

이름 공간은 이름에서 객체로 가는 매핑입니다. 대부분의 이름 공간은 현재 파이썬 딕셔너리로 구현되어 있지만, 보통 다른 식으로는 알아차릴 수 없고 (성능은 예외입니다), 앞으로는 바뀔 수 있습니다. 이름 공간의 예는: 내장 이름들의 집합 (abs() 와 같은 함수들과 내장 예외 이름들을 포함합니다); 모듈의 전역 이름들; 함수 호출에서의 지역 이름들. 어떤 의미에서 객체의 어트리뷰트 집합도 이름 공간을 형성합니다. 이름 공간에 대해 알아야 할 중요한 것은 서로 다른 이름 공간들의 이름 간에는 아무런 관계가 없다는 것입니다; 예를 들어, 두 개의 서로 다른 모듈들은 모두 혼동 없이 함수 maximize를 정의할 수 있습니다 — 모듈의 사용자들은 모듈 이름을 앞에 붙여야 합니다.

 

그런데, 저는 어트리뷰트 라는 단어를 점 뒤에 오는 모든 이름에 사용합니다 — 예를 들어, 표현식 z.real 에서, real 는 객체 z 의 어트리뷰트입니다. 엄밀하게 말해서, 모듈에 있는 이름들에 대한 참조는 어트리뷰트 참조입니다: 표현식 modname.funcname 에서, modname 은 모듈 객체고 funcname 는 그것의 어트리뷰트입니다. 이 경우에는 우연히도 모듈의 어트리뷰트와 모듈에서 정의된 전역 이름 간에 직접적인 매핑이 생깁니다: 같은 이름 공간을 공유합니다!

 

어트리뷰트는 읽기 전용 이거나 쓰기 가능할 수 있습니다. 후자의 경우, 어트리뷰트에 대한 대입이 가능합니다. 모듈 어트리뷰트는 쓰기 가능합니다: modname.the_answer = 42 라고 쓸 수 있습니다. 쓰기 가능한 어트리뷰트는 del 문으로 삭제할 수도 있습니다. 예를 들어, del modname.the_answer 는 modname 라는 이름의 객체에서 어트리뷰트 the_answer 를 제거합니다.

 

이름 공간들은 서로 다른 순간에 만들어지고 서로 다른 수명을 갖습니다. 내장 이름들을 담는 이름 공간은 파이썬 인터프리터가 시작할 때 만들어지고 영원히 지워지지 않습니다. 모듈의 전역 이름 공간은 모듈 정의를 읽는 동안 만들어집니다; 보통, 모듈 이름 공간은 인터프리터가 끝날 때까지 남습니다. 인터프리터의 최상위 호출 때문에 실행되는, 스크립트 파일이나 대화형으로 읽히는, 문장들은 __main__ 이라고 불리는 모듈 일부로 여겨져서 그 들 자신의 이름 공간을 갖습니다. (내장 이름들 또한 모듈에 속하는데; 이것을 builtins 라 부릅니다.)

 

함수의 지역 이름 공간은 함수가 호출될 때 만들어지고, 함수가 복귀하거나 함수 내에서 처리되지 않는 예외를 일으킬 때 삭제됩니다. (사실, 잊어버린다는 것이 실제로 일어나는 일에 대한 더 좋은 설명입니다.) 물론, 재귀적 호출은 각각 자기 자신만의 지역 이름 공간을 갖습니다.

 

스코프는 이름 공간을 직접 액세스 할 수 있는 파이썬 프로그램의 텍스트 적인 영역입니다. 여기에서 《직접 액세스 가능한》 이란 이름에 대한 정규화되지 않은 참조가 그 이름 공간에서 이름을 찾으려고 시도한다는 의미입니다.

 

스코프가 정적으로 결정됨에도 불구하고, 동적으로 사용됩니다. 실행 중 어느 시점에서건, 이름 공간을 직접 액세스 가능한, 세 개나 네 개의 중첩된 스코프가 있습니다:

 

  • 가장 먼저 검색되는, 가장 내부의 스코프는 지역 이름들을 포함합니다.
  • 둘러싸고 있는 함수들의 스코프는, 가장 가까이서 둘러싸는 스코프로부터 검색이 시작됩니다. 비 지역(non-local)이지만 비전역(non-global) 이름들을 포함합니다.
  • 마지막 직전의 스코프는 현재 모듈의 전역 이름들을 포함합니다.
  • (가장 나중에 검색되는) 가장 외부의 스코프는 내장 이름들을 포함하고 있는 이름 공간입니다.

 

이름을 global로 선언하면, 모든 참조와 대입은 모듈의 전역 이름들을 포함하는 중간 스코프로 바로 갑니다. 가장 내부의 스코프 바깥에서 발견되는 변수들을 재연결하려면, nonlocal 키워드를 사용할 수 있습니다; nonlocal 로 선언되지 않으면, 그 변수들은 읽기 전용입니다 (그런 변수에 쓰려고 하면 단순히 가장 내부의 스코프에 새 지역 변수를 만들게 되어, 같은 이름의 바깥 변수를 바꾸지 않고 남겨둡니다).

 

보통, 지역 스코프는 현재 함수의 지역 이름들을 (텍스트 적으로) 참조합니다. 함수 바깥에서, 지역 스코프는 전역 스코프와 같은 이름 공간을 참조합니다: 모듈의 이름 공간. 클래스 정의들은 지역 스코프에 또 하나의 이름 공간을 배치합니다.

 

스코프가 텍스트 적으로 결정된다는 것을 깨닫는 것은 중요합니다: 모듈에서 정의된 함수의 전역 스코프는, 어디에서 어떤 에일리어스를 통해 그 함수가 호출되는지에 관계없이, 그 모듈의 이름 공간입니다. 반면에, 이름을 실제로 검색하는 것은 실행시간에 동적으로 수행됩니다 — 하지만, 언어 정의는 컴파일 시점의 정적인 이름 결정을 향해 진화하고 있어서, 동적인 이름 결정에 의존하지 말아야 합니다! (사실, 지역 변수들은 이미 정적으로 결정됩니다.)

 

파이썬의 특별한 특징은 – global이나 nonlocal 문이 없을 때 – 이름에 대입하면 항상 가장 내부의 스코프로 간다는 것입니다. 대입은 데이터를 복사하지 않습니다 – 이름을 단지 객체에 연결할 뿐입니다. 삭제도 마찬가지입니다: 문장 del x 는 지역 스코프가 참조하는 이름 공간에서 x 의 연결을 제거합니다. 사실, 새 이름을 소개하는 모든 연산은 지역 스코프를 사용합니다: 특히, import 문과 함수 정의는 모듈이나 함수 이름을 지역 스코프에 연결합니다.

 

global 문은 특정 변수가 전역 스코프에 있으며 그곳에 재연결되어야 함을 가리킬 때 사용될 수 있습니다; nonlocal 문은 특정 변수가 둘러싸는 스코프에 있으며 그곳에 재연결되어야 함을 가리킵니다.

 

9.2.1. 스코프와 이름 공간 예

 

이것은 어떻게 서로 다른 스코프와 이름 공간을 참조하고, global 과 nonlocal 이 변수 연결에 어떤 영향을 주는지를 보여주는 예입니다:

 

def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

 

예제 코드의 출력은 이렇게 됩니다:

 

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

 

어떻게 지역 대입이 (이것이 기본입니다) scope_test 의 spam 연결을 바꾸지 않는지에 유의하세요. nonlocal 대입은 scope_test 의 spam 연결을 바꾸고 global 대입은 모듈 수준의 연결을 바꿉니다.

 

global 대입 전에는 spam 의 연결이 없다는 것도 볼 수 있습니다. 

 

 

파이선 클래스 정복

 

 

 

반응형