Programming/Python

Python 계의 npm, Pipenv 첫 사용 후기

동건 2017. 12. 21. 21:12

virtualenv + virtualenvwrapper를 통해 Python 가상 환경을 사용해왔었다. 사용하면서 딱히 불편한 점은 없었지만 가끔은 npm과 같이 기본적으로 고립된 환경과 비교하게 되는 때도 있었다. 일일이 가상 환경을 만든 것을 기억하고 관리하기가 조금 귀찮은 순간도 있는 것이다. 이렇게 npm, cargo 등의 여러 모듈 관리자의 좋은 점들을 모아서 더 나은 개발 환경을 제공하기 위해 Pipenv가 탄생했다고 한다.

Requests 모듈의 창시자이자, flask 등의 유명한 Python 도구 개발에 참여하고, "The Hitchhiker's Guide to Python" 이라는 제목으로 책도 낸 Kenneth Reitz의 프로젝트 중 하나인 Pipenv를 눈 여겨 보고 있었다. 언젠가 한 번 사용해보고 괜찮으면 갈아타려고 생각만 하고 있었는데, Python 공식 튜토리얼에서도 Pipenv 사용을 권장하더니 어느 순간 PYPA 프로젝트로 흡수된 것을 발견! 이참에 사용해보고 후기를 올려 본다.

이 글에서는 나의 첫 사용 예제를 위주로 간단한 사용법만 설명한다. 그리고 Pipenv가 어떻게 작동하는지, 그 특징이나 장단점이 무엇인지 말해보려고 한다. 자세한 사용법은 길지 않은 문서를 보면 금방 알 수 있다. OS 환경은 Ubuntu 16.04이고 여기에 기본으로 설치되어 있는 Python 3.5 버전을 사용했다.


최근에 esl이라는 가상 환경을 두고 Jupyter Notebook으로 machine learning 공부를 차근 차근 다시 하고 있고, 관련 알고리즘을 Python 코드로 적당히 짜고 있기 때문에, Pipenv를 시험 삼아 적용해보기에 적당했다. ESL 프로젝트에서 나는 이런 저런 모듈을 설치해서 사용하고 있었다.

  • scipy
  • pandas
  • matplotlib
  • ipython
  • jupyter

전형적인 "data science for Python" 패키지들이다.


기존 가상 환경 도구 virtualenv + virtualenvwrapper

나의 다른 글 [1], [2]에서 사용법을 다루었기도 해서, 여기서는 간단하게 원리 위주로 정리하겠다.


가상 환경 시작

프로젝트를 시작하는 순간이 가상 환경을 새로 하나 장만하는 순간이다.

$ mkvirtualenv --python=$(which python3) esl
(esl) $

간단하게 설명하자면 시스템에 설치되어 있는 Python과 별개로 당신의 어느 공간에 최소한의 Python을 또 설치하는 거라고 말할 수 있다. 그리고 앞으로 Python을 실행할 때의 경로를 새로운 Python으로 물꼬를 돌려놓는 것이다. 그 증거가 $ 앞에 붙는 (esl) 표시이다.

낮은 레벨의 virtualenv만 사용한다면 source esl.venv/bin/activate를 통해 가상 환경으로 직접 들어가야 하지만 virtualenvwrapper는 그 작업을 대신 해주기 때문에 확실히 편하다.

esl이라는 이름의 가상 환경을 만든 후에 shell은 바로 그 가상 환경에 들어가 있음을 알 수 있다. pip list 로 체크해보면 깔끔한 새 Python 환경을 볼 수 있다.

예전에 이미 가상 환경을 만들어 두었었고, 그 이름이 esl이라면 다음과 같이 간단하게 가상 환경으로 진입할 수 있다.

$ workon esl


모듈 설치

(esl) $ pip install scipy pandas matplotlib ipython jupyter

이건 뭐 어디서나 똑같다. 어느 가상 환경에 있느냐가 중요한 것이고, 기존의 pip 도구를 쓰는 것은 똑같기 때문에 더 이상 설명할 것이 없다.


실행

이미 가상 환경 안에 들어와 있는 shell 이기 때문에 설치한 모듈을 실행하면 executable의 경로를 찾아서 실행시켜준다. 예를 들어 이 프로젝트에서 내가 하는 일은 단 하나다. Jupyter Notebook 서버를 실행해서 책 내용을 정리하고 기계 학습 알고리즘을 구현해보는 것이다.

