본 글은 제가 Python 을 공부하며 알게 된 내용을 정리하기 위해 작성하였습니다. 생략된 부분이 많고, Python 의 특징을 다른 언어들과 비교하는 방식으로 기술하기도 하므로, 처음 프로그래밍을 접하는 분에게는 적합한 글이 아닐 수 있습니다.


체계적으로 Python 을 학습하고자 하는 분은 점프 투 파이썬 | 박응용 을 참고하기 바랍니다. 프로그래밍을 처음 접하는 사람들을 위해 쓴 책으로, 링크된 사이트에서 책의 내용 전부를 볼 수 있습니다. 현재 e-book 으로도 판매중입니다. 초보 학습자를 위한 강추 Python(Python) 링크 | 잉고래 의 링크도 도움이 될 것입니다.


다른 언어를 학습한 경험이 있는 분에게는 빠르게 활용하는 파이썬 3.2 프로그래밍 | 신호철, 우상정, 최동진을 권합니다. 풍부한 예제와 함께 중요 내용을 간결하게 담아낸 좋은 책입니다.


아래 내용의 출처는 위에서 소개한 두 권의 도서와, 파이썬 공식 홈페이지의 문서들 입니다.

클래스 선언

클래스 선언은 class 키워드를 이용 합니다.


생성자는 클래스 이름이 아니라 __init__ 입니다. 언더바 두개입니다. Python 에서 앞뒤로 __ 가 붙어 있는 변수나 함수는 특정 용도로 미리 정의해 둔 것입니다.


소멸자는 __del__ 입니다. 객체의 참조 카운터가 0 이 되면 자동 호출 됩니다.


클래스 내부에 선언되는 함수는 'self' 를 첫 번째 인자로 가져야 합니다. 'self' 는 해당 함수가 bind 된 인스턴스 객체를 뜻합니다. Java, C# 등에서는 함수가 속해 있는 인스턴스를 this 키워드로 접근할 수 있지만, 이름 함수 선언시에 매개변수로 명시하지는 않습니다. 그러나 python 에서는 현재 instance 를 'self' 로 명시하도록 했습니다. 함수의 매개변수에 self 를 명시하지 않으면 TypeError 가 발생 합니다. 그러나 함수를 호출할 때는 self 키워드를 매개변수에 입력하지 않습니다.


class Student:
     old=-1 # 2행
     def __init__(self, name, old=17):
          self.name = name # 4행
          self.old = old # 5행
     def print_name(self):
          print(self.name) # 7행
     def print_old(self):
          print(self.old) # 9행

s = Student("철수", 16)

Student.print_name(s)   # 13행 unbound method call
s.print_name()          # 14행 bound method call

print("철수의 나이 : " , s.old)     # 17 출력
del(s.old) # 17행
print("철수의 나이 : " , s.old)     # -1 출력

print("철수의 이름 : " , s.name)     # "철수" 출력
del(s.name) # 21행
print("철수의 이름 : " , s.name)     # 에러 발생!


2행 : 변수 old 를 클래스 변수로 선언한다.


4행 : 함수 내부에서 함수보다 범위가 큰 인스턴스 변수를 선언 합니다.


JavaScript 와 비교

JavaScript 에서는 변수를 var 을 통해 별도로 선언하지 않은 채 사용한 경우, global 변수로 선언된 것으로 간주하는 것과 같게 취급합니다. (JavaScript 의 global space 에 대한 링크) python 은 self 키워드를 통해 변수의 scope 를 명시해 주었다는 차이가 있지만, 함수 내부에서 함수의 범위보다 큰 scope 를 가진 변수를 선언할 수 있다는 점에서는 공통점이 있습니다. 이러한 점은 Java 에서 함수 내부에서 함수를 벗어나는 범위의 변수를 선언하는 것을 허용하지 않는 것과 대조적입니다.


