I. Intro

지난 번 WSL에서 Jupyter notebook을 사용하게 되었는데, 사실 Python은 여러 가지 패키지 상의 문제나 시스템 상의 이유로 다양한 버전을 다운로드 받아서 사용하고 있는 경우가 많습니다. 저의 경우에도 인제 막 시작한 Django를 혼자 쓸 때나 데이터 분석 툴들을 사용할 때에는 Python3.8을 사용하지만, 동아리에서 진행하는 Django sesison이나 Tensorflow를 건드려 볼때에는 3.8 버전이 아직 나오지 않아서 3.7 버전을 어쩔 수 없이 사용하게 됩니다. 그래서, 이 두 개의 버전을 Jupyter notebook에 사용하고 싶어서 처음에는 따로 따로 다운 받아서 사용하면 된다고 생각을 하였습니다.

 

python3.8 -m pip install jupyter notebook

python3.7 -m pip install jupyter notebook

 

이렇게 깔아주고 패키지 관리를 각각 파이썬 별로 해주면 됩니다. 근데, 이렇게 되었을 때 jupyter notebook에 가서 다음과 같은 명령어로 버전을 확인해주면,

 

import sys

sys.version

 

아마 처음에 깔린 python 버전만 나오지, python3.7 -m jupyter notebook이라는 명령어로 실행시키더라도 python3.8이 실행되는 분통터지는 모습을 볼 수가 있을 겁니다. 이러한 일이 일어나는 이유는 무엇이고, 어떤 방법으로 해결할 수가 있을까요?

 

II. 들어가기전 개념 정리

 

이상적인 패키지 정리

 

보통은 많은 패키지들은 위의 방식과 같이 각각의 Python version에 대해서 하나씩 깔리는 형태를 취하고 있습니다. 때문에 Python3.7 버전에서 다운 받은 django, pandas, numpy와 같은 패키지들이 3.8에서 사용되지 않는 것은 의존성이 각각의 버전에 맞추어져있고, 따로 따로 깔아서 사용해주어야 한다는 점으로 볼 수가 있을 겁니다.

Jupyter notebook은 어떨까요?

우리가 착각했던 관계

 

Jupyter notebook의 경우에도 pip를 사용하여 깔 수 있으나 조금은 다른 방식으로 접근을 해주어야한다는 것을 알 수가 있습니다. 바로 R과 같은 다른 언어에서도 기존의 Jupyter Notebook을 이용하여 사용할 수 있다는 점인데요. 아까의 패키지가 각각의 버전에 따로 깔려서 사용한다면 R에서는 작동이 되지 않아야하는데, R과 같은 다른 언어에서도 사용할 수 있다는 점이 살짝 이상하게 다가오긴 합니다. 때문에 관계는 다음과 같게 설정됨을 알 수가 있습니다.

 

진짜 관계

여기서 저희는 이런 프로그램 언어들이 돌아갈 수 있는 환경을 커널이라고 부르고 있습니다.

각각의 python3.7 python3.8 R과 같은 커널들이 존재를 하고 이 언어들을 Jupyter Notebook에 인식을 시켜서 우리는 Jupyter Notebook을 통해서 파일을 수정하고 실행을 해보게 됩니다. 때문에, python 다른 버전에서 설치를 해도, 이미 설치가 되어있는데, 뭘 설치를 하라는건지 컴퓨터에서는 아리송할 수 밖에 없는 것이죠. 만약 하나에 설치가 되어 있으면 커널 추가를 통해서 문제를 해결 할 수 있습니다.

 

III. 커널 추가

 

초창기 상태 입니다.

 

먼저 저의 상태는 python3이라는 python3.8의 버전이 깔려 있습니다. 여기에 3.7을 추가해보도록 하겠습니다.

 

입력할 명령어는

 

python3.7 -m pip insatll ipykernel

python3.7 -m ipykernel install --user --name="이름"

 

입니다. 이 때 추가를 원하는 버전의 python을 기준으로 앞의 python3.7 버전을 수정시켜주시면 되고, 이름의 경우에는 원하시는 인식명을 적어주시면 됩니다. 저의 경우에는 "Python(3.7)"로 하게 되었습니다.

이후 추가 되었다는 결과문이 나오게 되고 jupyter notebook을 다시 실행시켜주게 되면.

 

 

잘 설치 되었습니다.
시스템도 인식됩니다.

 