(esl) $ jupyter notebook


종료

오늘의 공부를 끝내고 가상 환경에서 나가고 싶다면 가상 환경에게 한 마디만 하면 된다.

(esl) $ deactivate


삭제

이제 더 이상 esl 가상 환경을 쓰지 않겠다 다짐하고 지우고 싶다면 다음 명령어를 통해 할 수 있다.

$ rmvirtualenv esl


단점을 찾아보자면

실제로 virtualenv가 어떻게 작동하는지 알고 있어야 맘 편하게 쓸 수 있기 때문에 초심자라면 어느 정도 공부가 필요하긴 하지만, 어려운 개념은 아니기 때문에 금방 적응해서 사용할 수 있다. 사실 쓰다 보면 자연스럽게 다가온다.

Pipenv와 비교했을 때의 단점을 꼽자면 dependency 관리가 쉽지 않다는 점이다. jupyter 하나만 설치해도 딸려와서 설치되는 의존 모듈이 굉장히 많다. 이런 모듈을 여러 개 설치해서 쓰다가 requirements.txt를 오랜만에 열어보는 순간, 뭐가 내가 직접 설치한거고 뭐가 딸려온 건지 알기는 굉장히 어렵다. 나는 위에서 5가지 모듈을 설치했는데 pip list의 결과는 다음과 같이 나온다.

(esl) $ pip list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
astroid (1.5.3)
bleach (2.1.1)
cycler (0.10.0)
decorator (4.1.2)
entrypoints (0.2.3)
flake8 (3.5.0)
html5lib (1.0b10)
ipykernel (4.6.1)
ipython (6.2.1)
ipython-genutils (0.2.0)
ipywidgets (7.0.5)
isort (4.2.15)
jedi (0.11.0)
Jinja2 (2.10)
jsonschema (2.6.0)
jupyter (1.0.0)
jupyter-client (5.1.0)
jupyter-console (5.2.0)
jupyter-core (4.4.0)
lazy-object-proxy (1.3.1)
MarkupSafe (1.0)
matplotlib (2.1.0)
mccabe (0.6.1)
mistune (0.8.1)
nbconvert (5.3.1)
nbformat (4.4.0)
notebook (5.2.1)
numpy (1.13.3)
pandas (0.21.0)
pandocfilters (1.4.2)
parso (0.1.0)
pexpect (4.3.0)
pickleshare (0.7.4)
pip (9.0.1)
prompt-toolkit (1.0.15)
ptyprocess (0.5.2)
pycodestyle (2.3.1)
pyflakes (1.6.0)
Pygments (2.2.0)
pylint (1.7.4)
pyparsing (2.2.0)
python-dateutil (2.6.1)
pytz (2017.3)
pyzmq (16.0.3)
qtconsole (4.3.1)
scipy (1.0.0)
setuptools (38.2.1)
simplegeneric (0.8.1)
six (1.11.0)
terminado (0.8)
testpath (0.3.1)
tornado (4.5.2)
traitlets (4.3.2)
wcwidth (0.1.7)
webencodings (0.5.1)
wheel (0.30.0)
widgetsnbextension (3.0.8)
wrapt (1.10.11)

나는 마음이 불편해진다.

한 가지 불편한 점을 또 들어보자면, 실제로 django나 flask 등을 이용해 웹서버를 개발한다거나, 여러 사람이 사용할 수 있도록 기똥찬 오픈 소스 프로젝트를 개발한다고 싶다면 모듈 관리는 더욱 복잡해진다. 아래와 같이 여러 requirements 파일을 만들게 된다.

  • requirements.txt
  • dev-requirements.txt
  • test-requirements.txt
  • ci-requirements.txt
  • 설마 더 필요한가?

"내가 이걸 언제 설치했었지..." 라는 생각을 하게 만드는 모듈 리스트가 세네 개가 되는 것이다. 그리고 이 텍스트 파일은 pip freeze > requirements.txt로 생성할 수 있지만, 손수 관리하기 쉬운 녀석은 아니다. Kenneth Reitz가 불평하는 것도 들어보자.



새로운 프로젝트 관리 도구, Pipenv


Pipenv는 npm 같이 기본적으로 프로젝트 단위로 고립된 환경을 구현했다. npm의 package.json의 역할을 Pipfile이 담당하는데, requirements.txt보다 한 차원 높은 친구다.


