Penugasan di dalam ekspresi lambda dengan Python

105

Saya memiliki daftar objek dan saya ingin menghapus semua objek yang kosong kecuali satu, menggunakan filterdan lambdaekspresi.

Misalnya jika inputnya adalah:

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

... maka outputnya harus:

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

Apakah ada cara untuk menambahkan tugas ke lambdaekspresi? Sebagai contoh:

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
)
Kucing
sumber
1
Tidak. Tapi kamu tidak membutuhkan ini. Sebenarnya saya pikir itu akan menjadi cara yang sangat tidak jelas untuk mencapai ini meskipun itu berhasil.
8
Mengapa tidak melewatkan saja fungsi lama biasa ke dalam filter?
dfb
5
Saya ingin menggunakan lambda agar ini menjadi solusi yang sangat ringkas. Saya ingat di OCaml saya bisa mencetak pernyataan berantai sebelum ekspresi kembali, berpikir ini bisa direplikasi dengan Python
Cat
Sangat menyakitkan berada dalam aliran pengembangan pipa berantai kemudian menyadari: "oh saya ingin membuat var temp untuk membuat aliran lebih jelas" atau "saya ingin mencatat langkah perantara ini": dan kemudian Anda harus melompat di tempat lain untuk membuat fungsi untuk melakukannya: dan beri nama fungsi itu dan pantau terus - meskipun hanya digunakan di satu tempat.
javadba

Jawaban:

215

Operator ekspresi penugasan yang :=ditambahkan di Python 3.8 mendukung penugasan di dalam ekspresi lambda. Operator ini hanya dapat muncul dalam ekspresi dalam tanda kurung (...), tanda kurung [...], atau tanda kurung {...}untuk alasan sintaksis. Misalnya, kita bisa menulis yang berikut ini:

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

Dalam Python 2, dimungkinkan untuk melakukan tugas lokal sebagai efek samping dari pemahaman daftar.

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

Namun, tidak mungkin untuk menggunakan salah satu dari ini dalam contoh Anda karena variabel Anda flagberada di lingkup luar, bukan lambdacakupannya. Ini tidak ada hubungannya dengan lambda, ini adalah perilaku umum dalam Python 2. Python 3 memungkinkan Anda menyiasati ini dengan nonlocalkata kunci di dalam defs, tetapi nonlocaltidak dapat digunakan di dalam lambdas.

Ada solusinya (lihat di bawah), tetapi saat kita membahas topik ...


Dalam beberapa kasus, Anda dapat menggunakan ini untuk melakukan semua yang ada di dalam 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()])()

Sebuah silinder dengan jari-jari 10.0cm dan tinggi 20.0cm memiliki volume 6.283.2cm³.
Sebuah silinder dengan jari-jari 20.0cm dan tinggi 40.0cm memiliki volume 50265.5cm³.
Silinder dengan jari-jari 30.0cm dan tinggi 60.0cm memiliki volume 169646.0cm³.

Tolong jangan.


... kembali ke contoh awal Anda: meskipun Anda tidak dapat melakukan tugas ke flagvariabel di lingkup luar, Anda dapat menggunakan fungsi untuk mengubah nilai yang ditetapkan sebelumnya.

Misalnya, flagbisa menjadi objek yang .valuekita atur menggunakan 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')]

Jika kita ingin menyesuaikan tema di atas, kita bisa menggunakan pemahaman daftar alih-alih setattr:

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

Tapi sungguh, dalam kode serius Anda harus selalu menggunakan definisi fungsi biasa daripada lambdajika Anda akan melakukan tugas luar.

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)
Jeremy
sumber
Contoh terakhir dalam jawaban ini tidak menghasilkan keluaran yang sama dengan contoh, tetapi menurut saya keluaran contoh salah.
Jeremy
Singkatnya, ini bermuara pada: use .setattr()and alikes ( kamus harus melakukannya juga, misalnya) untuk meretas efek samping menjadi kode fungsional, kode keren oleh @JeremyBanks ditampilkan :)
jno
Terima kasih untuk catatan di assignment operator!
javadba
37

