Everything is object in python

호두마리·2022년 9월 9일
0

Python Tips

목록 보기
2/3
post-thumbnail

Python Object

Python에서의 변수는 다른 프로그래밍 언어에서의 변수와는 달리 어떠한 데이터를 담는 container역할을 하지 않고 해당 데이터가 담겨있는 메모리 주소의 위치를 가리키는 reference로 볼 수 있다.

따라서 Python 변수는 다양한 type의 객체를 가리키기 때문에 변수를 "선언"할 필요가 없으며, 변수가 항상 같은 유형의 정보를 가리키도록 할 필요도 없기때문에 Python을 동적 타이핑 언어(Dynamic typing programming language)라고도 한다. 그리고 이러한 Python의 특징은 코드를 빠르게 작성하고 읽기 쉽게 만드는 장점 중 하나가 된다.

# Java
int x = 1
String x = "1"


# Python
x = 1		# 변수에 대한 type 선언이 필요없다
x = "1"

하지만 이러한 특징은 혼동을 불러오거나 버그를 야기할 수 있기 때문에, Python 개발 입문을 마쳤다면 python 변수의 특징을 정확하게 이해할 필요가 있다.

Python 변수는 데이터를 저장하는 container가 아닌, 데이터가 저장된 메모리 주소의 위치를 가리키는 reference를 의미한다


Type, Id

Python 내장함수인 type과 id를 활용하여 변수의 유형과 고유식별번호를 확인할 수 있다. 본문 제목처럼 Python의 모든것은 객체이며, 이를 통하여 각 객체를 비교할 수 있다.

Python의 모든 객체는 __init__ method와 함께 객체가 생성되면 해당 객체의 id가 생성된다. (해당 객체의 데이터가 저장된 Memory 주소와는 다르다) 내장함수 id()는 대상 객체에 대한 고유 ID를 반환하며, 이 id는 중복되지 않으며, 객체가 소멸될 때 까지 변경될 수 없다.

  • 내장함수 type()으로 객체의 유형을 확인할 수 있다
  • 내장함수 id()로 객체가 참조하고 있는 메모리주소 위치를 확인할 수 있다
  • 비교연산자 "is"로 두 변수가 참조하고 있는 객체(id)의 동일 여부를 확인할 수 있다
  • 비교연산자 "=="로 두 변수가 참조하고 있는 객체의 값(value)의 동일 여부를 확인할 수 있다

변수 참조 (Variable Reference)

Python 변수는 데이터를 담고있는 것이 아닌, 데이터를 가리키는 pointer역할을 하기 때문에 변수 참조를 사용하는 방법에 따라 생각하지 못한 결과가 출력될 수 있다.

"b = a"는 변수 b를 변수 a가 가리키는 포인터위치와 동일하게 하게한다.
따라서 변수 a의 값과 b의 값, 그리고 id가 동일하며, 변수 a에 6이라는 값을 append 하여도 결과가 동일하게 나타난다.
하지만 "a = [1,2,3,4,5,6,7]"는 변수 a가 새로운 객체를 가리키도록 변경했기 때문에 변수 b는 기존의 데이터를 유지하여 결과 값이 달라지게 된다.

이를 도식화하면 다음과 같다. (옅은 화살표가 표시한 코드가 현재 도식화된 부분의 상태를 의미한다.)

변수 a가 [1,2,3,4,5] 객체를 가리킨다

변수 b가 a가 가르키는 객체를 동일하게 가르킨다.

변수 a가 가르키는 객체에 6을 추가한 결과를 b가 그대로 가리키고 있기 때문에 두 변수가 가리키는 데이터는 동일하다.

변수 a에 연산자(=)를 활용하여 새로운 객체를 할당했기 때문에 변수 a, b가 가리키는 객체가 달라진다.

여기서 알 수 있는 것은 기존의 객체의 데이터가 바귀어도 변수 참조를 통해 수정된 데이터를 그대로 반영되기 위한 조건은 객체에 데이터가 재할당되는 것이 아닌 (객체가 소멸되고 다시 생성되어 id가 바뀜), 객체의 데이터가 변경(객체가 소멸되지 않고 데이터만 변경되어 id가 그대로 유지)되는 경우이다. 즉, 객체가 변경가능한(Mutable) 객체에 대해 적용이 가능하다.

Mutable 객체는 객체 생성 후 객체에 할당된 값을 변경할 수 있고, 값이 변경되어도 객체의 id가 그대로 유지된다. 따라서 동일한 list 객체를 두개의 변수(a, b)가 참조하고 있는 경우 하나의 변수(a)에 대해 특정 작업을 수 행했을 때 다른 변수(b)에 영향을 미치게 되기때문에 일반적으로 Mutable 객체로 작업할 때는 변수 참조를 피하는 것이 안전하다.

Mutable object

ClassDescriptionIs Mutable?
boolBoolean valueNo
intIntegerNo
floatfloating-point numberNo
listSequence of objectsYes
tupleSequence of objectsNo
strcharater stringNo
setunordered set of distinct objectsNo
frozensetimmutable form of set classYes
dictUnordered set with key and valueNo

