Pythonのラムダ式内の代入


105

オブジェクトのリストがあり、1つを除いて空のすべてのオブジェクトを削除したい filterして、lambda式。

たとえば、入力が次の場合:

[Object(name=""), Object(name="fake_name"), Object(name="")]

...出力は次のようになります。

[Object(name=""), Object(name="fake_name")]

lambda式に割り当てを追加する方法はありますか?例えば:

flag = True 
input = [Object(name=""), Object(name="fake_name"), Object(name="")] 
output = filter(
    (lambda o: [flag or bool(o.name), flag = flag and bool(o.name)][0]),
    input
)

1
いいえ。ただし、これは必要ありません。実際には、それが機能したとしても、これを達成するのはかなりあいまいな方法だと思います。

8
通常の古い関数をフィルターに渡すだけではどうですか?
dfb

5
ラムダを使用したかったので、本当にコンパクトなソリューションになります。OCamlでは、return式の前にprintステートメントをチェーンできたのを覚えていました。これはPythonでレプリケートできると思いました
Cat

チェーン型pipeilneを開発するフローの中にいると、「フローをより明確にするために一時変数を作成したい」または「この中間のステップをログに記録したい」と気づくのは非常に困難です。そして、ジャンプする必要があります。それを行うための関数を作成する別の場所:そして、その関数に名前付けて追跡します-たとえ1か所で使用されているとしても。
javadba

回答:


215

:=Python 3.8で追加された代入式演算子は、ラムダ式内での代入をサポートしています。この演算子は、構文上の理由から、括弧(...)、括弧[...]、または括弧で囲まれた{...}式内にのみ出現できます。たとえば、次のように記述できます。

import sys
say_hello = lambda: (
    message := "Hello world",
    sys.stdout.write(message + "\n")
)[-1]
say_hello()

Python 2では、リスト内包の副作用としてローカル割り当てを実行することが可能でした。

import sys
say_hello = lambda: (
    [None for message in ["Hello world"]],
    sys.stdout.write(message + "\n")
)[-1]
say_hello()

ただし、変数flagがのスコープではなく外部スコープにあるため、これらの例のいずれかを使用することはできませんlambda。これはとは関係ありませんlambda。Python2の一般的な動作です。Python3ではnonlocaldefsの内部でキーワードを使用してこれを回避できますが、内部ではnonlocal使用できません。lambda

回避策があります(下記を参照)が、このトピックについては...


場合によっては、これを使用して内ですべてを実行できますlambda

(lambda: [
    ['def'
        for sys in [__import__('sys')]
        for math in [__import__('math')]

        for sub in [lambda *vals: None]
        for fun in [lambda *vals: vals[-1]]

        for echo in [lambda *vals: sub(
            sys.stdout.write(u" ".join(map(unicode, vals)) + u"\n"))]

        for Cylinder in [type('Cylinder', (object,), dict(
            __init__ = lambda self, radius, height: sub(
                setattr(self, 'radius', radius),
                setattr(self, 'height', height)),

            volume = property(lambda self: fun(
                ['def' for top_area in [math.pi * self.radius ** 2]],

                self.height * top_area))))]

        for main in [lambda: sub(
            ['loop' for factor in [1, 2, 3] if sub(
                ['def'
                    for my_radius, my_height in [[10 * factor, 20 * factor]]
                    for my_cylinder in [Cylinder(my_radius, my_height)]],

                echo(u"A cylinder with a radius of %.1fcm and a height "
                     u"of %.1fcm has a volume of %.1fcm³."
                     % (my_radius, my_height, my_cylinder.volume)))])]],

    main()])()

半径10.0cm、高さ20.0cmの円柱の体積は6283.2cm³です。
半径20.0cm、高さ40.0cmの円柱の体積は50265.5cm³です。
半径30.0cm、高さ60.0cmの円柱の体積は169646.0cm³です。

しないでください。


...元の例に戻ります。flag外側のスコープで変数への割り当てを実行することはできませんが、関数を使用して、以前に割り当てられた値を変更できます。

たとえば、次のflagよう.valueに設定したオブジェクトを使用できますsetattr

flag = Object(value=True)
input = [Object(name=''), Object(name='fake_name'), Object(name='')] 
output = filter(lambda o: [
    flag.value or bool(o.name),
    setattr(flag, 'value', flag.value and bool(o.name))
][0], input)
[Object(name=''), Object(name='fake_name')]

上記のテーマに適合させたい場合は、setattr次の代わりにリスト内包表記を使用できます。

    [None for flag.value in [bool(o.name)]]

しかし、実際には、深刻なコードでは、lambda外部代入を行う場合は、代わりに常に通常の関数定義を使用する必要があります。

flag = Object(value=True)
def not_empty_except_first(o):
    result = flag.value or bool(o.name)
    flag.value = flag.value and bool(o.name)
    return result
