프로그래머로서 코드를 이리저리 짜다보면 분명 하라는대로 했는데도 생전 보도못한 오류나 예외가 발생하는 경우가 자주 있다. 그건 강의자 혹은 튜토리얼의 준비 부족이라기보다는 그러한 코드를 실행하는 환경이 정~~말 다양하기 때문에 생기는 문제인 경우가 많다.

그 중에 가장 많이 만나지만 꽤 당황스러운 경우 두 가지를 소개한다.

예를 들어 어떤 라이브러리를 쓰는 Python 코드의 예시가 있다고 하자. 그 당시에는 코드가 사용하던 라이브러리의 버전이 1.0이었고, 그 때는 문제없이 쓸 수 있는 함수들과 코드들로 이루어져 있었을 것이다. 하지만 그로부터 2년이 지난 시점에서 환경을 새로 갖추려고 하니 라이브러리의 버전이 3.0이 되었고 Python의 버전도 3.6에서 3.9 정도로 올라갔다고 하자. 이런 경우엔 1.0 버전의 라이브러리에 있던 함수가 여러가지 문제로 인해 3.0 버전에서 삭제되었다던가, 함수의 인자 목록이 달라졌다던가 할 수 있다. 혹은 Python의 버전이 이리저리 올라가면서 Python 자체의 문법이나 내장함수가 달라졌을 수도 있다. 그러면 2년전의 예제 코드를 지금 시점에선 제대로 실행할 수가 없게 되는 것이다.

다른 언어나 환경은 이런 일이 좀 덜한 편이지만, 유독 Linux, Python, 그리고 Python 라이브러리들이 이런 경우가 꽤 잦다. 그래서 conda, virtualenv 같은 가상환경과 라이브러리 및 라이브러리 버전이 적힌 목록을 통해 특정 버전의 라이브러리들을 설치하여 예제 코드와 똑같은 환경에서 실행할 수 있도록 하여 이러한 환경적 변수를 최대한 차단하는 것이 일반적이게 된 것이다. (사실 Android나 iOS/macOS 도 그런 편이긴 하지만, Android는 말로만 deprecation을 시키고 코드를 쓸 수 있도록 유예기간을 굉장히 길게 주는 편이다)

얼마전에 후배가 어떤 코드를 작업하고 있었는데, 인터넷에서 예제 코드를 받아 실행해봤을땐 잘 되었는데, 그걸 가지고 본인의 환경에서 작업을 해보려고 하니 에러가 나면서 작동이 되지 않는다고 했다.

문제의 원인으로 의심되던 코드와, 오류는 각각 아래와 같았다.

이건 원인을 바로 알아내기는 조금 애매한 경우인데, 파이썬은 명시적 타입을 쓰는 언어가 아닌데 깊게 들어가면 명시적으로 타입을 지정하거나 변환해줘야 하는 편인데 이런식으로 타입 변환을 잘못했다는 에러가 나오면 타입 변환을 잘못한건지 변수를 잘못 집어넣은건지 아니면 잘못된 함수를 쓴건지 원인이 다양하기 때문에 이것저것 원인을 잘 찾아봐야 한다.

이런 경우엔 Traceback 을 먼저 잘 살펴보고 우리가 작성한 코드 수준에서 정확히 어떤 줄의 코드가 원인이 되었는지 확인하는 것이 좋다. 예를들어 위의 Traceback에서 File “<__array_function__ internals>”, line 180, in linspace 는 Python 혹은 라이브러리 내부의 함수이기 때문에 웬만하면 우리가 수정하지 않는 것이 좋은 부분이다. 그럼 우리가 작성한 코드들 중에서 이것을 호출하는 코드를 찾아보면 77번줄에 있는 phi_advance 로 시작하는 코드가 그것임을 알 수 있다. 해당 줄에서 numpy.linspace() 함수를 사용하고 있는데 아무래도 여기서 문제가 생겼을 수 있는 것이다.

그러면 구글에 “TypeError: ‘float’ object cannot be interpreted as an integer numpy.linspace” 와 같이 검색해보면 된다.

(당시에 후배는 여기까지는 했었는데 오류의 원인을 찾아보니 빈 파일을 열려고 하면 이런 오류가 생기는 듯 했다고 얘길 해서, 그렇다면 상위 함수에서 절대경로로 넘겨준 입력 파일의 경로를 라이브러리가 제대로 해석하지 못해서 그런게 아닐까 생각하고 해결방법을 궁리했었다. 거기다 후배가 데이터셋 폴더에 있던 wav 파일을 소스코드와 같은 경로에 복사한 다음, 절대경로 말고 파일이름만 넘겨줬더니 잘 작동했다는 증언도 있어서 원인은 절대경로라고 잠정적으로 추정하고 있었다.

그런데 막상 내가 직접 데이터셋에 있던 wav 파일을 소스코드와 같은 경로에 복사해서 실행해보니 코드가 제대로 동작하지 않고 위와 같은 에러가 나왔었다. 그래서 혹시 파일의 소유자가 root여서 그런가 싶어서 소유자를 로그인한 유저로 바꾸고 권한도 777로 바꿔봤는데도 같은 에러가 나왔었다. 여기까지 해보면 원인은 절대 파일 경로에 있지 않다는 결론을 내리는데 무리가 없었다)