※ Interning

Python에는 메모리를 효율적으로 관리하기 위한 방법이 적용되어 있다. 객체가 생성되면 해당 객체를 내부적으로 관리하는 table에 보관한 뒤 다른 객체가 생성되면 table내에 동일한 값을 갖는 객체가 있는지 확인하여 테이블 내 보관중인 객체의 참조를 반환하여 사용함으로써 메모리를 효율적으로 관리하는 일종의 Caching 기법이다.
Python에서 기본적으로 적용되는 Interning 범위는 다음과 같다.

  • -5 ~ 256 사이의 정수
  • [a-zA-Z0-9_]에 해당하는 문자

a, b 변수에 같은 값을 할당하였을 때 interning되어 동일한 id를 갖는 결과

아래 예시에서는 interning이 적용되지 않는 값으로 작성하였을 때의 결과이다.

a, b 변수에 같은 값을 할당하였지만 id는 다르다

※ 만약 동일한 코드를 VSCode 등 IDE에서 실행한다면 IDE에서 지원하는 Compiler에서 자동으로 코드가 최적화되어 interning 범위 외의 변수도 interning될 수 있다.


얕은 복사 (Shallow Copy)

아마 Python 입문자 입장에서 두 변수 사이에 대입연산자(=)를 사용하였을 때는 해당 변수의 값을 복사하기 위한 의도였을 것이고, 실제 수행했을 때는 값이 복사되는 것이 아닌, 대상 객체와 동일한 목적지를 참조하게 되어 값이 복사된것 "처럼"된 것이다.

그렇다면 본래 의도대로 대상 객체의 값만 복사하려면, list 객체의 slicing을 활용하여 얕은 복사를 수행할 수 있다. 얕은 복사를 통해 새로운 객체를 생성한 뒤 기존 객체를 변경시켰을 때 어떤일이 일어나는지 확인해 보자

(b=a) b는 a가 가리키는 객체를 동일하게 "참조"

(b2=a[:]) b2는 a가 가리키는 객체의 데이터를 "복사" (shallow copy)

a의 변경사항은 b2에 영향을 주지 않음

list_b2는 list_a, list_b가 가리키는 객체와 별도의 객체로 생성되었기 때문에, list_a에 데이터가 추가되어도 list_b2에는 당연히 영향이 없다.
하지만 그림에 보이는것 처럼 list_a, list_b의 list 객체 내부에 객체가 가리키고 있는 객체는 list_b2의 list 객체 내부의 객체가 같은 목적지를 가리키고 있다...!??

list내 데이터가 참조하는 객체가 동일한 상태

따라서 list 내 데이터가 참조하고 있는 객체를 수정하면(list_a[0][0] = 9999) list_a, list_b, list_b2 객체 내부의 데이터가 모두 변경되게 된다.

list내 데이터가 참조하는 객체 값이 변경되어 각 list 객체에 모두 반영된 것처럼 보인다

  • 얕은 복사는 대상 객체가 참조하는 대상의 데이터를 복사하지만 해당 객체 내부에 있는 데이터가 참조하는 객체는 그대로 참조하는 상태가 된다.
  • Mutable 객체인 list, dictionary, set은 copy()메서드를 활용하여 얕은 복사를 할 수 있으며, 그 중 list 객체는 list slicing으로 얕은 복사와 동일한 역할을 수행하게 할 수 있다.

깊은 복사 (Deep Copy)

참조되는 객체 내부의 데이터가 참조하는 객체까지 모두 복사하기 위해서는 Python 내장 모듈인 copy 모듈의 deepcopy() 함수(깊은 복사)를 통해 해결할 수 있다.
(해결이라고 표현을 했지만, 그렇다고 해서 얕은복사가 문제라는 것은 아니다. 상황에 따라서 적절한 기능을 활용해야 한다.)

깊은 복사는 대상 객체가 참조하고 객체의 값을 재귀적으로 복사하여 객체를 재생성 한다.

list_a 내 데이터가 참조하는 객체 값이 변경되어도 list_b2 객체에는 영향이 없다

list_a내 데이터가 참조하는 객체가 참조하는 객체까지 신규로 생성되었다

  • 깊은 복사는 대상 객체가 참조하고 객체의 값을 재귀적으로 복사하여 객체를 재생성 한다
  • Python 내장모듈 copy 모듈의 deepcopy() 함수를 사용한다

결론

Python 변수의 객체를 참조하는 특징과 변수 값을 복사하면서 새로운 객체를 생성하는 방법에 대해 알아보았다. 대부분의 사람들이 기초과정을 마치고 프로그램을 만들다가 같은 문제를 겪는것 같다. Python의 모든 것들은 객체이고 변수는 항상 객체를 참조하는 것이라는 것, 그리고 내부적으로 어떻게 동작하는지에 대해 잘 이해하고 있어야겠다.

profile
자고싶당

0개의 댓글

관련 채용 정보