複数行のラムダはPythonに追加できないと言われたのは、Pythonの他の構文構造と構文的に衝突するためです。今日バスでこれについて考えていたところ、複数行のラムダが衝突する単一のPythonコンストラクトを考えることができないことに気付きました。私は言語をかなりよく知っているので、これは私を驚かせました。
さて、Guidoには言語に複数行のラムダを含めない理由があると思いますが、好奇心からです:複数行のラムダを含めることがあいまいになるのはどういう状況ですか?私が聞いたことは本当ですか、またはPythonが複数行のラムダを許可しない他の理由がありますか?
次を見てください:
map(multilambda x:
y=x+1
return y
, [1,2,3])
これは(y, [1,2,3])
を返すラムダですか?(したがって、マップは1つのパラメーターのみを取得し、エラーになります)?それともy
を返しますか?または、新しい行のコンマが間違って配置されているため、構文エラーですか? Pythonはあなたが望むものをどのように知るのでしょうか?
括弧内では、インデントはPythonにとって重要ではないため、複数行を明確に操作することはできません。
これは単純なもので、おそらく他にも例があります。
Guido van Rossum(Pythonの発明者)は、この正確な質問に 古いブログ投稿 で答えています。
基本的に、彼は理論的には可能であるが、提案されたソリューションはすべて非Pythonicであることを認めています。
「しかし、このパズルの提案されたソリューションの複雑さは計り知れません。パーサー(より正確にはレクサー)がインデントを区別するモードとインデントを区別しないモードを切り替えてスタックを維持できる必要があります技術的にはそれはすべて解決できます(すでに一般化できるインデントレベルのスタックがあります)。しかし、どれもそれがすべて精巧であるという私の直感を取り去ることはありません Rube Goldberg contraption 。 "
これは一般的に非常に見苦しい(しかし、代替案はさらに見苦しい)ため、回避策は中括弧式を作成することです:
lambda: (
doFoo('abc'),
doBar(123),
doBaz())
ただし、割り当ては一切受け付けないため、事前にデータを準備する必要があります。これが便利だと思ったのはPySideラッパーです。ここには短いコールバックが時々あります。追加のメンバー関数を作成すると、さらに見苦しくなります。通常、これは必要ありません。
例:
pushButtonShowDialog.clicked.connect(
lambda: (
field1.clear(),
spinBox1.setValue(0),
diag.show())
関連するリンク:
しばらくの間、私はReiaの開発をフォローしていました。Reiaは、最初はRubyブロックを含むPythonのインデントベースの構文もすべてErlangの上に置く予定でした。しかし、デザイナーはインデントの感度をあきらめたため、この決定について書いたこの投稿には、インデント+複数行のブロックで遭遇した問題についての議論と、Guidoのデザインの問題/決定について得た評価の増加が含まれています:
http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html
また、PythonのRubyスタイルブロックに関する興味深い提案があります。実際にそれを撃ち落とすことなくGuidoが応答を投稿しているところに出くわしました(ただし、その後撃ち落とされたかどうかはわかりません)。
[編集]読む この答え。 複数行のラムダが重要でない理由を説明しています。
簡単に言えば、それは素朴です。 Guido van Rossumのブログ投稿から:
式の途中にインデントベースのブロックを埋め込むソリューションは受け入れられません。文のグループ化(ブレースや開始/終了キーワードなど)の代替構文も同様に受け入れられないことがわかっているため、これにより、複数行のラムダは解決不可能なパズルになります。
この答えの残りに関しては。単一行を使用するか1 ラムダまたは名前付き関数。 exec
を使用しないでください。
11行のpythonで何ができるか驚くでしょう。
複数行のラムダ関数を取得する回避策(skriticosの答えの拡張):
(lambda n: (exec('global x; x=4; x=x+n'), x-2)[-1])(0)
それがすること:
Pythonは、区切り文字を読み取る前に、Tupleのすべてのコンポーネントを単純化(実行)します。
たとえば、lambda x: (functionA(), functionB(), functionC(), 0)[-1]
は、使用される唯一の情報がリストの最後の項目(0)であっても、3つの関数すべてを実行します。
通常、Pythonのリストまたはタプル内で変数を割り当てたり、宣言したりすることはできませんが、exec
関数を使用すると(常に返されることに注意してください:None
)。
変数をglobal
として宣言しない限り、そのexec
関数呼び出しの外側には存在しません(これはexec
ステートメント内のlambda
関数にのみ当てはまります)。
例えば、(lambda: exec('x=5;print(x)'))()
はglobal
宣言なしで正常に動作します。ただし、(lambda: (exec('x=5'), exec('print(x)')))()
または(lambda: (exec('x=5'), x)()
はそうではありません。
すべての ラムダ関数内のglobal
変数はグローバル名前空間に保存され、関数呼び出しが完了した後も存在し続けることに注意してください。このため、これは良い解決策ではなく、可能な限り避けるべきです。global
関数から宣言されたexec
変数は、global
名前空間とは別に保持されます。 (Python 3.3.3でテスト済み)
タプルの最後にある[-1]
は最後のインデックスを取得します。たとえば、[1,2,3,4][-1]
は4
です。これは、None
関数からのexec
およびその他の無関係な値を含むTuple全体ではなく、目的の出力値のみが返されるように行われます。
同等の複数行関数:
def function(n):
x = 4
x = x+n
return x-2
function(0)
複数行のラムダを必要としない方法:
再帰:
f = lambda i: 1 if i==0 or i==1 else f(i-1)+f(i-2)
ブール値は整数です:
lambda a, b: [(0, 9), (2, 3)][a<4][b>3]
イテレータ:
lambda x: [n**2 for n in x] #Assuming x is a list or Tuple in this case
輝かしいが恐ろしいハックをご紹介します。
import types
def _obj():
return lambda: None
def LET(bindings, body, env=None):
'''Introduce local bindings.
ex: LET(('a', 1,
'b', 2),
lambda o: [o.a, o.b])
gives: [1, 2]
Bindings down the chain can depend on
the ones above them through a lambda.
ex: LET(('a', 1,
'b', lambda o: o.a + 1),
lambda o: o.b)
gives: 2
'''
if len(bindings) == 0:
return body(env)
env = env or _obj()
k, v = bindings[:2]
if isinstance(v, types.FunctionType):
v = v(env)
setattr(env, k, v)
return LET(bindings[2:], body, env)
これで、このLET
フォームを次のように使用できます。
map(lambda x: LET(('y', x + 1,
'z', x - 1),
lambda o: o.y * o.z),
[1, 2, 3])
与えるもの:[0, 3, 8]
@balpha解析の問題に取り組みましょう。複数行のラムダの周りに括弧を使用します。括弧がない場合、ラムダ定義は貪欲です。ラムダは
map(lambda x:
y = x+1
z = x-1
y*z,
[1,2,3]))
(y*z, [1,2,3])
を返す関数を返します
しかし
map((lambda x:
y = x+1
z = x-1
y*z)
,[1,2,3]))
手段
map(func, [1,2,3])
funcは、y * zを返す複数行のラムダです。それは動作しますか?
(トピックにまだ興味がある人のために。)
これを考慮してください(「マルチライン」ラムダ内の他のステートメントでステートメントの戻り値を使用することも含まれますが、嘔吐のポイントにはいですが;-)
>>> def foo(arg):
... result = arg * 2;
... print "foo(" + str(arg) + ") called: " + str(result);
... return result;
...
>>> f = lambda a, b, state=[]: [
... state.append(foo(a)),
... state.append(foo(b)),
... state.append(foo(state[0] + state[1])),
... state[-1]
... ][-1];
>>> f(1, 2);
foo(1) called: 2
foo(2) called: 4
foo(6) called: 12
12
私は私のプロジェクトのいくつかでこの少し汚いハックを実践することで有罪です。
lambda args...:( expr1, expr2, expr3, ...,
exprN, returnExpr)[-1]
Pythonicのままにする方法を見つけられることを願っていますが、それを行う必要がある場合は、execを使用してグローバルを操作するよりも簡単です。
リデュースでディクテーションを理解し、この1つのライナーハックを考え出すために、私は少し遊んでいました。
In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])
Out[3]: {2: 1, 4: 3, 6: 5}
私はこのJavascript dict内包表記で行われていたことと同じことをしようとしていました: https://stackoverflow.com/a/11068265
ラムダ関数は、関数の最も単純な形式であるan entrance, then return
として、1行であると想定されているためです。
いハッキングの問題については、exec
と通常の関数の組み合わせを常に使用して、次のような複数行関数を定義できます。
f = exec('''
def mlambda(x, y):
d = y - x
return d * d
''', globals()) or mlambda
これを次のような関数にラップできます。
def mlambda(signature, *lines):
exec_vars = {}
exec('def mlambda' + signature + ':\n' + '\n'.join('\t' + line for line in lines), exec_vars)
return exec_vars['mlambda']
f = mlambda('(x, y)',
'd = y - x',
'return d * d')