いものやま。

雑多な知識の寄せ集め

私のコード履歴書。(その3)

前回の続き。

個人開発

そんなわけで組込み向けのIDEの開発をやってたんだけど、いろいろあって辞めて、個人開発をやったりもした。 その様子はこのブログの過去記事でも書いたりしてる。

この期間にまずやったのは、Rubyをもっと本格的に学ぶということ。 Rubyオブジェクト指向すごいというのと、RailsでWebサイトを作りたかったからというのがその理由。

Rubyもじっくりと触れ始めるといろいろ思うところはあり、命名の英語がちょっと変じゃない?とか、とにかくやろうと思えば何でもできてしまうパワフルさを怖く感じたり、コードを読んだり調べたりするときには型が分からずに苦労したりとかもあった。 けど、それを越えると言語設計がとにかくよくできてることに感動して、とくにブロックという一つの考え方をあれにもこれにも応用できるというところが、できるだけ少ない原理でより多くのことを説明したい自分にはとても素晴らしく感じた。

ちなみに、ちょっと脱線するけど、このRubyの設計の素晴らしさに比べて、Pythonの設計の酷いことよ・・・ Rubyなら全部ブロックで片付くことを、Pythonだとループだけでもforだったりmapだったりリスト内包表記だったり様々な書き方がある(しかも速度が違うとかいう罠がある)し、さらにファイル処理はwithを使う必要があったり、その主張に反して場当たり的に様々な方法で言語仕様が追加されてて、キメラみたいになってしまってる。 オブジェクト指向についてもPythonは正直Perlと同レベルだし(いずれもhash/dictに対して関数の紐付けを行なって、それを呼び出すシンタックスシュガーでオブジェクト指向を実現している)、Rubyならアクセサもただのメソッドなのに対してPythonアノテーションによるデコレータを言語仕様に追加してメソッドとは別物のプロパティとして実現したりする。 このプロパティの何が酷いって、プロパティは()をつけちゃいけないのに対し、メソッドは逆に()をつけないといけないので、プロパティなのかメソッドなのかをわざわざ確認しないといけないこと。 一例を挙げると、date = datetime.date(2021, 10, 23)としたとき、日付を確認したい場合はdate.dayと書くのに対し、曜日を確認したい場合はdate.weekday()と書く必要があったりする。 もうホントにPythonを使うのはストレス・・・

なお、このブログのRubyに関連する記事は以下から:

閑話休題

Rubyとは別に勉強したのがSwift(とObjective-C)。 これはMac OS XiOSでアプリを作りたかったから。

ちなみに何かと嫌われがちなObjective-Cだけど、個人的にはC++よりもずっと好き。 言語の流れとしてSmalltalkの流れをくんでいて、Rubyと同じようにメッセージパッシングの考えをベースにしたオブジェクト指向言語になっている。 メソッド名が長いとかいう声もあるけど、分かりやすいので、補完があれば全然いいと思う。

そんなObjective-Cを改善させてよりモダンにしたのがSwiftで、これも面白かった。 まぁ、最近は全然追えてないので、今どうなってるのかさっぱり分からないんだけど・・・

このブログのSwiftに関する記事は以下:

あと、実際にアプリを作った様子は以下:

他にもいくつかの言語に興味を持って勉強しようとしたこともあった。 けど、途中でつまらなくなって辞めてる。

たとえばHaskellクイックソートの書き方とか面白くて興味持ち、モナドとかも厨二心をくすぐるものがあっていろいろ調べたんだけど、結局のところ、関数をつないで実行順に一意性を持たせて、状態などをオブジェクトとして関数に流し、それをdo記法で書きやすくしてるだけと分かると、それだけのためにわざわざこんな大掛かりで分かりにくくしかも取り扱いの面倒な仕掛けを用意してるのはとてもバカらしく、それなら素直にコード書けばよくて、結局、関数型の考え方はサブシステムとして使うのにはいいんだけど、わざわざそれだけでシステム全体を作る必要はないというところに落ち着いた。

