前回の健康診断で、高脂血症のため要治療というショッキングな結果が出た。どうせ医者に行けば禁酒しろと言われると思い、2018年は完全に酒を絶った。本当に一滴たりとも酒は飲んでいない。
そして一年後の健康診断の結果がこちら。
変わってないよ! 禁酒しても健康にはならないという貴重な実験結果である。
辞めるべきだったのは酒や食物 1 ではなく人生だったようだ。病院へ行きます。
django ではURLのルーティングを django.urls パッケージで処理している。このパッケージのソースはなかなか読みにくいので、読むための手がかりを記しておく。
バージョンは2.1を仮定していることに注意1。
django.urls
パッケージでは継承もインタフェースも使っていないダックタイピングの見本のようなコードになっており 2 、この点が読みにくくしている一番の要因である。しかも、歴史的な都合なのか、クラスの命名規則にも致命的なわかりにくさが存在している。まずは主要なクラスの構造を説明することで見通しを良くする。
いちばん重要な概念はリゾルバである。リゾルバは自分が解決すべきURL ( PATH_INFO
)と、その解決方法を知っているデータである。 resolve()
メソッドを提供し、入力されたURLに合致するビュー 3 を返す。リゾルバは木構造をなしており、以下の2種類がある。
URLResolver
は他のリゾルバを含むリゾルバである。最上位のリゾルバは URLResolver
となる。また、 path
関数で include
を指定したときにも生成される。
一方、 URLPattern
は末端に位置するリゾルバで、ビューを保持している。 path
関数でビューを指定したときに生成される。
「自分が解決すべきURL」は、パターンクラスによって表されている。
パターンはリゾルバに付属する。URLが自分が扱うべきものであるかを判定し、さらにビューに必要なパラメータを抜き取ることができるクラスである。 match
メソッドを提供し、マッチ後に残ったURL、抽出したパラメータ ( args
または kwargs
)を返す。以下の3種類があるが、 URLPatternはパターンではない ことに注意する。
RegexPattern
はおそらく django 1.x 系の頃使われていたもの。今でも re_path
によって生成することはできるが、利用する必要はないだろう。
RoutePattern
が通常使われるもので、 path
関数を使ってパターンを記述すると生成される。結局入力されたパターン文字列は正規表現に変換されるが、 re_path
のように正規表現で直接記述するよりはわかりやすい記述が可能と言えよう。
LocalePrefixPattern
は特殊なもので、 i18n 対応でURLの先頭に言語 /ja/...
/en/...
などをつけるときに使われる。マッチする文字列は現在の言語設定によって異なる。例えば、現在の設定が日本語の場合、 ja/
という静的な文字列にマッチするパターンとして振る舞う 4 。このパターンを含め、URLの i18n 対応は無理やり突っ込んだ感がかなりある。例えば、このパターンは最上位のリゾルバにしか指定できないが、その判定も include
を使った構築時に 配下となる予定のリゾルバがこのパターンを使っていないかをチェックする と言っただいぶ強引な方法で判定している。後、URLの先頭に含まれる静的な文字列 ja/
は URLの先頭に含まれる文字列から決まったりする のだが、卵が先か鶏が先かよくわからない。国際化対応の実装は他にやりようがなかったんだろうか・・・。
与えられたURLを処理するビューと呼び出しに使うパラメータを得るための resolve
関数のコードを見ると、 最上位のリゾルバに依頼するだけ 。とてもシンプル。他の箇所もこれくらい読みやすければ文句がない。
念のためリゾルバ内の resolve
も読んでみると思ったより長くてウッとなるが、実際は 再帰的にresolveを呼び出している だけ。リゾルバのクラスが成す階層構造さえ知っていれば怖いことはない。ただし、ここにでてくる変数 pattern
は パターンではなくリゾルバ なので注意が必要(自分でも何を言っているかわからない)。
reverse
は resolve
の逆で、ビューの名前と呼び出し時に使うパラメータからURLを生成する。こいつもリゾルバに依頼すれば終了なのではないかと 淡い期待で読み始める と、全くそうではない難解なコードであることがわかる。なんでリゾルバに reverse
させないの・・・マジなの・・・?
reverse
関数内では、リゾルバの階層を潜ってビューを持つ URLResolver
を得る 5 。このとき、途中で現れたパターン(を表す正規表現)を拾う 6 。得られた URLResolver
は 新しいURLResolverでラップされる 。この URLResolver
は集めておいたパターン(正規表現)に紐付けられるので、新しい URLResolver
は完全なURLを再構築できる。
reverse
関数がリゾルバの階層を潜るには URLResolver
が持つ reverse_dict
という辞書が必要になるが、この辞書は _populate というメソッドが生成する。実質、 _populate
が逆引きロジックの本丸になっていると言え、非常に重要な役割を果たすメソッドである。
URLの再構築は、新たな URLResolver
の _reverse_with_prefix メソッドが行う。なんとプライベートメソッドである・・・。このメソッドは一見長いが、 reverse_dict
に入っている再構築に必要な情報を元にURLを再構築し、 パターンに適合するURLができたかをチェック した上で返すだけである。
ということで、URLの再構築に必要な情報は _populate
の中で構築される。 _populate
の実装を見てこのエントリは終わることにしよう。 _populate
は配下のリゾルバをすべて読み込み(再帰的に _populate
が呼ばれる)、ビュー名 ( path
の name
引数)をキーとする辞書を作る。これが reverse_dict
で、 reverse
関数が URLResolver
を見つけるために用いる。
そして、この辞書の値がURLの再構築に必要な情報である。値にはパターンの正規表現(検証用)、引数のデフォルト(穴埋め用)、コンバータの一覧が含まれるが、一番重要なのは正規表現を normalize関数 で変換したフォーマット文字列と引数リストである。例えば <int:year>/<int:month>
のような RoutePattern
からは (?P<year>[0-9]+)/(?P<month>[0-9]+)
のような 正規表現が作られる が、この正規表現から '/%(year)s/%(month)s/'
のようなフォーマット文字列と ['year', 'month']
という引数リストが抽出される。 normalize
の実装は真面目に正規表現をパースしており、利便性のためとは言えここまで頑張るか・・・と思わせる実装になっている。この情報を使うことで、 _reverse_with_prefix
は URL を再構築できるというわけである。長かった。
ところで、 normalize
の戻り値は1つではない。これは、 ?
や *
など、 0 個を許すパターンにおいて、フォーマット文字列の該当する引数があるパターンとないパターンを場合分けする必要があるからだ。URL再構築時にはキーワード引数などからどの引数があるか、ないかをチェックし、利用するフォーマット文字列を決めることになる。また、 .
や [0-9]
などがパターンに使われている場合はいずれの文字を選んでもマッチするので、前者は .
、後者は 0
を含むURLが代表として構築される。
YAPC::Tokyo 2019 に来ましたので、自分用のメモを残します。
$scalar
"0 but true"
は文字列として表示できる
42
join ",", localtime
"".localtime
だと英語表記0+localtime
は 0
to_i
すればよい)say localtime
の出力はドキッとする (現在時刻の数字のリストが連結されて表示される)subset Fizz of Int where * %% 3
というように3で割れる型を作れる
MAIN()
関数がある
MAIN()
MAIN()
を使うと、コマンドライン引数の型チェックができるMAIN
関数を引数によって分けることもUSAGE()
関数を実装できる$*USAGE
に入っている#|
#=
宣言子ブロックで補足ができる#|
は関数の前、 #=
は関数の後open
で書ける.slurp
ファイルの中身を全部取る$fh.slurp
は IO::Handdle
のメソッド、 slurp ""
は IO
role が提供する関数
close
は必要ないspurt
で文字列を一括で書ける.lines
で行ごとに取得。 for
を使う.IO
で IO::Path
インスタンス
IO::Path
はファイルの情報を提供する .e
.d
.s
zef
コマンドでモジュールを管理できる
use
で使える。 require
も一応ある%*ENV
mi6
を使え$GOPATH
の下で開発を行う必要がある
go get
bin/
の下に実行ファイル、 src/
の下にソースmaster
の HEAD を取ってくるgo1
というブランチがあるとそれをみるが、ほとんど浸透していないgo get
使わないgo mod
glide
dep
go
mod
vgo
は go mod
なので要らないMinilla
ExtUtils::makeMaker
とかあって便利だったgo mod
で解決する?var
で型を指定するか :=
で型を推論させるか[]string()
、 map map[string]int
...
が可変長引数panic()
で発生、 recover
で受けるnil
と書くpanic
は推奨されない。コーディングミスとかだけrecover
するくらいが良いerror
インタフェースか、 bool
で返すfile, err = os.Create
、 err != nil
でエラー処理log.Fatalf
は終わるので return
は不要defer file.Close()
などとして終了処理defer
は Perl の Scope::Guard
のようなもの
dismiss
はできない)if
のスコープで defer
しないように注意file.Close
のエラー処理は本当はしないといけない
defer
へ無名関数を指定してエラー処理。面倒gorutine
は説明すると長いので1ページまとめ
context.Context
if
はカッコが要らないrange
使ったりwhile
はないswitch
break
がいあらないので、多少は安心goto
がある。 goto
がない言語は辛いCompiler::Lexer
Data::Validator
を使っている
PerlSubroutinDeclaration
で関数定義を引っこ抜けるPerlQualifiedIdentifier
クォートされてない識別子PerlBlock
ブロック(dothis $foo, $bar)
の評価
(dothis($foo), $bar)
dothis($foo, $bar)
のどれ($foo->dothis, $bar)
dothis($foo, $bar)
sub f { { hoge() => "fuga" } }
{}
はブロック? ハッシュreturn
を書く、ファットカンマの左辺値を文字列にする、 +
をつける、のいずれかで曖昧じゃなくなるreturn
書いたらいいのではないか/
use
BEGIN
など動かさないとわからないものは避ける
Moose
の has
などはそれを解釈する解析機を書けばいいPlack->request->param
を使わない(複数値を指定したときに問題)ResultSet->search
を array コンテキストで受けないcapture
ブロックで囲むと、それがセグメントになるPlack::Middleware::XRay
勝手にトレースが始まるDevel::KYTProf
Devel::KYTprof::Logger::XRay
ロガーを差し替えてX-Rayを送ってもらうAWS_XRAY_DAEMON_ADDRESS
別ホストを指定するX-Amzn-Trace-ID
に 1-[timestamp]-[unique_id]
という形式のIDcapture
を書くと、自動的に親が入っているlocal
を使っているlocal
context.Context
を引数に引き渡すctx
を渡せないと厳しいdir
使えないから ls
を作った話 ( readdir
がある・・・)L
ドキュメントを書いていたら、好意的に受け止められたcronlog
のkazuhoさんはすごいcpm
作者)npm i -S
git のリポジトリ指定ができる
package.json
さえあればうごくでしょうnpm install
でモジュールを落としてこれるデモsed
awk
より高機能。 ruby では足りないかなYAPC::Tokyo 2019 前夜祭 LTソン presented by 吉祥寺.pm に参加予定ですので、自分用のメモを残します。
:=
しかない。 i++
できないけど ++i
は書ける()
の間に空白入れられない@INC
に sub {}
@INC
coderef hooksperldoc -v @INC
で出てくる@INC
の codref hooks を予め仕込んでおく作戦
@INC
に2つcoderefは入れられない自分のLTでした。
readobjnam()
関数
ハマったのでメモ。
大前提として、 WSL と Docker for Windows をうまく連携しておく必要がある。
以下のような手順。
ただし、
についてはもっと楽な解決法がある。 WSL 上に /etc/wsl.conf
というファイルをおけば WSL 側のマウントポイントを変えられるので、これを Docker for Windows に合わせてしまえば良い。
Automatically Configuring WSL – Windows Command Line Tools For Developers
$ cat /etc/wsl.conf [automount] root = / options = "metadata"
これで circleci local execute
も実行できる準備は整っているのだけど、一点だけ問題が。デフォルトの状態だと、 symlink が読めず、以下のようなエラーが出る。以下は、 pipenv
を .venv
で運用しているプロジェクトでのエラー例。
mkdir -p /home/circleci/project && cp -r /tmp/_circleci_local_build_repo/. /home/circleci/project cp: cannot read symbolic link '/tmp/_circleci_local_build_repo/./.venv/bin/python': No such file or directory
この解決策は以下に書かれている。
Yes, enabling developer mode fixes the symlink problem.
Developer mode は、Settings > Update & Security > For developers > Use developer features と進むとある。一つ重要な注意は、 Developer mode が有効となってから実行された ln -s
だけが Docker for Windows より読めるという点である。よって、 Developer mode はプロジェクトを始める前に有効にしておく必要がある。もしくは、問題となっている symlink をすべて削除して、作成し直す必要がある(というスクリプトを書いたほうが早そうだなあと思った)。
しかし、 pipenv
と .venv
で使っている場合について言えばこれでもうまくいかない。 WSL のローカルである /home/some-user/.pyenv
などへの symlink があり、これは Docker for windows からは見えないからだ。 symlink については、 env VIRTUALENV_ALWAYS_COPY=1 pipenv install
という実行の仕方で .venv
を作ればできなくなる。これで、 Checkout code フェーズでのエラーは抑えられた。
しかし、まだ終わらない。そもそもの問題として、 .pyenv
以下にインストールされているツールチェーンは、 #!/home/some-user/.pyenv/versions/3.7.2/bin/python3.7
などのシェバングを持っている。 .venv
はこれらをコピーしただけなので、当然同じシェバングを持つ。こんなものをコンテナ内に持っていっても、実行できるわけがない(試してないけど、この辺は Windows 以外でも起きる問題のはず)。
.venv
を複数の環境で使い回すというのがそもそも間違えていると考えられる。よって、 circleci local execute
する場合には .venv
を消してしまったほうがいいだろう。 .cricleci/config.yml
で、ローカル実行のときだけ .venv
を消すようにする。
- run: name: Remove .venv for local execute command: | if [[ $CIRCLE_SHELL_ENV =~ "localbuild" ]]; then rm -rf /home/circleci/project/.venv fi
ここまでやって、ようやく circleci local execute
できるようになった。なんとも辛い。
そもそもコンテナ内では PIPENV_VENV_IN_PROJECT
は指定しないほうがいい気はするのだが、 circleci のキャッシュを使うためには利用するディレクトリを固定したく、そのために指定している。
年末に書こうと思ってたけど、ニンテンドースイッチで遊んでたら書くの面倒になって書かなかったのだけど、まとめておかないと数年後には忘れてしまいそうなのでメモっておく。
今の職場に全く不満はなく、むしろいいことしかないのだが、黙っていて定年までこの生活ができるほどこの業界があまいとも思えない。そう考えたときに、5年間以上外の世界を見ていないというのは、自分の持っている技術が陳腐化し続けるという意味で、大きなリスクに思えた。そこで2018年の目標は転職か副業をすることだったのだが、年末に縁があり、知人の仕事を手伝わせてもらえることになった。今は Python3 を書いている。当たり前だが自社の環境とは全く違っており、勉強になって良い。2019年も、本業、副業の双方から様々な経験を得て、将来のためにいろいろ身につけておきたい。
cols_upper = map(str.upper, "a,B,c".split(",")) cols_lower = map(str.lower, cols_upper) for c in cols_lower: print(c) for c in cols_upper: print(c)
python2 だと動く。
A B C a b c
python3だと動かない。
A B C
python3では戻り値がイテレータに変わっているため。
Return an iterator that applies function to every item of iterable, yielding the results.
Built-in Functions — Python 3.7.2 documentation
Apply function to every item of iterable and return a list of the results.
list
でラップすることで、今までの挙動と同じにできる。
map
や filter
はリストに対する操作であるという印象が強いので、この仕様には面食らった。特に、イテレータはリストと違って内部に状態を持つミュータブルな値だということに、厳しみを感じる。
そんなわけで(?)、2019年もよろしくお願い致します。