Python3/Python3 language

[Python3] 코딩테스트 파이썬 사용시 유일한 단점 - 파이썬의 객체 복사 방식(깊은 복사와 얕은 복사)

Razelo 2022. 3. 19. 23:42

간만에 알고리즘 문제가 잘 풀린다 했더니 말도 안되는 에러가 발생해서 거의 1시간을 이것저것 만져보면서 고민했던것같다. 

도대체 왜 안되는건지? 이 고민을 아무리 해봤는데도 이상할게 전혀 없는 코드인데 원하는 대로 동작하지 않았다. 

 

한참을 코드를 들여다보다가 아차 싶은게 떠올랐다. 객체를 복사하는 과정이 굉장히 많았는데 이 부분에서 에러가 발생했던 것이었다. (한 변수를 사용하면서 그 값에다가 여러 변화를 주면서 사용했다. 이게 문제였다.) 이전에도 인지하고 있었던 내용이었지만 유독 오늘 만난 문제에서 실수를 했던 것이었다. (알고 있는 것과 실천하는 것은 다르다.)

 

파이썬이 모든 면에서 굉장히 편하고 알고리즘 문제를 풀기에 딱 좋은 언어라는 생각이 들었다. 너무 편하다. 문자열도 깔끔하게 풀어낼 수 있고 나머지 문제들도 심플하고 짧게 풀어낼 수 있다. 일부 블로그나 다른 의견을 들어보면 속도가 느리다는 것을 단점으로 꼽지만 사실 정상적인 방법으로 풀어냈을때 시간초과를 받은 적은 없었다. 시간 초과를 받은 문제는 애초에 다른 언어로도 시간 초과를 받을 풀이를 제출했을 경우였다. 즉 메모이제이션을 활용하지 않아서 시간 초과가 발생했다던지 혹은 브루트포스로 무작정 풀이했다던지의 경우이다. 따라서 속도가 느리다는 단점은 전혀 신경쓰이지 않았다. 그래서 모든 면에서 평균 이상의 만족도를 보이고 있었던 게 파이썬이었다. 

 

그런 와중에 굳이 뽑았던 파이썬의 단점이 오늘 포스팅의 주제이다. 그것은 바로 객체 복사에 있어서 파이썬만의 특이한 동작이다. 이 방식때문에 코드를 작성하는 와중에 나도 모르게 스리슬쩍 실수하게 되는 부분이라서 대부분의 사람들이 인지하지 못하고 있다가 왜 안되는지 갸우뚱하게될 부분이 바로 이 포인트다. 오늘 1시간동안 고민한게 억울해서 깔끔하게 정리를 하고 넘어가고자 한다.

(참고로 객체 복사 방식을 단점으로 꼽은 이유는 알고리즘 풀이에 한해서 실수할 여지가 있다고 생각해서이다. 언어 자체에 대한 비판이 아니다. 언어 자체에 대한 비판은 객체 복사 방식이 아니라 GIL이라고 생각한다. 이 부분은 다른 포스팅에서 다루도록 하겠다.) 


파이썬에서 변수는 객체를 가리키는 포인터와 같다. 즉 객체를 가리키는 개념이다. 

 

즉 파이썬에서 a = 1 이라고 하면 a에 1이 할당된 것이 아니라 1이라는 것을 a가 가리키고 있다고 보면 되는 것이다. 

 

그리고 중요한 개념 중 mutable 과 immutable 개념이 있는데 값이 바뀌냐 안바뀌냐 정도로 알면 쉬울듯 하다.  

 

대표적인 mutable에는 list가 있다. 

 

예를 들어 

 

a = [1, 2, 3, 4]

b = a

 

라고 선언하고 이후에 

 

b[1] = 0 

 

이라고 바꾼다면? 이 영향이 a한테까지 미친다는 것이다. 

 

즉 가만히 있던 a가 갑작스레 [1, 0, 3, 4] 가 되버리는 것이다. 이 부분을 주의해야 하는 것이다. a는 대체 왜 바뀔까? 그 이유는 a와 b가 지금 같은 주소를 참조하고 있기 때문이다. 

 

기본적으로 list, set, dict 는 모두 mutable 하다고 보면 된다.

 

immutable에는 기본적인 타입인 정수, 실수, bool 등이 있다. 그냥 위에서 말한 mutable 에 해당하는 것들 제외한 것들은 immutable이라고 보면 된다. 

 

자 그렇다면 얕은 복사와 깊은 복사에 대해 알아보자. 

 

얕은 복사는 객체를 새로운 객체로 복사하지만 원본 객체의 주소값을 복사하는 것이다. 

깊은 복사는 전체 복사로 참조값의 복사가 아닌 참조된 객체 자체를 복사하는 것이다. 

 

알고리즘 풀이를 하다보면 리스트를 복사할 필요가 분명 있을텐데 이럴때는 깊은 복사를 해줘야 한다. 

 

파이썬에서 깊은 복사의 방법에는 무엇이 있을까? 그 방법들에 대해 살펴보자. 

 

1. copy 모듈 사용 

import copy

b = copy.deepcopy(a)

 

2. 클래스가 기본적으로 가지고 있는 copy() 사용 

b = a.copy()

 

3. 새롭게 list 생성

a = [1, 2, 3, 4]

b = list(a)

 

4. 리스트 슬라이싱

b = a[:]

 

5. 하나씩 넣어주자. 

a = [1, 2, 3, 4]

b = [i for i in a]

 

이렇게 만들면 완전히 다른 객체를 만들어낼 수 있어서 문제가 발생하지 않는다. 

 

다만 리스트 슬라이싱이나 copy의 copy() 메소드는 리스트가 다른 오브젝트를 포함할 경우 그 오브젝트들은 얕은 복사만 되는 것에 주의해야 한다. 

 

처리속도? 

리스트 슬라이싱이 가장 빠르고 deepcopy 가 가장 느리다고 한다.

리스트 슬라이싱을 주로 쓰도록 하자. 

 

시간이 된다면 아래 블로그를 읽어보는 것도 좋다. 

https://crackerjacks.tistory.com/14

 

파이썬 (Python) - 깊은 복사 (Deep Copy)

파이썬 (Python) - 깊은 복사 (Deep Copy) 알고리즘을 풀다 보면 원본배열의 보존을 위해 배열을 복사할 필요를 느낄때가 많다. 객체를 무작정 복사해서 사용하면 원본 객체가 핸들링되어 데이터가 변

crackerjacks.tistory.com

 

 

 

반응형