Anda tidak dapat benar-benar mempertahankan status dalam filter/ lambdaekspresi (kecuali menyalahgunakan namespace global). Namun Anda dapat mencapai sesuatu yang serupa menggunakan hasil akumulasi yang diteruskan dalam reduce()ekspresi:

>>> 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']
>>> 

Anda dapat, tentu saja, sedikit mengubah kondisi. Dalam hal ini, ini menyaring duplikat, tetapi Anda juga dapat menggunakan a.count(""), misalnya, untuk hanya membatasi string kosong.

Tak perlu dikatakan, Anda bisa melakukan ini tetapi sebenarnya tidak boleh. :)

Terakhir, Anda dapat melakukan apa saja dengan Python murni lambda: http://vanderwijk.info/blog/pure-lambda-calculus-python/

Ivo van der Wijk
sumber
17

Tidak perlu menggunakan lambda, saat Anda dapat menghapus semua yang null, dan mengembalikannya jika ukuran input berubah:

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=""))
Gabi Purcaru
sumber
1
Saya pikir Anda memiliki kesalahan kecil dalam kode Anda. Baris kedua seharusnya output = [x for x in input if x.name].
halex
Urutan elemen mungkin penting.
MAnyKey
15

Penugasan normal ( =) tidak dimungkinkan di dalam lambdaekspresi, meskipun dimungkinkan untuk melakukan berbagai trik dengan setattrdan teman.

Namun, menyelesaikan masalah Anda sebenarnya cukup sederhana:

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
    )

yang akan memberimu

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

Seperti yang Anda lihat, ini menyimpan contoh kosong pertama, bukan yang terakhir. Jika Anda membutuhkan yang terakhir, balikkan daftar menjadi filter, dan balikkan daftar yang keluar dari filter:

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

yang akan memberimu

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

Satu hal yang harus diperhatikan: agar ini berfungsi dengan objek arbitrer, objek tersebut harus diimplementasikan dengan benar __eq__dan __hash__seperti yang dijelaskan di sini .

Ethan Furman
sumber
7

PERBARUI :

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

atau menggunakan filterdan lambda:

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

Jawaban Sebelumnya

Oke, apakah Anda terjebak menggunakan filter dan lambda?

Sepertinya ini akan lebih baik disajikan dengan pemahaman kamus,

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

Saya pikir alasan mengapa Python tidak mengizinkan penugasan dalam lambda mirip dengan mengapa ia tidak mengizinkan penugasan dalam pemahaman dan itu ada hubungannya dengan fakta bahwa hal-hal ini dievaluasi di Csamping dan dengan demikian dapat memberi kita sebuah peningkatan kecepatan. Setidaknya itulah kesan saya setelah membaca salah satu esai Guido .

Dugaan saya ini juga akan bertentangan dengan filosofi memiliki satu cara yang benar dalam melakukan satu hal dengan Python.

tukang susu
sumber
Jadi ini tidak sepenuhnya benar. Ini tidak akan mempertahankan ketertiban, juga tidak akan mempertahankan duplikat objek string yang tidak kosong.
JPvdMerwe
7

TL; DR: Saat menggunakan idiom fungsional, lebih baik menulis kode fungsional

Seperti yang telah ditunjukkan banyak orang, dalam Python penugasan lambdas tidak diperbolehkan. Secara umum bila menggunakan idiom fungsional lebih baik Anda berpikir secara fungsional yang berarti sedapat mungkin tidak ada efek samping dan tidak ada tugas.

Berikut adalah solusi fungsional yang menggunakan lambda. Saya telah menetapkan lambda ke fnuntuk kejelasan (dan karena itu sedikit panjang).

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 != '')

Anda juga dapat membuat kesepakatan ini dengan iterator daripada daftar dengan mengubah sedikit hal. Anda juga memiliki beberapa impor berbeda.

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

Anda selalu dapat mengubah kode untuk mengurangi panjang pernyataan.

dietbuddha
sumber
6