다만 Default로 설정되어있는 값이 존재를 하기 때문에, Jupyter notebook 파일을 열고나서는 커널을 기본 커널이 아닌 다른 커널로 변경을 해주셔야합니다.

'Programming > Python' 카테고리의 다른 글

Programming tip - Meta Programming  (0) 2020.04.04

0. C언어를 배우며

지난 인턴 기간 동안 저는 자료구조와 알고리즘에 대해서 어렴풋이 알고는 있었지만, 실제로 다른 문제에 부딪혔을 때, 어떻게 해결하면 좋을까에 대해서 막막한 상태였습니다. 이때, 자료 구조와 알고리즘이 큰 역할을 하면서 문제를 해결한다는 것을 알게 되었고, 지난 3월부터 계속해서 자료구조와 알고리즘에 대해서 복습하고 코드를 짜보며 복습 중입니다. 기존에 사용하던 언어인 Python으로도 얼마든지 공부를 진행할 수 있을 수도 있지만, Python의 경우에는 High Level의 언어 중에서도 많이 High Level이기 때문에 쉽게 쉽게 코드를 짤 수 있는 장점이 존재하지만, 자료구조를 공부할 때에는 포인터를 쓸 수 있는 C&C++이 더 좋다고 생각을 해서 옛날에 공부하였던 언어들을 다시 꺼내게 되었습니다. 그렇게 Python의 생산성에 감탄을 표하게 되고

 

C언어및 C++를 공부할 때 필요한 Visual studio code의 세팅의 경우에는 구글에 검색해보면 많은 양의 정보가 나오니 따로 적지는 않겠습니다. 

I. 문제 봉착

이렇게 C언어를 한 번 복습하고, 본격적으로 자료구조를 공부하려던 저에게 다음과 같은 문제가 생기게 되었습니다.

 

undefined reference to 

바로 다음과 같이 여러 변수들이나 함수들이 undefined reference to로 나온다는 점이었습니다. 기존의 C언어 프로그래밍 오류는 제가 포인터를 잘못 설정하거나 변수를 잘못 적어서 일어나는 경우가 많았는데, 이와 같은 경우는 말 그대로 referenence의 각각 함수와 변수들이 잘못 설정이 되었다고 나오게 되었습니다. 하지만 저는 분명 header파일과 c파일로 각각의 함수들을 지정해주고, 정의를 하며 마지막에 mainfuncition이 있는 곳에서 실행하도록 만들어서 겹치지도 않는데 다음과 같은 문제가 발생하였습니다. 실제로 설루션을 다시 가져와도 같은 반응입니다. (본격 공부하는데 환경 때문에 미치는 케이스)

 

어떤 점이 문제일까요?

 

II. C언어의 특수성:  헤더파일과 c파일

 

파일 리스트

제가 사용한 파일의 경우 이렇게 세 가지로 나뉘게 됩니다. 그중 linkedlist의 경우 헤더 파일과 c 파일이 존재를 합니다. 많은 분들이 인터넷에 설명을 해주셨듯이 헤더 파일은 메뉴판, c파일은 본격적으로 조리하는 단계라고 보시면 됩니다. 이러한 점은 python에서도 코드가 너무 길어질 경우에 따로 py파일로 분리하여서 import - 혹은 from ~ import - 이런 식으로 각각의 함수들이나 파일 통째로 main 프로그램에서 가져 쓸 수 있게 하여서, 함수를 한 파일이 아닌 다른 파일에서도 사용이 가능하게 만들 뿐만이 아니라 코드를 분리시켜서 유지 및 보수, 수정이 더욱 쉽게 만들 수 있다는 장점이 있습니다. C언어에서는 이러한 외부에서 가져오는 c파일을 헤더 파일과 c파일로 분리하여서 사용하게 된다고 이해하시면 될 것 같습니다. 하지만 이러한 점 때문에 문제가 생기게 됩니다.

 

undefined reference to의 의미는 분명 메뉴판에는 A라는 함수가 있는데, 정작 A를 어떻게 조리할까에 대한 내용이 없을 때 발생하게 되는 것 입니다. 하지만 이렇게 반문할 수가 있을 것 같습니다.

"어? 나는 linkedlist.c라는 파일에서 그 함수를 정의를 해줬는데?"

네 맞습니다. 하지만 visuasl studio code나 gcc에서 이 순서를 반대로 컴파일을 하게 된다면 인식이 되지 않는게 당연합니다. 마치 햄버거를 조립하려고 하는데, 패티가 아직 굽히지 않는 상태와 같다고 할까요? (비유 참...)

 