아무튼. 구글에 “TypeError: ‘float’ object cannot be interpreted as an integer numpy.linspace“로 검색해보면 linspace는 보통 좌표공간처럼 사용하는 행렬을 만드는 함수로, 정수 값들을 인자로 받고 그 범위에 해당하는 공간 행렬을 만들어주는데, 기존 버전의 numpy에선 인자로 부동소수점 값을 넣어줘도 잘 작동했지만 최신 버전의 numpy에선 무조건 정수값을 인자로 넣어줘야하도록 바뀌었다고 한다. [출처] 뭐 아마도 0과 2.5 사이의 정수를 만들어달라고 시키면 예전엔 적당히 2.5를 정수 2로 변환해서 0,1,2를 반환했지만 그로 인해 생길 수 있는 부작용을 막기 위해 그런 “배려 차원의 기능”을 없앤 듯 하다.

그럼 어떻게 해결하면 되느냐? 를 찾아보면, 그냥 numpy.linspace 대신에 numpy.arange를 사용하면 된다고 한다. [출처] 그래서 한번 기존 코드를 아래와 같이 바꿔보았다.

이렇게 함수를 단순히 linspace에서 arange로 바꿔서 실행시켜보니 코드가 문제없이 잘 작동하였다.

사실 이건 명탐정 코난 같은데서 이뤄지는 범인 추리과정과 비슷하다고 보면 된다. 주어진 단서들이 있고 본인의 이전 경험들을 토대로 각종 가설을 세우고 검증해가면서 원인과 해결방법을 찾아나가는 것이다. (프로그래밍 면접으로 보통 코딩테스트를 보지만, 사실 효율적이고 합리적인 코드를 짜는 능력 만큼 실무차원에서 비슷하게 갖춰야하는 능력이 바로 이런 능력이라고 생각한다)

두 번째 경우로는, 다국어 문제를 들 수 있다.

동북아 문화권에 살고 있는 우리로서는 “정말 배려심이 없는 세계구만”이라고 생각할 수 있지만, 프로그래밍 그리고 프로그래밍 환경은 보통 “이 프로그램을 이용하는 사람은 영어만 쓴다”는 암묵적 가정에 기반해 굴러가는 경우가 많다. 그래서 ASCII가 다룰 수 있는 기본 영대소문자, 기호, 숫자 이외의 문자를 컴퓨터에서 쓰는 경우에 이런걸 고려하지 않은 프로그램 또는 코드에게 한 방 먹을 수 있다.

아래 예시를 보자. 얼마전 어떤 학생이 실습과제를 하다가 만난 오류라면서 가져온 traceback이다.

개인정보 문제로 가려두긴 했지만, 이 학생은 윈도우 컴퓨터에서 Python을 Pycharm 환경에서 사용하는 학생이었고 사용자 폴더명은 학생 본인의 한글이름으로 되어있었다. 맨 마지막 줄에 있는 오류 메시지를 보면 OSError: Failed to open file 어쩌구 하면서 \xec\x9d\xb4 같은 경로가 나오는데, 한글로된 경로명이 이상하게 해석되어 있는걸 볼 수 있다.

보통 이럴 때는, 맨 마지막 줄에 있는 저 오류 메시지와, 저 에러를 발생시킨 환경(라이브러리, 언어 등. 여기선 scipy)의 이름을 조합해 구글에 복붙해서 검색해보면 꽤 높은 확률로 사람들의 경험담과 해결방법이 나온다. 이 경우에는 “OSError: Failed to open file …..” 과 “scipy”를 구글에 연이어 적고 검색하면 된다.

그렇게 해보면 원인이 아까 말했듯이 유저 폴더명이 영어가 아닌 문자들로 이루어져있어서 그런것을 알 수 있게 되고, 해결법인 “유저 폴더명을 한글에서 영어로 바꾸는 방법”을 다시 구글에서 찾으면 되는 것이다.

아무튼 두 가지 만만한 예시를 들어 혼자서 프로그래밍 문제를 해결하는 일반론적인 방법을 한번 설명해보았다. 어느정도는 브레인스토밍과 순발력이 필요한 부분으로 볼 수 있지만 사실 여러 번 경험해보면 결국 일반적인 해결 과정은 똑같다는 것을 알게 될 것이다.

답글 남기기

이메일 주소는 공개되지 않습니다.

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.