Jika alih-alih flag = Truekita dapat melakukan impor, maka menurut saya ini memenuhi kriteria:

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

Atau mungkin filter lebih baik ditulis sebagai:

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

Atau, hanya untuk boolean sederhana, tanpa impor apa pun:

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

Cara pythonic untuk melacak status selama iterasi adalah dengan generator. Cara itertools cukup sulit untuk dipahami IMHO dan mencoba meretas lambda untuk melakukan ini sangat konyol. Saya akan mencoba:

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))

Secara keseluruhan, keterbacaan mengalahkan kekompakan setiap saat.

pengguna2735379
sumber
4

Tidak, Anda tidak dapat meletakkan tugas di dalam lambda karena definisinya sendiri. Jika Anda bekerja menggunakan pemrograman fungsional, maka Anda harus berasumsi bahwa nilai-nilai Anda tidak dapat berubah.

Salah satu solusinya adalah kode berikut:

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 )
Baltasarq
sumber
4

Jika Anda memerlukan lambda untuk mengingat status antar panggilan, saya akan merekomendasikan fungsi yang dideklarasikan di namespace lokal atau kelas dengan kelebihan beban __call__ . Sekarang semua peringatan saya terhadap apa yang Anda coba lakukan sudah keluar dari jalan, kita bisa mendapatkan jawaban yang sebenarnya untuk pertanyaan Anda.

Jika Anda benar-benar membutuhkan lambda Anda untuk memiliki beberapa memori di antara panggilan, Anda dapat menentukannya seperti:

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

Maka Anda hanya perlu meneruskan fke filter(). Jika Anda benar-benar perlu, Anda bisa mendapatkan kembali nilai flagberikut ini:

f.__defaults__[0]["flag"]

Alternatifnya, Anda dapat mengubah namespace global dengan mengubah hasil globals(). Sayangnya, Anda tidak dapat mengubah namespace lokal dengan cara yang sama seperti mengubah hasil dari locals()tidak memengaruhi namespace lokal.

JPvdMerwe
sumber
Atau hanya menggunakan Lisp asli: (let ((var 42)) (lambda () (setf var 43))).
Kaz
4

Anda dapat menggunakan fungsi bind untuk menggunakan lambda pseudo multi-pernyataan. Kemudian Anda dapat menggunakan kelas pembungkus untuk Bendera untuk mengaktifkan tugas.

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)
pyrospade
sumber
0

Jenis solusi yang berantakan, tetapi penugasan di lambda adalah ilegal, jadi itu tidak terlalu penting. Anda dapat menggunakan exec()fungsi builtin untuk menjalankan tugas dari dalam lambda, seperti contoh berikut:

>>> 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
Pengguna 12692182
sumber
-2

pertama, Anda tidak perlu menggunakan tugas lokal untuk pekerjaan Anda, cukup periksa jawaban di atas

kedua, mudah untuk menggunakan local () dan global () untuk mendapatkan tabel variabel dan kemudian mengubah nilainya

periksa kode contoh ini:

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

jika Anda perlu mengubah menambahkan variabel global ke lingkungan Anda, coba ganti lokal () dengan global ()

comp daftar python keren tetapi sebagian besar proyek tridisional tidak menerima ini (seperti flask: [)

semoga bisa membantu

jyf1987
sumber
2
Anda tidak dapat menggunakan locals(), secara eksplisit dikatakan dalam dokumentasi bahwa mengubahnya tidak benar-benar mengubah cakupan lokal (atau setidaknya tidak selalu). globals()di sisi lain bekerja seperti yang diharapkan.
JPvdMerwe
@JPvdMerwe coba saja, jangan ikuti dokumen secara membabi buta. dan tugas dalam lambda adalah melanggar aturan sudah
jyf1987
3
Sayangnya, ini hanya berfungsi di namespace global, dalam hal ini Anda benar-benar harus menggunakannya globals(). pastebin.com/5Bjz1mR4 (diuji di 2.6 dan 3.2) membuktikannya.
JPvdMerwe