Python'da lambda ifadesi içindeki atama


105

Nesnelerin bir listesi var filterve bir tanesi dışında boş olan tüm nesneleri ve bir lambdaifadeyi kaldırmak istiyorum .

Örneğin, giriş şu ise:

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

... sonra çıktı şöyle olmalıdır:

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

Bir lambdaifadeye ödev eklemenin bir yolu var mı ? Örneğin:

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
Hayýr. Ama buna ihtiyacýn yok. Aslında işe yarasa bile bunu başarmanın oldukça belirsiz bir yolu olacağını düşünüyorum.

8
Neden normal bir eski işlevi filtreye geçirmiyorsunuz?
dfb

5
Lambda'yı gerçekten kompakt bir çözüm olması için kullanmak istedim. OCaml'de geri dönüş ifadesinden önce zincirleme baskı ifadeleri oluşturabileceğimi hatırlıyorum, bunun Python'da kopyalanabileceğini düşündüm
Cat

Zincirlenmiş bir boru hattı geliştirme akışında olmak oldukça acı verici ve sonra şunu fark edin: "ah, akışı daha net hale getirmek için geçici bir değişken oluşturmak istiyorum" veya "bu ara adımı günlüğe kaydetmek istiyorum": ve sonra atlamalısınız bunu yapmak için bir işlev oluşturmak için başka bir yer: ve bu işlevi adlandırıp takip et - tek bir yerde kullanılsa bile.
javadba

Yanıtlar:


215

:=Python 3.8'de eklenen atama ifadesi operatörü , lambda ifadelerinin içindeki atamayı destekler. Bu işleç, sözdizimsel nedenlerle yalnızca parantezli (...), köşeli parantezli [...]veya çaprazlı {...}ifade içinde görünebilir . Örneğin şunları yazabileceğiz:

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

Python 2'de, liste anlamalarının bir yan etkisi olarak yerel atamalar yapmak mümkündü.

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

Ancak, örneğinizde bunlardan herhangi birini kullanmak mümkün değildir çünkü değişkeniniz flagdış kapsamdadır, kapsamı değildir lambda. Bunun bununla bir ilgisi yok lambda, bu Python 2'deki genel davranıştır. Python 3 nonlocal, defs'nin içindeki anahtar sözcükle bunun üstesinden gelmenize izin verir , ancak s nonlocaliçinde kullanılamaz lambda.

Bir çözüm var (aşağıya bakın), ancak konu başındayken ...


Bazı durumlarda, bunu aşağıdakilerin içindeki her şeyi yapmak için kullanabilirsiniz 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()])()

Yarıçapı 10,0 cm ve yüksekliği 20,0 cm olan bir silindir 6283,2 cm³ hacme sahiptir.
20,0 cm yarıçaplı ve 40,0 cm yüksekliğe sahip bir silindir 50265,5 cm³ hacme sahiptir.
Yarıçapı 30.0cm ve yüksekliği 60.0cm olan bir silindirin hacmi 169646.0cm³'dür.

Lütfen yapma.


... orijinal örneğinize geri dönelim: flagdış kapsamdaki değişkene atamalar yapamasanız da, önceden atanan değeri değiştirmek için işlevleri kullanabilirsiniz.

Örneğin, şunu kullanarak ayarladığımız flagbir nesne olabilir :.valuesetattr

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

Yukarıdaki temayı sığdırmak isteseydik, bunun yerine bir liste anlama kullanabiliriz setattr:

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

Ama gerçekten, ciddi bir kodda, lambdadış atama yapacaksanız, her zaman a yerine normal bir işlev tanımı kullanmalısınız .

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)

Bu yanıttaki son örnek, örnekle aynı çıktıyı üretmiyor, ancak bana örnek çıktının yanlış olduğunu gösteriyor.
Jeremy

