17ec084
@17ec084 (智剛 平田)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【python】複数のメソッドをラムダ式のリスト内包表記で指定したときの挙動が理解できない([f() for f in [lambda:s for s in ["m1", "m2"]]] != ["m1", "m2"]なのは何故か)

解決したいこと

発生している問題に示した挙動の理由が知りたいです。

発生している問題

対話モード
>>> class Test:
...     def __init__(self):pass
...     [m1,m2] = [lambda self:s for s in ["m1", "m2"]]
...
>>> Test().m1()
'm2'
>>> Test().m2()
'm2'

Test().m1()'m1'を返すことを期待していましたが、実際はそうはなりませんでした。

自分で試したこと

実験1.

対話モード続き
>>> class Test(Test): # __init__の再定義が面倒なので過去に定義したTestを新しいTestに継承させた
...     [print(s) for s in ["m1", "m2"]]
...
m1
m2

リスト内包表記においてlambda式をprintで置き換えたところ、ちゃんとm1, m2の順で標準出力されました。

sが2回とも'm2'となっているわけではなさそうです

実験2.

対話モード続き
>>> class Test(Test):
...     [m1,m2] = [lambda self:s for s in ["m1", "m2"]]
...     print(m1)
...     print(m2)
...
<function Test.<listcomp>.<lambda> at 0x000001825FDA5280>
<function Test.<listcomp>.<lambda> at 0x000001825FDA5160>
>>> test = Test()
>>> test.m1 == test.m2
False
>>> test.m1() == test.m2()
True

print(m1)print(m2)で、idが異なることから、m1m2が同じオブジェクトになっているわけではなさそうです。

このことは、test.m1 == test.m2Falseと評価されたことにより、インスタンス生成後も成り立つとわかりました。

考察

実験1より、sは1回目には'm1'、2回目には'm2'と評価していることが分かります。
実験2より、m1m2idの異なるオブジェクトであることが分かりました。

疑問

2つのcallableなオブジェクトlambda self:slambda self:sについて、返却値として指定されるsが互いに異なり、
かつlambda self:s自身も互いに別のオブジェクトとして登録されています。
ところが実際の返却値(lambda self:s)()は互いに一致するのは何故でしょうか。

この疑問を一言でいうと、次の通りになります。

[f() for f in [lambda:s for s in ["m1", "m2"]]]['m2', 'm2']となり、['m1', 'm2']とならないのは何故ですか

0

1Answer

以下の例を考えると分かりやすいかもしれません。

def make_f():
    s = 'm1'
    f = lambda: s
    s = 'm2'
    return f

f = make_f()

print(f()) # => m2

この例で、ラムダ式は書いた時点での s の値 'm1' を返すのではなく、呼び出された時点の直前に s に入っていた値 'm2' を返しています。ラムダ式と外側の関数はローカル変数の名前空間を共有していると言い換えることができます。

ご質問のコードでも同様に、ラムダ式はその外側のスコープであるリスト内包表記と変数 s を共有しています。リスト内包表記は変数 s に次々に値を代入していき、ループを終えたとき s には最終ループの値 'm2' が入っていることになります。よって、この変数 s の値を返す2つの匿名関数はともに 'm2' を返します。


ちなみに、ラムダ式を書いた時点での s の値に固定したいなら、ラムダ式をラムダ式で包んで引数経由で渡してください。

>>> [f() for f in [lambda: s for s in ['m1', 'm2']]]
['m2', 'm2']

>>> [f() for f in [(lambda t: lambda: t)(s) for s in ['m1', 'm2']]]
['m1', 'm2']
3Like

Comments

  1. @17ec084

    Questioner

    ありがとうございます。
    参考にさせていただきます!

Your answer might help someone💌