« intをstrに変更できるか? |Main| 日記(大垣1日目) »

« intをstrに変更できるか? | Python | Pythonのジェネレータを使ってprivateな変数 »

一連の関数のリストを作る

Pythonのメーリングリストで、リスト閉包内包を用いて一連の関数を定義しようとした際に、以下のような現象が起きることについての話題がありました。
>>> funcs = [lambda x: k * x for k in range(10)]
>>> funcs[3](4)
36
>>> funcs[1](4)
36

これはリスト閉包内包がスコープを作らないので、それぞれの関数が「グローバルスコープのk」を参照しており、その値は関数を呼び出すときにはすでにループが最後まで回って4になってしまっていることが原因です。

まつもとさんは、Pythonの「デフォルト引数が定義時に評価される」という特徴を利用したイディオムを紹介してらっしゃいました。これはわかっている人間が自分だけで使うぶんにはクールで楽ですが、他の人が読むコードに使うのは好ましくないように思います。

>>> funcs = [lambda x, k = k: k * x for k in range(10)]
>>> funcs[3](4)
12
[lambda x: k * x for k in range(10)]がダメな理由は、lambdaの中のkがグローバルスコープのkを指しているからです。もう一枚関数で包めば(ローカル変数にしてやれば)期待通りに動くようになります。こちらの方がlambdaに慣れた人にはわかりやすい書き方かも知れません。
>>> funcs = [(lambda k: lambda x: k * x)(k) for k in range(10)]
>>> funcs[3](4)
12
でも、わかりにくいのはそもそも無理に1行で書こうとするからです。ワンライナーは悪。 おのおのの「呼び出せる」オブジェクトがそれぞれ別個の「かける数」を持つんですから、その通りに書けば何の問題もありません。
>>> class Multiplier:
	def __init__(self, k):
		self.k = k
	def __call__(self, x):
		return self.k * x

	
>>> funcs = [Multiplier(k) for k in range(10)]
>>> funcs[3](4)
12
この最後の例が一番美しいと思うのは、僕がJavaに毒されているからでしょうか?(笑)

トラックバック(Trackback)

Trackback URL: http://www.nishiohirokazu.org/mt/mt-tb.cgi/381

ご意見・ご感想をお送りください(フィードバック)

(フィードバックはメールで送信され、基本的に表示されませんが、内容によっては公開させていただくこともございます。ご了承ください。Your comment doesn't appear the page immediately. If the comment has value to other people, it will be put on the page or subsequent entries. Thank you.)

上の情報は、いずれも未記入でかまいません。 All of above questions are optional.