III. 해결 방법

III-1. 헤더파일이고 뭐고 제거

 

결국에는 헤더파일, c파일, 메인 파일은 하나의 프로그램 코드를 각각 편의성에 맞게 분리한 것에 불과합니다. 때문에 분리하는 것을 하나로 합치면 코드가 성공적으로 돌아가긴 합니다. 파일이 몇 개 없을 때는 이 점이 제일 간편하는 방법인 것 같습니다. 하지만 이러한 점의 문제점도 존재를 하는데요.

먼저, 헤더 파일이나, c파일이 여러 파일을 넘어 갈 경우에는 굉장히 번거로운 사태가 발생을 합니다. 하나의 메인 함수에 다 붙여한다니.. 순서를 고려하면서 말이죠. 또한, 파일들과의 순서 말고도 안에서 #define이나 #include를 위로 올려 보내는 과정, 또 각각의 함수가 어떤 의존성을 가지는지 모두 파악을 하고 오려 붙여 야한다는 점이 있습니다.

두 번째론, 헤더파일과 c파일을 분리시킨 이유가 명백히 있을 시에는 이를 반감시킬 수 있습니다. 앞서 말한 듯 분리시키는 경우에는 다른 c 프로그램에서도 사용할 수도 있다는 점이 있습니다. 하지만 이렇게 하나의 파일에 몰아놓고 집중을 하게 되면, 이 파일에 의존성을 가진 프로그램들이 작동을 하지 않을 가능성이 높습니다. 

이러한 점 때문에 이론 공부에 사용하는 비교적 간단한 파일의 경우에는 추천을 드리지만, 완벽한 방법은 아닙니다.

 

III.-2 Terminal에서 gcc를 이용한 

많은 Visual studio code에서 환경설정을 해주는 것은 결국에는 gcc를 실행하는 것을 자동화시켜준다는 점에 불구합니다. 이 때문에 터미널에서 저희가 직접 gcc를 실행하는데, 우선순위를 정해서 순서대로 컴파일을 하면 문제가 해결이 됩니다.

터미널에 입력할 저희의 명령어는 다음과 같습니다.

 

gcc -o (컴파일로 생성할 이름) (main 함수가 있는 c파일) (연결된 c파일)

이 때, 컴파일로 생성할 이름과 main 함수가 있는 c파일의 이름이 일치하면 좋습니다.

연결된 c파일의 경우에는 헤더 파일과 보통 같은 이름으로 정해주는 프로그램을 의미합니다. 의존성이 심한 즉 많은 함수나 변수들을 다른 c파일과 헤더 파일에서 정의한 것을 가져다 쓰는 파일을 가장 앞으로 보내는 것이 좋습니다. 보통은 main 함수가 있는 파일이 가장 많이 함수들과 변수들을 다른 헤더 파일들과 c파일에서 가져다 사용하기 때문에 컴파일로 생성할 이름 뒤에 main 함수가 있는 c파일의 제목을 넣어주는 것이 좋습니다.

 

저의 경우에는

 

다음과 같이 넣게 되었습니다. 컴파일로 생성할 이름과 main 함수가 있는 c파일을 이렇게 같게 넣어주는 이유는 많은 분들이 참고하시는 VS code의 설정이 main 함수가 들어 있는 c 파일과 같은 이름을 가진 컴파일된 프로그램을 찾아서 실행하고 있기 때문입니다. 다르게 설정하면 아무리 실행을 해도 실행 파일은 있는데 실행을 하지 않을 수도 있습니다. 하지만 터미널을 이용하여 실행파일도 내가 직접 실행을 하겠다는 분들은 원하시는 이름으로 적어도 될 것 같습니다.

 

위에서 나온데로 터미널에 명령을 입력하게 되면

 

이렇게 컴파일된 프로그램이 생기는 것을 알 수가 있습니다. 이때, 컴파일해서 만들 프로그램 이름을 따로 지정해주지 않으면 가장 앞 파일이 사라지는 것을 목격할 수가 있으니 주의를 해주시길 바랍니다.

 

이렇게 컴파일을 한 프로그램은 실행을 하여 결과를 확인할 수가 있게 됩니다.

 

원 코드에서 문자열이 깨져서 다음과 같이 나오긴 하지만, 실행은 됩니다!

 

 

IV. 마치며

