클래스의 메서드를 호출하는 방식은 2가지가 있습니다.

1) 클래스 name space로 접근하는 방법

2) 객체 name space로 접근하는 방법

class MyClass:
def foo(self, x):
print(x)

m = MyClass()
MyClass.foo(m, 10) # 클래스 name sapce


m.foo(10) # 객체 name space


다음 글에서 살펴보겠지만, 모든 객체는 name space가 존재합니다.

그리고 모든 객체가 공통으로 사용할 수 있는 class name space가 존재합니다.


MyClass.foo() 처럼 호출하는 방식은 class name space에 접근해서 foo라는 메서드를 호출한 것이고,

m.foo() 처럼 호출하는 방식은 Instance name space에 접근해서 foo 메서드를 호출한 것입니다.


두 방식에는 차이점이 있으며, 각 방식을 unbound와 bound 방식으로 호출했다고 합니다.




1. bound와 unbound 방식

메서드 호출에는 bound와 unbound 방식이 있습니다.

두 방식은 객체를 참조한다는 점에서 같지만, 메서드를 정의할 때 작성한 self가 객체와 자동으로 binding 되어 있느냐의 차이입니다.

따라서 메서드를 호출할 때 인자로 객체를 넣어줘야 하면 unbound 방식이고, 그렇지 않으면 bound 방식입니다.


여기서 self는 객체 자기 자신을 의미하는 키워드이며, 메서드를 정의할 때 첫 번째 파라미터에 위치합니다.

관례적으로 sefl라는 이름을 사용하며, 다른 이름을 작성하는 것도 가능하지만 관례를 따르는 것이 좋습니다.


x, y 좌표를 갖는 Point 클래스를 생성하여 bound와 unbound 방식을 확인해보겠습니다.

class Point:
# Point 클래스의 setter , getter
def set_x(self, x):
self.x = x # 인스턴스 멤버

def get_x(self):
return self.x

def set_y(self, y):
self.y = y # 인스턴스 멤버

def get_y(self):
return self.y


# class를 통해 메서드 호출 ( unbound 방식 )
def unbound_class_call():
p = Point()
Point.set_x(p, 10)
Point.set_y(p, 20)
print(Point.get_x(p), Point.get_y(p))

def bound_instance_call():
p = Point()
p.set_x(10)
p.set_y(20)
print(p.get_x(), p.get_y())


unbound_class_call()
bound_instance_call()

unbound 방식에서는 첫 번째 인자로 Point 객체를 전달했지만, bound 방식에서는 그러지 않은 것을 확인할 수 있습니다.





2. 클래스 멤버와 인스턴스 멤버

멤버에는 클래스 멤버와 인스턴스 멤버 두 가지가 있습니다.

클래스 멤버는 Class name space에 생성되지만, 인스턴스 멤버는 Insatnce name space에 생성됩니다.

객체를 생성하면 객체 마다 name space가 존재한다고 했죠?

클래스 멤버는 모든 객체에서 공유되며, 인스턴스 멤버는 각각의 객체에서만 참조할 수 있습니다.

class foo:
class_name_space_var = 20 # 클래스 멤버

def set_instance_name_space_var(self, x):
self.instance_name_space_var = x # 인스턴스 멤버
def get_nstance_name_space_var(self):
return self.instance_name_space_var

def test_member():
p = foo()
p.set_instance_name_space_var(10)

# Instance name space class_name_space_var가 없으므로
# class name space에서 class_name_space_var 변수를 찾는다.
# ( 자바스크립트의 prototype chaining과 비슷 )
print('{0}, {1}'.format(p.instance_name_space_var, p.class_name_space_var))

test_member()

Instance name space에 변수가 존재하지 않으면 class name space로 올라가서 변수를 확인합니다.





3. 인스턴스 메서드, 클래스 메서드, 정적 메서드

클래스 메서드는 클래스를 인스턴스화한 객체들이 공통적으로 사용할 수 있는 메서드이고,

인스턴스 메서드는 인스턴스가 사용할 수 있는 메서드입니다.

따라서 같은 클래스를 인스턴스화 해도 사용할 수 있는 메서드는 다를 수 있습니다.


Python에서 인스턴스 메서드와 클래스 메서드의 차이는 인스턴스 멤버에 접근할 수 있는지 없는지에 대한 차이이기 때문에,

