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 연산자 사용
  • == 연산자와 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


이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)




© 2017. by Seongyun Byeon

Powered by zzsza