[Python] 객체 참조

Hyeseong·2020년 12월 5일
0

python

목록 보기
10/22
post-thumbnail

객체 참조 중요한 특징들

Python Object Reference

print('ex1-1')
print(dir())

print()
print()
print(__name__) # <--- 이 스코프 안의 속성을 볼 수 있음

# id vs __eq__(==) 증명
x = {'name':'kim', 'age':33, 'city':'Seoul'}
y = x

print('ex2-1', id(x), id(y)) # 동일한 id값, 즉 얕은 복사임
print('ex2-2', x == y) # 값이 같은지 물어봄
print('ex2-3', x is y) # id 가 같은지 물어봄
print('ex2-4', x, y)

x['class'] = 10
print('ex2-5', x,y)

z = {'name':'kim', 'age':33, 'city':'Seoul', 'class':10}
print('ex2-6', x,z)
print('ex2-7', x is z) # 값이 같지만 id값이 다름 -> False
print('ex2-8', x is not z) 
print('ex2-8', x == z) 

객체 생성 후 완전 불변 -> 즉, id는 객체 주소 비교, ==(eq) 값 비교
참고로 is keyword를 == 비교 연산자보다 먼저 사용하는 습관이 필요함. 이유는 속도와 퍼포먼스에서 엄청난 차이를 보임

예를 들어 key - value가 백만건 이상 되어 있다면 동등비교 연산자(==)로 사용 할 경우 key값 100만개를 하나씩 비교하기에 엄청나게 소요되지만

is, is not 키워드의 경우 해당 객체의 id값 하나만 각각 비교하기에 수백, 수천배 그 이상의 효율성을 가질수 있음

print()
print()

tuple1 = (10, 15, [100, 1000])
tuple2 = (10, 15, [100, 1000])

print('ex3-1', id(tuple1), id(tuple2))
print('ex3-2', id(tuple1) is id(tuple2))
print('ex3-3', tuple1 == tuple2)
print('ex3-4', tuple1.__eq__(tuple2))

output

ex3-1 1672527758080 1672528021248
ex3-2 False
ex3-3 True
ex3-4 True

ex3-1에서 중요한점은 순수 튜플자료형이라면(내부 요소값도 동일한 구조라면) 주소값이 같지만 만약 mutable한 자료형이 있는 경우 객체참조를 하지 않고 다른(새로운) 주소값을 갖게 되는 거조.

고급으로 가는 길은 이렇게 자료형까지 염두해두어야 된다는점.


Copy, Deepcopy(깊은 복사, 얕은 복사)

Copy

tl1 = [10, [100, 105], (5,10,15)]
tl2 = tl1
tl3 = list(tl1) # list메서드를 이용하면 새로운 객체를 생성함.

print('ex4-1', tl1 == tl2)
print('ex4-2', tl1 is tl2)
print('ex4-3', tl1 == tl3)
print('ex4-4', tl1 is tl3) # list()메서드로 인해 새로운 객체를 반환하게됨.

# 증명
tl1.append(1000)
tl1[1].remove(105)

print('ex4-5', tl1)
print('ex4-6', tl2)
print('ex4-7', tl3)

print()

print('tl1: ',id(tl1[2]))
tl1[1] += [110, 120]
tl1[2] += (110, 120) # 튜플 값 변동은 == 객체 생성을 의미함.

print('ex4-8', tl1)
print('ex4-9', tl2) # 튜플 재할당(객체 생성). 튜플의 특징인 불변(값 변동X )때문
print('ex4-10', tl3)
# print('tl1: ',id(tl1[2]))
# 결론적으로 말하자면, 리스트안에 튜플을 넣어서 사용하게되면 id값 변동으로 인해 안전하지 않음. 
# 1) 데이터 소실문제 
# 2) 성능문제(새로운 객체생성은 cost가 많이듬)

Deep Copy

쇼핑몰 장바구니 결제에서 실수(Copy, Deep Copy)하는 예시를 보여 드릴게요.

class Basket():
	def __init__(self, products=None):
		if products is None: # 장바구니에 상품을 넣지 않았을 경우
			self._products = []
		else:				 # 장바구니 상품을 넣었을 경우
			self._products = list(products)

	# 장바구니에서 하는 action 1)넣고 2)빼는것
	def put_prod(self, prod_name): 
		self._products.append(prod_name)

	def del_prod(self, prod_name):
		self._products.remove(prod_name) 
        #remove는 인자로 값을 받아 지우고, pop과 del의 경우 index를 받아 삭제함.


import copy

# 경우에 따라 맞는 얕은, 깊은 복사를 하게되요.

