Python 변수, 객체, 복사
in Development on Python
Python 변수, 객체, 복사에 대해 작성한 글입니다
이야기할 내용들
- 파이썬 변수를 은유적 표현
- 변수는 이름표지 상자가 아님
- 객체의 정체성, 동질성, 별명
- 튜플은 불변형이지만 그 안에 있는 값은 바뀔 수 있음
- 얕은 복사, 깊은 복사
변수는 상자가 아니다
- 많은 책에서 변수를 상자로 표현하고 있는데, 객체지향 언어에서는 이 개념이 참조 변수를 이해하는데 방해가 됨
- 변수는 객체에 붙은 레이블이라고 생각하는 것이 좋음
상자로서의 변수를 설명할 수 없는 코드
a = [1, 2, 3] b = a a.append(4) b >>> [1,2,3,4
- 변수를 상자로 생각하지 말고 변수를 포스트잇으로 생각하자
- 할당문의 경우 오른쪽 부분이 먼저 실행된다
- 객체가 생성된 후, 변수가 객체에 할당됨
class Gizmo: def __init__(self): print("Gizmo id : " f'{id(self}) x = Gizmo() >>> Gizmo id : 431489152 y = Gizmo() * 10 >>> Gizmo id : 431489321 TypeError: unsupported operand type(s) for *: 'Gizmo' and 'int'
- 할당문의 오른쪽에서 객체를 생성한 후, 레이블을 붙이듯 할당문 왼쪽에 있는 변수가 객체에 바인딩
- 여러 레이블을 붙이는 것을 별명이라 생각하기
정체성, 동질성, 별명
동일한 객체를 참조하는 사례
byeon = {'name': 'Seongyun', 'born': 1990} kyle = byeon kyle is byeon >>> True id(kyle), id(byeon) >>> (430048989, 430048989) kyle['age'] = 29 byeon >>> {'name': 'Seongyun', 'born': 1990, 'age': 29}
값은 같으나 정체성이 다른 사례
byeonzzi = {'name': 'Seongyun', 'born': 1990, 'age': 29} byeonzzi == byeon >>> True byeonzzi is not byeon >>> True
- byeonzzi와 byeon은 동일한 값을 가지고 있기 때문에
==
연산자에 의해 동일하다고 판단 - 하지만 정체성은 다름. a is not b는 두 객체의 정체성이 다르다고 표현하는 파이썬 방식
- 정체성은 메모리 내의 객체 주소라 생각할 수 있으며
is
연산자가 두 객에츼 정체성을 비교 id()
함수로 정체성의 id를 반환. 실제 프로그래밍에선 주로is
연산자 사용
- byeonzzi와 byeon은 동일한 값을 가지고 있기 때문에
==
연산자와is
연산자의 선택- 값을 비교하는 경우엔
==
사용이 더 많이 보임 - 그러나 변수를 싱글턴과 비교할 땐
is
연산자 사용 is
연산자는 오버로딩할 수 없고, 연산이 간단해서 속도가 빠름- a == b는
a.__eq__(b)
의 편리 구문
- 값을 비교하는 경우엔
- 튜플의 상대적 불변성
- 참조된 항목이 가변형이면 튜플 자체는 불변형이지만 참조된 항목은 변할 수 있음
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
- 위 사례를 보면 정체성(id)은 동일하지만 값은 변경됨
기본 복사는 얕은 복사
- 복사하는 가장 쉬운 방법은 자료형 자체의 내장 생성자를 사용하는 것
list()
,tuple()
,dict()
등- 리스트나 가변형 시퀀스의 경우는 l2 = l1[:] 같이
[:]
로도 사본을 생성할 수 있음 - 이 두 방법은 얕은 사본(shallow copy)
- 얕은 사본시 모든 항목이 불변형이면 메모리를 절약해 문제를 일으키지 않지만, 가변 항목이 들어가면 문제가 생김
- pythontutor에서 아래 코드를 실행해보면 시각적으로 볼 수 있음
l1 = [3, [66, 55, 44], (7, 8, 9)] l2 = list(l1) l1.append(100) l1[1].remove(55) print('l1:', l1) >>> l1: [3, [66, 44], (7, 8, 9), 100] print('l2:', l2) >>> l2: [3, [66, 44], (7, 8, 9)] l2[1] += [33, 22] l2[2] += (10, 11) print('l1:', l1) >>> l1: [3, [66, 44, 33, 22], (7, 8, 9), 100] print('l2:', l2) >>> l2: [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)]
- 리스트에서 += 연산자는 리스트를 변경
- 튜플에서 += 연산자는 새로운 튜플을 만들어 l2[2]에 다시 바인딩
- 깊은 복사와 얕은 복사
deepcopy()
: 깊은 복사- 객체에 순환 참조가 있으면 무한루프에 빠질 수 있음
- 너무 깊이 복사하는 경우도 존재 : 복사하면 안되는 외부 리소스나 싱글턴을 객체가 참조하는 경우
copy()
: 얕은 복사
Reference
카일스쿨 유튜브 채널을 만들었습니다. 데이터 사이언스, 성장, 리더십, BigQuery 등을 이야기할 예정이니, 관심 있으시면 구독 부탁드립니다 :)
PM을 위한 데이터 리터러시 강의를 만들었습니다. 문제 정의, 지표, 실험 설계, 문화 만들기, 로그 설계, 회고 등을 담은 강의입니다
이 글이 도움이 되셨거나 다양한 의견이 있다면 댓글 부탁드립니다 :)