들어가기 전에: 이 글은 제목대로 "아주 간단하게 시작"하는 한 번도 Python에서 문서화를 해본 경험이 없는 초보자를 위한 글입니다.
어떤 코드든 문서화에서 오는 이점은 상당히 많습니다. 당장 깃허브만 봐도 좋은 프로젝트들 중에서 문서화가 되어있지 않은 프로젝트는 찾기 힘든 수준이며 Python의 경우 Read the Docs, Ruby의 경우 RubyDoc.info 등 여러 문서화 포맷에 맞춰 자동으로 페이지를 생성해주는 곳도 많이 생겨났죠.
하지만 막상 나도 문서화좀 해보자! 하고 시작해보면 어디서부터 시작해야 하는지 잘 모르는 경우가 많습니다. 저도 Sphinx를 알기 전까지, RubyDoc을 알기 전까지는 (굳이 불필요하게) 일일히 페이지를 만들어서 Markdown 또는 생HTML 포맷으로 직접 입력했던 시절이 있었으니까요.
하지만 (당연하지만) 이 경우는 문서화라는 필수 작업이 고스란히 비용 증가로 이어지게 되고, 때에 따라 전체적인 개발 흐름이 늦어지게 됩니다. 게다가 개발자들은 디자인을 못하지만(일반화는 아닙니다 ㅠㅠ) 예쁜 것을 좋아하죠. 이래서 존재하는 것이 PyDoc, RubyDoc 이고 이를 여러 환경에서 볼 수 있게 도와주는 Python용 툴이 Sphinx 입니다. (다만 Sphinx, RubyDoc 등이 엄청 기대하시는 것보다 예쁘지는 않습니다...)
Sphinx는 어떤 놈인가?!
PEP 257, Docstrong Conventions. 문서화에 아주 중요하게 쓰일 docstring 컨벤션에 대해 정의한 PEP 입니다.
Sphinx는 바로 Python 코드 내에 들어간 docstring을 자동으로 문서화해주는 도구이며 아주 간단한 설정만 끝내면 손쉽게 문서를 만들 수 있습니다.
문서화 시작하기
만약 어떤 함수를 봤는데 이 함수가 어떤 함수인지 전혀 모르는 상태에서 함수를 구현한 사람에게 이 함수는 어떤 함수냐고 묻는다면 다음 3가지 질문을 할 수 있겠죠:
1. 이 함수가 하는 일은 무엇인가요?
2. 이 함수는 무엇을 입력받나요?
3. 그래서 최종적으로 이 함수는 무엇을 반환하나요?
간단하게 위 3 질문을 문서화한다고 생각하면 됩니다. 다음과 같이 말이죠:
def func(a, b):
"""이 함수는 a와 b를 더하는 함수입니다.
a, b는 int 형으로 입력되어야 합니다.
최종적으로 a와 b를 더한 결과값을 리턴합니다.
"""
이제 위 코드를 sphinx가 인식할 수 있도록 바꿔봅시다:
def func(a, b):
"""This function is intended to return the sum of two integer values.
:param int a: An integer to be computed
:param int b: An integer to be computed
:returns: The sum of two integer values
"""
이렇게 작성하고 Sphinx를 이용해 HTML로 변환하면 다음과 같이 뜨게 됩니다:
감이 잡히셨나요? 사실 이게 Sphinx의 전부입니다.
하지만 여기서 끝나면 많이 아쉽겠죠. 튜토리얼 식으로 시작해봅시다.
튜토리얼
우선 Sphinx 패키지를 pip로 설치해주세요.
그리고 다음과 같은 폴더 구조로 시작한다고 가정합니다:
그리고 터미널에 sphinx-quickstart를 입력하고 다음과 같이 진행합니다:
Welcome to the Sphinx 1.3.1 quickstart utility.
Please enter values for the following settings (just press Enter to
accept a default value, if one is given in brackets).
Enter the root path for documentation.
> Root path for the documentation [.]: docs/
You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
> Separate source and build directories (y/n) [n]:
Inside the root directory, two more directories will be created; "_templates"
for custom HTML templates and "_static" for custom stylesheets and other static
files. You can enter another prefix (such as ".") to replace the underscore.
> Name prefix for templates and static dir [_]:
The project name will occur in several places in the built documentation.
> Project name: my_package
> Author name(s): ssut
Sphinx has the notion of a "version" and a "release" for the
software. Each version can have multiple releases. For example, for
Python the version is something like 2.5 or 3.0, while the release is
something like 2.5.1 or 3.0a1. If you don't need this dual structure,
just set both to the same value.
> Project version: 1.0
> Project release [1.0]:
If the documents are to be written in a language other than English,
you can select a language here by its language code. Sphinx will then
translate text that it generates into that language.
For a list of supported codes, see
http://sphinx-doc.org/config.html#confval-language.
> Project language [en]:
The file name suffix for source files. Commonly, this is either ".txt"
or ".rst". Only files with this suffix are considered documents.
> Source file suffix [.rst]:
One document is special in that it is considered the top node of the
"contents tree", that is, it is the root of the hierarchical structure
of the documents. Normally, this is "index", but if your "index"
document is a custom template, you can also set this to another filename.
> Name of your master document (without suffix) [index]:
Sphinx can also add configuration for epub output:
> Do you want to use the epub builder (y/n) [n]:
Please indicate if you want to use one of the following Sphinx extensions:
> autodoc: automatically insert docstrings from modules (y/n) [n]: y
> doctest: automatically test code snippets in doctest blocks (y/n) [n]:
> intersphinx: link between Sphinx documentation of different projects (y/n) [n]: y
> todo: write "todo" entries that can be shown or hidden on build (y/n) [n]: y
> coverage: checks for documentation coverage (y/n) [n]:
> pngmath: include math, rendered as PNG images (y/n) [n]:
> mathjax: include math, rendered in the browser by MathJax (y/n) [n]:
> ifconfig: conditional inclusion of content based on config values (y/n) [n]:
> viewcode: include links to the source code of documented Python objects (y/n) [n]: y
A Makefile and a Windows command file can be generated for you so that you
only have to run e.g. `make html' instead of invoking sphinx-build
directly.
> Create Makefile? (y/n) [y]:
> Create Windows command file? (y/n) [y]:
Creating file docs/conf.py.
Creating file docs/index.rst.
Creating file docs/Makefile.
Creating file docs/make.bat.
Finished: An initial directory structure has been created.
You should now populate your master file docs/index.rst and create other documentation
source files. Use the Makefile to build the docs, like so:
make builder
where "builder" is one of the supported builders, e.g. html, latex or linkcheck.
그러면 다음과 같은 구조가 생깁니다:
이제 시작해보죠, __init__.py 파일을 열어 뭐하는 패키지인지 기록하고 버전 정보를 연동해봅시다. 탭은 보기 쉽도록 넣은 것이며 넣지 않아도 상관 없습니다.
버전 정보를 연동하기 위해 docs/conf.py (Sphinx 설정파일)을 열어서 다음과 같이 수정합니다:
이제 시작이긴 하지만 문서 파일을 한 번 생성해볼까요? 다음 명령어를 입력해주세요:
$ sphinx-apidoc -F -o docs my_package/ --separate
Creating file docs/my_package.rst.
Creating file docs/my_package.my_module.rst.
File docs/conf.py already exists, skipping.
File docs/index.rst already exists, skipping.
File docs/Makefile already exists, skipping.
File docs/make.bat already exists, skipping.
그러면 sphinx-apidoc이 알아서 모듈을 찾고 그에 맞는 적절한 파일을 생성하게 됩니다.
이제 웹 파일을 만들어봅시다. docs 폴더 안으로 들어가 make html을 입력합니다.
몇 가지 경고가 드는데 일단은 무시하고 docs/_build/html 폴더에 들어가 my_module.html을 열어봅시다.
신기하게도 자동으로 페이지가 이렇게 만들어졌네요. 이제 docs/my_package.rst 파일을 열어 뭐가 있는지 한 번 볼까요?
어라? 소스에 관련된 사항은 아무 것도 없고 그저 automodule이라는 이상한 것만 들어있네요. 저게 뭐하는걸까요?
눈치 빠른 분들은 벌써 아셨겠지만 자동으로 문서화를 도와주는 Sphinx 확장입니다. automodule, autoclass, autoexception, autofunction, automethod 등 다양한 것들의 docstring을 문서화할 수 있도록 도와주며 때에 따라 직접 원하는 콘텐츠를 넣을 수 도있습니다.
위 코드에 :members:, :undoc-members:, :show-interitance:가 보이는데 각각 하나하나가 옵션입니다. 따로 설명하지 않아도 이해하셨겠죠. ㅎㅎ. 저 외에도 :private-members:, :special-members:, :inherited-members: 등 다양한 옵션이 있습니다. 이 부분은 따로 찾아보시면 됩니다.
이제 Math라는 클래스 하나를 만들어봅시다. Math 클래스 인스턴스를 초기화 할 때 a, b 각각의 값을 입력받아두고 .sum() 메소드를 호출하면 더한 결과가, .subtract() 메소드를 호출하면 뺀 결과가 나오는 클래스를 my_package/my_module.py 안에 만들어봅시다.
여기서 _(underline)이 들어간 변수 또는 메소드는 private에 해당합니다. 이에 관해서는 나중에 제 Python 코딩 스타일을 소개드릴 때 설명드리도록 하겠습니다. 일단 간단히 설명드리자면 위에 있는 sphinx autodoc 확장의 :private-members: 옵션을 사용하면 private 변수, 메소드도 함께 표시됩니다.
이제 각 클래스에 어떤 내용을 남길지 생각해봅시다.
Math 클래스는 "a와 b를 입력받아 다양한 연산을 할 수 있도록 하는 클래스." 정도로 남길 수 있곘네요. 아 여기에 더해서 a와 b는 무조건 int만 받는다는 가정을 추가해봅시다. 그리고 각 메소드에 알맞는 설명을 남기면 다음과 같은 소스가 나오겠네요:
무언가 많이 추가된 것 같죠? 우선 아래 결과 스크린샷을 먼저 확인해 보고 하나하나 짚어봅시다. (그 전에 먼저 알아야 할 것은 Python Docstring은 기본적으로 reStructuredText 포맷을 사용합니다.)
- 기본적으로 Sphinx에서 __init__ 코드에 대한 설명은 Class 설명에 같이 포함시킵니다.
- 서로 다른 내용을 적을 때는 무조건 2번 엔터를 친 후 작성합니다. (분리)
- 하위 레벨의 내용을 작성할 때는 한 번 더 탭을 입력합니다.
- 미리 정해진 필드(param, returns 등)를 입력할 때는 :: 사이에 입력합니다.
- (:class:`Class`)를 통해 특정 클래스를 내용에 포함시킬 수 있습니다. object, int, bool, str 등의 Python 기본 자료형 또한 사용이 가능하며 intersphinx 확장을 사용할 경우에는 위 사진에서와 같이 외부 모듈 또한 링크가 가능합니다.
- 위에서 설명했지만 내부에서는 reStructuredText 포맷을 이용해 원하는 내용을 추가할 수 있습니다.
이 정도만 기억하고 계셔도 될 것 같습니다. 이외에는 reStructuredText 포맷이 개입하는 부분이 꽤 많으니 나머지 원하는 부분은 reStructuredText에 관한 자료를 찾아보시면 될 것 같네요.
트리구조 이해하기
toctree는 기존에 reStructuredText에서 문서간에 연결 또는 문서를 여러 파일로 나눌 수 없는 단점을 해결하기 위해 만들어진 Sphinx markup입니다.
이미 짜여져 있던 구조에 맞춰 작성한다면 - 다음과 같이 생겼습니다:
.. toctree::
:maxdepth: 2
my_package
my_package.my_module
이렇게 입력하면 어떻게 나타나는지 확인을 해봐야겠죠? docs/index.rst에 위 내용을 넣고 한 번 확인해봅시다:
Contents: 섹션 안에 넣었더니 위와 같이 뜨게 되었습니다. reStructuredText에서 Head 우선 순위는 === -> --- -> ~~~ 순서입니다. 순서대로 html태그 h1, h2, h3 이라고 보시면 되겠네요. 즉, toctree의 maxdepth 옵션은 바로 얼마나 깊게 헤드에 접근할 지를 정하는 것입니다.
이제 docs/my_package.rst를 다음과 같이 수정해봅시다:
.. automodule:: my_package
:members:
:undoc-members:
:show-inheritance:
Modules
-------
.. automodule:: my_package.my_module
:members:
점점 복잡해지는 것 같죠? 페이지를 뽑아 확인해봅시다:
위에서 작성한 대로 나왔습니다. 사실 크기 어려운 건 하나도 없습니다. 트리 구조만 제대로 파악하고 계시면 됩니다.
모듈의 autodoc은 docs/my_package.rst 에 작성헀으니 docs/my_package.my_module.rst 파일은 과감히 삭제해버리고 이제 doc/index.rst Contents 부분을 조금 고쳐볼까요? (참고: 링크되지 않은 파일이 있다면 페이지를 생성할 때 경고가 뜨게 됩니다.)
그리고 페이지를 뽑아내면 메인 페이지가 다음과 같이 바뀌게 됩니다:
이렇게 몇 가지 수정을 통해 손쉽게 원하는 형태의 페이지를 뽑아낼 수 있으며 여기서 설명한 toctree 이외에도 수많은 유용한 것들이 많이 있습니다. 예를 들어 "이 패키지는 완성되지 않은 패키지입니다." 라는 경고를 추가하고 싶다면? 다음과 같은 내용을 추가하면 됩니다:
.. warning::
이 패키지는 완성되지 않은 패키지입니다.
그러면 다음과 같이 뜹니다:
정리
이 글에서는 Sphinx에 대한 내용에 대해서만 설명했습니다. 여기서 설명한 내용 외에도 Sphinx는 테마를 입힐 수도 있고, 로고를 넣을 수도 있고 그 외에 수많은 것들이 가능합니다.
이 글을 읽는 분들이 조금이라도 문서화에 더욱 더 가까워졌으면 좋겠습니다. 여기서 사용된 예제는 Github에 올려두었으니 원하는 분들은 참고를 하시면 될 것 같습니다.
팁: Github Pages에 문서 배포하기
1. Github 에서 gh-pages 라는 이름으로 새로운 브랜치를 만들어줍니다:
2. docs 폴더 안에서 터미널을 열고 다음 내용을 차례로 입력해줍니다:
$ rm -rf _build/html
$ mkdir -p _build/html
$ cd _build/html
$ git init
Initialized empty Git repository in /Users/ssut/sphinx-tutorial/docs/_build/html/.git/
$ git checkout --orphan gh-pages
# 주소는 알아서 바꿔줍시다.
$ git clone [email protected]:ssut/sphinx-tutorial.git --branch gh-pages --single-branch .
Cloning into '.'...
remote: Counting objects: 70, done.
remote: Compressing objects: 100% (53/53), done.
remote: Total 70 (delta 17), reused 69 (delta 16), pack-reused 0
Receiving objects: 100% (70/70), 392.82 KiB | 480.00 KiB/s, done.
Resolving deltas: 100% (17/17), done.
Checking connectivity... done.$ rm .git/index
$ git clean -fdx
Removing .gitignore
Removing docs/
Removing my_package/$ touch .nojekyll
# (사용하고 있는 터미널 환경에 따라 ../.. 로 입력해야 할 수도 있습니다.)
$ cd ...
$ make html
...
$ cd _build/html
$ git add .
$ git commit -m "Initial documentation"
$ git push
제대로 푸시된 것을 확인하고 나서 https://아이디.github.io/레포명 으로 접속하시면
문서가 제대로 올라간 것을 확인하실 수 있습니다. 위 페이지의 주소는 https://ssut.github.io/sphinx-tutorial/ 입니다.
이후에 문서를 갱신할 때는 똑같이 make html을 한 후에 docs/_build/html 폴더에 들어가 변경 내용을 추가한 후에 푸시를 해주시면 됩니다.
알고 계십니까?
Python 공식 문서는 Sphinx로 만들어졌습니다(!).