설치

Python 3에서의 사용을 권장하고 있다. 시스템 레벨에서 설치했다.

$ sudo pip3 install pipenv


시작

원하는 위치에서 pipenv 명령을 통해 가상 환경을 시작한다. 아니, Python 프로젝트를 시작한다.

$ cd dev/python/esl
$ pipenv --three
Virtualenv already exists!
Removing existing virtualenv…
Creating a virtualenv for this project…
Using /usr/bin/python3 to create virtualenv…
⠋Already using interpreter /usr/bin/python3
Using base prefix '/usr'
New python executable in /home/don/.virtualenvs/esl-S-0IVzfS/bin/python3
Also creating executable in /home/don/.virtualenvs/esl-S-0IVzfS/bin/python
Installing setuptools, pip, wheel...done.

Virtualenv location: /home/don/.virtualenvs/esl-S-0IVzfS
Creating a Pipfile for this project…

알아서 virtualenv를 생성한다. 나는 진작에 이 경로를 프로젝트 root로 두고 pipenv 환경을 사용했었기 때문에 이 경로에 연결되어 있는 가상 환경이 감지된다. 그래서 가상 환경이 이미 존재한다고 알려주는 첫 문장을 볼 수 있다. 그리고 pipenv --three 명령은 어떤 상황에서든 새롭게 시작하라는 명령이기 때문에, 기존에 존재하는 가상 환경은 지워버리고 새로운 가상 환경을 만드는 점을 알아두자.

마지막 문장에서 Pipfile을 만들었다고 말해준다. 어떻게 생겼는지 보자.

$ cat Pipfile
[[source]]

verify_ssl = true
name = "pypi"
url = "https://pypi.python.org/simple"


[requires]

python_version = "3.5"


[dev-packages]



[packages]


Dependency의 소스 정보, Python 버전 정보, 그리고 (지금은 비어있지만) 설치된 모듈 목록을 보게 된다. 보통 모듈과 개발용 설치가 구분되어 있는 점도 주목할 만한 점이다. 이 Pipfile이 불완전한 requirements.txt를 대체할 뿐만 아니라 더 넓은 범위의 프로젝트 정보를 담는 명세서가 될 것이다. 더욱이 중요한 점은 내가 앞으로 직접 손 댈 일은 전혀 없다는 점 (그래서도 안되고).


모듈 설치

pip 대신 pipenv

$ pipenv install scipy
Installing scipy…
Collecting scipy
  Using cached scipy-1.0.0-cp35-cp35m-manylinux1_x86_64.whl
Collecting numpy>=1.8.2 (from scipy)
  Using cached numpy-1.13.3-cp35-cp35m-manylinux1_x86_64.whl
Installing collected packages: numpy, scipy
Successfully installed numpy-1.13.3 scipy-1.0.0

Adding scipy to Pipfile's [packages]…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (f8bd49)!


scipy를 설치하는 명령이 가상 환경에 들어가지 않은 shell에서 돌아갔다. Pipenv는 사용자가 가상 환경에 대한 생각을 전혀 할 필요가 없는 환경을 제공하는 것이다. 내가 혹시나 딴 곳에 정신이 팔려있다가 가상 환경에 안 들어가고 jupyter를 시스템에다가 설치해버린다면? 나는 너무나 화가 나서 Python을 새로 설치해버릴 수도 있다. Pipenv는 이런 가능성을 아예 없애버린다.

아까랑 같이 5개 모듈을 설치한 뒤에 한 번만 더 Pipfile을 들여다보자.

[[source]]

verify_ssl = true
name = "pypi"
url = "https://pypi.python.org/simple"


[requires]

python_version = "3.5"


[dev-packages]



[packages]

scipy = "*"
pandas = "*"
matplotlib = "*"
ipython = "*"
jupyter = "*"


설치할 때 내가 버전 정보를 주지 않고 무조건 최신 버전의 모듈을 설치 했기 때문에 PIpfile에서도 그 내용을 담고 있다. 특정 버전을 기록하고 싶다면 설치할 때 잘 하면 된다.

