（インターン向けに書いた）Pythonパッケージを作る方法

# この記事は何？

- Pythonパッケージの作り方を説明する
- Pythonパッケージを作るときに意識して欲しいことを説明する
- この記事はポエムです

# これまでのあらすじ

インターンを迎える予定なのですが、彼らはパッケージを作ったことがないそうです。

一方で、企業としては、パッケージ化までしてくれないと、実務に使うまで時間がかかってしまって大変です。

そこで、社内向けに「Pythonパッケージの作り方」という文書を書きました。これをインターンの人に読んでもらっていい感じのパッケージを作ってもらうぜ！という都合の良い目論見です。

しかし、 私もいままでパッケージ化のノウハウをきちんと体系的に勉強したわけではないので、イマイチ不安です。そこで、Qiitaにポエム投稿して、ポエム修正をしてもらおうと思ったわけです。

ところどころ日本語が変なのはご容赦ください。日本語に不自由してます。[^1]

# この記事を読む人の前提

つまりはインターン生が持っている前提知識です。

- Pythonを日常的に研究に利用している。
- 機械学習とか自然言語処理の前処理作業はできる。（[自然言語処理１００本ノック](http://www.cl.ecei.tohoku.ac.jp/nlp100/)はクリアしているそうです）
- パッケージはまだ作ったことがない。
- Gitは使える。

- - -

↓ ここからポエム

# Dockerで環境構築

Dockerとは、すごく簡単に言えば「VM（バーチャルマシン）を簡単に作れるプラットフォーム」です。まずはこれだけを理解しておけば問題ないでしょう。

DockerでVMを作るときには、とりあえず次の知識だけを押さえておけばOKです。

- DockerイメージとDocker containerの概念
- Dockerfileの書き方
- Docker buildのやり方
- Docker containerの起動方法とcontainer login方法

長くても半日あればすべてを把握できます。

コマンドなどの実践的な知識は他の記事にゆずります。
この記事では「Dockerを使うとなんで嬉しいのか？」だけを書き残しておきます。

まずインターン生であるあなたには次のようなメリットがあります。
- sudoコマンドを好きなだけ使える。
- VMマシン環境をぶっ壊しても、他の人は困らない。精神的によろしい。
- ポートフォリオ、履歴書に書けるスキルが１つ増える。

一方で、開発チーム、つまり企業側のメリットは次のとおりです。

- 環境再現がものすごく楽になる。
- sudoコマンド実行権限・実行コミュニケーションのことをすべて考えなくても良い。

#### メンターの方へ

もし、あなたがインターン生のメンターをしていて、かつ、Dockerのことを知らなかったとしたら、勉強しましょう。
正直いって、AIエンジニアであっても、データサイエンティストであってもDockerは「知ってて当たり前」知識になりつつあります。未だにDockerを使えないエンジニアとか・・・・（笑）

煽り文はこれくらいにしておいて。
とりあえず、次のことをすればLinux環境でDockerが使えるようになります。
1. Dockerをインストールする。
2. インターン生のアカウントをDocker groupに追加する。


# ディレクトリ構成

実は、Python界には公式のディレクトリ構造はないです。いい加減なディレクトリ構成をしていても、コードは動きます。しかし、それでは、他の開発者にはとてもわかりづらいコードになってしまいます。だから、わかりやすいディレクトリ構造が必要なのです。

なので、私は[このブログ](http://docs.python-guide.org/en/latest/writing/structure/)の方法に従うのをオススメします。このブログでは、[Githubのリポジトリ](https://github.com/kennethreitz/samplemod)をクローンしています。ここでも、このリポジトリをクローンしましょう。

`MacBook-Pro% git clone git@github.com:kennethreitz/samplemod.git`

# パッケージの名付け規則

Pythonのパッケージ命名規則は__すべて小文字__で__アンダースコア区切り__が推奨されてます。[Googleのスゴい人](https://google.github.io/styleguide/pyguide.html#Naming)と[Pythonの中の人たち](https://www.python.org/dev/peps/pep-0008/#package-and-module-names)がそう言っているのだから、従った方が無難でしょう。

# パッケージを作る準備をする

今回は`hoge_hoge`というパッケージを作ることにしましょう。cloneしてきたディレクトリもパッケージ名に合わせるようにすると良いです。
また、cloneしてきたディレクトリは元のgitファイルを持っています。なので、まずはgitファイルを消してしまいましょう。
その後、スクリプトを置くディレクトリもパッケージ名と同じ名前にします。つまり`hoge_hoge`ですね。


```
MacBook-Pro% mv samplemod hoge_hoge
MacBook-Pro% cd hoge_hoge
MacBook-Pro% rm -rf .git
MacBook-Pro% mv sample hoge_hoge
```

上のコマンドを実行した後には、ディレクトリ構成はこんな感じになっているはずです。
(@shn さんのご指摘を受けて、変更しました。 `requirement.txt` を削除しています。)

```
MacBook-Pro% tree   
.
├── LICENSE
├── Makefile
├── README.rst
├── docs
│   ├── Makefile
│   ├── conf.py
│   ├── index.rst
│   └── make.bat
├── hoge_hoge
│   ├── __init__.py
│   ├── core.py
│   └── helpers.py
├── setup.py
└── tests
    ├── __init__.py
    ├── context.py
    ├── test_advanced.py
    └── test_basic.py
```

次のあなたのパッケージ用に、新しくgitを初期化しましょう。

```
MacBook-Pro% git init
MacBook-Pro% git add .
MacBook-Pro% git commit -m 'first commit'
```

これで準備ができました。

#### ついでに・・・

pythonパッケージのディレクトリ構成を初期化するようなツールはいくつかあります。[cookiecutter](https://pypi.python.org/pypi/cookiecutter/)とかね。でも、あまりオススメしません。余計なファイルが作られすぎるからです。


# パッケージの情報を書く

パッケージの情報とは、主に機械向けの情報です。
どんなパッケージ依存関係があるのか、バージョンはいくつか？などなどです。

このセクションでは、いつくかの方法を紹介します。


<details>
  <summary>昔話「昔はsetup.pyというものがあったんじゃ」</summary>

以下のお話は2010年ごろから2020年頃までをPythonと共に生きた人の思い出です。
2023年、もはやsetup.pyを採用する意味はほとんどありません。
poetryを使いましょう。

## setup.pyを書く

`setup.py`というのはパッケージに必須のファイルです。このファイルに、これから作るパッケージの依存情報、バージョン情報、パッケージ名を書きます。

このポエムでは、最低限のフィールドだけを説明します。他のフィールドにも興味があれば、[Python中の人の経典](https://docs.python.org/3/distutils/setupscript.html)を見ましょう。

なお、`setup.py` を直接的に書く方法はだんだんと時代おくれになってきています。2021年での話です。[^5]
もっと最新の方法を知りたい人は、`poetry`を検討するといいでしょう。次のサブセクションで書きます。
補足です。
`poetry`を使ったとしても、`setup.py`の存在は消えません。`poetry`が`setup.py`を自動生成するだけです。
このことを考えると、`setup.py`の構成を知っておくのも悪くはないでしょう。

### 最低限、書いて欲しいフィールド

元の`setup.py`の中身はこのようになっているはずです。

```py
setup(
    name='sample',
    version='0.0.1',
    description='Sample package for Python-Guide.org',
    long_description=readme,
    author='Kenneth Reitz',
    author_email='me@kennethreitz.com',
    url='https://github.com/kennethreitz/samplemod',
    license=license,
    packages=find_packages(exclude=('tests', 'docs'))
)
```


| フィールド名 | 説明 | もっと詳しい説明 |
|:----------------:|:-----------------:|:------------------:|
| name | パッケージの名前を書きます ||
| version | 現在のバージョン情報を書きます　| |
| description | 「このパッケージは何をするか？」を簡潔にかきましょう | |
| install_requires | 依存パッケージ情報を書きます | [これ](http://python-packaging.readthedocs.io/en/latest/dependencies.html#packages-not-on-pypi) |
| dependency_links | 依存パッケージがpypiに存在してない時に書きます。 | [これ](http://python-packaging.readthedocs.io/en/latest/dependencies.html#packages-not-on-pypi) |

__特に、絶対に__書かないといけないのは、`install_requires`です。あなたのパッケージを使う開発者は「どのパッケージに依存してるか？」なんてことを知りません。この情報がないと他の開発者は困ってしまいます。
（`dependency_links`は2019/1ごろの[pip更新で削除](https://github.com/pypa/pip/pull/6060)されることになりました。代替手段は後述します。）

たぶん、あなたもいままでに`python setup.py install`をしたことがあるでしょう。このコマンドが動くのはきちんと依存情報が書かれているからです。

### Pypiに存在するパッケージへの依存情報を書く

`install_requires`のフィールドにリストで依存しているパッケージを記述します。たとえば、`numpy`が必要なら、`numpy`と書きます。

```py
setup(
    name='sample',
    version='0.0.1',
    description='Sample package for Python-Guide.org',
    long_description=readme,
    author='Kenneth Reitz',
    author_email='me@kennethreitz.com',
    install_requires=['numpy'],
    url='https://github.com/kennethreitz/samplemod',
    license=license,
    packages=find_packages(exclude=('tests', 'docs'))
)
```


### 社内パッケージへの依存情報を記述する

一般企業ならば、社内で独自に開発したパッケージを運用していることでしょう。そいったパッケージはPypiにはないので、setup.pyにパッケージが存在している場所を教えてやらないといけません。`dependency_links`はそんな時に必要です。

#### 2019/1ごろのdependency_links書き方

`dependency_links`は今後、利用しない方がいいかもしれません。理由はpipでは`dependency_links`が[廃止されているから](https://github.com/pypa/pip/pull/6060)です。

代替手段はrequirements.txtに記述する方法です。

`requirements.txt`は`install_requires`に代替手段のパッケージ依存記述として、長く利用されてきました。

勝手な意見ですが、かなり一般的な方式だと思います。

`requirements.txt`にこんな感じで依存情報を記述します。

```
# pypiに存在するパッケージ名はそのまま書いていい
numpy
# バージョン指定したいときは等式を使う
scipy == 1.2.2
# pypiに存在しないパッケージの時は git://リポジトリのURL.git
git://git@github.com/foo/foo.git
# プライベートリポジトリの場合は+sshをつける git+ssh://git@github.com/foo/foo.git
git+ssh://git@github.com/foo/foo.git
```

で、setup.pyの中にrequirements.txtを読み込む処理を書きます。

[このコード](https://github.com/pypa/pip/issues/4187#issuecomment-452862842)のまんまです。

```python
import os, sys
from setuptools import setup, find_packages

def read_requirements():
    """Parse requirements from requirements.txt."""
    reqs_path = os.path.join('.', 'requirements.txt')
    with open(reqs_path, 'r') as f:
        requirements = [line.rstrip() for line in f]
    return requirements

setup(
    ..., # Other stuff here
    install_requires=read_requirements(),
    )
```


#### 2019/1より前のdependency_links書き方

Githubを使ってると想定し、gitの例だけを書きます。他のバージョン管理システムの場合は[StackOverFlowの記事](http://stackoverflow.com/questions/3472430/how-can-i-make-setuptools-install-a-package-thats-not-on-pypi)を見ましょう。

基本的な式は

`git+ssh://git@URL-TO-THE-PACKAGE#egg=PACKAGE-NAME`

です。
例えば、いま、`unko`というパッケージの依存情報を書きたいとします。その時は

`git+ssh://git@github.com/our-company-internal/unko.git#egg=unko`

です。
もし、特定のバージョンで固定しておきたいなら、`@`の後ろにブランチ名かタグ名を指定できます。

`git+ssh://git@github.com/our-company-internal/unko.git@v0.1#egg=unko-0.1`

`@v0.1` はgithubのブランチ名かタグ名です。`-0.1`はパッケージのバージョンです。

### name, authorなどを書き換える

最後に、`name`, `author`などの情報を書き換えます。
自分の名前を書くと、たぶん愛着がわきますよ ;)

最終的にできあがった`setup.py`はこうなります。

```python
import os, sys
from setuptools import setup, find_packages

def read_requirements():
    """Parse requirements from requirements.txt."""
    reqs_path = os.path.join('.', 'requirements.txt')
    with open(reqs_path, 'r') as f:
        requirements = [line.rstrip() for line in f]
    return requirements

setup(
    name='hoge_hoge',
    version='0.0.1',
    description='Sample package for Python-Guide.org',
    long_description=readme,
    author='Kensuke Mitsuzawa',
    author_email='me@kennethreitz.com',
    install_requires=read_requirements(),
    url='https://github.com/kennethreitz/samplemod',
    license=license,
    packages=find_packages(exclude=('tests', 'docs'))
)
```


</details>



## READMEに書く

依存パッケージ情報をREADMEに書いてしまう方法です。
依存パッケージがせいぜい２，３個ならばこの方法でも良いとおもいます。

でも、４個以上の依存パッケージがあるなら、この方法はやめましょう。


## poetryを使う

2021年になって、パッケージ管理システムにpoetryを使うプロジェクトが増えてきました。

poetryを簡潔に一言でいうと「setup.pyを上に乗っかってるイケてるパッケージ管理システム」と思えば良いです。

Pythonパッケージ管理システムがいくつか並列し、少し理解が大変・・・という方は[この記事](https://vaaaaaanquish.hatenablog.com/entry/2021/03/29/221715)を読むと良いです。

この記事でpoetryをがんばって説明しても、大して意味はなさそうです。
なので、ざっと手順だけを書いて、poetry使い方記事のリンクを貼っておきます。

1. `pyproject.toml` を書く。これは`setup.py`に相当するファイルです。
2. パッケージ開発する
3. おしまい

基本的な`poetry`の使い方は[この記事](https://qiita.com/ragnar1904/items/0e5b8382757ccad9a56c)が良さそうです。

ひとつだけ大切なことを書き残しておきます。パッケージ追加時は**必ず** `poetry add <パッケージ名>` コマンドを使いましょう。 `pip install <パッケージ名>` をやめましょう。 `pip install` は「パッケージ依存関係を壊す覚悟はできている！」という気持ちと共に実行するものだと思ってください。

社内インターンの文脈で`poetry`にコツが必要なので、こうした情報だけ記載しておきます。

### 社内のプライベートリポジトリに依存してるんだけど

Githubのプレイベートリポジトリなどへの依存情報を記述可能です。
[このように書きます。](https://www.keisuke69.net/entry/2021/01/10/003548#:~:text=2.%20github%E3%81%AA%E3%81%A8%E3%82%99%E3%81%AE%E3%83%AA%E3%83%9B%E3%82%9A%E3%82%B7%E3%82%99%E3%83%88%E3%83%AA%E3%82%92%E7%9B%B4%E6%8E%A5%E5%8F%82%E7%85%A7)

### 作ったパッケージをローカルにインストールしたいんだけど

状況的には次の様子です。
自分のローカルでパッケージAを作った。
次に自分のローカルでパッケージBを作る。
パッケージBはパッケージAに依存させたい。

パッケージAとBはどちらもプライベートにしたい。
さて、どうしましょうか？

このようにします。

```
poetry install
poetry build --format sdist
tar -xvf dist/*-`poetry version -s`.tar.gz
cd [パッケージ名のディレクトリをここに]* && pip install -e .
```

2021年現在、poetryにはこの要望を叶えるコマンドを持っていない様子です。
今後に期待しましょう。

ぼくは一連の流れを`Makefile`にまとめてます。[参考にどうぞ](https://github.com/Kensuke-Mitsuzawa/sumo_docker_pipeline/blob/main/Makefile)

# コードを書く　

コードの書き方にもいろいろあるのですが、今回は細かいことは言いません。
いままで通りに書いてみてください。
でも、[__「覚えておいて欲しいこと」__](http://qiita.com/Kensuke-Mitsuzawa/items/7717f823df5a30c27077#%E8%A6%9A%E3%81%88%E3%81%A6%E3%81%8A%E3%81%84%E3%81%A6%E3%81%BB%E3%81%97%E3%81%84%E3%81%93%E3%81%A8)にできるだけ従って欲しいです。

# テストを書く

テストは必ず書いてください。

## なにをどうテストすればいいんだ？

この記事の文脈では２種類のテストに分類できます。

1. システムとしてのテスト
2. アルゴリズムとしてのテスト

「システムとしてのテスト」は**エラーがないことの証明**が目的です。
「アルゴリズムとしてのテスト」は**入力値に対して出力値が想定範囲内と証明すること**が目的です。

「システムとしてのテスト」はそんなに難しい話じゃないです。
考えられる入力ファイルやフォーマットを入力して、エラーがでなければOKです。

「アルゴリズムとしてのテスト」は、機械学習の文脈を考えるといいでしょう。
だいたいのケースでは、実装するアルゴリズムの論文があるはずです。
その論文の中では、たいてい公開データでの数値が書いてあります。
と、いうことは「公開データを入力して、出力数値がだいたい同じであればOK」というテストを書けばよいです。

機械学習のProductionを考えると、もっとテストの話は複雑になります。
が、それはインターンの話では関係ないでしょう。
もっと知りたい方はメンターに聞いてみるといいでしょう。[^8]


## どんな手順でテストすればいいんだ？

ぼくのアプローチを書いておきます。
いい加減だけど、すばやく始めるには良いアプローチだと思ってます。

1. スクリプトの構成をだいたい考える。
2. スクリプトを書き始める。
3. スクリプトの中に `test()` を作っておく。`if __name__ == '__main__'` から`test()` を呼ぶ
4. 「システムとしてのテスト」を意識して、テスト文を書いておく。だいたいのケースで、assert文と型チェックだけで十分。
5. 余裕ができたら、「アルゴリズムとしてのテスト」も書いてみる
6. `test()` をテストスクリプトへ移植する

テストスクリプトへの移植はこのあとのセクションで書きます。


## １つのスクリプト、１つのテストスクリプト

パッケージのモジュールスクリプトを１つ作ったら、テストスクリプトも１つ作りましょう。その方が、対応関係がはっきりとわかって、メンテナンス性がよくなります。いまは、`hoge_hoge/core.py`をモジュールスクリプトとして作成したとしましょう。

```
├── hoge_hoge
│   ├── __init__.py
│   ├── core.py
```

`core.py`のテストスクリプトは`tests/`配下に置きます。`test_`というprefixをつけて、`test_core.py`とすると、わかりやすいでしょう。

```
└── tests
    ├── __init__.py
    ├── test_core.py
```


## pytestを使う

`pytest` はPythonテストフレームワークのひとつです。
インストールが必要なので、`poetry add pytest`をしておきましょう。

基本的な使い方は[他の記事](https://qiita.com/everylittle/items/1a2748e443d8282c94b2)に譲ります。

意識しておきたいことは２つだけ。

1. テスト関数名は `test_` prefixから始める
2. テストスクリプトは `test_` prefixから始める

テストを関数で表現できるので、[前のセクションで書いたtestのコピペ](https://qiita.com/Ultra-grand-child/items/7717f823df5a30c27077#%E3%81%A9%E3%82%93%E3%81%AA%E6%89%8B%E9%A0%86%E3%81%A7%E3%83%86%E3%82%B9%E3%83%88%E3%81%99%E3%82%8C%E3%81%B0%E3%81%84%E3%81%84%E3%82%93%E3%81%A0)に適してます。

ここではインターンと機械学習の文脈でpytestのヒントを書いておきます。

### サンプルスクリプトをテストしたいんだけど？

サンプルスクリプトって必要ですよね。
でも、サンプルスクリプトというからには、`test_`っていう名前にはしたくない。
かといって、サンプルスクリプトが動かないのも困る。

解決策は`pytest [サンプルスクリプト].py` をするだけです。

### jupyter notebookをテストしたいんだけど？

機械学習プロジェクトだけjupyter notebookってよく使いますよね。
サンプルの表現にはぴったりだと思います。

さて、jupyter notebookのコードが動かなかったら困りますよね。
テストしておきたいですよね。
できます。

`nbmake` というパッケージを使うだけです。

使い方はとっても簡単。[このセクション](https://github.com/treebeardtech/nbmake#quick-start)を見ましょう。

### 昔話「昔はunittestというものがあったんじゃ」


<details>
  <summary>昔話を読む</summary>


## (別解)unittestを使う

Pythonでは`unittest`という標準のテストフレームワークがあります。
以前は、ぼくもunittestで書いてました。しかし、文字量の差からpytestに切り替えていきました。
「テスト」という点ではunittestもpytestも変わりません。書き方が違うだけです。

unittestを使う場面はおそらく`pytest`をインストールできないっっ！という場面だと思います。

unittestの詳細は割愛しますが、[この記事-JP](http://futurismo.biz/archives/4395)や[この記事-EN](https://pymotw.com/2/unittest/)を見て、使い方をマスターしましょう。

テストコードを配置する場所は`tests/`ディレクトリの配下です。

```
├── setup.py
└── tests
    ├── __init__.py
    ├── context.py
    ├── test_advanced.py
    └── test_basic.py
```

### test_core.pyの例


テストスクリプトを書くときに覚えておいて欲しいことは２点です。

- テストクラス名はキャメルケース
- `test_`というprefixがメソッド名の前に必要。このprefixがないとtestが無視されてしまいます。

テストがかけたら、スクリプトを実行してみましょう。`python test_core.py`.
または_pycharm_であれば、通常の実行ボタンを押せば、テストが走ります。

```py
import unittest


class TestCore(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # procedures before tests are started. This code block is executed only once
        pass

    @classmethod
    def tearDownClass(cls):
        # procedures after tests are finished. This code block is executed only once
        pass

    def setUp(self):
        # procedures before every tests are started. This code block is executed every time
        pass

    def tearDown(self):
        # procedures after every tests are finished. This code block is executed every time
        pass

    def test_core(self):
        # one test case. here.
        # You must “test_” prefix always. Unless, unittest ignores
        pass

if __name__ == '__main__':
    unittest.main()
```

### すべてのテストケースをtest_allに書く

（@podhmo氏のコメントを受けて更新しました。感謝！）

いくつかのテストスクリプトが書けたら、`python setup.py test`を実行可能なようにしましょう。

大きな変更をした場合はかならずこのコマンドを実行するクセをつけてください。ついついて頭から抜けていた問題を発見することが多いです。パッケージの改良作業などをしていると、「あ、ここも変更しとこ」と変更して、テスト実行を忘れてしまっていることはよくあります。

また、他の開発者も自分の環境で簡単にテストできるので、非常に助かります。


### 1: setup.pyを編集する

いまのテストディレクトリはこのような構成になっています。

```
└── tests
    ├── __init__.py
    ├── test_all.py
    ├── test_advanced.py
    └── test_basic.py
```

setup.pyにテストディレクトリが存在する場所を指定します。
setup.pyは自動的にテストクラスを読みこんでテストを実施してくれます。

```py
setup(
    name='hoge_hoge',
    version='0.0.1',
    description='Sample package for Python-Guide.org',
    long_description=readme,
    author='Kensuke Mitsuzawa',
    author_email='me@kennethreitz.com',
    install_requires=['numpy', 'unko'],
    dependency_links=['git+ssh://git@github.com/our-company-internal/unko.git#egg=unko'],
    url='https://github.com/kennethreitz/samplemod',
    license=license,
    packages=find_packages(exclude=('tests', 'docs')),
    test_suite='tests'
)
```

### 2: 全テスト実行

`python setup.py test`を実行します。すべてのテストが実行されている様子がわかります。
エラーがあれば、最後に表示されます。



</details>




# インターフェースとサンプルコードを書く

あなたのパッケージの中ではめちゃんこスゴいアルゴリズムが実装されているのかもしれません。でも、他の開発者は、いちいち中身をチェックしているヒマがないです。（残念ながら）

インターフェースはあなたのめちゃんこスゴいアルゴリズムを、猿開発者でもすぐに使えるようにしてあげるために必要なのです。

簡単なインターフェースがあっても、猿開発者は「使い方がわからん」と文句を言います。サンプルコードを書いて、使い方を教えてあげましょう。

## インターフェース

インターフェースを書く場所は好みによると思います。
オブジェクト指向でコードを書いていれば、クラスの中にインターフェースメソッドを書いてあげればいいでしょう。Pythonライクにスクリプトファイルの中に関数オブジェクトを書いていれば、インターフェース用のスクリプトファイルを用意してあげればいいでしょう。（ちなみに私は後者です。）

どちらにせよ、**入出力を明確にする**ということは徹底してください。
[requestパッケージのインターフェース](https://github.com/kennethreitz/requests/blob/fb014560611f6ebb97e7deb03ad8336c3c8f2db1/requests/api.py)を例に挙げておきます。[^2]

## サンプルコード

サンプルコードでも大切なことは**入出力を明確にする**ことです。
猿開発者はよくサンプルコードをコピペします。コピペ利用でも入出力がすぐにわかるサンプルコードがベストです。
[一例としてjaconvのusage](https://github.com/ikegami-yukino/jaconv#usage)を挙げておきます。


# READMEを書く

もし、所属チームにREADMEの書き方の作法があれば**必ずその作法に従ってください**。

READMEを書くときの原則は「他の開発者がREADMEを読んだだけで、インストール法を理解できて、実行方法も理解できること」です。

だいたいの場合は以下の情報があればいいでしょう。

- インストール方法
- テストの実行方法（だいたいは `pytest tests` or `python setup.py test`でいいはずです）
- どうやって使うか？（だいたいは「サンプルコードを見てください」でいいはずです）

もし、事前に準備が必要な項目があれば、必ずREADMEに書かなければいけません。
例えば、

- Mecabを事前にインストールしておかなければならない
- DBのセットアップをしておかなかればならない

こういう場合は、必ずREADMEにその事を書かなければいけません。
余裕があったら、[Makeファイル](http://matome.naver.jp/odai/2134894821753237901)を書きましょう。他の開発者が泣いて喜びます。


# 覚えておいてほしいこと

## 入出力を明確にすること

頭に入れて置いて欲しいことは「他の開発者はコードを見ただけでは入出力を理解することができない」ということです。だからこそ、サンプルコードが必要だったり、docコメントをしっかりかかなきゃいけなかったりします。
でも、すべてのメソッドや関数に丁寧なコメントを書いていては大変です（インターンに使える時間は有限だし）。せめて、**インターフェース関数の入出力** は丁寧な説明つきで書くようにしてください。


## Stable code と developing code

コードの管理にはgithubを使います。（少なくとも弊社では）なので、あなたの作ったパッケージはすぐにでも他の開発者が使いはじめるかもしれません。

だからこそ、branchの持つ意味を大切にしてください。大体の場合、`master`のブランチは「安定版」です。開発版は別のbranchにpushしなければいけません。

一例ですが、私の開発スタイルです。

- 最初のアルファバージョンまではmasterにpushする（かならずREADMEの冒頭に「これは開発中だ」と明記します。）
- アルファバーション以降は、別の開発ブランチを作成します。ブランチの扱い方はgit-flow[^3]に従います。

# Git-flowに従うようにする

`git-flow`はブランチの扱い方を決めたルールです。このルールがあるおかげでチームで開発を柔軟にできます。
たった１人で開発していたとしても、git-flowに従いましょう。なぜなら、他の開発者がスムーズに開発に参加できるからです。git-flowの流れは[この良記事](http://www.atmarkit.co.jp/ait/articles/1311/18/news017.html)を見ましょう。

# 再現性を意識する

他の開発者も同じように実行して、同じ結果を得られるようにしましょう。あなたの環境でしか実行できないような情報をハードコードしてはダメです。 
よくありがちなハードコードされる情報は

- unixコマンドの実行パス
- 外部ファイルへの絶対パス

こういう情報がハードコードされていると、他の開発者は実行ができません。困ります。
例えば、悪い例がこちら

```py
def do_something():
    path_to_external_unix_command = '/usr/local/bin/command'
    path_to_input_file = '/Users/kensuke-mi/Desktop/codes/hoge_hoge/input.txt'
    return call_some_function(path_to_input_file)
```

"input.txt"の中身は誰にもわからないままです。
この場合はこうすると良いです。

- 外部ファイルが必要なら同じリポジトリにpushしておく。スクリプトからの相対パスを使う
- 関数には引数として情報を渡せるようにしておく。
- unixコマンドが必要なら、設定ファイルに記述する。

上の良い例はこうなります。

```py
def do_something(path_to_external_unix_command, path_to_input_file):
    return call_some_function(path_to_input_file)

path_to_input_file = './input.txt'
path_to_external_unix_command = load_from_setting_file()
do_something(path_to_external_unix_command, path_to_input_file)
```

# type hintを使おう！

「入出力を明確に」と何回か書きましたが、正直いって面倒です。しかも、変更したときに忘れがちです。こうなると、せっかく明確にコメントを書いても意味がありません。
そこで、type hintです。Javaの様にオブジェクトの型を記述できる仕組みです。ただ、Javaとは違う点は、Pythonのtype hintは「実行になんら影響しない」という点です。[^4]

### 主な利点

- 他の開発者が入出力の型を理解しやすい
- IDEが入出力で不一致があった時に警告だしてくれるので、間違いに気が付きやすい
- IDEが型情報からメソッドのsuggestしてくれるので、開発が早くなる

### 良くない点

- ちょっとだけ文字をタイプする量が増える

### 例

せっかくなんで、例を出しておきます。
この実装では、`names`, `persons`は一体何者なのか？よくわかりません。それに`greeting`は何を返してくれる関数なのか？も不明です。

```py
def greeting(names, persons):
    target = decide_target_person(persons)
    greet = generate(target, names)
    return greet
```

さて、ここでtype hintをつけてみましょう。

```py3
def greeting(names: List[str], persons: Dict[str, str])->str:
    target = decide_target_person(persons)
    greet = generate(target, names)
    return greet
```

これで、入出力は明らかになりました！

### pycharmのサポートについて

Pycharmは完全にtype hintをサポートしてくれてます。

- オブジェクトが持つメソッドのsuggest
- 型の不一致があった場合の警告

下の画像はPycharmの開発画面です。`target`の入出力を間違えているので、警告が出ています。

<img width="534" alt="スクリーンショット 2016-07-29 2.49.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/33533/35d0dce0-a361-cc6f-c7ef-171663209a8a.png">


### python-3だけのパッケージの場合

この辺りが良記事です。

- [PEP 0484型ヒント(JP)](http://qiita.com/t2y/items/f95f6efe163b29be59af)
- [Python3.5のType hintについて(JP)](http://blog.sce-toku.jp/blog/2015/12/24/python-typehints/)

<details>
<summary>昔話「昔はPython2というものがあったんじゃ」</summary>

### python-2 と python-3の両方対応のパッケージの場合

そもそもpython-2で書く必要があるんでしょうか？まず、この点を考えるべきです。

Python2ではPython3式のtype hintが解釈できずエラーになります。でも、「コメントとしてtype hintを書く」という素晴らしい方法がPEPで提示されています。
まだPycharmは部分的な対応しかできていませんが、近いうちにFullサポートになるはずです。

- [Python2と型ヒント(JP)](https://blog.ymyzk.com/2016/02/python-2-type-hints/)

</details>


- - -

↑ ここまでポエム

# ポエムを書いてみて振り返り

雑っぽい感じだけど、いいのかな？こんな情報だけでパッケージつくれるのかな？と不安であります。ツッコミ大歓迎です。

2021年に記事を更新しました。更新前の情報を読んで「古い方法でやっていたのだなぁ」と自らを回顧してました。記事更新までの間で、ヨーロッパの開発者たちと一緒に仕事したり、アメリカの開発チームと遠隔仕事したり、ヨーロッパの大学で学生向けに技術協力したり、といろいろ技術の使い方を見てきました。
基本的な感想は日本とさほど変わらん、という気分です。古い企業はいまだに「しがらみのお仕事」してますし、スタートアップは賢いやり方に集中してます。
大学にしても「プログラミングはjupyter notebookしか使ったことない」という学生もそれなりにいました。[^9]
たぶん、世界レベルでもさほど変わりはないでしょう。

パッケージングという作業は、「使い捨てスクリプトのPython」から「エンジニアリングとしてのPython」に進むための良い１ステップだと考えてます。

仮に世界レベルでエンジニアリングができる学生が多くないと考えみましょう。
すると、「エンジニアリングができれば、世界レベルでレベルアップできる」と解釈もできます。
ぜひ、「エンジニアリングもできる人」になって良い人生を進んでほしいもの、とささやかながら思ってます。


追記: 2023年10月ごろ。いまだに記事を読んでくれている方がいるようです。ありがたい。嬉しい。「昔話を書くべきでないな」と思ったので、編集しました。あとDocker環境構築の話を書いておきました。これがぼくの経験に基づいて、インターンには必須だと思ったからです。

<details>
<summary>言われたとおりのコマンド実行したら嫌味いわれた話</summary>


これはぼくが短期で企業に研究のお仕事しにいったときの話です。つまり、ぼくがインターンの立場だったときのお話。

その企業でGPUマシン（共有）のアカウントをもらいました。sudo権限つきでした。
次に「新しく来た人はこれ実行してね」マニュアルを渡されました。
ぼくは特に考えることなくコマンドをコピペ実行しました。

GPUサーバー管理者はそのときは休暇に出ていたので、いなかったのですね。
管理者は休暇から帰ってくると、ぼくにこう言いました「CUDAを呼び出せなくなった。何をしたんだ？」

実はぼくが実行した「新しく来た人はこれ実行してね」マニュアルには「暗黙的にnvidiaライブラリを更新してしまうコマンド」があったようです。
それでCUDAの依存関係がくずれたようです。

おかげで嫌味を言われました。管理者「せっかく１ヶ月もかけて環境を作ったのに。。。どうしてくれるんだ（くどくど）」。

ぼくはエラーログを調べてググると。。。「再起動でだいたい解決する」ということがわかりました(笑)

管理者はLinuxシステムへの理解も非常に低い人で。。。例えば「ぜんぶrootで実行するから普段はrootアカウントで開発作業してる」とか言っちゃうような人です。ああ、、、ヤバい。
そもそもCUDAの件にしても、自分でエラーログを調べられない人な時点でお察し感あります。

こういう人がサーバー管理をしているケースもあるんですね。
サーバー管理者が詳しい人とは限らないのです。
たまたま雇用権限的にサーバー管理をすることになったという人もいるんですね。
別に「この管理者はバカだ」と言ってるだけじゃないんです。
管理者は、もともとソフトウェアエンジニアリング出身ではないので、正しい使い方を知らないだけ。
でも、そういう人が権限を持っている。

こういう嫌な思いをするのは、もうコリゴリだと思いました。
それで、自分ではDockerを必須にしようと思ったわけです。
こういうことはインターン先でも起こり得るでしょう。

時代的に、Dockerが当たり前になりました。
Dockerを使いましょう。
以上です。

</details>

[^1]: 実は元の文書自体を英語で書いています。インターンに来る人は留学生もいるのです。
[^2]: requestパッケージは「とても読みやすいコード」と評判が高いです。
[^3]: 本当は`git-flow`はツールの名前なのですが、開発スタイルを示すことも多いです（私の周りだけ？？）
[^4]: それってPythonの哲学に反してるんじゃない？という声をちらほら聞きますが、Python中の人達が活動してるPEPで`Accepted`になっている項目です。従っておいた方がいいことありますよ。（そもそもこの提案を出したメンバーに__Guido van Rossum__いるし）
[^5]: 研究者界隈ではいまだにsetup.pyを書いている人が多いように見えます。
[^8]: こうやってメンターにプレッシャーを与えていく。メンターになる方はいまのうちにぜひ、機械学習の継続テストについて勉強しましょう！
[^9]: 現在の学生（2000年代生まれくらい）になると、「CやJavaの授業はなかった。Pythonだった」と聞くこともあります。20代後半の人と話をすると、「CやJavaの基礎講義があった」と言いいます。もちろん、専攻によるところもあるでしょう。ただ、時代と共にプログラミング教育の流れも感じます。