Vimconf2019に行ってきました
11月3日に開催されたvimcof2019に行ってきました。
https://vimconf.org/2019/
セッション中はいろいろメモしていたのですが、それをそのまま書くとまとまりがないので、セッションに対する感想を書いていきます。
全体の感想
vimはvimとneovimがあるという多様性だけでなく、vimの使い方も人によって様々でとても多様性があって素晴らしいエディタだということを改めて感じました。
vimの最新の動向や内部動作・便利な設定・プラグインなど凄まじい量の情報を知ることができただけでもすばらしかったのですが、vim歴が1年や2年といった方が何人も登壇しており、自分もただvimを使うだけでなくコミュニティに何か還元していきたいと思わせてくれる素晴らしい体験ができました。
運営のみなさま・発表者のみなさまありがとうございます!
Keynote - Vim Renaissance Prabir : Shrestha さん
vim-lspの作者の方が最近のvimやvim-lspをどうして作ったのか、LSPの通信の中身、今後のvimの進化になりえる機能について語ってくれました。
vim scriptの書き方がわからないとこからvim-lspを作り始めたのすごすぎ。
また「何かができるか、といわれると、全ては可能だ、と答える。すべてはトライすれば」と言っていたのがとても印象的でした。
Keynote - We can have nice things : Justin M. Keyes さん
neovimのメンテナーの方がneovimの思想について語ってくれました。
neovimはとてもvimをリスペクトしていて、vimを置き換えるわけではなくvimでは手が入れづらい所を変えていくためのプロジェクトなんだということを感じることができました。
Your Vim is Only for You : mopp さん
vimrcを編集する上でmoppさんが意識していることを紹介していくセッションでした。
スライドにあったどうvimrcを育てるのかのフローチャートは「なるほど!」と納得のいくものだったので早速実践していってみようと思います。
Grown up from Vim User to Vim plugin developer side : IK さん
自分もPRを出してコミュニティに貢献しようって気持ちにさせてくれるすばらしい話でした。
Usage and manipulation of the tag stack : daisuzu さん
vimのtagの内部動作を図を交えてわかりやすく解説してくださいました。
tag周りはなんとなくで使っており、正直よくわかっていなかったので参考になると共に面白かったです。
make test: m-nishiさん
vimのテストについて紹介とvimのテストを書いてvimにコントリビュートする方法についての話でした。
テストでのコントリビュートの仕方をとても具体的に説明してくださっており、非常にためになりました。
とりあえず、vimとneovimのテストを発表中に動かしてみたので、自分も機会があれば貢献してみようと思います。
My Vim life : gorilla0513 さん
vimにはまって1年ほどで行った活動についての話でした。
1年という期間で非常にいろんなこと成し遂げていてすばらしいと思うと同時に、自分も何かplugin作ってみたくなりました。
Using Vim at Work! : Danish Prakash さん
vimを学習したり、vimを使う上での方法を心理学的要素を交えながら解説するといった発表でした。
vimと心理学という組み合わせは非常に新鮮で面白かったです。
Let's Play with Vanilla Vim : Hezby Muhammad さん
ここでのvanillaは生のvimではなく、最小限のvimrcのvimにことだそうです。
紹介してくれた機能はだいたい知っていてので、ちょっと安心しました。
vimはプラグインをいれないでも、ある程度の機能を使いこなすだけで生産的に作業ができるエディタだと改めて思わせてくれる発表でした。
13 Vim plugins I use every day : Tatsuhiro Ujihisa さん
普段使っているプラグインの説明をしながら、ライブコーディングでHTTP serverを作成するセッションでした。
プラグイン自体は知っているものが多かったですが、他の人操作を見るのはいろいろと気付きがあって面白かったです。
My dark plugins development history ~ over 10 years ~ : Shougo さん
Shougoさんが作ったプラグインのついて世代ごとにどういう思想で作っていたのかの解説をしてくださりました。
自分は2世代のころからお世話になっていたのでどこか懐かしい気持ちで聞いていました。
それにしてもShougoさんの速さに対する追求はすばらしいです!
ゴリラ.vim #1 参加記
ウホ、ウホホ、ウホホホ
ゴリラ.vimに参加してきました。
gorillavim.connpass.com
懇親会で3缶ほど飲んだ勢いで帰りの電車内で書いてます。
会場はDeNAさん提供。
https://twitter.com/MarcyWorkOut/status/1097431483181019137
ハッシュタグは #gorilliavim
[https://search.yahoo.co.jp/realtime/search;ylt=A2RCCzR4v2pcF1wAeARnl_p7;ylu=X3oDMTBiNzloa3JsBHZ0aWQDanBjMDAx?p=+gorillavim&ei=UTF-8]
感想
発表は素晴らしいものがたくさんあって、vim熱の高まりを感じました。
それぞれの発表については発表者の方が #gorillavim で資料を上げてくださっているので、全体の感想だけ書いておきます。
ゴリラ.vimという名前ですが、ゴリラ要素は驚くほどなかったです。(Twitterでウホウホいっていたぐらい)
初心者向けの発表から、言葉の節々から技術力の高さを感じさせられる発表があったりととても楽しい時間でした。
ゴリラさんVim歴半年ぐらいだったの!?
Vimの中毒性わかる!
r! 便利そう!
vimが歌った!
reply.vim 便利そう、REPL多様してるし発表聞きながら入れた!
Twitterのトレンドにも入ったようです。
https://twitter.com/gorilla0513/status/1097453791186579456
素晴らしい会を主催してくださった ゴリラ (@gorilla0513) | Twitter さん及び発表者の方・会場を提供してくださったDeNAさんありがとうございます!!!
懇親会で参加者の方と話していたら、学生さんが結構いて驚きました。(さすがVim!)
またライブコーディングならぬライブvimrc設定が行われてたりと非常に濃い懇親会で最高でした。
Goのjson.Unmarshalについてのメモ
jsonを文字列をgoの構造体にするときにお世話になるjson.Unmaarshal
を使い、json文字列ではネストした構造のデータを基本型だけをフィールドとしてもつ構造体にunmarshalする方法についてのメモ。
結論
先に結論を書いておくと、以下の例のように変換先の構造体でUnmarshalメソッドを実装すればいいです。
package main import ( "encoding/json" "fmt" "log" ) type Person struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` Age int64 `json:"age"` } func (s *Person) UnmarshalJSON(data []byte) error { type Name struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` } type person struct { Name Name `json:"name"` Age int64 `json:"age"` } var tmp person if err := json.Unmarshal(data, &tmp); err != nil { log.Fatal(err) } *s = Person{ FirstName: tmp.Name.FirstName, LastName: tmp.Name.LastName, Age: tmp.Age, } return nil } func main() { data := []byte(`{ "name": { "firstname" : "first", "lastname" : "last" }, "age": 20 }`) var p Person json.Unmarshal(data, &p) bytes, _ := json.Marshal(p) fmt.Printf("%s", bytes) }
https://play.golang.org/p/m5dH2gp8OGU
実行結果はこちら。
{"firstname":"first","lastname":"last","age":20}
Unmarshalの仕組み
Unmarshal関数でJSONをGoの構造体に変換するときは、以下のようなルールで変換が行われます。
詳しいルールはドキュメントを参照してください。
https://golang.org/pkg/encoding/json/#Unmarshal
- Unmarshalに引数として渡した構造体のフィールド名またはjsonタグがJSONのキー名と一致した場合に、フィールドの値としてJSONの値を格納する。
- 構造体のフィールド名またはjsonタグに一致しないフィールドは無視される。
シンプルなJSONをunmarshalする
ネストがないシンプルなJSONをUnmarshlするのはとっても簡単。
JSONのキー名に対応するフィールド名を持つ構造体を定義してあげて、構造体のポインターをjson.Unmarshal
に渡して上げればいいです。
import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name"` Age int64 } func main() { data := []byte(`{ "name" : "first last", "age" : 20 }`) var person *Person if err := json.Unmarshal(data, &person); err != nil { panic(err) } fmt.Printf("%+v", person) }
実行結果
&{Name:first last Age:20}
すごく簡単!!
ネストがあるJSONをunmarshlする
JSONの中にネストがある場合には少しだけ複雑で、jsonタグやフィールド名ではネストが表現できないので、
ネストの中身に対応した構造体を定義して(下記の例だとName型)、その型をフィールドとしてもつ構造体のポインターをjson.unmarshal
に渡す必要があります。
import ( "encoding/json" "fmt" ) type ( Name struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` } Person struct { Name `json:"name"` Age int64 `json:"age"` } ) func main() { data := []byte(`{ "name": { "firstname" : "first", "lastname" : "last" }, "age": 27 }`) var p Person json.Unmarshal(data, &p) bytes, _ := json.Marshal(p) fmt.Printf("%s", bytes) }
実行結果
{"name":{"firstname":"first","lastname":"last"},"age":27}
ネストされたデータがあるJSONをネストがないようにunmarshalする(flatten)
unmarshalを行うだけなら上記の方法で特に困ることはないのですが、unmarshalした後の処理でデータにネストがあると扱いづらい時もあると思います。
要はこれを
{ "name": { "firstname" : "first", "lastname" : "last" }, "age": 27 }
この構造体にしたい。
type Person struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` Age int64 `json:"age"` }
JSONの中にネストがある場合には、ネストしたデータに対応する構造体を定義する必要があるので、先程の例のような変換を行ったあとに、基本型だけをフィールドとして持つ構造体にマッピングさせます。
import ( "encoding/json" "fmt" ) type ( Name struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` } Person struct { Name `json:"name"` Age int64 `json:"age"` } FlatPerson struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` Age int64 `json:"age"` } ) func main() { data := []byte(`{ "name": { "firstname" : "first", "lastname" : "last" }, "age": 27 }`) var p Person json.Unmarshal(data, &p) // unmarshal後に別の構造体にマッピングしている f := FlatPerson{ FirstName: p.FirstName, LastName: p.LastName, Age: p.Age, } bytes, _ := json.Marshal(f) fmt.Printf("%s", bytes) }
実行結果
{"firstname":"first","lastname":"last","age":27}
やりたかったことはできたが、unmarshal後に毎回マッピングするのは面倒、unmarshal時にやってしまいたいです。
json.Unmarshal
関数は第二引数として渡された型がUnmarshaler
インターフェイスを実装している場合には、その型のUnmarshalJSONを呼び出します。
Unmarshaler
インターフェイスはUnmarshalJSON
だけを持つシンプルなインターフェイスなので、json.Unmarshalの第二引数として渡したい型にUnmarshalJSON
を実装してあげるだけでいいです。
type Unmarshaler interface { UnmarshalJSON([]byte) error }
ということで、第二引数に渡している型にUnmarshalJSON
を実装したものが、一番最初に結論として記載しているコードになります。
参考にさせて頂いたサイト
docker-composeで開発用にelasticsearchとkibanaを立てる
利用するimage
elasticsearchの公式imageは3種類ある。
The images are available in three different configurations or "flavors".
The basic flavor, which is the default, ships with X-Pack Basic features pre-installed and automatically activated with a free licence.
The platinum flavor features all X-Pack functionally under a 30-day trial licence. The oss flavor does not include X-Pack, and contains only open-source Elasticsearch.
https://www.elastic.co/guide/en/elasticsearch/reference/6.2/docker.html
- basic : フリーライセンス、X-Pack Basicがインストールされている
- platinum : X-Packの全ての機能が入っていて、30日のトライアルライセンス
- oss X-Packがインストールされておらず、OSSのelasticsearchが入っている。
kibanaの公式imageは2種類ある。
The images are available in two different configurations or "flavors".
The x-pack flavor, which is the default, ships with X-Pack features pre-installed. The oss flavor does not include X-Pack, and contains only open source Kibana.
https://www.elastic.co/guide/en/kibana/6.2/docker.html
今回は開発用としてとりあえず立てたいだけなので、X-Packが入っていないOSS版を利用する。
docker-compose
公式サイトにかかれているdocker-compose
の設定を参考に作成したものがこちら。
version: "3" services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.4 container_name: elasticsearch environment: - cluster.name=docker-cluster - "ES_JAVA_OPTS=-Xms512m -Xmx512m" ulimits: memlock: soft: -1 hard: -1 volumes: - esdata1:/usr/share/elasticsearch/data ports: - 9200:9200 kibana: image: docker.elastic.co/kibana/kibana-oss:6.2.4 container_name: kibana links: - elasticsearch:elasticsearch ports: - 5601:5601 volumes: esdata1: driver: local
設定していることは下記。
- kibanaからelasticsearchに接続
- 9200ポートでelasticsearch、5601ポートでkibanaにアクセス
- elasticsearchのデータを残すためにvolumeをマウント
この状態でdocker-compose up
すればelasticsearchとkibanaが立ち上がる。
$docker-compost up # 別ウィンドウでシェルを立ち上げて起動していることを確認 $ curl '127.0.0.1:9200' { "name" : "6bfm0uu", "cluster_name" : "docker-cluster", "cluster_uuid" : "Wv9oXM4mS9O66WhI4d4DuA", "version" : { "number" : "6.2.4", "build_hash" : "ccec39f", "build_date" : "2018-04-12T20:37:28.497551Z", "build_snapshot" : false, "lucene_version" : "7.2.1", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search" } $ curl '127.0.0.1:9200/_cat/indices?v' health status index uuid pri rep docs.count docs.deleted store.size pri.store.size green open .monitoring-es-6-2019.01.27 wJHPWNTlTmakw24pR1oLzA 1 0 5623 6 5.3mb 5.3mb green open .monitoring-kibana-6-2019.01.27 8kPG3bE6Rr2bykskvBaESA 1 0 800 0 649kb 649kb green open .kibana bf2Q6gT-SI67CkvQS_8-mA 1 0 1 0 4kb 4kb
この状態でlocalhost:9200
にアクセスでelasticsearchにつながる。
ブラウザでlocalhost:5601
にアクセスするとkibanaが表示される。
おまけ
上で立てたelasticsearchとkibanaをしばらく使っていたのですが、kibanaでfilterした結果をCSVファイルとしてダウンロードしようと思ったら、X-Packが必要とのことでX-Packを入れた版のdcoker-compose.yaml
も作成。
X-Packを入れる場合
ライセンス登録(6.2以下を使用して、1ヶ月以上使う予定の場合)
何も登録しない場合には1ヶ月でライセンスが切れるので、それ以上使用する場合にはライセンスの登録が必要。
ちなみに、X-Packのbasicライセンスは6.3からデフォルトで入っているようになったので、6.3以降は使用する場合はライセンス登録する必要はない。
ライセンスの登録は下記にサイトにアクセスして、指示に従って適当に進めればOK。
登録が完了して、ダウンロードしたライセンス情報のjsonは後で使うので、場所がわかるようにしておく。
https://register.elastic.co/registration
docker-compose
X-Packを入れた以外は、特に変更点はないです。(imageが変わってる)
version: "3" services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:6.2.4 container_name: elasticsearch environment: - cluster.name=docker-cluster - "ES_JAVA_OPTS=-Xms512m -Xmx512m" ulimits: memlock: soft: -1 hard: -1 volumes: - esdata1:/usr/share/elasticsearch/data ports: - 9200:9200 kibana: image: docker.elastic.co/kibana/kibana:6.2.4 container_name: kibana links: - elasticsearch:elasticsearch ports: - 5601:5601 volumes: esdata1: driver: local
X-Packを入れたので、メニューが増えている。
ライセンスの適用 (6.2以下を使用して、1ヶ月以上使う予定の場合)
elasticsearchに対して、ダウンロードしたライセンスファイルを指定することで、ライセンスを適用します。
# ライセンスの確認 $ curl -XGET 'http://localhost:9200/_xpack/license' { "license" : { "status" : "active", "uid" : "65e1b347-ed45-4f36-8a1f-5233ef4608a7", "type" : "basic", "issue_date" : "2019-01-27T08:58:11.429Z", "issue_date_in_millis" : 1548579491429, "expiry_date" : "2019-02-26T08:58:11.429Z", "expiry_date_in_millis" : 1551171491429 "max_nodes" : 1000, "issued_to" : "docker-cluster", "issuer" : "elasticsearch", "start_date_in_millis" : -1 } } # 適用 $ curl -XPUT -u elastic:changeme 'http://localhost:9200/_xpack/license?acknowledge=true' -H "Content-Type: application/json" -d @license.json {"acknowledged":true,"license_status":"valid"}% # 確認 $ curl -XGET 'http://localhost:9200/_xpack/license' { "license" : { "status" : "active", "uid" : "c5e7b07a-9630-4712-bbef-3e6f37df29ee", "type" : "basic", "issue_date" : "2019-01-27T00:00:00.000Z", "issue_date_in_millis" : 1548547200000, "expiry_date" : "2020-01-27T23:59:59.999Z", "expiry_date_in_millis" : 1580169599999, "max_nodes" : 100, "issued_to" : "##########", "issuer" : "Web Form", "start_date_in_millis" : 1548547200000 } }
参考
goのWaitGroupでハマった話
goで並列処理をするときにsync.WautGroupを使って、Goroutineが終わるのを待つ処理を書いて、意図した通りに動かず30分ほど無駄にした話です。
結論
先に結論を書いておくと、Goroutineの中でAdd
するのではなく、Goroutineを呼び出す前にAdd
をしておけば問題ないです。
func something(wg *sync.WaitGroup) { fmt.Println("hello") defer wg.Done() } func main() { var wg sync.WaitGroup for i := 0; i < 2; i++ { // これをGorutineの中でやらない wg.Add(1) go something(&wg) } fmt.Println("wait") wg.Wait() fmt.Println("end") }
なにでハマったのか
最初に書いていたコードはこんな感じ。
func something(){ fmt.Println("hello") } func main() { for i := 0; i < 2; i++ { something() } fmt.Println("end") }
これはhello
が2回表示されたあとにend
が表示されるだけの、なんの問題もないコード。
hello hello end
main
から呼んでいるsomething
をGoroutineとして実行するように、something
の呼び出し時にgo
を追加。
func something(){ fmt.Println("hello") } func main() { for i := 0; i < 2; i++ { go something() } fmt.Println("end") }
この状態だとmain
はGoroutineの終了をまたずに終了してしまうので、実行すると下記のように出力された。
end
このままだとGoroutineの処理が最後まで終わらないので、main
がsomething
が終わってから終わるようにsync.WautGroup
を使うように変更しました。
func something(wg *sync.WaitGroup) { fmt.Println("hello") wg.Add(1) defer wg.Done() } func main() { var wg sync.WaitGroup for i := 0; i < 2; i++ { go something(&wg) } fmt.Println("wait") wg.Wait() fmt.Println("end") }
これで以下のような出力になることを想定してました。
wait hello hello end
が、実際に出力されたのは下記。
wait end
hello
が出力されないままmain
が終わってしまってるようです。
試しにfor
の中でsleepするように変更する。
func something(wg *sync.WaitGroup) { fmt.Println("hello") wg.Add(1) defer wg.Done() } func main() { var wg sync.WaitGroup for i := 0; i < 2; i++ { go something(&wg) time.Sleep(1 * time.Millisecond) } fmt.Println("wait") wg.Wait() fmt.Println("end") }
hello
が出力されるようになりました。
これで一応はGoroutineが終わるまでまってmain
が終わるようになりました…
(sleepさせる時間は、少し試してみたところ1msほどsleepさせれば安定してhello
が出力された)
hello hello wait end
sleepをfor
ループの中で毎回するのは、ループの回数が増えてきた単純に遅くなっていくのでなるべく避けたいので、調べてみたら下記の記事でわかりやすく説明してくれてました。
https://qiita.com/ruiu/items/dba58f7b03a9a2ffad65
Goroutineがスケジューリングされるタイミングは即時ではなく、任意なのでGoroutineが動いてAdd
される前にWait
まで到達することがあるということで、Goroutineの中ではなく、Goroutineの呼び出し前にAdd
をしておく必要があるということでした。
最初にも載せてありますが、直したコードがこちら。
func something(wg *sync.WaitGroup) { fmt.Println("hello") defer wg.Done() } func main() { var wg sync.WaitGroup for i := 0; i < 2; i++ { wg.Add(1) go something(&wg) } fmt.Println("wait") wg.Wait() fmt.Println("end") }
今までは、Goroutineを生成する個数自体が多かったり、Goroutineを呼び出してからすぐにWait
をすることがなかったので、気づくことがなかっただけで、何回かGoroutineの中でAdd
をしているコード書いてしまっていました。
しかもそれがなんの問題もなく動いていたので、原因に気づくのにずいぶん遠回りすることになりました。
今後は気をつけて書いていこうと思います。
pyconjp2018に参加して来ました
pyconに参加したあと、私生活がごたごたしていたずいぶん遅くなってしまいましたが、blog書きました。
概要
9月15,16,17,18で開催されたpyconjp 2018に参加してきました。
15日はスプリント、16日がチュートリアル、17,18日がカンファレンスといったスケジュールで、自分はスプリントとカンファレンスに参加してきました。
今年のテーマは「ひろがる Python」
公式サイト : https://pycon.jp/2018/
スプリント
自分はQuantXというサービスでpythonを使用して株価の予想を行うプログラムを作って見るスプリントに参加しました。
Quantは作ったアルゴリズムを売ることができるので、いいアルゴリズムが実装できればお小遣い稼ぎができるといったサービスです。
スプリントでは既にこのサービスでアルゴリズムを売っている人や、QuantXを運営しているSmart Trade社の人がいたためとてもスムーズに開発を進めることができました。
金融の知識も簡単なことならば、その場にいる人に質問したら教えてくれたりと非常に助かりました。
加えて、talib
めっちゃ便利。
これなかったら時間内に何もできなかったと思う。
自分がやったこととしては下記のような感じです。
- チュートリアルを実施して2つのアルゴリズムを実装
- この2つのアルゴリズムを組み合わせてみた
- jupyter環境でQuantX FactoryのAPIを叩ける環境をさわってみた
user slack : https://smarttradeusers.slack.com/messages/C5MMXBTRC/? 公式サイト : https://factory.quantx.io/ SDK Doc : https://factory.quantx.io/quantx-sdk/index.html#
1日目
keynote
アルゼンチンでpythonを広げる活動をやっている人。
pythonを広げる活動をするようになった話をその活動の話だった。
活動の中で彼女に振られたり、ヤギを交通事故を起こしたり、ボリビアの内戦に遭遇しながらも活動を続けており、pythonへの情熱が凄まじかった。
「print "Hello world"
から始まって、現実でもいろんな世界に行くことができた」というまとめが最高にエモかった。
招待講演 東大松尾研流 実践的AI人材育成法
東大 松尾研のリサーチディレクターの中山さん
最初の15分pythonの話がまったくでてこなかったので、途中退出してブースを回っていたのでわからない。
「numpy力」というワードがtwitterで急に流れてきてた。
Webアプリケーションの仕組み
https://scrapbox.io/shimizukawa/PyCon_JP_2018:_Webアプリケーションの仕組み
すごく丁寧なWebの裏側の話だった。
web開発は上から下まで幅広い知識が必要だが、フレームワークを使えば全体を知らなくてもOK。
フレームワークがなぜ生まれたのかをゼロから作って追体験していく話。
会場で使っているフレームワークを聞いていたが、django派が多かった。
実際にコードを動かしながら説明しており、非常にわかりやすかった。
発表内容を参考にして、自分でも一度ゼロから追体験してみるべきだと思った。
あなたと私いますぐパッケージング
ここ1年のパッケージングまわりのライブラリの状況とパッケージ管理のベストプラクティスについて。
パッケジング周りについて、ざっくりと理解できるセッションだった。
パッケージングがsetuptoolsに依存しなくなり、他のパッケージング方法がでてくるかもしれないとのこと。
pipenvでパッケージ管理は問題なさそう。
Pythonで時系列のデータを分析してみよう
https://www.slideshare.net/ssuser49f8441/python-114923273
時系列データにstatsmodelsを適用するだけで、わりと簡単にデータ分析ができるといった内容だった。
もう少し発展的な内容を期待していたので、若干物足りなさはあったが、statsmodelsの使用感はなんとなくわかったのでよかった。
Pythonistaの選球眼(せんきゅうがん) - エンジニアリングと野球の目利きになる技術
野球のデータ分析のPFを作ってきた話を過去のpyconでの発表を振り返りながら、最近作った環境の説明。
選球眼とは「毎日発生する【選択と決断】の繰り返しの中で【選択と集中】ができること」
選択するためには「技術力」を磨くこと「決断」を恐れないこと。
集中するためには、実現したいことを「言語化」し、言語化したものから決断する「ルール」を作り、「時間」・「お金」といったリソースを集中することが大事とのこと。
2日目
2日目は若干体調が悪かったので、午後からyoutube liveで視聴しながらtwitterでつぶやいて参加してました。
REST APIに疲れたあなたに送るGraphQL 入門
RESTの問題点を上げながらGraphQLでそれをどうやって解決しているのかの説明。
後半はAWSのApp SyncでGraphQLを使ってみるデモ。
pythonでGraphQLを使用する話を期待していてので、少し期待外れだったが、GraphQLの概要を理解する意味ではよかった。
複数アプリケーションのプロセスとログを管理するための新しいツールと手法
Jaffleというツール説明 : 複数のpythonアプリケーションと外部プロセスをまとめて管理
https://jaffle.readthedocs.io/en/latest/
開発環境でログの管理を手軽にできるツールがあると確かに便利そうだった。
あとで触ってみる。
Pythonによる異常検知入門
異常検知自体の説明から入り、異常検知の各アルゴリズムの説明とpythonでの実装方法の話だった。
スライドが非常にわかりやすく、異常検知についての理解は深まったが、時間的にpythonでの実装にあまり触れられてなかったのが残念。
異常検知自体の入門としては非常に参考になった。
Django を Zappaで構築してServerless Python のベストプラクティスを探る
いろいろ調査した結果、python で serverless なら zappa だとなったのでそれの発表。
serverless framework ・ zappa ・ chalice について調査し、結果としてzappaが今のところベストプラクティスとのこと。
zappaはDjango zappaが元になっていて、pythonistaが使うなら一番しっくり来るらしい。
Djangoだけじゃなく、flask等でも使うことができるとのこと
感想
python コマンドラインインタフェース比較
目次
- 概要
- 環境
- 調査対象ライブラリ
- 比較
- まとめ
- おまけ
概要
今までpython
を使用してコマンドラインツールを作成する時は、docopt
を使っていたのですが、ドキュメントを書いて、パースするタイミングでエラーが出た時の原因の特定に時間がかかって辛いので、他にいい方法がないか調べてみました。
環境
コードを動かした環境は以下。
調査対象ライブラリ
- optparse
- argparse
- docopt
- click
optparse
https://docs.python.jp/3.6/library/optparse.html
- 標準ライブラリ
- 現在は廃止予定となっており、
argparse
の利用が推奨(https://www.python.org/dev/peps/pep-0389/)- 別に使えないわけじゃないので、とりあえず今回の選定対象には入れてあります
- ライブラリの機能として以下をサポートしていない(実装しだいで実現できなくはない)
- 位置引数
- サブコマンド
argparse
https://docs.python.jp/3.6/library/argparse.html#module-argparse
- 標準ライブラリ
optparse
の後継なので、optparse
から乗り換えがし易い
docopt
- ドキュメントベースのコマンドラインインターフェース
- 同じインターフェースが様々な言語で実装されている
click
比較
シンプルなインターフェイスの例としては ls
コマンドのような command [option]
のインターフェイスを、サブコマンドをサポートするインターフェースの例としてgit
コマンドのようにgit command [option]
のインターフェイスを実装して比較します。
実装したコードは下記になります。
https://github.com/replicity/compare_python_command_line_parse_libraries
実装したまとめと使ってみた所感
- それぞれパースした結果を
optparse
はマップとリスト、argparse
はNamespace、docopt
はマップ、click
はメソッドの引数として扱う - オプションの作成方法自体は異なるが、シンプルなインターフェイスの場合には機能的な大きな違いはオプション以外の引数がある時に
optparse
以外はエラーになり、optparse
はリストとして返すぐらい - シンプルなインターフェイスを作るだけなら、
optparse
以外ならどれを使っても問題はなさそう - サブコマンドをサポートするインターフェイスを作成しようとすると
optparse
はライブラリの機能としてはサポートしていないので、サブコマンドを判断して処理する箇所を自前で実装することになる - どのサブコマンドでも使用するオプション(共有オプション)をサポートしているのは
argparse
だけclick
でも提案されていたが、ライブラリの思想としてサポートしないらしい : https://github.com/pallets/click/issues/108argparse
以外でも各サブコマンドで同じオプションを作成すれば、結果としては同じインターフェイスは作れはする
click
は位置引数にhelp
メッセージを指定できない : http://click.pocoo.org/6/documentation/
まとめ
結局どれを使うのがいいかというとこですが、機能についてまとめた表が下記になります。
ライブラリ | 位置引数 | オプション | 型変換 | ヘルプ自動生成 | サブコマンド | 共有オプション |
---|---|---|---|---|---|---|
optparse | × | ○ | ○ | ○ | × | - (サブコマンドをサポートしていない) |
argparse | ○ | ○ | ○ | ○ | ○ | ○ |
docopt | ○ | ○ | × | - (ヘルプを元にインターフェイスを作成) | ○ | × |
click | △(helpメッセージは設定不可) | ○ | ○ | ○ | ○ | × |
機能だけで見るならargparse
が一番高機能です。
ただ、正直argparse
でサブコマンドがあるインターフェイスを作るとそれだけでコード量が多くなって辛いです。
click
はサブコマンドの作成が非常に楽でよく考えられている印象でした。
docopt
はドキュメントを最初に書くタイミングである程度実装のイメージが固まったり、設計を見直すきっかけになったりするので、ドキュメントを書くのが苦痛じゃないなら選択肢としては良さげだと思います。
ただ、docopt
でエラーが出た時にエラーの原因を特定するのが結構困難なので、そこは注意が必要かと。
まとめると、基本的にはclick
を使用して他の言語とインターフェイスを揃えたい時はdocopt
、どうしてもargparse
じゃないと実装できないものがある場合だけargparse
を使うのが良さげ。
おまけ
invoke
コマンドラインインターフェイスを構築するものとして少し毛色が違うがinvoke
というライブラリもあるので、使ってみました。
http://docs.pyinvoke.org/en/latest/index.html#
invoke
はデコレーターベースのコマンドラインインターフェースで、複数のタスクを定義し、そのタスクを組み合わせて何かをする時に場合にはこちらが良さげでした。
具体的にはtask1の後にtask2を連続で実行するといったような場合。
inv task1 task2
また、オプションの解釈に関しては触ったライブラリの中ではinvoke
が一番優秀な印象でした。
詳しくはこちらに書いてあります : http://docs.pyinvoke.org/en/latest/concepts/invoking-tasks.html#task-command-line-arguments
clickとの比較として下記。
ライブラリ | 位置引数 | オプション | 型変換 | ヘルプ自動生成 | サブコマンド | 共有オプション | 複数のタスクの連続実行 |
---|---|---|---|---|---|---|---|
click | △(helpメッセージは設定不可) | ○ | ○ | ○ | ○ | × | × |
invoke | △(複数の値を指連続で指定する方法にくせがある) | ○ | △(boolとそれ以外) | ○ | × | - (サブコマンドをサポートしていない) | ○ |
python-prompt-toolkit
インタラクティブなコマンドラインツールを作成できるライブラリとしてpython-prompt_toolkit
があり、これを利用することで実行後にサブコマンドを選択したり、オプションをインタラクティブにセッティングしていくようなツールを簡単に作ることが可能になる(e.g. click-repl)。
https://github.com/jonathanslenders/python-prompt-toolkit/tree/master
なお、2018/5/5現在pipでいれると1.0.15がインストールされるが、githubには2.0のブランチがあり、リポジトリのREADMEに書かれているように1.0と2.0ではいろいろと変わっているようなので、これから使い方を覚えるなら2.0を使うほうが良さげでした。
Please notice that this is the 2.0 branch of prompt_toolkit. It is incompatible with the 1.0 branch, but much better in many regards. On Pypi you'll still find the 1.0 branch, but a release should follow soon. In the meantime, don't hesitate to test this branch and report any bugs.