Kısacası, bu , yan etkileri işlevsel koda dönüştürmek için kullanım .setattr()ve benzerleri ( örneğin, sözlükler de yapmalıdır), @ JeremyBanks'in harika kodu gösterildi :)
jno

Üzerindeki not için teşekkürler assignment operator!
javadba

37

Bir filter/ lambdaifadesinde durumu gerçekten koruyamazsınız (genel ad alanını kötüye kullanmadıkça). Bununla birlikte, bir reduce()ifadede aktarılan birikmiş sonucu kullanarak benzer bir şey elde edebilirsiniz :

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

Elbette, koşulu biraz değiştirebilirsiniz. Bu durumda, kopyaları filtreler, ancak a.count("")örneğin, yalnızca boş dizeleri kısıtlamak için de kullanabilirsiniz .

Söylemeye gerek yok, bunu yapabilirsin ama gerçekten yapmamalısın. :)

Son olarak, saf Python ile her şeyi yapabilirsinizlambda : http://vanderwijk.info/blog/pure-lambda-calculus-python/


17

Tüm null olanları kaldırabileceğiniz ve giriş boyutu değişirse bir tane geri koyabileceğiniz zaman lambda kullanmaya gerek yoktur :

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
Kodunuzda küçük bir hata olduğunu düşünüyorum. İkinci satır olmalıdır output = [x for x in input if x.name].
halex

Elemanların sırası önemli olabilir.
MAnyKey

15

=Bir lambdaifadenin içinde normal atama ( ) mümkün değildir , ancak setattrarkadaşlarla ve arkadaşlarla çeşitli numaralar yapmak mümkündür .

Ancak probleminizi çözmek aslında oldukça basittir:

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
    )

hangisi sana verecek

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

Gördüğünüz gibi, son yerine ilk boş örneği tutuyor. Bunun yerine sonuncuya ihtiyacınız varsa, gelen listeyi filtertersine çevirin ve çıkan listeyi tersine çevirin filter:

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

hangisi sana verecek

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

Dikkat edilmesi gereken bir şey var: Bunun rastgele nesnelerle çalışması için, bu nesnelerin uygun şekilde __eq__ve burada__hash__ açıklandığı gibi uygulaması gerekir .


7

GÜNCELLEME :

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

veya kullanarak filterve lambda:

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

Önceki Cevap

Tamam, filtre ve lambda kullanmaya başladınız mı?

Bu bir sözlük anlayışı ile daha iyi hizmet verecek gibi görünüyor.

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

Python'un bir lambda'da atamaya izin vermemesinin nedeninin bir anlayışta atamaya izin vermemesine benzer olduğunu düşünüyorum ve bunun, bu şeylerin bir Cyandan değerlendirilmesiyle bir ilgisi var ve bu nedenle bize bir hızda artış. En azından Guido'nun denemelerinden birini okuduktan sonra izlenimim bu .

Tahminimce bu , Python'da herhangi bir şeyi tek bir doğru şekilde yapma felsefesine de aykırıdır .


Yani bu tamamen doğru değil. Düzeni ve boş dizeli olmayan nesnelerin kopyalarını korumaz.
JPvdMerwe

7

TL; DR: İşlevsel deyimleri kullanırken işlevsel kod yazmak daha iyidir

Pek çok kişinin işaret ettiği gibi, Python'da lambdas atamaya izin verilmez. Genel olarak işlevsel deyimleri kullanırken, işlevsel bir şekilde düşünmeniz daha iyi olur, bu da mümkün olan her yerde yan etki ve ödev olmaması anlamına gelir.

İşte bir lambda kullanan fonksiyonel çözüm. Lambda'yı fnnetlik için atadım (ve biraz uzun olduğu için).

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

Bu anlaşmayı, işleri biraz değiştirerek listeler yerine yineleyicilerle de yapabilirsiniz. Ayrıca bazı farklı ithalatlarınız da var.

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

İfadelerin uzunluğunu azaltmak için kodu her zaman yeniden yapılandırabilirsiniz.


