あたりまえのことだが、 欲しがるということは、所有していないということである。
おしらせ:
tabesugi.net は、そろろそ復活すべき。
技術者の目標というのはものをつくることだ。 しかし新山は、トランプの家 (20階建て) を 10分でつくってしまう“技術者”よりも、 一生かけて固いレンガ 1個をつくるような技術者でありたい。
もうまぎれもない春である。鬱々と考えこむ人間にとっては危険な季節、 一年のサイクルの中でぽっかりと落ちこむ時だと、ダヴィドは前にどこかで 読むか聞くかしたことがある。ふっと気がゆるんでしまう夜中の 4時ごろのようなものだろう。ダヴィドは自分の陰鬱な思考をおし殺そうとはせず、 四方八方からおしよせてくるあらゆる励まし、赦しや忠告や問いかけを すべて断って、身も世もなく悲痛な思いにふけるようになった。-- トーヴェ・ヤンソン 「エンメリーナ」
どうでもいいけど (どうでもゆおろ)、 「オリジンべんとう」という言葉をきくと、 どうしても「リベンジべんとう」というふうに解釈したがる、 オレの脳というやつは。
自由かい?
次のプログラムを考えてみよう:
import os.path def foo(): if os.path.exists('/etc'): import os print os.listdir('/etc') return foo()
(追記: この部分に「↑↑読みとばすな↑↑」って書いていたのですが、 あとで見て自分でムカついたので消しました)
これは、実行するとエラーになる。
$ python foo.py Traceback (most recent call last): File "foo.py", line 8, in ? foo() File "foo.py", line 4, in foo if os.path.exists('/'): UnboundLocalError: local variable 'os' referenced before assignment
なぜか? これは Python の変数スコープというものが、 ひとつの関数内で、すべて並列に定義されるためである。
もっとわかりやすい例をあげる。
a=1 def bar(): b=a # ← この a は、実は a=2 # ← この a を見ている。 bar()
このコードでも、同じエラーがでる:
$ python bar.py Traceback (most recent call last): File "bar.py", line 7, in ? bar() File "bar.py", line 4, in bar b=a UnboundLocalError: local variable 'a' referenced before assignment
なんでなのか?
Python はレキシカルなスコープを許している。
つまり、ある変数が現在のスコープで定義されていなかったら、
その「外側の」スコープを参照することが許される。
だから最初の b=a
の a
は、
普通に考えればグローバルな (外側の a=1
で定義されている)
"a
" をさすと考えていいはずだ。
ところが、実際にはそうならない。
実は、Python の関数では、
その中で定義される (代入される) 変数はすべて関数の先頭で
並列に定義されたことになっているのだ。
ここで関数の内と外のスコープを視覚的にあらわすと、こうなっている:
# グローバルな定義
int a;
a=...
def bar():
# 関数内の定義
int a,b;
b=...
a=...
ここで bar()
内の式に現れるローカル変数 b
と
a
は最初からどちらもスコープに入っている、
つまり「見えた」状態になっている。ただし、各変数には
"まだ値が代入されていない" という印がついている。
このため、上のようなエラーが出るのだ。
しかし、いったい Python はなぜこんな構造をとっているのだろうか? スコープを「直列に」、つまり新しい代入文が現れるたびに ネストしたスコープが定義されるようにしてもいいはずだ。 その場合にはこうなる:
# グローバルな定義
a←...
def bar():
# 関数内の定義1
b←a # グローバルな a を見る
# 関数内の定義2
a←...
こうするとすべては平和におさまるであろう…と思うだろうが、 そうならない。Python がこのようにしなかった理由のひとつは、 効率の問題がある。代入文が現れるたびに新しい環境 (Scheme 用語、Python では正式に何ていうのか知らない) を作るのは効率がわるいので、全部いっぺんにやっておきたい。 しかし、本当の理由は、おそらく「相互再帰呼び出し」を 定義させるためだと思われる。
このような関数は、名前def foo(x): if x == 0: return 0 return bar(x-1) def bar(y): if y == 0: return 0 return foo(y-1)
foo
と bar
が
同時に見えているような環境でなければ定義できない。
ところが、ソースコードを上から読んで解釈していく段階では、
foo
を定義している最中には bar
が見えず、
かといって bar
を先に持ってくると
今度は foo
が見えなくなる。
Python には C のプロトタイプ宣言のようなものがないので、
ふたつの名前 foo
と bar
を同時に
束縛 (またも Scheme 用語) するしか方法はない。
実際には、このテクニックは多くの Scheme 実装でも使われていると思われる。
なぜって、どこにでも再帰を使う Scheme のような言語で、
再帰的な (末尾再帰を含む) 関数を定義するたびに
letrec
を書くなんてのはやっていられないから:
(define foo #f) (define bar #f) (letrec ((real-foo (lambda (x) (if (= x 0) 0 (real-bar (1- x))))) (real-bar (lambda (y) (if (= y 0) 0 (real-foo (1- y)))))) (set! foo real-foo) (set! bar real-bar))
ほとんどの Scheme 実装ではこう書ける:
これは、(define (foo x) (if (= x 0) 0 (bar (1- x)))) (define (bar y) (if (= y 0) 0 (foo (1- y))))
foo
と bar
が並列に
見えている (と想定できる) 状況でなければ書けない。
まあ、たぶん、インタプリタであれば define
時には
関数の中身まで評価せず、実行時まで名前解決を延期できるので、
別の方法でも実現できるだろうけど…。コンパイラでは無理だ。
話が脱線した。Scheme のことは、どーでもいい。 ふつうは Python のこういう例は気づきやすいけど、ヤッカイなのは 実際に明示的な代入文がなくても変数が「定義」されることである。 たとえば、こんなのだ:
それからこんなのも:a=1 def baz(): print a for a in range(10): print a
a=1 def yo(): print a try: dog() except Exception, a: pass
さて、ここでとりあえずエラーを防ごうとして global
なんてのを
つけると、結果はまったく意図しないものになる:
a=1 def bar(): global a b=a a=2
たしかにエラーはこれで出なくなるが、ここでは
a
はすべて同じ変数を指すことになってしまい、
「a=2
」が代入しているのは関数内にローカルに定義された
a
ではなくて (そもそも、ここではそんなものは存在しなくなる)
グローバルな a
である。
つまり、グローバル変数を変更してしまっていることになる。
きょうの教訓: 名前をつけるときは気をつけよう。なまえ、なまえ! トントン。
(ややキチガイ的に)
毎秒400回のクシャミと格闘しながら同時に python の構文木とも格闘しております。 つまり、「ふたついっぺんに格闘」ですが (そのままだ)、 お元気ですか。ぼくわもう寝ますよ、眠れればの話ですが。 (ゴミ箱がテッシであふれないうちに!)
新山にはこう聞こえる:「なんとかかんとか ホネホネ なんとか…ホゲェ〜〜〜!!」
オレはこんなとこばっか見てるわけではないよ!
とか、書いてある。
この発言は、あまりにも要出典すぎる。
「アニメーションって何。」
「アニメーションとはフレームごとに処理する写真技術で、
ものが動いているかのような幻想が創り出されるんだよ、
アニメーションの世界ではなんでも起こりうるんだ」
「人々はいまぼくらを見てるわけ?」
「まさに今ね!」
「…でもぼく今、パンツはいてないんだけど」
「ぼくもだよ!!」
「アニメーションは嫌いだ!」
「オレの頭はいま巨大なタマゴだ!!」
「ぅぉおおお〜〜〜?!」
「egg〜〜〜!」
「ぅおぁあ〜〜〜!?」
「flower〜〜〜!」
「わぁアー」
「egg〜〜〜〜!」
「ぅあああお〜〜〜!!」
まあいい。今日はやっといつものセンタクも終了して、午後は 図書館へ行く予定。そのあとどうしよう?
図書館というところは“受信する”ために行くところで、 本を借りるのは 2のつぎ。かもしれなくない。
ちなみに新山がいま欲しいのは日本手話のネット教材である。 ASL はあるのに、日本の手話が動画でアップロードされている場所は 今のところほとんどない。こういうものは本を見ても、 絵じゃダメなんだよ。しょうがないから 4月から区の講習会に行くけどさ…。
何げなくオレは気づいてしまうのだが、 われわれ人類はこの世の中をもっとツマラナクするように 努力しなければだめだ、と思う。なぜなら人が何万年も変わりばえのしない テレビ番組に釘づけになったり、ネットの同じサイトに目から指が生えるほど アクセスしたり、あるいは宇宙誕生前からあるC++ライブラリをひたすら 変更したりするのは、ひとえにこうした行為が おもしろすぎるからである。それではだめなのだよ。 この世の中がもうどうしようもなくツマラナクなった時、 人は新たな世の中を模索しはじめる。そういう意味で、世界を変えるためには 世の中をもっともっとつまらなくするべきだ。もっともそれが意味するところは、 さしあたっては、何もしないということだが!
(あまりにも皮肉をひねりすぎると三周して元に戻ってしまうのであった。 なぜ一周ではなく三周か? というと、それは折りたたまれた次元のせいであって、 銃規制とは今のところ関係がない。)今のところ;
さあ、もう一度!
はぁああ〜〜〜っはっはっはっはっはぁ!!!
一言いっておく。 おまえがバカならオレはうましかだ!!
世の中における (世界の中の、における) アブクどもを、 ペッコン、ペッコンとクリック (あるいは、雉のひとごえ) だ(de)きるこの後事正においては、 そんんぬムんも棚ごとさっぱり扉があかねぇ。うりゃ!
And crealy, I dodon't untarschtand thos mispronounsiaizionne, y, misspronunciationkel.
さて、そういうわけで、今日は また python のモジュールをうだうだと…。C(++) でムカつくのは、 こんなドーでもいい部分のコードで必要以上にだらだらと長くなることだ。 っていうか、これは API の設計がまずいせいなのかしらん。 でも一部の人々は、まさにこの C++ のダラダラ感が好きなのだと思う。 なぜなら、長いし、しかも適度にワケわかんなそうだし (thanks to templates!)、作ってるもののロジックはアホみたく簡単でも、 いかにも「ボクはこんだけ仕事しました!」って感じの、 上司をだませそうなコードになるじゃん。 あとは誰も読まない (読みたがらない) doxygen とか UML (うむる) のダイアグラムをえんえん時間かけて書くと。 こういう給料ドロボーがどれくらいいるのか知らないが、 少なくとも新山のまわりには今んとこいないのでよしとする。
とにかく、python モジュール書くのにはかなり慣れてしまった。 これっていいことなのか? どうも、そうでないような気がする (気のする)。who cares? (どうでもよろ)
オレが日本に言論の自由はあると思っているかって? たぶん、ない。 実質的には。日本で好き勝手なことを言おうとした人間は法的には 何もおとがめがなくても、非公式な処刑がなされるようになっている。 法律に文字でどう書いてあろうと、観測結果をみれば「ない」んだからしょうがない。 この国はそういうところだ (たぶんアジア一般がそうだ)。 社会的なマインドセットの変化は氷河のごとくのろいので、 たぶん今後もあと 100年ぐらいはないままだろう。あなたは100年待つ根気がありますか?
まあ、100年ぐらいなら。
あといろいろ書きたいことあったんだけど忘れた。
たとえば以下のようなコードがあるとする:
def foo(x): if x == None: raise FooError return x+1 def bar(y): for i in range(y): print foo(y) return def zzz(): try: bar(10) except FooError: pass return
ここで、関数 foo
が例外 FooError
を送出する
可能性があることは明らかだが、ここから推論して、bar
も
例外 FooError
を送出する可能性があることを警告してもらいたい。
しかし zzz
は警告してほしくない (捕捉してるから)。
プログラムが複雑になってくると、思わぬところで例外を捕捉し忘れて
全体が落ちることがあるので、こういうのを警告してくれるツールが欲しかったのである。
ちなみに、pychecker はそこまでやってくれない。
この問題は一見したほど簡単ではない。なぜなら、上のような例で
関数がすべてグローバルに特定できる場合はいいけど、
多態のあるクラスで obj.foo()
とかいう
メソッドを呼び出した場合、どのメソッドが実行されるのかわからないからだ。
Python の場合は lambda もあるのでさらに話はややこしくなる。
つまり、これをやるためにはどの変数がどの型かを知っていなければならなくって、
とはいっても、ここで真面目に型推論なぞをやろうとすると話が爆発してしまう。
型がいいかげんな Python では ML風のまともな型推論などはしょせん無理なので、
もうちょい現実的な方法を考えたい。関数の引数の型を指定しておけば
(つまり C風の関数宣言を書くってこと)、なんとかいけるだろうと思った。
でもそれを Python の枠組みでどこに入れるか? docstring を使うのがいいか。
…つうことで、しばらくやってみたのだが、これはけっこう大変だ。
簡単なコードならば動くサンプルができたのだが、現実的なコードで
これを走らせようとするとカバーしなければならないケースがすごく多いのである。
完全な型推論はしなくても、再帰呼び出しは扱いたいので unification もどきは
ある程度やらなけりゃならないだろう。ちなみにどうやって実装したかっつうと
最初は parser
モジュールで
構文解析した木をじかに扱っていたのだが、
じきに compiler
という
パッケージをみつけたので、これを使うとだいぶ楽しくなった。
しかし組み込みの関数や定数をぜんぶ制約として書かなきゃいけないし、
使えるものになるまでに、まだまだ道は長いであろうと思はれる。
これって jwz が最初に作ったんだろうかね?
Document ID: 3225f50bdb0ad05ad841ac3e86175076
Yusuke Shinyama