電動昇降デスクをどうするか
最近、電動昇降デスクをどうするか検討をしてます。
もちろん、揺れにくいとか、基本的な機能もですが、条件としては、
- 天板の広さ
- 横幅は、120 cm までで
- 奥行きは、60 cm じゃなくて、70 cm の方がいい
- 昇降範囲
- 一番下げた時に、標準的な机の高さの70〜72cmではなくて、数センチ低めの66cmぐらいから利用したい。
- 高さのメモリ機能は欲しいなぁ
- 出せても10万円だよなー
といったところがありました。
で、いろいろなメーカーから電動昇降デスクが出ていますが、割と60cmぐらいまで下げて利用できるものにしようとすると、結構絞られてきます。
あと、有名なオフィス家具メーカーから出ているものは、ちょっと高すぎて、予算オーバーとなっていました。
今年の春に、ガラージから、新しい昇降デスクが発売されることになりました。気になります。
条件に近いものの候補としては、以下の3つとなりました。
- Garage 電動昇降デスク EL 幅114cm
- IKEA IDÅSEN イドーセン
- FLEXSPOT E7
表にまとめると・・・
名前 | Garage 電動昇降デスク EL | IKEA IDÅSEN イドーセン | FLXISPOT E7 |
---|---|---|---|
値段 | 75,900円+4950円 組立込み? |
91,990円+5,300円 (送料※キャンペーンあり) |
64,900円 ※送料込み |
机のサイズ | 114x70cm | 120x70cm | 120x60cm |
昇降範囲 | 60-125cm | 63-127cm | 58-123cm+机の厚み(2.5cm) |
耐荷重 | 45 kg | 70 kg | 125 kg |
重量 | 33.7 kg | 12.55 kg+14.78 kg+15.23 kg=42.56 ※パッケージの重量合計 |
約32.1KG(天板除く?) |
操作 | メモリ機能あり | アプリが必要? | メモリ機能あり |
デザイン(好み) | 普通な感じ | 脚が三角形でおされ | 普通な感じ |
FLEXSPOTの圧倒的な安さ。スペック上では、良さげですね。ただ、いろんな人が使っていて、なんとなく敬遠したい気持ちもある。自分で好きな天板を付けられるとはいえ、セットで売っている天板は、奥行きが60cmな点がマイナス。
IKEAは、サイズやデザインは良さげなのですが、FLEXSPOTの安さを見ると割高感が感じられるのと、操作の面で、高さメモリの物理的なボタンがなくて、アプリ利用なところが、不安要素。
で、本命のGaragaというと、なんで横幅が6cm短いの!?まだ販売されてないからぐらつきに関するクチコミがないから不安。。。耐重量も他のに比べて少なめだけど、大丈夫か不安。。。
(けど、今注文すると、ケーブルラックがお得についてくるのいいよなー)。
ということで、もうちょっと、この3つで悩んで見ようと思います。
Pythonのsys.exit()についてのメモ
先日、Pythonのsys.exit()について、いろいろ調べたので、そのことについてまとめておく。
なにはともあれ、公式ドキュメント sys --- システムパラメータと関数 — Python 3.8.5 ドキュメント を読む。
Python を終了します。 exit() は SystemExit を送出するので、 try ステートメントの finally 節に終了処理を記述したり、上位レベルで例外を捕捉して exit 処理を中断したりすることができます。
オプション引数 arg には、終了ステータスとして整数 (デフォルトは0)や他の型のオブジェクトを指定することができます。整数を指定した場合、シェル等は 0 は "正常終了"、0 以外の整数を "異常終了" として扱います。多くのシステムでは、有効な終了ステータスは 0--127 で、これ以外の値を返した場合の動作は未定義です。システムによっては特定の終了コードに個別の意味を持たせている場合がありますが、このような定義は僅かしかありません。Unix プログラムでは構文エラーの場合には 2 を、それ以外のエラーならば 1 を返します。arg に None を指定した場合は、数値の 0 を指定した場合と同じです。それ以外の型のオブジェクトを指定すると、そのオブェクトが stderr に出力され、終了コードとして 1 を返します。エラー発生時には sys.exit("エラーメッセージ") と書くと、簡単にプログラムを終了することができます。
究極には、 exit() は例外を送出する "だけ" なので、これがメインスレッドから呼び出されたときは、プロセスを終了するだけで、例外は遮断されません。
バージョン 3.6 で変更: Python インタープリタが (バッファされたデータを標準ストリームに吐き出すときのエラーなどの) SystemExit を捕捉した後の後始末でエラーが起きた場合、終了ステータスは 120 に変更されます。
まず、基本的な使い方は、きっと
import sys print("Hello!") sys.exit() print("Hello!!")
(二回目のprintは実行されない)です。
知りたかったこと1. sys.exitのリターンコードは一体どうなるのか
ドキュメントでは、オプション引数argが、整数か他の型で異なると書いてあります。整数とNone以外のオプジェクトを渡すと、stderrにオブジェクトを表示させて、プログラムを終了すると書かれていますが、
その時のリターンコードが1だと書かれています。
整数を渡したときは、どうでしょうか。例えば、-1や300を渡したら、どうなるかを調べてみました。(環境依存?)
$ python3.7 rtn_code.py 1; echo $? 1 1 $ python3.7 rtn_code.py 255; echo $? 255 255 $ python3.7 rtn_code.py 256; echo $? 256 0 $ python3.7 rtn_code.py -1; echo $? -1 255
となり、循環した整数になるようです。
知りたかったこと2 sys.exitするのと、raise SystemExitとの違い
これは、ほぼほぼ違いがないようです。raise SystemExit(99)とか書けば良いっぽい。
知りたくなったこと SystemExitとはなんぞや。
通常の例外は、Exceptionを継承していますが、SystemExitは、BaseExceptionを継承しているようです。この違いっていまいち不明だが・・・。
話は変わり、flake8のE722
flake8で、
try: f = open("hoge.txt", "r") except: print("except")
のようなコードを書くと、flake8で怒られます。
E722 do not use bare 'except'
exceptのままで使うなと。(この例だと、ちゃんとFileNotFoundErrorをExceptしてあげれば良い。)
で、実験
以下のようなコードがあったとき、何が出力されるのか。
import sys try: sys.exit(10) except Exception: print("sys.exit E") except BaseException: print("sys.exit BE") try: raise SystemExit(10) except Exception: print("SystemExit E") except SystemExit: print("SystemExit BE") try: raise except Exception: print("raise E") except BaseException: print("raise BE")
答えは、
sys.exit BE SystemExit BE raise E
sys.exit()しても、raise SystemExitしても、BaseExceptionで補足されている。つまり、Exceptionでは補足されないです。(ドキュメント通り)
で、次は、bare exceptを使った場合。
import sys try: raise except: print("raise bare except") try: raise SystemExit(10) except: print("SystemExit bare except")
結果は、
raise bare except SystemExit bare except
となる。つまり、bare exceptを使うと、SystemExitも、Exceptionの例外も両方握り潰す処理することになる。
(昔知ったTips)
Jupyter notebookで、処理の途中で、exit相当のことをしたいなと思って、sys.exitを書くと、カーネルごとお亡くなりになります。
そこで、raiseをかませるというTipsを、昔StackOverflowか何かで読んだ記憶があります。(けど、最近のjupyter labとか使うと、カーネル落ちなくなってて、賢い!と思ったり)
まとめ
E722 do not use bare 'except'
Pythonパッケージ作りの初歩の初歩 その2
fijixfiji.hatenablog.com
前回(上のページ)に続いて、今回も、ライトなPythonユーザーがパッケージについていろいろ調べながら理解したことをまとめていきたいと思います。
今回のテーマは、setup.pyのsetupです。
主に読んだところはここ:
- 7. 使用例 — Python 3.6.4 ドキュメント
- 2. setup スクリプトを書く — Python 3.6.4 ドキュメント
- sampleproject/setup.py at master · pypa/sampleproject · GitHub
- Building and Distributing Packages with Setuptools — setuptools 38.5.1 documentation
pipを使っていて、いくつかの疑問がありました。
例えば、pip install pillow
でインストールした場合、実際にimportする場合は、from PIL import Image
といった感じで、pillowではなく、PILと指定したりします。pip installする時の名前と、importする時の名前が異なるパッケージがたまにある(scikit-learnならsklearnとか *1 )ので、なんでだろうというものです。
あと、自分でsetup.pyを書いた時に、py_modules
とpakages
とpackage_dir
と、importする時の名前がごっちゃになって、わかんないよーとなりました。
基本の「き」
配布物のパッケージを作るには、まず、作業するようのディレクトリを用意します。そしてその中に配布したいものと一緒にsetup.pyというファイルを用意します。
<root>/ setup.py 《その他いろいろなファイル》
setup.py
from setuptools import setup setup(name='hoge', version='1.0', ...略...
という感じになります。その他いろいろなファイルには、配布したい(モジュールコンテナとしての)パッケージや、パッケージ、拡張モジュール用のソースコード、データから、READEMEファイル、ライセンスのファイルなどなどを含めることができます。だいたい、必要なものは、決まっているので、(インターン向けに書いた)Pythonパッケージを作る方法 - Qiita(や GitHub - kennethreitz/samplemod: Sample module for Python-Guide.org.)を参考にするとだいたい間違わないと思います。
setup.pyの引数たち
setupの引数を全てではないですが、いろいろと見ていきます。Building and Distributing Packages with Setuptools — setuptools 38.5.1 documentation
まずは、name
について。
name
は、sampleproject/setup.py at master · pypa/sampleproject · GitHubの説明からいくと、"the name of your project"です。プロジェクトの名前。または、下記の引用にあるように、「配布物の名前」です。ここに記述してある名前が、pip install
の時に指定できます。あと、インストールしたあとに、pip list
したときに表示される名前になります。後述しますが、ここのname
はimportのときに使う文字とは(習わしでは一致しますが、基本的には)関係がありません。
配布物の名前は name オプションで個々に指定し、配布されるモジュールの一つと配布物を同じ名前にする必要はないことに注意してください (とはいえ、この命名方法はよいならわしでしょう)。ただし、配布物名はファイル名を作成するときに使われるので、文字、数字、アンダースコア、ハイフンだけで構成しなければなりません。
https://docs.python.jp/3/distutils/examples.html#pure-python-distribution-by-module
次に、version
について。
「バージョン番号は major.minor[.patch[.sub]] の形式をとるよう奨めます。」(2. setup スクリプトを書く — Python 3.6.4 ドキュメント)のように書きます。PEP 440(PEP 440 -- Version Identification and Dependency Specification | Python.org)もあるようなので、参照ください。
description
url
author
author_email
などなどを記述して行きます。
https://docs.python.jp/3/distutils/setupscript.html#additional-meta-dataや https://github.com/pypa/sampleproject/blob/master/setup.pyが参考になると思います。
どんなキーがあるかというと、Building and Distributing Packages with Setuptools — setuptools 38.5.1 documentationに一覧があります(多分公式のドキュメントがこれかな、きっと)。
で、続いて、本題のpy_modules
packages
package_dir
の3つについてです。
setup.pyの中でのモジュールやパッケージの指定方法についてですが、https://docs.python.jp/3/distutils/examples.htmlがかなりわかりやすいです。で、若干、つまずいたところとしては、利用する(importするとき)の記述とどういう関係になるのかという点でした。import mypkg
みたいなことをしたときに、どう指定するかという利用する側からの補足を入れてみたいと思います。
まず、7.1のモジュール形式という形です。(配布物としての)パッケージだけど、配布するものが(モジュールコンテナとしての)パッケージじゃなくモジュールのパターンです。
ディレクトリ構成:setup.pyと同じところにfooモジュールとbarモジュールが置いてある。
<root>/ setup.py foo.py bar.py
setup.py:
setup(name='foobarpkg', version='1.0', py_modules=['foo', 'bar'], )
foo.py:fooモジュール
def foo(): print('foooo!')
こういう状態で、インストールを行うと、pip listの中には、foobarpkg
が現れます。利用するときは、"モジュール"がインストールされているので、
import foo foo.for()
のように利用します。個人的にはちょっと驚いたところでした。一切にname
のfoobarpkg
が登場しません。fooモジュールとbarモジュールが、(配布物としてのパッケージの名前と関係なく)グローバルな名前で入ってしまいました。
続いて、「典型的なケース」です。(モジュールコンテナとしての)パッケージを配布する(配布物としての)パッケージを作るときです。
ディレクトリ構成:foobarディレクトリに、foobarパッケージ(fooモジュールとbarモジュール)がある。
<root>/ setup.py foobar/ __init__.py foo.py bar.py
setup.py:
setup(name='foobarpkg', version='1.0', packages=['foobar'], )
foo.py:foobar/fooモジュール
def foo(): print('foooo!')
pakages
で指定したので、foobarディレクトリをfoobarパッケージとしてインストールしてあります。構造的には、foobarパッケージの中にfooモジュールがあるので、使うときは、
from foobar import foo foo.foo()
または
import foobar.foo
foobar.foo.foo()
みたいに記述します。で、ここでもname
で指定したfoobarpkg
は登場しません。
このようにsetup.pyのpy_modules
かpakages
でパッケージかモジュールを羅列していけば、パッケージかモジュールを配布するパッケージを作成することができます。
で、だいたい原則がわかったので、いろいろ応用させることができます。foobarpkgパッケージ1つで、foobarパッケージとfooパッケージとbarパッケージの3つを配布することにしようと思うと、
<root>/ setup.py foobar/ __init__.py foo.py bar.py foo/ __init__.py foofoo.py bar/ __init__.py barbar.py
setup.py:
from distutils.core import setup setup(name='foobarpkg', version='1.0', packages=['foobar', 'foo', 'bar'], )
のようにすればよく、利用するときは、
from foobar import foo from foo import foofoo from bar import barbar
のように指定します。
2つ前の例で、「foobarディレクトリをfoobarパッケージとしてインストール」と書きましたが、packages
に書く文字列は、配布した際に使って欲しい(importで指定して欲しい)名前になります。パッケージの中のディレクトリ名とは限らない(デフォルトではそう解釈するみたいです)。
なので、「配布したいパッケージの名前(foobar) <---> foobarpkgパッケージ内でのそのパッケージがあるディレクトリ」(典型的な例では一致)の対応を決めることもできます。それが、pakage_dir
になります。package_dirの辞書は、キーがimportするときに使うパッケージ名、値が実際に存在しているディレクトリになります。
ややこしいですが、fooディレクトリのパッケージをbarという名前のパッケージとして、barディレクトリのパッケージをfooディレクトリのパッケージとして配布することもできます(一番簡単な例は、ドキュメントに書いてあるとおり、srcっていうディレクトリの中にあるパッケージに、foobarというパッケージ名をつけられる例だと思います。)
<root>/ setup.py src/ __init__.py foo.py bar.py foo/ __init__.py foofoo.py bar/ __init__.py barbar.py
from distutils.core import setup setup(name='foobarpkg', version='1.0', packages=['foobar', 'foo', 'bar'], package_dir={'foobar': 'src', 'foo': 'bar', 'bar': 'foo'} )
importするときは、
from bar import foofoo foofoo.foo()
のようになります。
が、こんな無駄な対応はしないほうが良いと思います。(あと、別件ですが、pip install -e ./foobarpkgって、別にpackage_dirを読んでくるわけじゃないの??ここは謎いが深まった・・・)
ドキュメントの例にあるルートディレクトリにパッケージの名前をつけたりするやつとかも、もう理解できるようになりました。
で、ちょっと脇道へ。
上記のようにパッケージした場合、関数を一つ呼び出すのに、import foobar
したあとにfoobar.foo.foo()
のようにモジュールを書くか、from foobar import foo
したあとにfoo.foo()
しなきゃいけなく、特に小さなパッケージの場合は、めんどくさいです。(つまり、モジュールみたいな感じで使いたいけど、パッケージなんですけどという状況)。その場合は、__init__.py
に一工夫加えると、import foobar
したあとに、foobar.foo()
のように呼び出せます。
__init__.py:(fooモジュールからfoo関数をインポートする)
from .foo import foo
です(多分)。このへんは、6. モジュール (module) — Python 3.6.4 ドキュメントとかを読むと、良いかもしれません。
もしも __all__ が定義されていなければ、実行文 from sound.effects import * は、パッケージ sound.effects の全てのサブモジュールを現在の名前空間の中へ import しません 。この文は単に(場合によっては初期化コード __init__.py を実行して) パッケージ sound.effects が import されたということを確認し、そのパッケージで定義されている名前を全て import するだけです。 import される名前には、 __init__.py で定義された名前 (と、明示的にロードされたサブモジュール) が含まれます。パッケージのサブモジュールで、以前の import 文で明示的にロードされたものも含みます。
6. モジュール (module) — Python 3.6.4 ドキュメント
配布したいPythonのスクリプトと、配布したパッケージの名前の対応関係がわかりましたが、他のも拡張モジュールとして配布する場合があると思います。
拡張モジュールは、プログラミング言語もPythonしか書けないライトなユーザー(僕)の場合、あまり縁がないと思っていましたが、Cythonを書くと、拡張モジュールを作ることになるので、簡単な例と参考になるリンクを書いておこうと思います。
- Welcome to Cython’s Documentation — Cython 0.28a0 documentation
- Cython ドキュメント(和訳) — Cython 0.17.1 documentation(日本語訳)
- 2. setup スクリプトを書く — Python 3.6.4 ドキュメント
Cythonのソースからだけで拡張モジュール作る場合などは簡単なのですが、Cのライブラリを使う場合ではリンクやコンパイルの時のオプションを指定する必要があります。
from distutils.core import setup from distutils.extension import Extension from Cython.Build import cythonize extensions = [ Extension("primes", ["primes.pyx"], include_dirs = [...], libraries = [...], library_dirs = [...]), # Everything but primes.pyx is included here. Extension("*", ["*.pyx"], include_dirs = [...], libraries = [...], library_dirs = [...]), ] setup( name = "My hello app", ext_modules = cythonize(extensions), )http://cython.readthedocs.io/en/latest/src/reference/compilation.html#configuring-the-c-build
のように、書きます。つまり、include_dirs
やlibraries
library_dirs
を指定します。
んで、このオプションがなんなのか理解しないといけないのですが、これは、Cのコンパイルとリンクの知識になるので、
あたりを読みつつ、なんとなく把握しました。
ちなみに、はまりポイントとしては、python - Cython compiled C extension: ImportError: dynamic module does not define init function - Stack Overflowにある通り、
第一引数の末尾のモジュール名と、Cythonのソースコードのファイル名を一致させないと、インポートがうまくできませんので注意です。つまり、
Extensiton("foobarpkg.foo", ["foo.pyx"], ....
みたいな感じです。
install_requires
について。
依存関係を記述します。ここを記述することで、依存するパッケージも勝手にpipさんが一緒にインストールしてくれます。
最後に、scripts
またはconsole_scripts
です。
flake8などのツールは、flake8コマンドで、シェルから実行できるようになっていたりします。
なので、シェルで動かすようのコマンドも配布することができます。
指定する方法は2つあり、scripts
に書くか、console_scripts
のentry_points
に書くかします。
わかりやすところとしては、Command Line Scripts — Python Packaging Tutorialがあります。
以上、今日は、ここまで。
setup.pyの引数として紹介していましたが、もろもろのパラメータは、
https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-filesのように、setup.cfgに書くことも可能らしいです(あんまりよくわからん)
次回へ続く(?)
*1:ちなみに、 sklearn 0.0 : Python Package Indexというnameがついたものが存在していた笑
tryしてreturnしたあとのfinallyについて
タイトルがちょっと意味不明なのですが、こういうことです。
問1. 以下のコードで、ansの値は1と2のどちらでしょうか?
def f(a): try: a = a + 1 return a finally: a = a + 1 ans = f(0) print(ans) # ??
問2. 以下のコードで、ansのlistは、[0, 1]と[0, 1, 2]のどちらでしょうか?
def h(a): try: a.append(1) return a finally: a.append(2) ans = h([0]) print(ans) # ??続きを読む
NEPというもの
PEPじゃなくて、NEP(NumPy Enhancement Proposals *1)というものもあることを先日(誰かのtweetで)知りました。
NumPyにおけるPython2のサポートについて書かれているProposalがあります。
全然知らなかったのですが、2系の機能追加等のサポートは、2018年いっぱいまでで、2019年の間はバグフィックスなどのリリースのみで、2020年1月1日からは、Python2系のサポートを終了するらしいです。
とはいうものの、Python2を最近めっきり書かなくなったので、個人的には、時代が変わってゆくなぁーという感慨があるくらいで、影響はほとんどないかなと思っています。
NEPというものがあることは、勉強になりました。
VPS作業記録 vimのビルドの巻
かなり前ですが、vim pluginまわり作業内容。 - みんなのふぃじー(はてなブログ版)で、vimのプラグイン周りを整理整頓しようとしたら、Lua付きのvimじゃないことが発覚して、自前でのビルドしよう案件の続きをやりたいと思います。
で、さらに、今回は、+luaだけではなく、+python +python3 +terminalを狙っていきたいと思います。
参考サイト
事前準備
vimを入れる場所は、/usr/localにしたいと思います。
Luaは、yumで入れておく。
# yum install lua # yum install lua-devel # yum install luajit # yum install luajit-devel
Pythonに関しては、--enable-sharedをつけて(?)、/usr/local/bin/python2と/usr/local/bin/python3として、インストールしておく。
オプション
git clone https://github.com/vim/vim.git cd vim/src ./configure ここのオプションをどうするか make make install
オプション | 意味 |
--enable-fail-if-missing | 何か足りなければ、止まってくれる。これつけたまま、configureが通るまで頑張る |
--with-features=huge | よくわからないけど、いっぱい入れておけということで、huge |
--prefix=/usr/local | ゆにっくしゅ しぇあー ろーかりゅ |
--enable-multibyte | 今度調べる |
--enable-luainterp | lua付きのオプション |
--with-luajit | jitは早いらしい? |
--enable-pythoninterp | これで+python |
--enable-python3interp | これで+python3 |
--enable-rubyinterp | なんとなくつけておく |
--enable-terminal | :terminalを使えるようにする。 |
結果
Huge 版 with GTK2 GUI. 機能の一覧 有効(+)/無効(-) +acl +file_in_path +mouse_sgr +tag_old_static +arabic +find_in_path -mouse_sysmouse -tag_any_white +autocmd +float +mouse_urxvt -tcl +balloon_eval +folding +mouse_xterm +termguicolors +browse -footer +multi_byte +terminal ++builtin_terms +fork() +multi_lang +terminfo +byte_offset +gettext -mzscheme +termresponse +channel -hangul_input +netbeans_intg +textobjects +cindent +iconv +num64 +timers +clientserver +insert_expand +packages +title +clipboard +job +path_extra +toolbar +cmdline_compl +jumplist -perl +user_commands +cmdline_hist +keymap +persistent_undo +vertsplit +cmdline_info +lambda +postscript +virtualedit +comments +langmap +printer +visual +conceal +libcall +profile +visualextra +cryptv +linebreak +python/dyn +viminfo +cscope +lispindent +python3/dyn +vreplace +cursorbind +listcmds +quickfix +wildignore +cursorshape +localmap +reltime +wildmenu +dialog_con_gui +lua +rightleft +windows +diff +menu +ruby +writebackup +digraphs +mksession +scrollbind +X11 +dnd +modify_fname +signs -xfontset -ebcdic +mouse +smartindent +xim +emacs_tags +mouseshape +startuptime -xpm +eval +mouse_dec +statusline +xsmp_interact +ex_extra -mouse_gpm -sun_workshop +xterm_clipboard +extra_search -mouse_jsbterm +syntax -xterm_save +farsi +mouse_netterm +tag_binary
Pythonパッケージ作りの初歩の初歩
私は、ライトなPythonユーザーなので、pipコマンドでPyPIからパッケージを取ってきてインストールはやるのですが、いまいち、pip/easy_install/distutils/setuptools/setup.pyが一体なんなのか、どう違うのかなど、よくわからないまま、なんとなくpipだけを使ってきていました。
PyPIで何かパッケージを公開することも、特段、他人にパッケージを配布することも、なかったのですが、「パッケージ」について調べたので、ブログにまとめておくことにしました。
と、調べ始めたところ・・・
Python パッケージ管理技術まとめ (pip, setuptools, easy_install, etc)がめちゃくちゃよくまとまってます。
最初これを読んで、情報量が多すぎて圧倒されてしまったのですが、一周回って戻ってきたら、とてもわかりやすくまとまっていて、良いです!!
(自分の記事の存在価値がほぼなくなった・・・)
用語「パッケージ」
最初、私が混乱していた用語が「パッケージ」です。
パッケージ (package) は、Python のモジュール名前空間を “ドット付きモジュール名” を使って構造化する手段です。
https://docs.python.jp/3/tutorial/modules.html#packages
という意味でのパッケージと、もう一つあります。
重要な注意: この文脈で「パッケージ」とは、distribution (つまり、インストールされるひとかたまりのソフトウェア)の同義語であり、Python ソースコード内で import する package (モジュールコンテナ)を指すのではない。Python コミュニティでは、distribution を表すのに「パッケージ」の語を用いるのが普通だ。 “distribution” という用語はしばしば避けられる。というのは、Linux distribution だとか、あるいは Python 自身のような他のより大きなソフトウェアの distribution と混同しやすいからだ。
http://python-packaging-user-guide-ja.readthedocs.io/ja/latest/installing.html
「配布物としてのパッケージの中に、モジュールコンテナとしてのパッケージとその他もろもろ(設定やReadmeやテストなど)を作っておいて、pipでsetuptoolsを利用して、便利にインストールする」という理解でしょうか。
ということで、配布物としてのパッケージ作り方については、
(インターン向けに書いた)Pythonパッケージを作る方法 - Qiita
がとてもわかりやすかったです。
pip
さらに、思い違いをしていたことがありました。それは、pipコマンドでできることです。
PyPIから取ってきてインストールしてくれるのみのコマンドだと思っていました(なんとなくcondaがanaconda cloudからとってきてくれるから、対象サーバーごとのコマンドかと思っていた)。
しかし、そんなことはなくて、ローカルにある自分で作った(配布物としての)パッケージに対しても使えるということです*1。
(お前は一体今さら何をいっているんだと怒られそうな予感)
例えば、ローカルにあるパッケージのディレクトリを指定して、
pip install ./mypakage
のようなことをすれば、PyPIに存在していないmypakageをインストールすることができます(./とかをつけて、パスっぽい感じにしてあげるところがポイント)*2。
- -eオプションを使えば、インストールしたふう*3になるそうです(つまり、パッケージをアップグレードしなくても、パッケージのディレクトリの変更が反映されます)
- ローカルのパッケージだけじゃなくて、gitのリポジトリも指定できるみたいです。
- pip — pip 9.0.1 documentation(なにはともあれ公式ドキュメント)
- pipの使い方 (2014/1バージョン) — そこはかとなく書くよん。(日本語でまとめられてるので、素晴らしい!)
pipと配布物としてのパッケージをつなぐもの
pipはパッケージインストール&管理ツールなのですが、pipがあるおかげで、パッケージの依存関係を解決できて必要なパッケージも一緒にインストールできたり、パッケージのバージョンを指定してインストールできたり、アンインストールできたり*4、アップグレードしたりできるようです。
こういったpipの恩恵に預かるには、ある一定の作法(?)に従って、パッケージに関する情報を記述しておく必要があるようです。
それが、setup.pyというスクリプトであったり、setup.cfg、README.rst、MANIFEST.inといったファイルになるそうです(この辺は、PyPIにパッケージを登録するかしないかで、どのファイルが必要かは変わりそう。)
setup.pyなどに何を記述すべきかというのは、Packaging and Distributing Projects — Python Packaging User Guideが一次情報になるでしょうか。再掲になりますが、(インターン向けに書いた)Pythonパッケージを作る方法 - Qiitaがsetup.pyについて書かれているのがわかりやすかったです。
setup.pyは、現在では、setuptoolsのsetupを呼び出すことをするようです。昔は(?)、ここで使われるツールが、setuptoolsではなくて、distutilsだった模様(今もsetuptoolsの内部的に利用している??)。
その他事項
- 試しに何かやってみようと思った場合は、venv等で仮想環境作ってから、pip installすることをおすすめします。
- PyPIは「パイ・ピー・アイ」と読むらしい。PyPyと区別をするためらしい。
- PyPIに自分のパッケージを登録することもpipのコマンドで行います。(テストPyPIサーバーというものがあって、登録の練習ができるみたいなので、いつかの日のために今度やってみたいですね。)PyPIデビューしたい人の為のPyPI登録の手順 - Qiita
- 例えばflake8のように、pipで入れると、コマンドになってくれるやつもありますが、setup.pyの中のsetupにentory_pointsを書いて(ちゃんと動くようにやれば)、できるようです。
- C/C++の拡張もできるみたいです。
- NumPyは、distutils(Packaging (numpy.distutils) — NumPy v1.14 Manual)があるらしく、Fortran拡張もいける?*5
- 自前でPyPIっぽいものが欲しい場合は、devpiやpypiserverといったパッケージがあるらしい。
- git等の場合は、version等も指定してインストールとかもできる。
(やりたかったこと)
自分で使うようにモジュールコンテナとしてのパッケージを作っていたのですが、あるディレクトリで利用したいときはgit cloneして、また、別のディレクトリで使いたいときは、またgit cloneして・・・と、毎回git cloneするのがめんどくさくて、何かうまい方法がないのかなと思ったのが、今回、パッケージについて調べるきっかけでした。
実態を一つにしておいて、sys.pathに追加するといったことも検討しましたが、これも毎回スクリプトの先頭で追加の部分を書かないといけないのかなーと思っていました。
特に仮想環境等を用意せずにホームディレクトリやどこでもでipythonを起動した時に、import numpyするように、自分のパッケージを利用できたらいいなと思っていたのでした。
その目的を達成する方法としては、自分でsetup.pyまで書いて置いて、そのディレクトリ(かgitリポジトリ)に対して、pip install ./mypackage(-eオプションをつけるかどうかは、やりたいことによると思いますが)でインストールする(できる)というのが、一つの答えなのかなと思いました。