Phép gán bên trong biểu thức lambda bằng Python


105

Tôi có một danh sách các đối tượng và tôi muốn loại bỏ tất cả các đối tượng trống ngoại trừ một, sử dụng filtervà một lambdabiểu thức.

Ví dụ: nếu đầu vào là:

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

... thì đầu ra phải là:

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

Có cách nào để thêm phép gán vào lambdabiểu thức không? Ví dụ:

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
Nhưng bạn không cần cái này. Trên thực tế, tôi nghĩ rằng nó sẽ là một cách khá mù mờ để đạt được điều này ngay cả khi nó hoạt động.

8
Tại sao không chỉ chuyển một hàm cũ thông thường vào bộ lọc?
dfb

5
Tôi chỉ muốn sử dụng lambda vì vậy nó sẽ là một giải pháp thực sự nhỏ gọn. Tôi nhớ trong OCaml, tôi có thể in chuỗi các câu lệnh trước biểu thức trả về, nghĩ rằng điều này có thể được sao chép trong Python
Cat

Thật là khó khăn khi đang trong quá trình phát triển một pipeilne có chuỗi sau đó nhận ra: "ồ tôi muốn tạo một temp var để làm cho luồng rõ ràng hơn" hoặc "tôi muốn ghi lại bước trung gian này": và sau đó bạn phải nhảy ở nơi khác để tạo một hàm để thực hiện nó: và đặt tên cho hàm đó và theo dõi nó - mặc dù nó chỉ được sử dụng ở một nơi.
javadba

Câu trả lời:


215

Toán tử biểu thức gán :=được thêm vào trong Python 3.8 hỗ trợ gán bên trong biểu thức lambda. Toán tử này chỉ có thể xuất hiện trong một biểu thức dấu ngoặc đơn (...), dấu ngoặc [...]hoặc dấu ngoặc nhọn {...}vì lý do cú pháp. Ví dụ, chúng ta có thể viết như sau:

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

Trong Python 2, có thể thực hiện các phép gán cục bộ như một tác dụng phụ của việc hiểu danh sách.

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

Tuy nhiên, bạn không thể sử dụng một trong hai điều này trong ví dụ của mình vì biến của bạn flagnằm trong phạm vi bên ngoài, không phải lambdaphạm vi của. Điều này không liên quan lambda, đó là hành vi chung trong Python 2. Python 3 cho phép bạn giải quyết vấn đề này với nonlocaltừ khóa bên trong của defs, nhưng nonlocalkhông thể sử dụng bên tronglambda s.

Có một cách giải quyết (xem bên dưới), nhưng trong khi chúng ta đang nói về chủ đề ...


Trong một số trường hợp, bạn có thể sử dụng điều này để thực hiện mọi thứ bên trong 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()])()

Một hình trụ có bán kính 10,0cm và cao 20,0cm có thể tích là 6283,2cm³.
Một hình trụ có bán kính 20,0cm và cao 40,0cm có thể tích là 50265,5cm³.
Một hình trụ có bán kính 30,0cm và cao 60,0cm có thể tích là 169646,0cm³.

Xin đừng.


... quay lại ví dụ ban đầu của bạn: mặc dù bạn không thể thực hiện các nhiệm vụ cho flag biến trong phạm vi bên ngoài, nhưng bạn có thể sử dụng các hàm để sửa đổi giá trị đã được gán trước đó.

Ví dụ: flagcó thể là một đối tượng mà .valuechúng tôi đặt bằng cách sử dụng 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')]

Nếu chúng ta muốn phù hợp với chủ đề trên, chúng ta có thể sử dụng cách hiểu danh sách thay vì setattr:

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

Nhưng thực sự, trong mã nghiêm túc, bạn nên luôn sử dụng định nghĩa hàm thông thường thay vì định nghĩa lambdanếu bạn sắp thực hiện nhiệm vụ bên ngoài.

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)

Ví dụ cuối cùng trong câu trả lời này không tạo ra kết quả giống như ví dụ, nhưng tôi thấy có vẻ như đầu ra ví dụ không chính xác.
Jeremy,

trong ngắn hạn, điều này nắm tới: sử dụng .setattr()và alikes ( từ điển nên làm là tốt, ví dụ) để hack các tác dụng phụ vào mã chức năng nào, mã mát mẻ bởi @JeremyBanks đã được thể hiện :)
jno

