この記事は何?
- Pythonパッケージの作り方を説明する
- Pythonパッケージを作るときに意識して欲しいことを説明する
- この記事はポエムです
これまでのあらすじ
インターンを迎える予定なのですが、彼らはパッケージを作ったことがないそうです。
一方で、企業としては、パッケージ化までしてくれないと、実務に使うまで時間がかかってしまって大変です。
そこで、社内向けに「Pythonパッケージの作り方」という文書を書きました。これをインターンの人に読んでもらっていい感じのパッケージを作ってもらうぜ!という都合の良い目論見です。
しかし、 私もいままでパッケージ化のノウハウをきちんと体系的に勉強したわけではないので、イマイチ不安です。そこで、Qiitaにポエム投稿して、ポエム修正をしてもらおうと思ったわけです。
ところどころ日本語が変なのはご容赦ください。日本語に不自由してます。1
この記事を読む人の前提
つまりはインターン生が持っている前提知識です。
- Pythonを日常的に研究に利用している。
- 機械学習とか自然言語処理の前処理作業はできる。(自然言語処理100本ノックはクリアしているそうです)
- パッケージはまだ作ったことがない。
- Gitは使える。
↓ ここからポエム
Dockerで環境構築
Dockerとは、すごく簡単に言えば「VM(バーチャルマシン)を簡単に作れるプラットフォーム」です。まずはこれだけを理解しておけば問題ないでしょう。
DockerでVMを作るときには、とりあえず次の知識だけを押さえておけばOKです。
- DockerイメージとDocker containerの概念
- Dockerfileの書き方
- Docker buildのやり方
- Docker containerの起動方法とcontainer login方法
長くても半日あればすべてを把握できます。
コマンドなどの実践的な知識は他の記事にゆずります。
この記事では「Dockerを使うとなんで嬉しいのか?」だけを書き残しておきます。
まずインターン生であるあなたには次のようなメリットがあります。
- sudoコマンドを好きなだけ使える。
- VMマシン環境をぶっ壊しても、他の人は困らない。精神的によろしい。
- ポートフォリオ、履歴書に書けるスキルが1つ増える。
一方で、開発チーム、つまり企業側のメリットは次のとおりです。
- 環境再現がものすごく楽になる。
- sudoコマンド実行権限・実行コミュニケーションのことをすべて考えなくても良い。
メンターの方へ
もし、あなたがインターン生のメンターをしていて、かつ、Dockerのことを知らなかったとしたら、勉強しましょう。
正直いって、AIエンジニアであっても、データサイエンティストであってもDockerは「知ってて当たり前」知識になりつつあります。未だにDockerを使えないエンジニアとか・・・・(笑)
煽り文はこれくらいにしておいて。
とりあえず、次のことをすればLinux環境でDockerが使えるようになります。
- Dockerをインストールする。
- インターン生のアカウントをDocker groupに追加する。
ディレクトリ構成
実は、Python界には公式のディレクトリ構造はないです。いい加減なディレクトリ構成をしていても、コードは動きます。しかし、それでは、他の開発者にはとてもわかりづらいコードになってしまいます。だから、わかりやすいディレクトリ構造が必要なのです。
なので、私はこのブログの方法に従うのをオススメします。このブログでは、Githubのリポジトリをクローンしています。ここでも、このリポジトリをクローンしましょう。
MacBook-Pro% git clone git@github.com:kennethreitz/samplemod.git
パッケージの名付け規則
Pythonのパッケージ命名規則は__すべて小文字__で__アンダースコア区切り__が推奨されてます。Googleのスゴい人とPythonの中の人たちがそう言っているのだから、従った方が無難でしょう。
パッケージを作る準備をする
今回は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とかね。でも、あまりオススメしません。余計なファイルが作られすぎるからです。
パッケージの情報を書く
パッケージの情報とは、主に機械向けの情報です。
どんなパッケージ依存関係があるのか、バージョンはいくつか?などなどです。
このセクションでは、いつくかの方法を紹介します。
昔話「昔はsetup.pyというものがあったんじゃ」
以下のお話は2010年ごろから2020年頃までをPythonと共に生きた人の思い出です。
2023年、もはやsetup.pyを採用する意味はほとんどありません。
poetryを使いましょう。
setup.pyを書く
setup.py
というのはパッケージに必須のファイルです。このファイルに、これから作るパッケージの依存情報、バージョン情報、パッケージ名を書きます。
このポエムでは、最低限のフィールドだけを説明します。他のフィールドにも興味があれば、Python中の人の経典を見ましょう。
なお、setup.py
を直接的に書く方法はだんだんと時代おくれになってきています。2021年での話です。2
もっと最新の方法を知りたい人は、poetry
を検討するといいでしょう。次のサブセクションで書きます。
補足です。
poetry
を使ったとしても、setup.py
の存在は消えません。poetry
がsetup.py
を自動生成するだけです。
このことを考えると、setup.py
の構成を知っておくのも悪くはないでしょう。
最低限、書いて欲しいフィールド
元のsetup.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 | 依存パッケージ情報を書きます | これ |
dependency_links | 依存パッケージがpypiに存在してない時に書きます。 | これ |
__特に、絶対に__書かないといけないのは、install_requires
です。あなたのパッケージを使う開発者は「どのパッケージに依存してるか?」なんてことを知りません。この情報がないと他の開発者は困ってしまいます。
(dependency_links
は2019/1ごろのpip更新で削除されることになりました。代替手段は後述します。)
たぶん、あなたもいままでにpython setup.py install
をしたことがあるでしょう。このコマンドが動くのはきちんと依存情報が書かれているからです。
Pypiに存在するパッケージへの依存情報を書く
install_requires
のフィールドにリストで依存しているパッケージを記述します。たとえば、numpy
が必要なら、numpy
と書きます。
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
が廃止されているからです。
代替手段は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を読み込む処理を書きます。
このコードのまんまです。
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の記事を見ましょう。
基本的な式は
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
はこうなります。
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'))
)
READMEに書く
依存パッケージ情報をREADMEに書いてしまう方法です。
依存パッケージがせいぜい2,3個ならばこの方法でも良いとおもいます。
でも、4個以上の依存パッケージがあるなら、この方法はやめましょう。
poetryを使う
2021年になって、パッケージ管理システムにpoetryを使うプロジェクトが増えてきました。
poetryを簡潔に一言でいうと「setup.pyを上に乗っかってるイケてるパッケージ管理システム」と思えば良いです。
Pythonパッケージ管理システムがいくつか並列し、少し理解が大変・・・という方はこの記事を読むと良いです。
この記事でpoetryをがんばって説明しても、大して意味はなさそうです。
なので、ざっと手順だけを書いて、poetry使い方記事のリンクを貼っておきます。
-
pyproject.toml
を書く。これはsetup.py
に相当するファイルです。 - パッケージ開発する
- おしまい
基本的なpoetry
の使い方はこの記事が良さそうです。
ひとつだけ大切なことを書き残しておきます。パッケージ追加時は必ず poetry add <パッケージ名>
コマンドを使いましょう。 pip install <パッケージ名>
をやめましょう。 pip install
は「パッケージ依存関係を壊す覚悟はできている!」という気持ちと共に実行するものだと思ってください。
社内インターンの文脈でpoetry
にコツが必要なので、こうした情報だけ記載しておきます。
社内のプライベートリポジトリに依存してるんだけど
Githubのプレイベートリポジトリなどへの依存情報を記述可能です。
このように書きます。
作ったパッケージをローカルにインストールしたいんだけど
状況的には次の様子です。
自分のローカルでパッケージ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
にまとめてます。参考にどうぞ
コードを書く
コードの書き方にもいろいろあるのですが、今回は細かいことは言いません。
いままで通りに書いてみてください。
でも、「覚えておいて欲しいこと」にできるだけ従って欲しいです。
テストを書く
テストは必ず書いてください。
なにをどうテストすればいいんだ?
この記事の文脈では2種類のテストに分類できます。
- システムとしてのテスト
- アルゴリズムとしてのテスト
「システムとしてのテスト」はエラーがないことの証明が目的です。
「アルゴリズムとしてのテスト」は入力値に対して出力値が想定範囲内と証明することが目的です。
「システムとしてのテスト」はそんなに難しい話じゃないです。
考えられる入力ファイルやフォーマットを入力して、エラーがでなければOKです。
「アルゴリズムとしてのテスト」は、機械学習の文脈を考えるといいでしょう。
だいたいのケースでは、実装するアルゴリズムの論文があるはずです。
その論文の中では、たいてい公開データでの数値が書いてあります。
と、いうことは「公開データを入力して、出力数値がだいたい同じであればOK」というテストを書けばよいです。
機械学習のProductionを考えると、もっとテストの話は複雑になります。
が、それはインターンの話では関係ないでしょう。
もっと知りたい方はメンターに聞いてみるといいでしょう。3
どんな手順でテストすればいいんだ?
ぼくのアプローチを書いておきます。
いい加減だけど、すばやく始めるには良いアプローチだと思ってます。
- スクリプトの構成をだいたい考える。
- スクリプトを書き始める。
- スクリプトの中に
test()
を作っておく。if __name__ == '__main__'
からtest()
を呼ぶ - 「システムとしてのテスト」を意識して、テスト文を書いておく。だいたいのケースで、assert文と型チェックだけで十分。
- 余裕ができたら、「アルゴリズムとしてのテスト」も書いてみる
-
test()
をテストスクリプトへ移植する
テストスクリプトへの移植はこのあとのセクションで書きます。
1つのスクリプト、1つのテストスクリプト
パッケージのモジュールスクリプトを1つ作ったら、テストスクリプトも1つ作りましょう。その方が、対応関係がはっきりとわかって、メンテナンス性がよくなります。いまは、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
をしておきましょう。
基本的な使い方は他の記事に譲ります。
意識しておきたいことは2つだけ。
- テスト関数名は
test_
prefixから始める - テストスクリプトは
test_
prefixから始める
テストを関数で表現できるので、前のセクションで書いたtestのコピペに適してます。
ここではインターンと機械学習の文脈でpytestのヒントを書いておきます。
サンプルスクリプトをテストしたいんだけど?
サンプルスクリプトって必要ですよね。
でも、サンプルスクリプトというからには、test_
っていう名前にはしたくない。
かといって、サンプルスクリプトが動かないのも困る。
解決策はpytest [サンプルスクリプト].py
をするだけです。
jupyter notebookをテストしたいんだけど?
機械学習プロジェクトだけjupyter notebookってよく使いますよね。
サンプルの表現にはぴったりだと思います。
さて、jupyter notebookのコードが動かなかったら困りますよね。
テストしておきたいですよね。
できます。
nbmake
というパッケージを使うだけです。
使い方はとっても簡単。このセクションを見ましょう。
昔話「昔はunittestというものがあったんじゃ」
昔話を読む
(別解)unittestを使う
Pythonではunittest
という標準のテストフレームワークがあります。
以前は、ぼくもunittestで書いてました。しかし、文字量の差からpytestに切り替えていきました。
「テスト」という点ではunittestもpytestも変わりません。書き方が違うだけです。
unittestを使う場面はおそらくpytest
をインストールできないっっ!という場面だと思います。
unittestの詳細は割愛しますが、この記事-JPやこの記事-ENを見て、使い方をマスターしましょう。
テストコードを配置する場所はtests/
ディレクトリの配下です。
├── setup.py
└── tests
├── __init__.py
├── context.py
├── test_advanced.py
└── test_basic.py
test_core.pyの例
テストスクリプトを書くときに覚えておいて欲しいことは2点です。
- テストクラス名はキャメルケース
-
test_
というprefixがメソッド名の前に必要。このprefixがないとtestが無視されてしまいます。
テストがかけたら、スクリプトを実行してみましょう。python test_core.py
.
または_pycharm_であれば、通常の実行ボタンを押せば、テストが走ります。
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は自動的にテストクラスを読みこんでテストを実施してくれます。
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
を実行します。すべてのテストが実行されている様子がわかります。
エラーがあれば、最後に表示されます。
インターフェースとサンプルコードを書く
あなたのパッケージの中ではめちゃんこスゴいアルゴリズムが実装されているのかもしれません。でも、他の開発者は、いちいち中身をチェックしているヒマがないです。(残念ながら)
インターフェースはあなたのめちゃんこスゴいアルゴリズムを、猿開発者でもすぐに使えるようにしてあげるために必要なのです。
簡単なインターフェースがあっても、猿開発者は「使い方がわからん」と文句を言います。サンプルコードを書いて、使い方を教えてあげましょう。
インターフェース
インターフェースを書く場所は好みによると思います。
オブジェクト指向でコードを書いていれば、クラスの中にインターフェースメソッドを書いてあげればいいでしょう。Pythonライクにスクリプトファイルの中に関数オブジェクトを書いていれば、インターフェース用のスクリプトファイルを用意してあげればいいでしょう。(ちなみに私は後者です。)
どちらにせよ、入出力を明確にするということは徹底してください。
requestパッケージのインターフェースを例に挙げておきます。4
サンプルコード
サンプルコードでも大切なことは入出力を明確にすることです。
猿開発者はよくサンプルコードをコピペします。コピペ利用でも入出力がすぐにわかるサンプルコードがベストです。
一例としてjaconvのusageを挙げておきます。
READMEを書く
もし、所属チームにREADMEの書き方の作法があれば必ずその作法に従ってください。
READMEを書くときの原則は「他の開発者がREADMEを読んだだけで、インストール法を理解できて、実行方法も理解できること」です。
だいたいの場合は以下の情報があればいいでしょう。
- インストール方法
- テストの実行方法(だいたいは
pytest tests
orpython setup.py test
でいいはずです) - どうやって使うか?(だいたいは「サンプルコードを見てください」でいいはずです)
もし、事前に準備が必要な項目があれば、必ずREADMEに書かなければいけません。
例えば、
- Mecabを事前にインストールしておかなければならない
- DBのセットアップをしておかなかればならない
こういう場合は、必ずREADMEにその事を書かなければいけません。
余裕があったら、Makeファイルを書きましょう。他の開発者が泣いて喜びます。
覚えておいてほしいこと
入出力を明確にすること
頭に入れて置いて欲しいことは「他の開発者はコードを見ただけでは入出力を理解することができない」ということです。だからこそ、サンプルコードが必要だったり、docコメントをしっかりかかなきゃいけなかったりします。
でも、すべてのメソッドや関数に丁寧なコメントを書いていては大変です(インターンに使える時間は有限だし)。せめて、インターフェース関数の入出力 は丁寧な説明つきで書くようにしてください。
Stable code と developing code
コードの管理にはgithubを使います。(少なくとも弊社では)なので、あなたの作ったパッケージはすぐにでも他の開発者が使いはじめるかもしれません。
だからこそ、branchの持つ意味を大切にしてください。大体の場合、master
のブランチは「安定版」です。開発版は別のbranchにpushしなければいけません。
一例ですが、私の開発スタイルです。
- 最初のアルファバージョンまではmasterにpushする(かならずREADMEの冒頭に「これは開発中だ」と明記します。)
- アルファバーション以降は、別の開発ブランチを作成します。ブランチの扱い方はgit-flow5に従います。
Git-flowに従うようにする
git-flow
はブランチの扱い方を決めたルールです。このルールがあるおかげでチームで開発を柔軟にできます。
たった1人で開発していたとしても、git-flowに従いましょう。なぜなら、他の開発者がスムーズに開発に参加できるからです。git-flowの流れはこの良記事を見ましょう。
再現性を意識する
他の開発者も同じように実行して、同じ結果を得られるようにしましょう。あなたの環境でしか実行できないような情報をハードコードしてはダメです。
よくありがちなハードコードされる情報は
- unixコマンドの実行パス
- 外部ファイルへの絶対パス
こういう情報がハードコードされていると、他の開発者は実行ができません。困ります。
例えば、悪い例がこちら
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コマンドが必要なら、設定ファイルに記述する。
上の良い例はこうなります。
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は「実行になんら影響しない」という点です。6
主な利点
- 他の開発者が入出力の型を理解しやすい
- IDEが入出力で不一致があった時に警告だしてくれるので、間違いに気が付きやすい
- IDEが型情報からメソッドのsuggestしてくれるので、開発が早くなる
良くない点
- ちょっとだけ文字をタイプする量が増える
例
せっかくなんで、例を出しておきます。
この実装では、names
, persons
は一体何者なのか?よくわかりません。それにgreeting
は何を返してくれる関数なのか?も不明です。
def greeting(names, persons):
target = decide_target_person(persons)
greet = generate(target, names)
return greet
さて、ここでtype hintをつけてみましょう。
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
の入出力を間違えているので、警告が出ています。
python-3だけのパッケージの場合
この辺りが良記事です。
昔話「昔はPython2というものがあったんじゃ」
python-2 と python-3の両方対応のパッケージの場合
そもそもpython-2で書く必要があるんでしょうか?まず、この点を考えるべきです。
Python2ではPython3式のtype hintが解釈できずエラーになります。でも、「コメントとしてtype hintを書く」という素晴らしい方法がPEPで提示されています。
まだPycharmは部分的な対応しかできていませんが、近いうちにFullサポートになるはずです。
↑ ここまでポエム
ポエムを書いてみて振り返り
雑っぽい感じだけど、いいのかな?こんな情報だけでパッケージつくれるのかな?と不安であります。ツッコミ大歓迎です。
2021年に記事を更新しました。更新前の情報を読んで「古い方法でやっていたのだなぁ」と自らを回顧してました。記事更新までの間で、ヨーロッパの開発者たちと一緒に仕事したり、アメリカの開発チームと遠隔仕事したり、ヨーロッパの大学で学生向けに技術協力したり、といろいろ技術の使い方を見てきました。
基本的な感想は日本とさほど変わらん、という気分です。古い企業はいまだに「しがらみのお仕事」してますし、スタートアップは賢いやり方に集中してます。
大学にしても「プログラミングはjupyter notebookしか使ったことない」という学生もそれなりにいました。7
たぶん、世界レベルでもさほど変わりはないでしょう。
パッケージングという作業は、「使い捨てスクリプトのPython」から「エンジニアリングとしてのPython」に進むための良い1ステップだと考えてます。
仮に世界レベルでエンジニアリングができる学生が多くないと考えみましょう。
すると、「エンジニアリングができれば、世界レベルでレベルアップできる」と解釈もできます。
ぜひ、「エンジニアリングもできる人」になって良い人生を進んでほしいもの、とささやかながら思ってます。
追記: 2023年10月ごろ。いまだに記事を読んでくれている方がいるようです。ありがたい。嬉しい。「昔話を書くべきでないな」と思ったので、編集しました。あとDocker環境構築の話を書いておきました。これがぼくの経験に基づいて、インターンには必須だと思ったからです。
言われたとおりのコマンド実行したら嫌味いわれた話
これはぼくが短期で企業に研究のお仕事しにいったときの話です。つまり、ぼくがインターンの立場だったときのお話。
その企業でGPUマシン(共有)のアカウントをもらいました。sudo権限つきでした。
次に「新しく来た人はこれ実行してね」マニュアルを渡されました。
ぼくは特に考えることなくコマンドをコピペ実行しました。
GPUサーバー管理者はそのときは休暇に出ていたので、いなかったのですね。
管理者は休暇から帰ってくると、ぼくにこう言いました「CUDAを呼び出せなくなった。何をしたんだ?」
実はぼくが実行した「新しく来た人はこれ実行してね」マニュアルには「暗黙的にnvidiaライブラリを更新してしまうコマンド」があったようです。
それでCUDAの依存関係がくずれたようです。
おかげで嫌味を言われました。管理者「せっかく1ヶ月もかけて環境を作ったのに。。。どうしてくれるんだ(くどくど)」。
ぼくはエラーログを調べてググると。。。「再起動でだいたい解決する」ということがわかりました(笑)
管理者はLinuxシステムへの理解も非常に低い人で。。。例えば「ぜんぶrootで実行するから普段はrootアカウントで開発作業してる」とか言っちゃうような人です。ああ、、、ヤバい。
そもそもCUDAの件にしても、自分でエラーログを調べられない人な時点でお察し感あります。
こういう人がサーバー管理をしているケースもあるんですね。
サーバー管理者が詳しい人とは限らないのです。
たまたま雇用権限的にサーバー管理をすることになったという人もいるんですね。
別に「この管理者はバカだ」と言ってるだけじゃないんです。
管理者は、もともとソフトウェアエンジニアリング出身ではないので、正しい使い方を知らないだけ。
でも、そういう人が権限を持っている。
こういう嫌な思いをするのは、もうコリゴリだと思いました。
それで、自分ではDockerを必須にしようと思ったわけです。
こういうことはインターン先でも起こり得るでしょう。
時代的に、Dockerが当たり前になりました。
Dockerを使いましょう。
以上です。
-
実は元の文書自体を英語で書いています。インターンに来る人は留学生もいるのです。 ↩
-
研究者界隈ではいまだにsetup.pyを書いている人が多いように見えます。 ↩
-
こうやってメンターにプレッシャーを与えていく。メンターになる方はいまのうちにぜひ、機械学習の継続テストについて勉強しましょう! ↩
-
requestパッケージは「とても読みやすいコード」と評判が高いです。 ↩
-
本当は
git-flow
はツールの名前なのですが、開発スタイルを示すことも多いです(私の周りだけ??) ↩ -
それってPythonの哲学に反してるんじゃない?という声をちらほら聞きますが、Python中の人達が活動してるPEPで
Accepted
になっている項目です。従っておいた方がいいことありますよ。(そもそもこの提案を出したメンバーに__Guido van Rossum__いるし) ↩ -
現在の学生(2000年代生まれくらい)になると、「CやJavaの授業はなかった。Pythonだった」と聞くこともあります。20代後半の人と話をすると、「CやJavaの基礎講義があった」と言いいます。もちろん、専攻によるところもあるでしょう。ただ、時代と共にプログラミング教育の流れも感じます。 ↩