[버그 해결 과정] 다차원 리스트 리스트 복사

문제 발생

파이썬에서 리스트 자료형은 대입 연산자가 단순 복사가 아닌 참조인 것을 깜빡했다. Pandas에서만 참조라고 생각하고 넘어갔는데, 아니였다.

문제 발생 위치


FAR_origin = FAR.copy()

for i, value in enumerate(unique_num_count):  
    for j in range(max):  
        try:  
            FAR[i][j] = (FAR[i][j] / value) * 100  
            FAR[i][j] = round(FAR[i][j], 2)  
        except ZeroDivisionError:  
            pass  

try 바로 밑 부분에서 FAR 값을 변경할 때 FAR_origin도 같이 변한다.

해결 방법

얕은 복사로 해결해보기.

리스트 대입할때 슬라이싱이나 copy함수를 사용하여 해결할 수 있다 [1]. 그런데 잘 되야하는게 안된다.

리스트의 .copy 함수나 슬라이싱 [ : ] 는 얕은 복사로 알고 있다.


A = [0,[1,2,3]]
B = A.copy()

A
[0, [1, 2, 3]]
B
[0, [1, 2, 3]]

A[0] = 10

A
[10, [1, 2, 3]]
B
[0, [1, 2, 3]]


얕은 복사도 복사라고 생각하고 있기에 위 코드처럼 값이 변하지 않을 것이라고 생각했다.


A[1][2] = A[1][2]/10 * 100

A
[10, [1, 2, 30.0]]
B
[0, [1, 2, 30.0]]

내가 작성한 문제와 똑같이 리스트 안에 있는 리스트의 값을 변경해봤더니 복사한 리스트와 원본 리스트의 값 둘 다 변한다.

이차원 리스트의 객체 구조

아무래도 FAR_origin = FAR.copy() 에서 copy(얕은 복사)된 부분은 최외각 리스트만 복사된 것이고 하위 리스트(리스트 안의 리스트)는 객채로 참조된 것이라 그런 것이 아닐까 하는 결론을 얻었다 [2].

깊은 복사 (deepcopy 메서드) 쓰지 않고 해결해보기.

고정되어야하는 값이 참조값처럼 변한다 [3]. 그렇다고 이 한줄을 위해서 deepcopy 메서드[4]를 사용하기에는 미세한 성능 저하가 마음에 걸렸다.

튜플로 복사하는 과정의 문제들..

그래서 어차피 복사되는 리스트는 값을 변경시키지 않을 것이기에, 튜플로 변경했다. 튜플로 변경하는 과정에서도 여러가지 문제가 있었다.

  1. 2차원 리스트에 tuple() 함수를 사용하면 하위 리스트(리스트 안에 있는 리스트)는 튜플로 형변환이 이뤄지지 않는다.
  2. 튜플 안에 리스트는 값을 변경할 수 있다.
  3. 튜플은 append가 없다.

1번을 해결하기 위해 하위 리스트를 튜플로 바꿔줘야 한다. 그래서 난 단순하게 리스트에서 하는 것처럼 하위 리스트를 튜플로 강제 형변환하고 튜플에 append했다.

튜플에는 append 함수가 없어서 당연히 에러가 발생했다. 생각해보면 당연하다. 튜플은 수정이 불가능한데, append는 튜플을 생성한 후에 추가해주는 것이니 불가능한 것이 당연하다.

그래서 하위 리스트를 튜플로 바꾸고 리스트에 넣은 다음 상위 리스트(최외각 리스트)를 튜플로 강제 형변환 하였다.


FAR_origin = []  
  
for i, data in enumerate(FAR):  
    FAR_origin.append((tuple(data)))  
FAR_origin = tuple(FAR_origin)

결론

잘 동작한다.

나중에도 굳이 deepcopy를 사용하지 않아도 되는 상황(copy한 리스트를 따로 변경하지 않는 경우)는 이런 방법으로 문제를 해결할 것 같다.

copy한 리스트를 변경해야하는 경우는 리스트 안의 리스트 객체를 전부 리스트의 .copy() 를 사용하여 처리하거나 deepcopy 로 처리할 듯 하다.

for문을 잘 활용하면 하위 리스트 객체에 .copy() 를 전부 적용할 수 있을 것 같은 느낌이 든다.

소감

오랜만에 디버깅을 켜고 어떻게 버그가 생기는지 관찰했다. 단순한 copy를 이정도까지 생각하고 사용하지는 않아서 이런 버그를 만난 것 같다. 아주 기초 중에 파이썬은 모든 것을 객체로 저장한다는 내용이 있는데, 그걸 생각하면 리스트 안에 있는 하위 리스트가 객체인 것은 아주 당연하다.

재밌다. 굳이 deepcopy 메서드를 사용하면 편하고 빠르게 해결할 수 있는 문제를 2시간 동안 고민하면서 새로운 방법을 찾아낸 점이.

다만, 오개념일 수 있으니, 틀린 내용은 지적해주시면 감사하겠습니다.

참고자료

[1] Stella_NY. [Python3] 리스트 복사 처리 (tistory.com). Tistory. (accessed Jul 3. 2021)

[2] bono. 얕은 복사(shallow copy) vs 깊은 복사(deep copy) | bono blog (blueshw.github.io). Github. (accessed Jul 3. 2021)

[3] hayeon. Unit 23. 2차원 리스트 사용하기 (velog.io). velog. (accessed Jul 3. 2021)

[4] 효팍이의 프로그래밍 ihp001. "### [Python] 파이썬 객체 복사가 잘 안될 때 복사하는 방법 - 슬라이싱(Slicing), 깊은복사(deepcopy), 얕은복사(copy)". Tistory. (accessed Jul 3. 2021)

이 글이 도움이 되었나요?

신고하기
0분 전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.