참고로 JavaScript 에서는 var 로 선언된 변수와 그렇지 않은 변수를 nondeletable 과 deletable 로 구분 합니다. var 로 선언되지 않은 변수는 나중에 delete 로 지울 수 있지만, var 로 선언된 변수는 delete 로 지울 수 없는 것입니다. python 은 어떨까요? 아래에서 17 행과 21 행을 비교 해 두었습니다.


5, 7, 9행 : 인스턴스 객체의 변수를 가리킬 때에는 self.변수명 으로 접근 해야 합니다. self 를 명시하지 않으면 instance name space 또는 class name space 를 탐색하지 않고, local name space 를 탐색한 뒤 바로 global name space 를 탐색 합니다. (여기서 name space 란 package 의 개념이 아니라, variable pool 의 개념입니다.)


13행 : 클래스를 통해 함수를 호출하며, 인스턴스 객체를 parameter 로 전달 합니다. 이를 unbound method call 이라 합니다.


14행 : 인스턴스 객체에 bind 된 함수를 호출 합니다. 이를 bound method call 이라 합니다.


17, 21행 : 17, 21행에서 각각 old, name 변수를 del() 한 뒤, 이를 출력해 보았습니다. 16, 20행은 확인을 위해 삽입한 행입니다.

17 행의 경우, 인스턴스 객체에 더이상 old 변수가 존재하지 않으므로 클래스 변수를 출력하는 것을 볼 수 있습니다.

그러나 21 행의 경우는 name 변수를 클래스 변수로 선언한 적이 없으므로, 인스턴스 변수를 삭제하면 변수 name 이 가리킬 대상이 없어져 print() 함수 호출시 에러가 발생 합니다.


Java 와 비교

Java 에서는 인스턴스 변수를 삭제할 수도 없고, 인스턴스 변수가 없다고 해서 클래스 변수를 가리키는 경우도 없습니다. 클래스 변수는 static 키워드를 통해 따로 선언되기 때문입니다.


그러나 Python 에서는 인스턴스 변수가 없으면 동일한 이름의 클래스 변수를 탐색 합니다. 자세한 설명은 빠르게 활용하는 파이썬 3.2 프로그래밍 | 신호철, 우상정, 최동진 도서의 "5.3 클래스 객체와 인스턴스 객체의 이름공간" 을 참고 합니다.


Python 의 설계 철학은 개발자에게 많은 제약을 가하지 않는 것이라고 합니다. 그래서인지 논리적 구조에 프로그래밍 문법을 맞춰낸 Java 와는 여러 면에서 차이를 보이는 것 같습니다. 하지만 한편으로는, Java 가 구조적으로 잘 짜인 언어라는 생각도 듭니다.



__class__ 는 인스턴스가 속하는 클래스를 나타냅니다. isinstance(i,c) 는 Java 의 i instanceof c 와 같습니다.


class Test:
     i = 3

t = Test()
print(Test)     # Test 클래스
print(t.__class__)     # Test 클래스

t.__class__.i=4     # 클래스 변수를 수정
print(Test.i)     # Test 클래스의 클래스 변수 i

print(isinstance(t, Test))     # t 가 Test 클래스의 인스턴스인지 여부 - True


Python 에서는 접근 한정자가 없이 모두 public 입니다. 개발자에게 되도록 제약을 가하지 않는 것이 Python 의 철학이기 때문입니다. 함수 또는 변수가 private member 임을 표시하고 싶다면 언더바(_) 를 붙이면 됩니다. 언더바가 2개 있으면 _클래스이름__변수명[함수명] 으로 변수[함수] 명이 변경되어, 외부에서 접근하기가 불편하게 됩니다. 언더바가 1개 있으면 private member 임을 표시하는 역할을 하며, "from M import *" 키워드 사용 시 import 를 방지 합니다.


staticmethod VS classmethod

class method : 암묵적으로 클래스 객체를 인자로 전달받으므로, 클래스의 변수[함수]를 사용할 수 있습니다.

static method : 클래스에 대한 정보를 갖지 않습니다. 단순히 모듈의 함수를 호출하는 것과 같습니다.