Thx cho ghi chú trên assignment operator!
javadba

37

Bạn không thể thực sự duy trì trạng thái trong một biểu thức filter/ lambda(trừ khi lạm dụng không gian tên chung). Tuy nhiên, bạn có thể đạt được điều gì đó tương tự bằng cách sử dụng kết quả tích lũy được chuyển trong một reduce()biểu thức:

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

Tất nhiên, bạn có thể điều chỉnh điều kiện một chút. Trong trường hợp này, nó lọc ra các bản sao, nhưng bạn cũng có thể sử dụng a.count(""), chẳng hạn, để chỉ hạn chế các chuỗi trống.

Không cần phải nói, bạn có thể làm điều này nhưng bạn thực sự không nên. :)

Cuối cùng, bạn có thể làm bất cứ điều gì bằng Python thuần túy lambda: http://vanderwijk.info/blog/pure-lambda-calculus-python/


17

Không cần sử dụng lambda, khi bạn có thể loại bỏ tất cả những cái rỗng và đặt lại một cái nếu kích thước đầu vào thay đổi:

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
Tôi nghĩ rằng bạn có một lỗi nhỏ trong mã của bạn. Dòng thứ hai nên được output = [x for x in input if x.name].
halex

Thứ tự của các phần tử có thể quan trọng.
MAnyKey

15

Phép gán thông thường ( =) không thể thực hiện được bên trong một lambdabiểu thức, mặc dù có thể thực hiện nhiều thủ thuật khác nhau với setattrvà bạn bè.

Tuy nhiên, giải quyết vấn đề của bạn thực sự khá đơn giản:

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
    )

cái nào sẽ cho bạn

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

Như bạn có thể thấy, nó giữ phiên bản trống đầu tiên thay vì phiên bản cuối cùng. Thay vào đó, nếu bạn cần cái cuối cùng, hãy đảo ngược danh sách đi vào filtervà đảo ngược danh sách ra khỏi filter:

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

cái nào sẽ cho bạn

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

Một điều cần lưu ý: để điều này hoạt động với các đối tượng tùy ý, các đối tượng đó phải thực hiện đúng __eq____hash__như được giải thích ở đây .


7

CẬP NHẬT :

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

hoặc sử dụng filterlambda:

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

Câu trả lời trước

OK, bạn có gặp khó khăn khi sử dụng bộ lọc và lambda không?

Có vẻ như điều này sẽ được phục vụ tốt hơn khi hiểu từ điển,

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

Tôi nghĩ lý do Python không cho phép gán trong lambda tương tự như lý do tại sao nó không cho phép gán ở dạng hiểu và điều đó có liên quan đến thực tế là những thứ này được đánh giá ở Cbên và do đó có thể cho chúng ta tăng tốc độ. Ít nhất đó là ấn tượng của tôi sau khi đọc một trong những bài luận của Guido .

Tôi đoán là điều này cũng sẽ đi ngược lại triết lý của việc có một cách đúng để thực hiện bất kỳ một việc nào trong Python.


Vì vậy, điều này không hoàn toàn đúng. Nó sẽ không bảo toàn thứ tự, cũng như sẽ không bảo tồn các bản sao của các đối tượng có chuỗi không rỗng.
JPvdMerwe

7

TL; DR: Khi sử dụng các thành ngữ chức năng, tốt hơn nên viết mã chức năng

Như nhiều người đã chỉ ra, trong Python, việc gán lambdas không được phép. Nói chung, khi sử dụng các thành ngữ chức năng, tốt hơn hết bạn nên suy nghĩ theo cách chức năng, nghĩa là bất cứ khi nào có thể, không có tác dụng phụ và không bị chỉ định.

Đây là giải pháp chức năng sử dụng lambda. Tôi đã gán lambda fncho rõ ràng (và vì nó hơi dài dòng).

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

Bạn cũng có thể thực hiện việc này với các trình lặp thay vì danh sách bằng cách thay đổi một chút mọi thứ xung quanh. Bạn cũng có một số nhập khẩu khác nhau.

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

Bạn luôn có thể đăng ký lại mã để giảm độ dài của các câu lệnh.