6

Bunun yerine flag = Truebir ithalat yapabilirsek, bunun kriterleri karşıladığını düşünüyorum:

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

Ya da filtre şu şekilde daha iyi yazılır:

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

Veya, herhangi bir içe aktarma olmadan sadece basit bir boole için:

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

6

Yineleme sırasında durumu izlemenin pitonik yolu, jeneratörlerle olur. Itertools yöntemini IMHO'yu anlamak oldukça zordur ve bunu yapmak için lambdas'ı hacklemeye çalışmak aptalca. Denerdim:

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

Genel olarak, okunabilirlik her seferinde kompaktlığın önüne geçer.


4

Hayır, kendi tanımı nedeniyle bir lambda içine atama koyamazsınız. Fonksiyonel programlama kullanarak çalışıyorsanız, değerlerinin değiştirilebilir olmadığını varsaymalısınız.

Çözümlerden biri aşağıdaki kod olabilir:

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

Çağrılar arasındaki durumu hatırlamak için bir lambda'ya ihtiyacınız varsa, yerel ad alanında bildirilen bir işlevi veya aşırı yüklenmiş bir sınıfı öneririm __call__. Yapmaya çalıştığınız şeye karşı tüm uyarılarım artık yoldan çıktığına göre, sorgunuza gerçek bir yanıt verebiliriz.

Lambda'nızın aramalar arasında biraz hafızaya sahip olması gerekiyorsa, bunu şu şekilde tanımlayabilirsiniz:

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

O zaman sadece geçmen fgerekiyor filter(). Gerçekten ihtiyacınız varsa flag, aşağıdakilerle değerini geri alabilirsiniz :

f.__defaults__[0]["flag"]

Alternatif olarak, sonucunu değiştirerek genel ad alanını değiştirebilirsiniz globals(). Ne yazık ki, yerel ad alanını, sonucunu değiştirmenin locals()yerel ad alanını etkilemediği gibi değiştiremezsiniz .


Ya da sadece orijinal Lisp'i kullanın: (let ((var 42)) (lambda () (setf var 43))).
Kaz

4

Sözde çoklu ifadeli lambda kullanmak için bir bağlama işlevi kullanabilirsiniz. Ardından, atamayı etkinleştirmek için bir Bayrak için bir sarmalayıcı sınıfı kullanabilirsiniz.

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

Biraz karmaşık bir çözüm, ancak lambdalarda atama yine de yasa dışıdır, bu yüzden gerçekten önemli değil. Aşağıdaki exec()örnekte olduğu gibi, atamayı lambda içinden çalıştırmak için yerleşik işlevini kullanabilirsiniz :

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

ilk olarak, işiniz için yerel bir atama kullanmanıza gerek yoktur, sadece yukarıdaki cevabı kontrol edin

ikincisi, değişkenler tablosunu almak ve ardından değeri değiştirmek için locals () ve globals () kullanmak basittir

bu örnek kodu kontrol edin:

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

çevrenize bir global değişken eklemeniz gerekiyorsa, yerelleri () globals () ile değiştirmeyi deneyin.

python'un liste kompozisyonu havalı ama üçlü projenin çoğu bunu kabul etmiyor (tıpkı flask gibi: [)

umarım yardımcı olabilir


2
Sen kullanamaz locals()değiştirmeden aslında yerel kapsamını değişmemesini belgelerinde açıkça söylüyor (veya en azından her zaman olmaz). globals()Öte yandan beklendiği gibi çalışıyor.
JPvdMerwe

@JPvdMerwe sadece deneyin, belgeyi körü körüne takip etmeyin. ve
lambda'daki

3
Maalesef yalnızca global isim alanında çalışır, bu durumda gerçekten kullanmalısınız globals(). pastebin.com/5Bjz1mR4 (hem 2.6 hem de 3.2'de test edildi) bunu kanıtlıyor.
JPvdMerwe
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.