class Test:
     i=3
     
     def classFoo(cls):   # 클래스 객체를 암묵적으로 인자로 전달받음.
          print("class method")
          print(cls.i)     # 클래스 변수 i 출력
          print()
     CFoo = classmethod(classFoo)

     def staticFoo():   # 클래스 객체를 인자로 전달받지 않음.
          print("static method")
          print()     # 클래스 변수 출력 못함
     SFoo = staticmethod(staticFoo)

Test.CFoo()
Test.SFoo()

Operator Overloading

Python 에서는 +, -, << 같은 연산자도 overloading 이 됩니. overriding 의 오타가 아닙니다. overloading 이라고 합니다.


class Integer:     # 이름과 값을 가지는 정수 클래스
          name = ""
          value = 0
          
          def __init__(self, name, value):
               self.name = name
               self.value = value
               
          def printVal(self):
               if(self.name) : print("이름 : " + self.name)
               else : print("이름 없음")
               print("값 : " + str(self.value))
               
          def __add__(self, other): # + 연산자 오버로딩
               return Integer("", self.value + other.value)
               
a = Integer("a", 2)
b = Integer("b", 5)

a.printVal()     # 이름=a, 값=2
b.printVal()     # 이름=b, 값=5
print()

a += b     # a 와 b 의 값을 더한 새로운 Integer 객체가 a 에 할당된다

a.printVal()     # 이름 없음, 값=7
b.printVal()     # 이름=b, 값=5

상속

클래스 상속은 () 를 이용 합니다.



다중 상속은 () 안에 상속할 클래스들을 쉼표로 구분하여 나열하면 됩니다.

ex) class Subclass(Superclass1, Superclass2): ...


클래스이름.__dict__ 를 해 보면, 클래스의 name space 정보가 dictionary 구조로 이루어진 것을 볼 수 있습니다. 따라서 키 값인 함수의 이름만 같게 하면 함수가 overriding 됩니다. 매개변수나 리턴값을 일치시킬 필요가 없습니다.


아래는 상속, overriding 예시코드입니다.


class HyperClass:
     pass

class SuperClass(HyperClass):   # 4행 HyperClass 를 상속
     def __init__(self, x):
          self.a = x

     def print_all(self):
          print(self.a)

class SubClass(SuperClass):   # 11행 SuperClass 를 상속
     def __init__(self, x, y):
          SuperClass.__init__(self, x)    # 13행 unbound method call
          self.b = y

     def print_all(self):     # method override
          SuperClass.print_all(self)    # 17행 unbound method call
          print(self.b)


s = SubClass(3, 7)
s.print_all()

print(issubclass(SubClass, SuperClass))   # 상속관계 반환 - True
print(issubclass(SubClass, HyperClass))   # 상속관계 반환 - True

print(SubClass.__bases__)     # 27행 super class 를 출력
print(s.__class__.__bases__)  # 28행 인스턴스 객체가 아닌, 클래스로 접근

4, 11행 : 상속 관계는 HyperClass - SuperClass - SubClass 입니다.


13행 : 상위 클래스의 생성자를 호출하고 있습니다. unbound method call 으로써, 상위 클래스 생성자의 매개변수로는 하위 클래스인 자신(self)을 전달하고 있습니다. 상위 클래스의 멤버 변수(field)를 사용하기 위해서는 이처럼 상위 클래스의 생성자를 호출해 줘야 합니다.


17행 : 상위 클래스의 print_all 함수를 overriding 할 때, overrided method 를 호출하고 있습니다. 역시  unbound method call 입니다.


27, 28행 : super class 를 알고 싶다면 "클래스명.__bases__" 를 이용 합니다. 인스턴스 객체가 아닌 클래스 객체를 통해 __bases__ 를 호출해야 합니다.



이상으로 파이썬 클래스에 대한 기본 내용을 정리 해 보았습니다.

도움이 되었기를 바랍니다.