C언어는 공부할 때마다 정말 포인터 때문에 미칠 거 같고 변수도 일일히 형태를 다 지정을 해주어야 해서 솔직히 쉽다고도, 또 생산성이 높다고도 이야기를 못하겠습니다. 오늘 다룬 주제와 같이 컴파일과 실행의 개념이 또 존재를 하기 때문에 진입장벽이 낮다고도 이야기를 못할 것 같습니다. 하지만, 컴퓨터 이론 공부를 할 때, 가장 깊게 이해하는데 도움을 주고, 파이썬이나 다른 프로그래밍 언어에서 배울 수 없던 점들을 C 언어에서 일일이 수동으로 할당해가면서 할 경우에는 가장 아래의 단계에서 문제를 이해하고 해결하는데 도움을 줄 수 있는 언어인 것 같습니다. 비록 인제 C언어를 사용해 다른 공부를 시작하였지만, 앞으로도 계속 이런 점들을 정리해나가면서 C언어 고수가 되어봐야겠습니다.

 

I. Meta Programming?

인턴을 하면서 기본적인 알고리즘이나 여러 개념말고도 문제를 해결하면서 필요한 센스들이 많았습니다. 특히 C언어 배울 때 스쳐지나간 기본적인 Call by Reference, Call by Value 그리고 자료 구조와 알고리즘, 메모리 할당 등을 이용한 최적화 문제까지 컴퓨터 과학 커리큘럼을 따랐으면 기본적으로 세팅이 되어있을 개념이라 기초가 모자란 것에 대해서 많이 반성을 하게 되는 계기가 되었습니다. 하지만 프로그래밍에서 이러한 책에서 나오는 것이 아닌 다른 개념을 활용해야 쉽게 풀리는 문제들이 있습니다.

저같은 경우에는 프로세스와 쓰레드 개념을 익히면서 정말 긴 일을 하나의 무거운 일을 여러 작은 일들로 나누어서 컴퓨터가 가지고 있는 코어를 다 사용해야하는 방법을 찾아야만 했습니다. 근데 여러 일을 한 5가지로 나누는건 괜찮은데 100개 정도로 나눠서 작업을 하려하니

 

work1
work2
work3

 

이런식으로 변수를 100까지 붙혀주어야하는 것이 너무 번거롭기도 하고, 나중에 문제가 생겼을 때, 한꺼번에 고치기도 힘들더라고요. 그래서 이러한 불편함을 (찡찡거리면서) 어떻게 해결을 할까? 고민을 하는 도중에 저의 천사같은 선임 개발자 분께서

 

"그 문제는 String Implementation과 Meta Programming 을 잘 섞어서 쓰면 해결할 수 있을 것 같아요!"

 

라고 힌트를 주셨습니다.

 

이 개념의 경우에는 복잡도나 자료구조에 대한 문제도 아니고 자원을 어떻게 관리할까의 개념도 아니기 때문에 책에 절대로 나오지 않는 개념이지만 정말 유용한 개념이기도 합니다.

실제로 MIT에서 컴퓨터과학 클래스가 놓친 한 학기라는 개념에 들어간 내용이기도 합니다.

 

1/27 Meta Programming이 확실하게 들어가있습니다.

어떤 내용이길래 저의 고민을 한번에 해결하고 더 나아가서 다른 곳에서도 중요시하는 개념일까요?

 

II. 기존 프로그래밍의 한계

 

저희는 변수를 선언할 때, 변수의 이름을 먼저 넣고 그 다음에 변수에 어떤 값들을 넣을 것인가를 정해서 컴퓨터에 명령을 넣게 됩니다.

 

 

Example1

 

하지만 이러한 변수들을 100개 넘게 선언을 하려면 work1,work2,...work100까지 일일히 넣어야한다는 단점이 있습니다.

 

"그렇다면 for 문을 사용해서 쉽게 코드를 짜면 안되나요?"

 

 

Example2

이러한 시도는 막히기 마련인데, 변수를 지정할 때 완벽하게 끝까지 지정해준 상태에서 해주어야하지, 중간에 연산자가 들어가면 Python에서 인식을 할 수 없는 상태에 빠지게 됩니다. 이러한 문제는 Meta Programming을 통해서 해결할 수가 있습니다.

 

III. Meta Programming

1. eval 활용

이러한 Meta Programming의 가장 쉬운 방법은 eval을 활용하는 방법입니다.

