티스토리 뷰
변수는 상자가 아니다
파이썬에서 변수는 이름표
파이썬 변수는 자바에서의 참조 변수와 같다. 변수는 객체에 붙은 레이블이다
a = [1, 2, 3]
b = a
a.append(4)
b
# [1, 2, 3, 4]
참조 변수의 경우 변수가 객체에 할당되었다는 표현이 객체를 변수에 할당했다는 표현보다 훨씬 타당.
결국 객체는 변수가 할당되기 전에 생성 된다.
class Gizmo:
def __init__(self):
print('Gizmo id: %d' % id(self))
x = Gizmo()
# Gizmo id: 4301489152
y = Gizmo() * 10
# Gizmo id: 4301489432 # 곱셈을 시도하기 전에 Gizmo 객체가 실제로 생성되었음을 입증
# TypeError: unsupported operand type(s) for *: 'Gizmo' and 'int'
dir()
# 할당문의 오른쪽이 실행되는 동안 예외가 발생했기 때문에 변수 y는 생성되지 않음
파이썬에서 할당문을 이해하려면 언제나 오른쪽을 먼저 읽어야 함. 할당문의 오른쪽에서 객체를 생성하거나 가져 옴. 그 후 레이블을 붙이듯이 할당문 왼쪽에 있는 변수가 객체에 바인딩 됨.
변수는 단지 레이블일 뿐이므로 객체에 여러 레이블을 붙일 수 있다.
여러 레이블을 붙이는 것을 별명 이라고 한다.
정체성, 동질성, 별명
charles = {'name': 'Charles L. Dodgson', 'born': 1832}
lewis = charles # lewis는 charles의 별명
lewis is charles
# True
id(charles), id(lewis)
# (4300473992, 4300473992)
lewis['balance'] = 950
charles
# {'name': 'Charles' L. Dodgson', 'born': 1832, 'balance': 950}
alex = {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950} # alex는 charles에 할당된 객체의 복제본을 가리킴
alex == charles # dict 클래스의 __eq__()를 구현하는 방식 때문에 두 객체를 비교해서 같다고 판단
# True
alex is not charles
# True
코드 안에서 lewis는 charles는 별명. 두 변수가 동일 객체에 바인딩되어 있다.
alex는 charles에 대한 별명이 아님. 두 변수는 서로 다른 객체에 바인딩 되어 있음
모든 객체는 정체성, 자료형, 값을 가지고 있다. 객체의 정체성은 일단 생성한 후에는 결코 변경되지 않는다. 정체성은 메모리 내의 객체 주소라고 생각할 수 있다. is 연산자는 두 객체의 정체성을 비교한다. id() 함수는 정체성을 나타낸는 정수를 반환한다.
id()는 객체의 메모리 주소를 반환. ID는 객체마다 고유한 레이블이라는 것을 보장하며 객체가 소멸될 때까지 결코 변하지 않는다는 점이 핵심
== (동치 연산자) 객체의 값을 비교, is 연산자 객체의 정체성을 비교
변수를 singleton과 비료할 때는 is 연산자를 사용. is 연산자를 사용할 때는 변수를 None과 비교하는 경우가 일반적
`x is None` `x is not None`
is 연산자는 오버로딩할 수 없으므로 파이썬이 이 값을 평가하기 위해 특별 메서드를 호출할 필요가 없고, 두 정수를 비교하는 정도로 연산이 간단하므로 is 연산자가 == 연산자보다 빠르다.
a == b는 a.__eq__(b)의 편리 구문이다. object 객체에서 상속받은 __eq__() 메서드는 객체의 ID를 비교하므로 is 연산자와 동일한 결과를 산출. 그러나 대부분의 내장 자료형은 __eq__() 메서드를 오버라이드해서 객체의 값을 비교. 대형 컬렉션이다 깊이 내포된 구조체를 비교하는 경우 동치 비교는 상당한 처리를 요구
리스트, 딕셔너리, 집합 등 대부분의 파이썬 컬렉션과 마찬가지로 튜플도 객체에 대한 참조를 담는다.
str, bytes, array.array 같은 단일형 시퀀스는 참조 대신 문자, 바이트, 숫자 등의 데이터를 물리적으로 연속된 메모리에 저장한다.
참조된 항목이 가변형이면 튜플 자체는 불변형이지만 참조된 항목은 변할 수 있다. 즉, 튜플의 불변성은 tuple 데이터 구조체의 물리적인 내용(즉, 참조 자체)만을 말하는 것이며, 참조된 객체까지 불변성을 가지는 것이 아니다. 튜블 안에서 결코 변경되지 않는 것은 튜플이 담고 있는 항목들의 정체성뿐이다.
t1 = (1, 2, [30, 40])
t2 = (1, 2, [30, 40])
t1 == t2
# True
id(t1[-1])
# 4302515784
t1[-1].append(99)
t1
# (1, 2, [30, 40, 99])
id(t1[-1])
# 4302515784
t1 == t2
False
+= 복합 할당
t = (1, 2, [30, 40]
t[2] += [50, 60]
# TypeError: 'tuple' object does not support item assignment
t
# (1, 2, [30, 40, 50, 60])
기본 복사는 얕은 복사
리스트나 대부분의 내장 가변 컬렉션을 복사하는 가장 손쉬운 방법은 그 자료형 자체의 내장 생성자를 사용하는 것
l1 = [3, [55, 44], (7, 8, 9)]
l2 = list(l1) # l1의 사본을 생성(shallow copy)
l3 = l1[:] # l1의 사본을 생성(shallow copy)
l2
# [3, [55, 44], (7, 8, 9)]
l2 == l1
# True
12 is l1
False
l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1)
l1.append(100)
l1[1].remove(55)
l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1)
l1.append(100)
l1[1].remove(55)
l2[1] += [33, 22]
l2[2] += (10, 11)
객체의 깊은 복사와 얕은 복사
class Bus:
def __init__(self, passenger=None):
if passengers in None:
self.passenger = []
else:
self.passengers = list(passengers)
def pick(self, name):
self.passengers.append(name)
def drop(self, name):
self.passengers.remove(name)
import copy
bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])
bus2 = copy.copy(bus1)
bus3 = copy.deepcopy(bus1)
id(bus1), id(bus2), id(bus3)
# 4301498296, 4301499416, 4301499752
bus1.drop('Bill')
bus2.passengers
# ['Alice', 'Claire', 'David']
id(bus1.passengers), id(bus2.passengers), id(bus3.passengers)
# 4302658568, 4302658568, 4302657800
bus3.passengers
['Alice', 'Bill', 'Claire', 'David']
객체 안에 순환 참조가 있으면 단순한 알고리즘은 무한 루프에 빠질 수 있다. deepcopy() 함수는 순환 참조를 제대로 처리하기 위해 이미 복사한 객체에 대한 참조를 기억하고 있다.
a = [10, 20]
b = [a, 30]
a.append(b)
a
#[10, 20, [[...], 30]]
from copy import deepcopy
c = deepcopy(a)
c
# [10, 20, [[...], 30]]
참조로서의 함수 매개변수
파이썬은 공유로 호출(call by sharing)하는 매개변수 전달 방식만 지원. 공유로 호출한다는 말은 함수의 각 매개 변수가 인수로 전달받은 각 참조의 사본을 받는다는 의미. 즉, 함수 안의 매개변수는 실제 인수의 별명이 된다.
함수는 인수로 전달받은 모든 가변 객체를 변경할 수 있지만, 객체의 정체성 자체는 변결할 수 없다. 즉, 어떤 객체를 다른 객체로 바꿀 수는 없다.
def f(a, b):
a += b
return a
x = 1
y = 2
f(x, y)
a = [1, 2]
b = [3, 4]
f(a, b)
t = (10, 20)
u = (30, 40)
f(t, u)
가변형을 매개변수 기본값으로 사용하기: 좋지 않은 생각
class HauntedBus:
def __init__(self, passengers=[]): # passengers 인수를 전달하지 않은 경우 이 매개변수는 기본값인 빈 리스트에 바인딩된다.
self.passengers = passengers # 이 할당문은 self.passengers를 passengers에 대한 별명으로 만드므로, passengers 인수를 전달하지 않는 경우 self.passengers를 기본값인 빈 리스트에 대한 별명으로 설정한다.
def pick(self, name):
self.passengers.append(name)
def drop(self, name):
self.passengers.remove(name)
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() # 기본값인 빈 리스트가 self.passengers에 할당됨.
bus3.passengers # 기본값이 비어 있지 않음
# ['Carrie']
bus3.pick('Dave')
bus2.passengers # bus2.passengers와 bus3.passengers가 동일한 리스트를 참조하는 문제
# ['Carrie', 'Dave']
bus2.passenger is bus3.passengers
# True
bus1.passenger # bus1.passengers는 별개의 리스트
['Bill', 'Charlie']
명시적인 승객 리스트로 초기화되지 않은 Bus 객체들이 승객 리스트를 공유하게 되는 문제가 발생