일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 스위프트
- CORDA
- Hyperledger fabric gossip protocol
- 그라파나
- 주키퍼
- play 강좌
- 이더리움
- 스칼라
- 엔터프라이즈 블록체인
- 스칼라 강좌
- 파이썬 강좌
- 블록체인
- 파이썬 동시성
- play2 강좌
- 스칼라 동시성
- 플레이프레임워크
- 안드로이드 웹뷰
- Akka
- akka 강좌
- 파이썬 머신러닝
- 하이브리드앱
- Golang
- hyperledger fabric
- Play2 로 웹 개발
- Actor
- 파이썬
- 하이퍼레저 패브릭
- Play2
- Adapter 패턴
- 파이썬 데이터분석
- Today
- Total
HAMA 블로그
파이썬의 함정 - 3, 참조,얕은복사,깊은 복사 본문
파이썬의 함정 - 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>
레퍼런스:
전문가를 위한 파이썬
'Python' 카테고리의 다른 글
파이썬에서 가장 쉽게 범할 수 있는 10가지 실수들 [번역] (0) | 2017.06.15 |
---|---|
파이썬 주식 패턴 분석 [펌] (0) | 2017.06.12 |
파이썬 Asyncio 를 이해하기 위한 여정 (2) | 2017.05.02 |
파이썬과 동시성에 대한 정리 (0) | 2017.05.01 |
파이썬 동시성 프로그래밍 - (9) 제네레이터 & 코루틴 & asyncio & async/await (2) | 2017.04.30 |