String을 코드로 바꿔주어서 활용할 수가 있습니다. 

 

eval 활용 방법1

다음과 같이 따옴표 안에 들어간 string을 코드로 바꾸어서 작동을 하는 것을 알 수가 있습니다. 원래 work1에는 1이라는 값을 넣어주게 되었는데, eval 함수가 작동을 하면서 work1에는 11이라는 값이 들어간 가게 되었습니다. 이러한 점은 string을 쉽게 함수로 변형시켜서 활용을 할 수가 있는데요.

 

eval 활용 방법2

다음과 같이 여러 mass와 work의 뒤의 값들을 string의 결합 원리들을 이용하여 값을 바꿔나가면서 모든 변수들에 대한 코드 실행을 할 수 있음을 볼 수가 있습니다.

 

eval 활용방법 3

이러한 eval의 장점들에도 불구하고, eval 함수는 변수를 선언해주지 못한다는 단점이 있습니다. 이 때문에 저희가 목표로 하는 여러 변수들을 자동으로 생성해주는 목표에는 도달하지 못하였습니다. 이러한 점을 해결하기 위해서 저희는 exec 함수도 같이 활용해보도록 하겠습니다.

 

2. exec 함수

 

string의 값을 단순히 작동시키는 eval과 달리 exec는 Statement, 긴 문장을 실행시킬 수 있는 장점이 있습니다. 따라서, string안에 여러 연산자들이나 함수를 넣어서 작동도 가능하고, 그 안에 변수 선언도 가능이 하다는 장점이 있습니다. 이 때, Statement에 대해서 코드를 짜줄 때 문장단위로 짤 것이기 때문에 따옴표를 세개 써야할 것을 주의를 드리겠습니다.

 

exec 함수 작동 예시1

예시에서 보면 example_code라는 변수는 Statement로 이루어져있는 것을 알 수가 있는데, exec을 사용해서 이 코드를 돌렸을 때, if문에 따른 조건 판별 뿐만이 아니라 work_result에 대한 값도 같이 할당이 되면서 훌륭하게 string을 코드로 바꾸어서 작동이 된 것을 알 수가 있습니다.

그렇다면, 처음 고민인 work1,work2,...work100까지의 선언 문제도 이를 활용하여 해결 할 수가 있습니다.

 

 

exec 활용 예시

다음과 같이 직접적으로 저희가 work99와 work100을 선언을 해주지 않았는데도 exec함수를 이용해서 work 함수가 

여기서는 eval과 별 다를바 없이 코드를 짧게 짰지만, work 뒤에 들어가는 코드의 양이 길 경우에는 exec이 유용하게 사용될 수가 있습니다. 하지만 저희는 선언도 같이 해주어야하기 때문에 eval보다는 exec이 훨씬 더 적합한 선택임을 알 수가 있습니다.

저는 이러한 exec함수를 효율적으로 활용해서 work100까지 변수들을 알맞게 나누어 선언하여서 코어를 다 활용하여 일을 빠르게 끝낼 수가 있었습니다. Meta Programming을 활용하는 목적은 다르지만 일을 효율적으로 끝낼 수 있는 최고의 방법 중 하나라고 생각합니다. 

 

IV. 마치며

제가 마주친 문제를 어떻게 eval과 exec함수를 사용하여 해결했는지에 대해서 설명을 하게 되었습니다. 하지만 이러한 Meta Programming에는 이 둘의 함수를 활용한 방법 이외에도 Compile함수를 사용하여 문제를 해결하는 방법도 있습니다. 대략적인 함수의 형태는

example_code = compile('원하는 코드', '<string>', 'eval' 혹은 'single')

라는 형태로 사용이 됩니다. 여기서 eval과 single의 경우 eval은 하나의 줄안에서 끝날 수 있는 식, single은 여러 식으로 이루어져있는 Statement를 사용한다고 생각하시면 편할 것 같습니다.

이러한 compile 방법이 가장 깊고 세세하게 컨트롤 할 수 있는 방법이지만, 저는 eval과 exec으로 문제를 해결 할 수가 있어서 많이 활용하지는 않았습니다.

 

많은 양의 변수를 선언해야하거나, string 포멧을 코드로 바꿔서 사용하고 싶으면, Meta Programming에 도전해보시는 건 어떨까요?

'Programming > Python' 카테고리의 다른 글

Python 다양한 버전 Jupyter notebook에서 사용하기  (2) 2020.05.22

+ Recent posts