6

Nếu thay vào đó, flag = Truechúng tôi có thể thực hiện nhập, thì tôi nghĩ điều này đáp ứng các tiêu chí:

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

Hoặc có thể bộ lọc được viết tốt hơn là:

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

Hoặc, chỉ cho một boolean đơn giản, không có bất kỳ nhập khẩu nào:

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

6

Cách tuyệt vời để theo dõi trạng thái trong quá trình lặp là với máy phát điện. Cách sử dụng itertools khá khó hiểu đối với IMHO và việc cố gắng hack lambdas để làm điều này là hoàn toàn ngớ ngẩn. Tôi sẽ thử:

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

Nhìn chung, khả năng đọc vượt trội hơn sự nhỏ gọn mọi lúc.


4

Không, bạn không thể đặt một phép gán bên trong lambda vì định nghĩa riêng của nó. Nếu bạn làm việc bằng lập trình hàm, thì bạn phải giả định rằng các giá trị của bạn không thể thay đổi.

Một giải pháp sẽ là đoạn mã sau:

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

Nếu bạn cần lambda để ghi nhớ trạng thái giữa các lần gọi, tôi khuyên bạn nên sử dụng hàm được khai báo trong không gian tên cục bộ hoặc một lớp có quá tải __call__. Bây giờ tất cả những cảnh báo của tôi đối với những gì bạn đang cố gắng thực hiện đều không có lợi, chúng tôi có thể đi đến câu trả lời thực tế cho truy vấn của bạn.

Nếu bạn thực sự cần lambda của mình để có một số bộ nhớ giữa các cuộc gọi, bạn có thể định nghĩa nó như sau:

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

Sau đó, bạn chỉ cần chuyển fđến filter(). Nếu bạn thực sự cần, bạn có thể lấy lại giá trị flagbằng những thứ sau:

f.__defaults__[0]["flag"]

Ngoài ra, bạn có thể sửa đổi không gian tên chung bằng cách sửa đổi kết quả của globals(). Thật không may, bạn không thể sửa đổi không gian tên cục bộ giống như cách sửa đổi kết quả của locals()không ảnh hưởng đến không gian tên cục bộ.


Hoặc chỉ cần sử dụng Lisp gốc: (let ((var 42)) (lambda () (setf var 43))).
Kaz

4

Bạn có thể sử dụng một hàm liên kết để sử dụng lambda nhiều câu lệnh giả. Sau đó, bạn có thể sử dụng một lớp trình bao bọc cho Cờ để kích hoạt phép gán.

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

Đây là một cách giải quyết lộn xộn, nhưng dù sao thì việc chuyển nhượng trong lambdas cũng là bất hợp pháp, vì vậy nó không thực sự quan trọng. Bạn có thể sử dụng exec()hàm nội trang để chạy phép gán từ bên trong lambda, chẳng hạn như ví dụ sau:

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

đầu tiên, bạn không cần sử dụng phân công cục bộ cho công việc của mình, chỉ cần kiểm tra câu trả lời ở trên

thứ hai, rất đơn giản để sử dụng local () và global () để có bảng biến và sau đó thay đổi giá trị

kiểm tra mã mẫu này:

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

nếu bạn cần thay đổi việc thêm biến toàn cục vào môi trường của mình, hãy thử thay thế local () bằng global ()

Danh sách của python rất tuyệt nhưng hầu hết các dự án bộ ba không chấp nhận điều này (như flask: [)

hy vọng nó có thể giúp


2
Bạn không thể sử dụng locals(), nó nói rõ ràng trong tài liệu rằng việc thay đổi nó không thực sự thay đổi phạm vi cục bộ (hoặc ít nhất nó sẽ không luôn luôn). globals()mặt khác hoạt động như mong đợi.
JPvdMerwe,

@JPvdMerwe cứ thử đi, đừng làm theo tài liệu một cách mù quáng. và phân công trong lambda là quy tắc phá vỡ đã
jyf1987

3
Rất tiếc, nó chỉ hoạt động trong không gian tên chung, trong trường hợp đó bạn thực sự nên sử dụng globals(). pastebin.com/5Bjz1mR4 (được thử nghiệm trong cả 2.6 và 3.2) chứng minh điều đó.
JPvdMerwe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.