input = [Object(name=""), Object(name="fake_name"), Object(name="")] 
output = filter(not_empty_except_first, input)

この回答の最後の例では、例と同じ出力は生成されませんが、出力例が正しくないように見えます。
ジェレミー

要するに、これは次のよう.setattr()に要約されます:使用と同様(辞書も同様に機能する必要があります)とにかく関数コードに副作用をハックするために、@ JeremyBanksによるクールなコードが示されました:)
jno

のノートのTHX assignment operator
javadba

37

(グローバル名前空間を悪用しない限り)filter/ lambda式で実際に状態を維持することはできません。ただし、reduce()式で渡される累積結果を使用して、同様のことを実現できます。

>>> f = lambda a, b: (a.append(b) or a) if (b not in a) else a
>>> input = ["foo", u"", "bar", "", "", "x"]
>>> reduce(f, input, [])
['foo', u'', 'bar', 'x']
>>> 

もちろん、状態を少し調整することもできます。この場合、重複を除外しますが、a.count("")、たとえばを空の文字列のみを制限ます。

言うまでもありませんが、これは可能ですが、実際にはできません。:)

最後に、純粋なPythonで何でも行うことができますlambdahttp : //vanderwijk.info/blog/pure-lambda-calculus-python/


17

ラムダを使用する必要はありません。ヌルをすべて削除、入力サイズが変更された場合はラムダを戻すことができます。

input = [Object(name=""), Object(name="fake_name"), Object(name="")] 
output = [x for x in input if x.name]
if(len(input) != len(output)):
    output.append(Object(name=""))

1
コードに小さな間違いがあると思います。2行目はですoutput = [x for x in input if x.name]
halex 2013年

要素の順序が重要な場合があります。
MAnyKey 2016年

15

や友人とさまざまなトリックを実行することは可能ですが=lambda式の中で通常の割り当て()はできませんsetattr

ただし、問題の解決は実際には非常に簡単です。

input = [Object(name=""), Object(name="fake_name"), Object(name="")]
output = filter(
    lambda o, _seen=set():
        not (not o and o in _seen or _seen.add(o)),
    input
    )

あなたに与える

[Object(Object(name=''), name='fake_name')]

ご覧のとおり、最後のインスタンスではなく最初の空白のインスタンスを保持しています。代わりに最後が必要な場合は、に入るリストをfilter逆にし、から出るリストを逆にしますfilter

output = filter(
    lambda o, _seen=set():
        not (not o and o in _seen or _seen.add(o)),
    input[::-1]
    )[::-1]

あなたに与える

[Object(name='fake_name'), Object(name='')]

注意すべきことの1つ:これが任意のオブジェクトで機能するためには、これらのオブジェクトが適切に実装され__eq__ここで__hash__説明されている必要があります。


7

更新

[o for d in [{}] for o in lst if o.name != "" or d.setdefault("", o) == o]

またはfilterand を使用lambda

flag = {}
filter(lambda o: bool(o.name) or flag.setdefault("", o) == o, lst)

前の回答

OK、フィルターとラムダを使い続けていますか?

これは辞書の理解がある方が良いようです。

{o.name : o for o in input}.values()

Pythonがラムダでの割り当てを許可しない理由は、内包での割り当てを許可しない理由と同様であり、これはこれらのものがC側で評価され、したがって、速度が上がる。少なくとも、グイドのエッセイを読んだ後の私の印象ですです。

私の推測では、これはPythonで1つのことを行うための1 つの正しい方法を持つという哲学にも反することでしょう。


したがって、これは完全に正しくはありません。順序は保持されず、空でない文字列のオブジェクトの複製も保持されません。
JPvdMerwe 2013年

7

TL; DR:関数型イディオムを使用する場合は、関数型コードを記述する方が良い

多くの人が指摘しているように、Pythonではラムダの割り当ては許可されていません。一般に、機能的なイディオムを使用する場合は、機能的な方法で考えることをお勧めします。つまり、可能な限り副作用や割り当てがないことを意味します。

ここにラムダを使用する機能的なソリューションがあります。fnわかりやすくするためにラムダを割り当てました(少し長めになっているため)。

from operator import add
from itertools import ifilter, ifilterfalse
fn = lambda l, pred: add(list(ifilter(pred, iter(l))), [ifilterfalse(pred, iter(l)).next()])
objs = [Object(name=""), Object(name="fake_name"), Object(name="")]
fn(objs, lambda o: o.name != '')

また、少し変更することで、リストではなくイテレータを処理することもできます。また、いくつかの異なるインポートがあります。

from itertools import chain, islice, ifilter, ifilterfalse
fn = lambda l, pred: chain(ifilter(pred, iter(l)), islice(ifilterfalse(pred, iter(l)), 1))

いつでもコードを再編成して、ステートメントの長さを減らすことができます。


6

代わりにflag = Trueインポートを実行できる場合、これは基準を満たしていると思います。