basket1 = Basket(['Apple', 'Bag', 'TV', 'Snack', 'Water'])
basket2 = copy.copy(basket1)	   # 일반 카피로 복사. 
basket3 = copy.deepcopy(basket1)   # deepcopy로 원본의 속성값까지 쫓아가서 복사한다는 말임

print('ex5-1', id(basket1), id(basket2), id(basket3)) 
# basket1과 basket2는 서로 다른 주소를 가짐. 그렇다고 다른 객체라고 볼수 없는 것이 basket1에서 값변동이 발생하면 basket2에서도 동일하게 변동을 수직으로 받게됨. 
#대신 basket2변동이 basket1의 값 변동에 영향을 주진 않음. basket1과 basket3은 주소가 다르고 값은 다른 별도의 객체임. 값변동이 서로에게 영향을 주지 않음.

print('ex5-2', id(basket1._products), id(basket2._products), id(basket3._products)) 
# basket1과 basket2의 인스턴스 속성값 동일, but basket1과 basket3의 속성값은 다름

print()

basket1.put_prod('Orange') # 1번 바구니에 오렌지를 넣음
basket2.del_prod('Snack') # 2번 바구니에 snack을 뺌

print('ex5-3', basket1._products) 
# 1번 바구니에 오렌지가 들어 간거 확인

print('ex5-4', basket2._products) 
# 2번 바구니에 오렌지를 넣지 않았는데 들어가 있음 이는 2번바구니가 1번바구니를 copy.copy()로 참조하기 때문

print('ex5-5', basket3._products) # 3번 바구니는 정상적으로 오랜지가 들어가지도 스낵이 빠지지 않는 별도의 바구니로서 구실을 하고 있다는게 확인되고 있음, 즉 처음 클래스의 인자값으로 받은 것을 제외하고는 어떠한 변동이 없음
# 만약 뭔가가 불안할 경우 객체의 경우 깊은 복사를 한다면 더 안전하게 취급 할 수 있음.

함수 매개변수 전달 사용법

def mul(x,y):
	x += y
	return x

x = 10
y = 5

print('ex6-1', mul(x,y), x,y)
print()

a = [10, 100]
b = [5, 10]

print('ex6-2 - ', mul(a,b), a,b) 
# 변수 a의 원본이 변경된 문제 발생. 지금 한것이라고는 리스트를 함수의 인자로 넘겼는데 원본이 변경되었네요?
# 가변형 a -> 데이터 변경


# 만약 튜플인 경우는 어떻게 될까요?

c = 10, 100
d = 5, 10

print('ex6-2', mul(c,d), c,d) 
# 리스트를 인자로 넘겼을때와 사뭇다르게. 값이 변경되지 않습니다. 이 말은 튜플이 특징이 불변형 이기 때문이에요.

# 이때 우리가 생각해야 할 것은. 인자값으로 리스트로 넘길지 튜플로 넘길지 입니다. 
# 원본의 값이 변경되도 상관없다면 리스트를 넣어도 되지만 변경되서 안된다면 tuple로 하면 안전합니다.
# 사용하고자 하는 데이터의 구조와 원칙에 맞게 사용하면되요. 무조건 리스트를 쓰는게 아니에요.

파이썬 불변형 예외

str, bytes, frozenset, Tuple : 사본 생성 X -> 참조 반환

tt1 = 1, 2, 3, 4, 5
tt2 = tuple(tt1) 
tt3 = tt1[:]

print('ex7-1', tt1 is tt2, id(tt1), id(tt2)) # 불변형이지만 성능을 위해 기존에 고수하던 원칙을 깨고 참조를 반환함! 
# 결과: ex3-1 True 2407472866384 2407472866384

print('ex7-2', tt3 is tt1, id(tt3), id(tt1)) 

# 여기서는 str이나 tuple은 동일한 값을 갖고 있는 경우 동일한 주소 값!!을 가지게 된다.
tt4 = 10, 20, 30, 40, 50
tt5 = 10, 20, 30, 40, 50
ss1 = 'hello'
ss2 = 'hello'

print('ex7-3', tt4 is tt5, tt4 == tt5, id(tt4), id(tt5))
print('ex7-4', ss1 is ss2, ss1 == ss2, id(ss1), id(ss2)) 

print(ss1 is ss2, id(ss1) is id(ss2))
# 그냥 아~ 이런게 있구나 하고 넘어가면 좋을듯 해요. 결과는 True, False로 뜨네요.
profile
어제보다 오늘 그리고 오늘 보다 내일...

0개의 댓글