また、Erlangも軽量なプロセスを使ってメッセージをやり取りするというのが面白かったんだけど、そこで実現されてるのが結局オブジェクト指向のそれで、やっぱりサブシステムでしかなくて、わざわざ手作りでオブジェクト指向を実現する必要ないよなとなった。 (これはErlangの設計者の一人も認めてる;その後のJoe Armstrongのオブジェクト指向に対する見解 - tokoma1's blog

社会人(研究開発)

上記のゲームを作るために強化学習SVMを勉強して、そこで使われてたのが大学でやっていた最適化だったので、やっぱり最適化はすごいなと思い、最適化や機械学習を使っていきたいと転職活動した。

そして、機械学習で現在のデファクトスタンダードになっているのがめちゃくちゃ残念ながらPythonなので、Python機械学習関連のライブラリについて学習した。 Python自体はとても酷い言語だし環境もアレで、ライブラリ自体も設計がいいとは言えないんだけど、とりあえず使えるものではあるので使ってる。 さすがに一から実装しようとすると大変なので。 これがRubyだったらよかったのになぁ・・・

まぁ、統計とかでRも少し見たりしたんだけど、RはPythonにも増してアレでマジ本当にどうしょうもないくらいに酷い言語(これは言語なのか?)なので、そういう環境だった人からすればPythonが神のように思えるというのは分かるんだけど・・・

他にも最近はJuliaとかが話題に上ってるけど、個人的には様子見。 たしかにPythonの中途半端なオブジェクト指向使うくらいなら、学習コスト下げてオブジェクト指向を使わずにプログラム書いた方がとっつきやすく、それならとオブジェクト指向を取っ払ったJuliaは潔いとも言える。 今後、どうなっていくのかなぁ・・・


長々と語ったけど、コード履歴書はこれでオシマイ。

今日はここまで!

私のコード履歴書。(その2)

前回の続き。

大学(後半)〜大学院

そんなわけでPerlでいろいろ遊んでたんだけど、世間はオブジェクト指向だってことでJavaも勉強したりした。 時期的にはジェネリクスや拡張for文が導入されたり、AWT&AppletからSwing&Java Web Startへ移行したりという時期のはず。 もっとも、そんなのは使えずにCに毛の生えたようなコード書いてたけど・・・

Javaの印象は「やたら大袈裟だなぁ」という感じだった。 Perlだったら文字の入出力なんて何も考えずにできたし、Cでもそこまで大袈裟ではなかったけど、Javaだとラップしてラップしてと、なんでこんなになってるのかと。 main()を書くにしてもクラスを用意してクラスメソッドとして実装しないといけなかったりで、とにかく無駄に書く量が多い。 標準ライブラリも山のようにあって、もちろんそれがいいところでもあったんだけど、当時は圧倒されて「これ全部理解しなきゃいけないの?」という感じがプレッシャーがすごかった。

根本的にはオブジェクト指向をよく理解してなかったというのが大きい。 そもそもヒープにメモリを確保するとかを理解してなかったので。 (CとかJavaとかはコンピュータの具体的な動き、メモリの様子などを理解してないと、理解が難しいところあるよねぇ・・・)

そんなんだったけど、まずC言語のいい本に出会ってみっちり鍛えたのが大きかった。

それとPerlオブジェクト指向を学んだ。 読んだのはたしか『続・初めてのPerl』だったはず・・・

Perlオブジェクト指向は本当に薄皮一枚被せただけの素朴なものなんだけど、その分仕組みがよく分かってとてもよかった。 今あえてそこからオブジェクト指向を学ぶ必要性はないと思うけど、Javaとかで「オブジェクト指向よく分からん・・・」とかなるくらいなら、こっちから学んだ方がいいと思う。

もう一つオブジェクト指向を理解する上で大きかったのは、C言語でリスト構造の実装をやったこと。 これで「データ構造があればそれに伴って操作も定まってしまうんだから、セットで扱うようにすればコードが局所化される」とか「ユーザが興味あるのは操作だけなんだから、関数というインタフェースで操作だけ見せればいいんだ」とか「ポインタ(とその先にあるメモリ)がオブジェクトなんだ」と理解できたので、それでやっとJavaが実際にどう動いているのかとか参照型ってなんなのかとかが腑に落ちた。

あとRubyに初めて触れたのもこの頃。 当時の衝撃は以下の記事に書いてる。

まぁ、すごいと思いつつ当時はPerlにハマってたので、結局は使わなかったんだけど。

あとはちょっと変わったところとして、授業でVerilogを少しやったり、GLPK使って線形計画問題を解いてみたり、OctaveというMATLAB互換のソフトを使って半正定値計画問題を単体法っぽく解くプログラムを修論として書いたりした。(自分の専攻は数学・・・)

社会人(組込み)

卒業(修了)してから就職したのは組込みソフトの受託を行なってる会社。 ここが面白いのは自社で組込みOSも作って売っていたところ。 コンピュータのもっと深いところを理解したり、なんならOSを作ってみたいと思っていたので、とても興味があった。

実際、ここで学んだことはかなり多い。 I/Oがどうなってるのかとかカーネルがどう動いてるのかとかデバッガがどういう仕組みになってるのかとか。 あるいは開発プロセスとか品質の話とか。 趣味で一人でプログラムを書くのと、仕事でチームでプログラムを書くのは、かなり違うところがある。

ちなみに配属されたのはIDEを開発する部隊で、ここはターゲット側の知識も必要でありつつ、ホスト側でUIを作ったりする知識も必要とされたりする。 これはかなり珍しい経験だったと思う。 (と同時に、それが転職で足を引っ張ることになるんだけど・・・そんなことやってるところ、他にほとんどないわけで)

ここのコードは何年もの積み重ねの結果、ザ・カオスという感じだった。 かなり鍛えられた・・・

当時はホスト側の足回りのコードをCからC++に置き換えてる途中だったけど、Cで仮想関数テーブルとかも全部自前でやってて、Cでオブジェクト指向をちゃんとやるならこうだよねというものになってたんだけど、いやー、難しかった。

さらにはプロセス間通信でメッセージキューを自前で実装してたりと、このあたりは組込みOSでメッセージボックスを実装してたから同じような感覚で実装してたんだろうけど、いやー、ハチャメチャだったなぁ。 DLLがロードされると仮想関数テーブルに関数ポインタが設定されて、インタフェースとなる関数を呼び出すと渡したオブジェクトのポインタから仮想関数テーブル経由で関数の実体が呼び出され、それは自前のメッセージキューにメッセージをpushして、それがプロセス感通信で別のプロセスに送られて、そのプロセスではメッセージをディスパッチして対象となる関数を呼び出して、処理をして、みたいな。。。

数千行ある関数とか、do { ... } while (0);とかいう謎のコードでズッコケたりとか(Cだとtry-catchがないのでgotoでエラー処理するのがいいんだけど、コーディング規約でgotoが禁止されてたので大域的ジャンプをするためにこんなコードが書かれてた)、いろいろあったなぁ・・・(遠い目

まぁ、そんな感じで主にIDEのためのコードをCとかJavaで書いてた。

あとはETロボコンというのに参加したときにUMLの勉強をしたりライントレーサーのコードを書いたり。

他にもターゲット側で動くものだと、ELFファイルのローダを書いたりGDBスタブを書いたりとか。 ELFフォーマットについて詳しくなれたのはよかったし、GDBスタブを書くのも面白かったなぁ・・・(デバッグの難しさからは目を逸らしつつ・・・デバッガを実現させるためのコードなので、デバッグにデバッガが使えないし、printfデバッグなんて贅沢なものも使えない)


今日はここまで!

私のコード履歴書。(その1)

技術書典で買った『失敗プログラマー 第2版』を最近読んだ。

その中で「私のコード履歴書」というのがあり、著者がプログラミングを始めてからどんなものを書いてきたかを列挙してるんだけど、これが面白かった。

そこで、自分もコード履歴書を書いてみようと思う。

中学〜高校

初めてプログラミングっぽいことをしたのは中学の情報の授業。 LOGOでタートルグラフィックスに触れたのが最初のはず。

まぁ、このときはとくに刺さらず、むしろペイントでお絵描きする方が面白かった印象。

そして、本格的にプログラムを書いてみようとしたのは、高校の頃。

入学したときにiMac DVを買ってもらって、ネット見たりクラリスワークス(後のAppleWorks、そしてiWork)で遊んでたんだけど、ゲームを自分でも作ってみたいなと思ったのがきっかけ。 当時のMacでゲームプログラミングだとHyperCardという素晴らしい環境があったんだけど、全盛期は過ぎていて下火になっていたこともあり、スルー。 じゃあどうしたかというと、C言語Macアプリのプログラムを組むとかいうハードモードからスタートした。。。

C言語自体の難しさもあるんだけど、MacGUIアプリを作るのは、今GUIアプリを作るのに比べても何倍も難しさがあった。 main()関数から普通に書き始めて、各種UIもコードで書いて、さらにイベントループやイベントのディスパッチまで一から自分で書くというね。 他のアプリのウィンドウの後ろに隠れてた状態から前に出たときにはウィンドウの内容を自分でリフレッシュしたりとか。

しかも、当時は全然分かってなかったけど、MacClassic環境は協調型の擬似マルチタスクで動いていて、OSに制御を返すにはI/O待ちになるか明示的にsleep()を呼び出す必要があった。 下手なループに入ってOSに制御を返せないと、アプリが固まるだけじゃなくてOSも固まる。。。

さらにはメモリも仮想メモリの仕組みがなくて(今から考えると信じられない・・・)、全部のアプリ(OSも含む!)で1つのメモリ空間を使ってた。 このため、各オブジェクトはハンドル(ポインタのポインタ)を使って管理するという仕組みになってた。 ポインタだけでも難しいのに、さらにそのポインタとかなると大変だし、メモリ確保のときにはハンドルのポインタを渡したりするとかいうね(今はメモリのことをよく分かってるので理屈は分かる)。 そしてちょっと間違ったアドレスに触れば、アプリが死ぬだけじゃなくてOSもろとも死ぬとかいう世界。。。

当然、当時はそんなことは全然分かっていなかったので、サンプルプログラムは動かせてもちょっといじると動かなくなって、その原因も分からなくて、ダメだった。 いや、今から考えてもこれを最初にやるのは無理すぎる。。。

逆にいうと、そういう経験があるからこそ、オブジェクト指向仮想メモリプリエンプティブマルチタスクの素晴らしさが分かるとも言える。 関心を局所に抑えて他に影響を与えないようにして、自分の関心のあるところだけ見れるようにするのはすごい大切。。。

ちなみに、使ってたツールはMacintosh Programmer's Workshop(通称MPW)やResEditというApple公式で出してた無料のツール。 これらのツールも懐かしい・・・

面白かったのはResEditで、当時のMacはファイルがデータフォークとリソースフォークという領域に分かれていて、リソースフォークにメタ情報であるアイコンやどのアプリで開くのかなどが収められていたんだけど(この辺りも特殊でMacでのプログラミングを難しくしてた)、ResEditを使うとリソースフォークが簡単に編集できたので、アイコンを変えたりして遊ぶことができた。 懐かしいなぁ・・・

大学(前半)

大学に入る前にジオシティーズで趣味のホームページを作り始めて、そしてインフォシークに引っ越し。 そのときに掲示板とかを導入したのもあって、CGIに興味をもった。

そんなわけで、大学に入ってから『初めてのPerl』(通称「リャマ本」)でPerlの勉強をした。

この本がとてもいい本で、読み進めながらいろいろとプログラムを作っては動かせるのが楽しく、Perlにハマった。 C言語だと大変なことがPerlだとラクラクとできたのも大きい。 Perlはいろいろと批判も多い言語だけど、正直Pythonオブジェクト指向崩れのコードを書くくらいなら、設計を実用性に全振りしたPerlを使った方がいいと思う。

そして、CGIオライリーの本で勉強。

その集大成として学部1年から2年になるときの春休みに作ったのが、サークルで運営していたWebサイトのCMS。 今から思えばかなりハチャメチャなことをしてて、あるソースコードから別のソースコードを使う方法も知らない状態だったので、「複数のソースコードを読み込んで一つのソースコードに吐き出すプログラム」を書いたりもしてた。 テンプレートエンジンを使うとかの発想もなかったので、かなり愚直なプログラム。 まぁ、それでもちゃんと動いてたんだから、正直よくできたものだよなぁと思う。

ちなみにHTMLとかCSSについてもこの頃に独学で学んだ。 あとviもこの頃に『入門vi』で学んだはず。

そんな感じで、ちょっとMacからLinuxへ興味が移ってる。 これはMac OS Xへの絶望が大きくて、最初はMac OS Xのかっちょいい見た目に憧れたんだけど、実際に触れてみるとClassic環境とはかなり印象が違ったのが原因。 かといってWindowsを使うのもなぁというのもあって(結局は一時期Windowsにしてたんだけど)、それならカスタマイズ性の高いLinuxを使って、いつかは「ぼくの考えた最強のMac OS」をLinux上に実現できたらなぁと思ったから。 まぁ、普通にUnixの考え方に慣れて、かつ、最強にユーザフレンドリーなUnix環境にmacOSが成長したので、今は普通にmacを使ってるわけだけどw


長くなったので一旦区切り。

今日はここまで!

もうPythonで迷わないために。(まとめ)

これまでの各記事は、以下から。

ただ、やっぱり一つ言いたいことは、

Pythonを使わなくて済むなら使うな」

ということ。

まぁ、残念ながらそれは難しいんだけど。。。

せめてPythonがもっとまともになってくれたらいいんだけどなぁ。

今日はここまで!

もうPythonで迷わないために。(その4)

前回は開発環境としてJupyterLabを使う話を書いた。

今回はプロジェクトの構成について。

プロジェクト構成

RubyであればBundlerで標準化されているのでプロジェクト構成をどうしようとか迷う必要はないんだけど、Pythonはそうではないので迷う。 ちょっと調べるだけで複数の構成が出てくるんだけど、一般的なプロジェクト構成はおそらく以下のようになる:

  • (プロジェクトルート)/
    • pyproject.toml
    • setup.py
    • setup.cfg
    • (Python仮想環境ルート)/
    • (パッケージルート)/
      • __init__.py
      • (パッケージ、ソース)
    • examples/
      • (サンプルコード)
    • tests/

なお、プロジェクトルートの直下にパッケージルートを置かずにsrc/を一段挟む構成もあって、個人的にはそちらの方がいいと思うんだけど(examples/tests/src/以下と同じディレクトリ構成にできるので統一感がある)、Pythonではプロジェクトルート直下にパッケージルートを置くのが一般的なようなので、悩ましいところ。

pipパッケージとしてビルド可能にする

さて、プロジェクトルート直下にpyproject.tomlsetup.pysetup.cfgというファイルが置かれているけど、これはpipパッケージとしてビルド可能にするためのもの。 ライブラリとして使わないのであれば不要と思うかもしれないけど、これはあった方がいい。 というのも、以下のようなメリットがあるから:

  • 依存するパッケージを賢く指定できる
  • パスを気にしなくてよくなる
  • コマンドラインツールを簡単に作れる

依存するパッケージを賢く指定できる

たとえば、ある依存ライブラリについて、開発時には必要だけど実際に使うときには不要ということがある。 そういう場合、状況に合わせてインストールする依存ライブラリを切り替えたい。

けど、よくあるrequirements.txtによる管理ではそれは難しい。 そこで、公式でない仮想環境のpipenvを導入してる人もいるけど、実はこれはsetup.cfgをちゃんと書けば実現できる。 もちろんsetuptoolsも公式なツールではないけど、事実上の標準といえるので、setuptoolsを使った方がいい。

ちなみに、この方法を使うならrequirements.txtを使う必要はまったくなくなる。 むしろrequirements.txtは何も考えないと依存関係で入ったパッケージまでも並べられてしまって、本当に必要なパッケージ(場合によってはバージョンを固定したい)とそうではないパッケージ(バージョンは依存関係で定まる有効なもので最新なものであればいい)が区別がつかなくて、時間が経つごとに害悪度が増していくので、使わない方がいい。

パスを気にしなくてよくなる

また、コードを理解しやすくするためにサンプルコードを置くのはよくあるけど、このときにちょっと面倒なのがパスを追加すること。 ちゃんとパスを通してやらないとimportがうまくいかずにコードが動いてくれない。

けど、これもpipパッケージとしてビルド可能にして開発用としてインストールすることで、勝手にパスが通ってくれるようになる。

コマンドラインツールを簡単に作れる

さらに、setup.cfgを書くことでコマンドラインツールを簡単に作ることもできる。

せっかく書いたコードだから簡単に使えるといいわけだけど、普通これはなかなか大変。 それが設定を書くだけで使えるようになるんだから、使わない手はない。

pyproject.toml

pyproject.tomlの内容は以下:

[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

これに加えて後述するpysenの設定を書くといい。

setup.py

setup.pyの内容は以下:

from setuptools import setup
setup()

「これだけ?」と思うかもしれないけど、これは設定をsetup.cfgに書くように変わったから。 ただの設定なんだから設定ファイルで指定すればいいのは当たり前なわけで。 いまだにアホのような数の引数をsetup()に渡しているセンスのないコードが巷には溢れかえってるけど。。。

setup.cfg

setup.cfgではパッケージの設定を書いていくことになる。

いろいろ設定はあるんだけど、基本的なものを書くと以下のようになる:

[metadata]
name = <pipパッケージ名>
version = <pipパッケージのバージョン>
description = <pipパッケージの簡単な説明>
license = <ライセンス>

[options]
packages =
    <pipパッケージに含めるパッケージ1>
    <pipパッケージに含めるパッケージ2>
    ...
install_requires =
    <依存するpipパッケージ1>
    <依存するpipパッケージ2>
    ...

[options.extras_require]
<条件名1> =
    <条件名1で追加で依存するpipパッケージ1>
    <条件名1で追加で依存するpipパッケージ2>
    ...
<条件名2> =
    <条件名2で追加で依存するpipパッケージ1>
    <条件名2で追加で依存するpipパッケージ2>
    ...

[options.entry_points]
console_scripts =
    <コマンド名1> = <モジュール>:<main関数>
    <コマンド名2> = <モジュール>:<main関数>
    ...

「pipパッケージに含めるパッケージ」というのは、たとえばhogehoge.hugaを指定すると、プロジェクトのhoge/hoge/huga/の下にあるソースがpipパッケージに含むべきソースとして含まれることになる。

「依存するpipパッケージ」は、たとえばnumpyが必要ならnumpyと書けばいいし、バージョンを指定したいならnumpy == 1.21numpy >= 1.19, < 1.20といった感じで書くといい。

「条件名」というのはdeveloptestといった任意の名前で、pipパッケージをインストールするときに後ろに[<条件名>]をつける(例: pip install hoge[develop]pip install hoge[develop, test])と、指定された条件名で依存するpipパッケージも追加でインストールされるようになる。

コマンドラインツールのは、たとえばhoge.appモジュールでmain()関数を定義していて、do_hogeコマンドでその関数が呼び出されるようにしたければ、do_hoge = hoge.app:mainといった感じに指定しておけばいい。 これだけでコマンドが作れるので便利。 (引数はsys.argvから取得する)

他にも、ソース以外のファイル(csvなど)をpipパッケージに含める方法などもある。 詳細は以下を参照:

pipパッケージのビルド、インストール

こうして準備したものをpipパッケージとしてビルドするには、以下のようにする:

# setuptools, wheel, buildをPython仮想環境にインストールしておく
$ pip install setuptools wheel build

# ビルドを実行
$ python -m build

ビルドが終わるとdist/の下にwheelファイルが作られる。 これをインストールするには以下のようにすればいい:

$ pip install <wheelファイル>

なお、開発中は何度もビルドしていると大変。 そこで、ビルドはせずに以下のようにインストールすると、ローカルの変更がすぐに反映されていい:

# pipパッケージとしてインストールするけどソースは開発中のものがそのまま使われる
$ pip install -e .

ちなみにインストールすると依存するpipパッケージも一緒にインストールされる。 さらにoptions.extras_requireで条件名が設定されてるなら、それを指定することで開発中に追加で必要な依存するpipパッケージをインストールすることもできる:

# options.extras_requireで条件名としてdevelopがあり、追加でインストールする例
$ pip install -e .[develop]

Linter, Formatter

さらに、チームで開発する場合、LinterやFormatterを使った方がいい。

これについてはpysenがお手軽でよさそうだった:

pysenは複数のツールをとりまとめてくれるものになっている。

インストールしたい場合、単に

$ pip install "pysen[lint]"

とするか、あるいはsetup.cfgoptions.extra_require

[options.extra_require]
develop =
    pysen[lint]

のように書いておいてインストールするといい。

そして設定はpyproject.tomlで行う。

以下は設定例:

[tool.pysen]
version = "0.9"

[tool.pysen.lint]
enable_black = true
enable_flake8 = true
enable_isort = true
enable_mypy = true
line_length = 88
py_version = "py37"
[[tool.pysen.lint.mypy_targets]]
  paths = ["."]

blackはFormatter、flake8はコーディング規約であるPEP8に違反しないかチェックするLinter、isortはimportの順番を並び替えてくれるFormatter、mypyは型ヒントから型チェックを行ってくれるLInterとなっている。

Linterでチェックを行いたい場合は以下を実行:

$ pysen run lint

Formatterでコードを整形したい場合は以下を実行:

$ pysen run format

今日の内容をまとめると、以下:

  • プロジェクト構成
    • パッケージルート、examples/tests/をプロジェクトルート直下に用意する
  • pipパッケージとしてビルド可能にする
    • pyproject.tomlsetup.pysetup.cfgを置いてビルド可能にする
    • pipパッケージとしての設定はsetup.cfgに書く
    • requirements.txtを使わない
    • pipパッケージとしてビルドするにはsetuptools、wheel、buildをインストールして$ python -m build
    • ビルドしたパッケージをインストールするには$ pip install <wheelファイル>
    • 開発用としてインストールするには$ pip install -e .
    • 開発時にだけ必要な依存するpipパッケージはsetup.cfgoptions.extra_requireで指定しておく
  • Linter、Formatterとしてpysenを使う
    • インストールは$ pip install "pysen[lint]"もしくはoptions.extra_requireを使う
    • 設定はpyproject.tomlに書く
    • linterを実行するには$ pysen run lint
    • formatterを実行するには$ pysen run format

今日はここまで!

もうPythonで迷わないために。(その3)

前回はPythonの仮想環境について書いた。

今回は開発環境について。

JupyterLab

実際のところ、開発環境は好みもあるので、「絶対にこれを使うべきだ」というのはない。 vimなどのエディタがあれば十分という人もいるだろうし、一般的に人気のあるVSCodeや、Python開発に特化したPyCharmなどもある。

ただ、データ分析をするのであれば、やはりJupyterを使うのがオススメ。 とくに、Jupyter Notebookの後継であるJupyterLabを使うといい。

Jupyter Notebookのいいところといえば、実行した結果がすぐに見れるので、探索的にコードが書いていけるところ。 これがデータ分析にすごくあってる。 また、データ分析でなくても、ノートブックでコードをいろいろ試し、ちゃんと動作することを確認できてからその結果をもとにスクリプトを書くといった使い方もできる。

JupyterLabではこれに加え、さらに以下のようなメリットがある:

  • 複数のノートブックをタブで開け、レイアウトも自由に変えられる
  • ターミナルが使える
  • Markdownを書いてプレビューを見れる
  • ファイルブラウザが使える
  • 拡張機能が使える
  • テーマを入れて切り替えることで外観を変えられる
  • ノートブックやスクリプトデバッグできる(ver3以降)

このおかげでほとんどの作業はJupyterLabの中でできてしまう。 強い。

JupyterLabのインストールと起動

さて、そんなJupyterLab、インストールも簡単。

まず、拡張機能を使えるようにするためにNode.jsをインストールしておく。

あとはpipを使ってインストールするだけ:

$ pip install jupyterlab

ちなみに、もうPythonで迷わないために。(その2) - いものやま。のように仮想環境を使っているなら、汎用的な仮想環境でインストールするといい。

そして起動は以下のとおり:

$ jupyter lab

なお、デバッグ機能を使いたい場合、JupyterLabは3.1以上、依存関係で一緒に入るipykernelは6.2以上になっている必要がある。 もしそうなっていない場合、以下のようにアップグレードしておくといい:

$ pip install --upgrade jupyterlab ipykernel

カーネルの追加

ノートブックから使われるインタプリタカーネルと呼ぶけど、これは普通、JupyterLabを動かしているインタプリタと同じものが使われる。 ただ、プロジェクトによっては仮想環境にある別のインタプリタを使いたいことも。 そういった場合、JupyterLabにカーネルを追加するといい。

カーネルを追加するには、まずカーネルとして追加したいPythonの仮想環境を有効にする。

そして、以下のコマンドでipykernelをインストール:

$ pip install ipykernel

そのあと、ipykernelを呼び出してそのPythonの仮想環境をカーネルとして追加する:

$ python -m ipykernel install --user --name <カーネル名>

これでカーネルが追加されるので、ノートブックで使用したいカーネルを選べばいい。 (JupyterLabの再起動が必要かも)

ターミナルの設定

あとは細かいTipsをいくつか紹介。

ターミナルが使えるのは便利なんだけど、何もしないとフォントがすごく見づらい可能性がある。 その場合、メニューの「Settings」-「Advanced Settings Editor」を選んで設定を開き、「Terminal」の項目で設定を修正しておくといい。

たとえば、フォントとして等幅フォントのMonacoを使い、サイズを16ptにしたいなら、以下のような感じ:

{
    "fontFamily": "Monaco",
    "fontSize": 16
}

テーマの追加、指定

拡張機能を使ってテーマを追加すると見た目を変えられる。

メニューの「Settings」-「Enable Extension Manager」にチェックを入れると拡張機能をUIからインストールできるようになる。 そして、テーマを検索して「Install」ボタンを押せばテーマは追加され、あとは「Settings」-「JupyterLab Theme」から使いたいテーマを選べばいい。

テーマはいろいろあるので探してほしいけど、自分が最近使ってるのは「One Dark Pro for JupyterLab」というもの。

ダークテーマで、デバッガやターミナルでも色使いがいい感じだったので気に入っている。

UIの日本語化

それほど必要性は感じないけど、一応UIを日本語化もできる。

やり方は簡単で、pipでインストールするだけ:

$ pip install jupyterlab-language-pack-ja-JP

これでメニューの「Settings」-「Language」から「Japanese」を選べばいい。 (JupyterLabの再起動が必要かも)

単独ウィンドウとして実行

JupyterLabを普通に使うのだと、Webブラウザの1つのタブの中で実行される。

ただ、これだと1つの独立したアプリっぽくなくて、また、WebブラウザのUIに画面のスペースを少し取られてしまうのが不満。

そんな場合、使っているブラウザがChromeなら以下のChrome拡張が使える:

Open-as-Popup - ht0.de

このChrome拡張を入れると、タブをポップアップウィンドウで表示できるようになる。 そこで、JupyterLabのタブをポップアップウィンドウにして全画面表示にすると、普通のデスクトップアプリのように使えてとてもいい。

なお、調べたらつい最近JupyterLab AppというElectronベースのデスクトップアプリがリリースされてた:

これを使うのもありかもしれない。


今日の内容をまとめると、以下:

  • データ分析をするならJupyterLabがオススメ
  • インストール方法は以下:
    1. Node.jsをインストールしておく
    2. 汎用的なPython仮想環境を有効化
    3. $ pip install jupyterlabでインストール
  • 起動は$ jupyter lab
  • 他のPython仮想環境を使いたい場合、カーネルを追加する:
    1. 追加したいPython仮想環境を有効化
    2. $ pip install ipykernelでipykernelをインストール
    3. $ python -m ipykernel install --user --name <カーネル名>カーネルを追加
  • その他Tips:
    • ターミナルのフォント設定を変えられる
    • テーマを追加して見た目を変更できる
    • UIを日本語化できる
    • 単独ウィンドウとして実行できる

今日はここまで!

もうPythonで迷わないために。(その2)

前回はどのPythonを使うべきか書いた。

今日はPythonの仮想環境について。

Pythonの仮想環境について

単に「仮想環境」といった場合、たとえば古くはVMWareVirtualBox、あるいはQEMU、最近だとDockerのように、ホストOSの上にゲストOSを載せ、まるで独立した環境であるかのように見せる技術のことをいうことが多い。

ただ、Pythonの仮想環境といった場合、上記とは少し違ってくる。

Pythonの仮想環境では、仮想環境ごとに独立したPythonの実行環境を用意できて、仮想環境を切り替えることで、Pythonのバージョンやインストールされたライブラリ、そのバージョンを切り替えることができる。 たとえば、仮想環境1ではPython3.6を使ってTensorFlowの1系を使用し、仮想環境2では同じPython3.6を使うけどTensorFlowを2系を使用、仮想環境3ではPython3.9を使ってTensorFlowの2系を使用するといったことができる。

なんでそんなことをするかというと、まず大前提として、Pythonは仮想環境なしだと同名のライブラリは1つのバージョンしか入れられないから。なので、あるプロジェクトではこのバージョン、別のプロジェクトでは別のバージョンといった使い分けができない。 さらに、ライブラリ間には依存関係があって、場合によっては特定のバージョンの組み合わせでしか動作しないということもある。 そこで、仮想環境を用意して、仮想環境ごとにライブラリをインストールするようにすれば、仮想環境を切り替えることで使うライブラリのバージョンを変えることができるし、依存関係の問題も最小限に抑えることができる。

まぁ、RubyのBundlerを知ってる身からすると、なんて頭が悪いデザインで無駄なディスクの使い方するんだろうというのが正直なところだけど・・・シンプルな仕組みで実現できているという意味ではいいのかもしれない。

何はともあれ、何かと厄介なライブラリ周りの問題を避けるためにも、仮想環境を使った方がいい。

どの仮想環境ライブラリを使うべきか?

さて、そこで問題になるのが、「どの仮想環境ライブラリを使うべきか?」という問題。 どのPythonを使うべきかという問題に続き「またか」ってウンザリすると思うんだけど、仕方ない。

実際、関連するライブラリがいろいろあって、pyenvとかvirtualenv、pipenvとか、Anacondaのconda、そしてそれらをどう組み合わせて使うのとか、もうカオスだった。

ただ、それも昔の話で、今はvenv一択。

  • 公式のライブラリ
  • 最初から入ってるのでインストール不要
  • Pythonのバージョンの切り替えもインストールされたライブラリの切り替えもできる
  • 扱いが簡単

venvの使い方

venvで仮想環境を作るには、使いたいバージョンのPythonでモジュールにvenvを指定し、引数として仮想環境名を与えて実行すればいい。

たとえばPython3.9でpy39という名前の仮想環境を作りたい場合、以下のようにする:

$ python3.9 -m venv py39

こうすると、py39というディレクトリが作られ、その中に仮想環境に必要なファイルが置かれる。 (インストールしたライブラリなどもこの中に置かれる)

仮想環境を作っただけだとまだこの仮想環境は使われなくて、有効化(アクティベーション)することでこの仮想環境が使われるようになる。 有効化するには作った仮想環境の下にあるbin/activateを現在のシェルで実行する。

たとえば上記の仮想環境を有効にするには、以下のようにする:

$ source py39/bin/activate

こうするとプロンプトの前に(py39)という仮想環境名がつき、pythonコマンドでPython3.9が起動するようになって、この仮想環境にインストールされたライブラリが使用されるようになる:

(py39) $ python --version
Python 3.9.4

有効になっている仮想環境を無効化(デアクティベート)するには、deactivateコマンドを実行すればいい:

(py39) $ deactivate

ちなみに、いらなくなった仮想環境はディレクトリごと削除すればいい。

仮想環境は何個用意すべき?

あとは仮想環境を何個用意すべきかという問題。 できるだけ分かれていた方が問題は起こりにくいけど、かといって何個も作ってしまうとディスク容量も食うし、散らばってしまうと厄介。

これについては、汎用的な仮想環境を1つ、そしてプロジェクトごとに専用の仮想環境を1つずつ用意するといい。

汎用的な仮想環境は、開発環境として使うJupyterLabを動かすのや、簡単なスクリプトを動かすのに使う。

一方、プロジェクトごとの仮想環境はプロジェクトのディレクトリの下に作り、そのプロジェクト専用で使う。

こうすることで、普段は気楽に使いつつ、それぞれのプロジェクトでは独立した環境で作業できるので問題が起きにくくなる。


今日の内容をまとめると、以下:

  • Pythonの仮想環境を使うようにする
  • 公式のvenvを使う
  • 使い方は以下:
    • 仮想環境を作るには$ <使いたいバージョンのPython> -m venv <仮想環境名>
    • 仮想環境を有効化するには$ source <仮想環境のディレクトリ>/bin/activate
    • 仮想環境を無効化するには$ deactivate
  • 汎用的な仮想環境を1つ、プロジェクトごとに仮想環境を1つずつ、用意するといい

今日はここまで!