따라서 메서드를 정의할 때 첫 번째 파라미터에 self를 작성하면 인스턴스 메서드, 그렇지 않으면 클래스 메서드입니다.


그렇다면 정적 메서드는 무엇일까요?

정적 메서드도 클래스 메서드와 같이 인스턴스 멤버에 접근할 수 없는 메서드입니다.

더불어서 클래스 메서드가 접근할 수 있는 클래스 멤버에도 접근 할 수 없는 메서드가 정적 메서드입니다.


정적 메서드와 클래스 메서드는 장식자( 자바의 어노테이션 같은 것 )를 통해 구분하며,

클래스 메서드를 정의할 때 첫 번째 매개변수에로 클래스 멤버에 접근할 수 있는 cls를 작성합니다.

( self와 마찬가지로 관례상 cls 라는 이름을 사용합니다. )

class foo:
a = 0 # class member

def instance_method(self):
self.b = 10 # instance member
print("instance method called")

@classmethod
def class_method(cls):
print("class method called")
return cls.a # class member에 접근 가능

@staticmethod
def static_method():
print("static method called")

f = foo()
f.instance_method()
f.class_method()
f.static_method()




4. 생성자와 소멸자

생성자는 객체가 생성될 때 실행되고, 소멸자는 객체가 소멸할 때 실행됩니다.

생성자는 __init__ 속성으로, 소멸자는 __del__ 속성으로 정의하면 됩니다.

class foo:
count_of_instance = 0

def __init__(self):
foo.count_of_instance += 1

def __del__(self):
foo.count_of_instance -= 1

f1 = foo()
f2 = foo()
print(foo.count_of_instance) # 2
del f2
print(foo.count_of_instance) # 1





5. __str__ 속성

__str__ 속성은 자바에서 Obejct 객체의 toString() 메서드와 유사합니다.

class foo:
pass

f = foo()
print(f) # <__main__.goo object at 0x007324D0>


class goo:
def __str__(self):
return "__str__이 오버라이딩 되었습니다."

g = goo()
print(g) # __str__이 오버라이딩 되었습니다.





6. 연산자 오버로딩

연산자 오버로딩은 알게 모르게 사용해왔던 내용입니다.

문자열 자료형에서 문자열을 연결하는 + 연산자를 기억하시나요?

문자열 연결이 가능한 이유는 str 클래스에 __add__ 메서드가 정의되어 있기 때문에 문자열 연결이 가능한 것입니다.

문자열과 정수는 +연산자를 사용하지 못했는데, 그 이유는 __add__에 문자열과 정수를 연결할 수 있도록 정의를 하지 않았기 때문입니다.


Python에서는 이와 같이 연산자 오버로딩을 사용하고 있는데, 사용자 입장에서도 얼마든지 연산자 오버로딩을 할 수 있습니다.

class MyOperator:
def __mul__(self, i):
self.a = 5
return self.a + i

s = MyOperator()
print(s * 10) # 15

다음은 곱셈 연산자인 *를 사용하면 덧셈이 되도록 오버로딩한 것입니다.

곱셈 연산자를 오버로딩 하기 위해서는 __mul__ 함수를 재정의 하면 됩니다.


연산자를 오버로딩 하기 위해서는 그 연산자에 대응하는 함수를 재정의 하면 됩니다.

그러나, 연산자 오버로딩을 지나치게 많이 사용하면 혼란을 줄 수 있으니 사용에 신중해야 합니다.


여기서는 수치 연산자에 대한 오버로딩을 알아볼 것이며, 그 밖에 확장 산술 연산자, 비교 연산자 등의 오버로딩은 공식 문서를 참고하시길 바랍니다. ( 링크 )


 수치 연산자

 __add__

 +

 __sub__

 -

 __mul__

 __floordiv__

// 

 __mod__

 __divmod__

divmod() 

 __pow__

pow() , ** 

 __lshift__

<< 

 __rshift__

>> 

 __and__

 __xor__

 __or__





이상으로 Python의 클래스에 대해 알아보았습니다.

Python에서 모든 객체는 name space를 갖는다는 점과 클래스 메서드, 인스턴스 메서드, 정적 메서드의 차이점과 선언방법이 중요합니다.