관리 메뉴

HAMA 블로그

파이썬의 함정 - 1 (클래스변수 와 객체 변수) 본문

Python

파이썬의 함정 - 1 (클래스변수 와 객체 변수)

[하마] 이승현 (wowlsh93@gmail.com) 2016. 9. 11. 18:36

파이썬의 함정 - 1  (부제: 나의 삽질기)


클래스 변수와 객체 변수에  대한 함정


자바나  C++  베이스에서 파이썬으로 옮겨 왔을때 가장 실수하기 쉬운 부분에 대해서 살펴보겠습니다.

먼저 아래 코드를 보시죠.

class Test :

num =
0

def show(self):
print 'num :' + str(num)

t =
Test()
t.show()

어떻게 될까요? 

에러입니다.

어디서?

print 'num :' +  str(num)

네 여기에서 num 를 못찾아서 에러가 납니다.  클래스 변수 num 을  찾지 못하네요.

이걸 해결하려면 

print 'num :' + str(self.num)

이렇게 self 를 붙여 주어야 하는데요.

self 는 JAVA나 C++에서 this 와 같으며  현재 객체를 말합니다. 

print 'num :' + str(Test.num)

이렇게 클래스 변수로 나타낼 수도 있습니다.

그럼 아래는 어떻게 될까요? 

class Test :

num = 0

def show(self):
print 'num :' + str(self.num)


t = Test()
Test.num = 10
t.show()

Test.num = 10 이렇게 클래스 변수에 직접 접근하여 10을 대입해 주었습니다. 클래스 공통 10 인거죠
self.num 은 객체 스스로라고 했으니깐 0 일꺼 같지만

결과는 "num  :10"  이렇게 됩니다.  아직 클래스변수와 객체변수로 나누어지지 않았네요.

그럼 아래는 어떻게 될까요?

class Test :

num = 100

def __init__(self):
self.num = 0
def show(self):
print 'num :' + str(self.num)


t = Test()
Test.num = 10
t.show()

__init__ 라는 생성자를 이용하여  self.num 을 초기화 하였습니다. 이때 결과는 "num  :0"  이렇게 됩니다. 
즉 __init__ 생성자에서 초기화를 해 줘서 객체별 변수로 바뀝니다.

그럼 아래는 어떨까요?

class Test:
num = 100

def show(self):
print 'num :' + str(self.num)

def inc(self):
self.num = self.num + 1


t1 = Test()
t2 = Test()

t1.inc()
t2.inc()
t1.show()
t2.show()

객체를 2개 만들었습니다. 그리고 각각 inc()  함수를 호출하여 1씩 증가 시켰는데요.
show()  호출하면 둘다 101 을 나타냅니다. 

__init__ 생성자에서 초기화 하지도 않았는데  self.num 에 값을 대입하면서  객체변수가 된거 같습니다.

(기존의 클래스 변수의 값을 가지고 객체변수로 초기화) 


마지막으로 하나 보시죠. 이게 히트입니다. (동시에 아주 큰 실수입니다.  본문 끝까지 읽으셔야해요. 제발~)

class Test:
num =
0
ar = []

def show(self):
print 'num :' + str(self.num)
print 'list :' + str(self.ar)

def inc(self, n):
self.num = self.num + n

def inc_list(self, n):
self.ar.append(n)


t1 = Test()
t2 = Test()

t1.inc(2)
t2.inc(3)

t1.inc_list(
1)
t2.inc_list(
5)

t1.show()
t2.show()

이거 show() 하면 t1 객체의 num 는 2 ,  t2 객체의 num 는 3이 됩니다.  즉 값이 객체별로 분리되지만
리스트의 경우는 분리되지 않네요..

num :2

list :[1, 5]

num :3

list :[1, 5]

이렇게 나옵니다.

즉 리스트의 경우는 self 를 해도 두개의 객체가 리스트 ar 을  공유하게 됩니다.
자바와  C++ 에서는 상상도 못할..

이 경우는 
Test  클래스에 __init__(self) 를 추가하여 여기에서 초기화 해야 객체별로 리스트(ar)가 생성되는 건 가 싶어.. 
class Test:
num = 0
ar = []

def __init__(self):
self.ar = []

def show(self):
print 'num :' + str(self.num)
print 'list :' + str(self.ar)

def inc(self):
self.num = self.num + 1

def inc_list(self, n):
self.ar.append(n)


t1 = Test()
t2 = Test()

t1.inc()
t2.inc()

t1.inc_list(1)
t2.inc_list(5)

t1.show()
t2.show()

해보니 잘 됩니다. 

__init__ 에서 초기화 해주어야 객체 별 변수가 된다면 모두 그게 적용되야 하는데 

파이썬에서는 일반타입하고 배열타입하고 다르게 작동하는거 같습니다.
즉 일반타입은 자동으로 __init__ 초기화가 이루어지는 모냥.. ??   땡~~
여기까지 저의 착각이었습니다.  아래  읽으세요.


앗 큰 실수!! 

위에 작동방식이 너무 상식밖이라.. 파이썬 창조자 머리에 꽃 맞은것도 아니고 

굳이 저렇게 헤깔리게 할까 싶어서 다시 살펴본결과

 def inc(self):
self.num = self.num + 1

def inc_list(self, n):
self.ar.append(n)

이거 두개 사이에 차이가 있음을 발견 

위에 num 은 초기화 해주고 있으나, ar 는 초기화가 아니네요. 
self.ar.append(1) 이것을 sefl.num = 1 같은 초기화라고 착각한것 입니다. 
self.ar = [] 이렇게 초기화를 해줘야 했었는데 말이죠.


즉 __init__ 뿐 아니라 다른 어느 곳 에서 든지 ar 도 초기화를 한다면 동일하게 객체변수화 합니다. 

예를들어 아래 처럼 아무데서나  초기화 해주면 됩니다.

 def listInit(self):
self.ar = []

아래처럼 외부에서 초기화 해줘도 되구요.


t1.ar = []
t2.ar = []



결론 

애초에 공유목적 ( 즉 정적변수 ) 로 작동할때만 class 아래에 클래스 변수로 놓고 , 객체별로 사용될 변수들은 클래스 변수 자리가 아니라 __init__ 에 넣는게 파이썬 스타일/이디엄 인듯.  이렇게 되면 저런 실수는 하지 않게 되겠지요.  그래도 실수를 통해 하나 진득히 배우게 된거 같습니다. ^^

Comments