관리 메뉴

HAMA 블로그

파이썬의 함정 - 3, 참조,얕은복사,깊은 복사 본문

Python

파이썬의 함정 - 3, 참조,얕은복사,깊은 복사

[하마] 이승현 (wowlsh93@gmail.com) 2017. 6. 6. 18:32


파이썬의 함정 - 3 


참조,얕은 복사,깊은 복사


모든 언어가 동일한 정책을 취하지 않기 때문에, 이 문제는 어떤 문제에서나 뒷목을 잡게 만들 수 있다. 개인적으로 여러 언어를 다루는 사람들은 이런 참조 문제를 외우지 말고, 항상 테스트를 해봐야 한다고 생각한다. 뒷통수 맞기 싫으면~

파이썬도 마찬가지로 함정이 숨어있는데 , 사실 이게 어떤 깊은 이해를 필요로 하는 문제가 아니기 때문에 그냥 코드를 보고 느껴보자.  (물론 call by value, call by reference , call by share 등에 대한 기본 이해는 있다고 가정)


코드1) 

a = [1,2,3]
b = a
a.append(4)

print b # 결과 [1,2,3,4]

자, 파이썬에서 변수는 값을 담는 그릇이 아니다. 그냥 값에 대한 라벨링 정도? 
그래서 a , b 는 둘다 어떤 (여기선 [1,2,3] ) 이것을 가르키고 있으며, a 가 그것에 추가하면 당연히 b 도 같은 것을 바라보기 때문에 [1,2,3,4] 로 출력된다.


코드2) 

#coding=utf-8
a = [1,2,3]
b = a

if a == b:
print "a b 는 값이 같다"

if a is b:
print "a b 는 정체성이 같다"

c = [1,2,3]
if a == c:
print "a b 는 값이 같다"

if a is c:
print "a b 는 정체성이 같다"

파이썬에서 값이 같은지 비교하는것은 == 이고, is 의 경우는 같은 객체인지를 검토한다.
(스칼라는 == 는 값을 비교하고, 자바는 == 이것이 같은 객체인지 비교한다. 언어마다 다르니 확인해야함)


코드3

#coding=utf-8

a = [1,[2,3],(4,5,6)]
b = list(a) # 또는 a[:]

if a == b:
print ("a b 는 값이 같다")

if a is b:
print ("a b 는 같은 객체이다")

그냥 b = a 하면 같은 객체가 되나, list 나 a[:] 를 통해서 복사생성하면 다른 객체가 된다.


* 참고로 파이썬에서는 import copy 를 하고 copy.copy() 를 통해서는 얕은 복사를 지원하고, copy.deepcopy() 를 통해서는 깊은 복사를 지원한다.


코드4

class Bus:

def __init__(self, passengers=None):
if passengers is None:
self.passengers = []
else:
self.passengers = list(passengers)

def pick(self, name):
self.passengers.append(name)

def drop(self, name):
self.passengers.remove(name)

bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])
bus2 = copy.copy(bus1)
bus3 = copy.deepcopy(bus1)
bus1.drop('Bill')
print bus2.passengers
#['Alice', 'Claire', 'David']
print bus3.passengers
#['Alice', 'Bill', 'Claire', 'David']

원본에서 하나의 요소가 삭제되었을때,
얕은복사를 한 것 (bus2) 은 영향을 받고 있고, 깊은 복사 (bus3) 를 한 것은 영향을 받지 않는다.


코드5)

가변형을 매개변수 기본값으로 사용 했을 때의 문제점을 살펴보자.


class HauntedBus:
def __init__(self, passengers=[]): # <1>
self.passengers = passengers # <2>

def pick(self, name):
self.passengers.append(name) # <3>

def drop(self, name):
self.passengers.remove(name)

__init__ 생성자의 매개변수로 [] 가 기본값으로 사용되었다. 

bus1 = HauntedBus(['Alice', 'Bill'])
bus1.passengers
['Alice', 'Bill']
bus1.pick('Charlie')
bus1.drop('Alice')
bus1.passengers
#['Bill', 'Charlie']
bus2 = HauntedBus()
bus2.pick('Carrie')
bus2.passengers
#['Carrie']
bus3 = HauntedBus()
print bus3.passengers
#['Carrie'] # 이거 뭡미??? 아무것도 넣은게 없는데 Carrie 라 빡~~~ 나옴
bus3.pick('Dave')
bus2.passengers
#['Carrie', 'Dave']
bus2.passengers is bus3.passengers
#True
bus1.passengers
#['Bill', 'Charlie']


dir(HauntedBus.__init__) # doctest: +ELLIPSIS
#['__annotations__', '__call__', ..., '__defaults__', ...]
HauntedBus.__init__.__defaults__
#(['Carrie', 'Dave'],)
HauntedBus.__init__.__defaults__[0] is bus2.passengers # True

여기서 문제는 각 기본값이 함수가 정의 될 때 (즉,일반적으로 모듈이 로딩 될 때) 평가되고 기본값은 함수 객체의 속성이 된다는 것이다. 따라서 기본값이 가변 객체고, 이 객체를 변경하면 변경 내용 이 향후에 이 함수의 호출에 영향을 미친다. 

가변 기본값에 대한 이러한 문제 때문에, 가변 값을 받는 매개변수의 기본값으로 None 을 주로 사용하며,  __init__ 메서드는 passengers 인수가 None 인지 확인하고 새로 만든 빈 리스트를 할당한다. 

즉 더 방어적으로 프로그래밍 하기 위해서는 아래처럼 코딩해야 한다.

class TwilightBus:

def __init__(self, passengers=None):
if passengers is None:
self.passengers = [] # <1>
else:
self.passengers = passengers #<2>

def pick(self, name):
self.passengers.append(name)

def drop(self, name):
self.passengers.remove(name) # <3>


레퍼런스:

전문가를 위한 파이썬




Comments