Pipfile과 같이 한 쌍을 이루는 파일이 하나 더 있는데, Pipfile.lock 이다. Pipfile이 사용자를 위한 파일이라면, lock은 시스템을 위한 파일이다. lock 파일은 Pipfile이 담고 있는 정보를 훨씬 자세히 기록해두고 있으며, 설치된 모든 모듈의 버전을 명시하고 있다. 텍스트의 양이 많으므로 이 곳에 쓰진 않으려고 한다. 직접 구경해보시길 바란다.


정보 조회

이 프로젝트와 연결된 가상 환경이 어디 있는지 알고 싶다면

$ pipenv --venv
/home/don/.virtualenvs/esl-S-0IVzfS


pip list, pip freeze 같은 걸 보고 싶다면

$ pipenv lock -r


아래 명령어는 설치된 모듈들의 의존 관계를 매우 파악하기 쉽게 들여쓰기를 해서 보여준다, 개꿀이기 때문에, 출력 양이 많더라도 실어두고 싶다.

$ pipenv graph
jupyter==1.0.0
  - 너무 많아서 생략...
matplotlib==2.1.1
  - cycler [required: >=0.10, installed: 0.10.0]
    - six [required: Any, installed: 1.11.0]
  - numpy [required: >=1.7.1, installed: 1.13.3]
  - pyparsing [required: !=2.1.2,!=2.0.4,!=2.1.6,>=2.0.1, installed: 2.2.0]
  - python-dateutil [required: >=2.0, installed: 2.6.1]
    - six [required: >=1.5, installed: 1.11.0]
  - pytz [required: Any, installed: 2017.3]
  - six [required: >=1.10, installed: 1.11.0]
pandas==0.21.1
  - numpy [required: >=1.9.0, installed: 1.13.3]
  - python-dateutil [required: >=2, installed: 2.6.1]
    - six [required: >=1.5, installed: 1.11.0]
  - pytz [required: >=2011k, installed: 2017.3]
scipy==1.0.0
  - numpy [required: >=1.8.2, installed: 1.13.3]


보안 취약점 체크라는데, 나는 이 쪽은 잘 모르겠다.

$ pipenv check
Checking PEP 508 requirements…
Passed!
Checking installed package safety…
All good!


장단점을 찾아보자면

pipenv graph와 같은 기능을 보면 모듈 관리가 훨씬 깔끔하게 되는 것을 알 수 있다. 내가 설치한 모듈, 그 의존 모듈, 테스팅 도구 등의 개발 모듈 등을 구별할 수 있고, Pipfile은 모듈 리스트 뿐만 아니라 Python 버전 정보와 모듈 소스 위치도 담고 있기 때문에 프로젝트의 전반적인 관리가 가능해진다.

그리고 중요한 점은 쉽다!는 점이다. 특히 npm처럼 프로젝트 폴더 단위의 환경에 익숙한 사람이 적응하기 편하고, Python 가상 환경의 작동 원리를 몰라도 원래 npm이 그러는 것처럼 사용할 수 있다. Kenneth Reitz가 좋아하는 문구처럼 "pip for human"인 것이다.

하이/로우 레벨의 취향에 따라 선호도는 다를 수 있다. 저 레벨의 가상 환경을 빠삭하게 파악하고 있다면 Pipenv의 필요성을 느끼지 못할 가능성도 높다. 반대로, 가상 환경에 대해 잘 모르고 Python을 시작하게 된다고 생각할 수도 있다.

뭐든지 극단적인 것은 안 좋은 법, 양 쪽 모두 알아두는데 많은 시간이 걸리진 않는다.

아, 또 한 가지 단점을 꼽자면 꽤 느리다. 그 만큼 하는 일이 많겠지만 답답한 바가 없지 않다.


정리

Pipenv는 결국 virtualenv를 기반으로 두고, 프로젝트 폴더를 기준으로 자동으로 고립된 가상 환경을 구축함으로써 기존 환경보다 안전하고 편리한 환경을 만들어주는 관리자라고 생각한다. 나는 꽤나 매력적인 도구라고 생각이 들었다, 특히 협업에 있어서.

내가 직접 경험한 것은 아니지만 pyenv, tox 등의 여러 버전의 Python을 넘나드는 도구나 Travis-CI와 같은 DevOps 서비스와 연동해서 사용할 수도 있다니 더욱 높은 레벨의 프로젝트 관리 도구로서 기능할 수 있다. 만약 그런 것들을 경험하게 된다면 또 후기를 남기고 싶다.

반응형