>>> from itertools import count
>>> a = ['hello', '', 'world', '', '', '', 'bob']
>>> filter(lambda L, j=count(): L or not next(j), a)
['hello', '', 'world', 'bob']

または、フィルターは次のように記述する方がよいでしょう。

>>> filter(lambda L, blank_count=count(1): L or next(blank_count) == 1, a)

または、インポートなしの単純なブール値の場合:

filter(lambda L, use_blank=iter([True]): L or next(use_blank, False), a)

6

反復中に状態を追跡するpythonicの方法は、ジェネレーターを使用することです。itertoolsの方法はIMHOを理解するのが非常に難しく、ラムダをハッキングしてこれを行うのはばかげています。私は試してみます:

def keep_last_empty(input):
    last = None
    for item in iter(input):
        if item.name: yield item
        else: last = item
    if last is not None: yield last

output = list(keep_last_empty(input))

全体として、可読性は毎回コンパクトさを上回ります。


4

いいえ、独自の定義があるため、ラムダ内に代入を置くことはできません。関数型プログラミングを使用して作業する場合は、値が変更可能ではないと想定する必要があります。

1つの解決策は次のコードです。

output = lambda l, name: [] if l==[] \
             else [ l[ 0 ] ] + output( l[1:], name ) if l[ 0 ].name == name \
             else output( l[1:], name ) if l[ 0 ].name == "" \
             else [ l[ 0 ] ] + output( l[1:], name )

4

呼び出し間の状態を記憶するためにラムダが必要な場合は、ローカル名前空間で宣言された関数、またはオーバーロードされたクラスのいずれかをお勧めします __call__。これで、あなたがしようとしていることに対する私のすべての警告が邪魔にならないので、クエリに対する実際の答えを得ることができます。

呼び出しの間にメモリを確保するためにラムダが本当に必要な場合は、次のように定義できます。

f = lambda o, ns = {"flag":True}: [ns["flag"] or o.name, ns.__setitem__("flag", ns["flag"] and o.name)][0]

次に、あなただけ渡す必要がありますffilter()。本当に必要な場合flagは、次の方法での値を取り戻すことができます。

f.__defaults__[0]["flag"]

または、の結果を変更して、グローバル名前空間を変更できますglobals()。残念ながら、結果を変更してlocals()もローカル名前空間に影響を与えないのと同じ方法でローカル名前空間を変更することはできません。


または、元のLispを使用するだけです (let ((var 42)) (lambda () (setf var 43)))
Kaz

4

バインド関数を使用して、疑似マルチステートメントラムダを使用できます。次に、フラグのラッパークラスを使用して割り当てを有効にできます。

bind = lambda x, f=(lambda y: y): f(x)

class Flag(object):
    def __init__(self, value):
        self.value = value

    def set(self, value):
        self.value = value
        return value

input = [Object(name=""), Object(name="fake_name"), Object(name="")]
flag = Flag(True)
output = filter(
            lambda o: (
                bind(flag.value, lambda orig_flag_value:
                bind(flag.set(flag.value and bool(o.name)), lambda _:
                bind(orig_flag_value or bool(o.name))))),
            input)

0

ちょっと厄介な回避策ですが、ラムダでの割り当てはとにかく違法なので、それは本当に問題ではありません。次の例のように、組み込みexec()関数を使用してラムダ内から割り当てを実行できます。

>>> val
Traceback (most recent call last):
  File "<pyshell#31>", line 1, in <module>
    val
NameError: name 'val' is not defined
>>> d = lambda: exec('val=True', globals())
>>> d()
>>> val
True

-2

最初に、あなたはあなたの仕事のためにローカルな割り当てを使う必要はありません、上の答えをチェックしてください

次に、locals()およびglobals()を使用して変数テーブルを取得し、値を変更するのは簡単です

このサンプルコードを確認してください:

print [locals().__setitem__('x', 'Hillo :]'), x][-1]

環境変数にグローバル変数を追加する必要がある場合は、locals()globals()に置き換えてみてください

Pythonのリストコンプはクールですが、トリディショナルプロジェクトのほとんどはこれを受け入れません(フラスコ:[のように)

それが役に立てば幸い


2
を使用することはできません。locals()ドキュメントでは、これを変更してもローカルスコープは実際には変更されない(または少なくとも常に変更されるわけではない)と明示的に述べています。globals()一方、期待どおりに動作します。
JPvdMerwe 2013年

@JPvdMerweは試してみてください。ドキュメントを盲目的にフォローしないでください。そしてラムダでの割り当てはすでにルールを破っています
jyf1987

3
残念ながら、これはグローバル名前空間でのみ機能します。その場合は、実際にを使用する必要がありますglobals()pastebin.com/5Bjz1mR4(2.6と3.2の両方でテスト済み)はそれを証明します。
JPvdMerwe 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.