Tương đương với python3 tốt để giải nén tự động tuple trong lambda là gì?


94

Hãy xem xét mã python2 sau

In [5]: points = [ (1,2), (2,3)]

In [6]: min(points, key=lambda (x, y): (x*x + y*y))
Out[6]: (1, 2)

Điều này không được hỗ trợ trong python3 và tôi phải làm như sau:

>>> min(points, key=lambda p: p[0]*p[0] + p[1]*p[1])
(1, 2)

Điều này rất xấu xí. Nếu lambda là một hàm, tôi có thể làm

def some_name_to_think_of(p):
  x, y = p
  return x*x + y*y

Loại bỏ tính năng này trong python3 buộc mã phải làm theo cách xấu xí (với các chỉ mục ma thuật) hoặc tạo các chức năng không cần thiết (Phần làm phiền nhất là nghĩ ra tên hay cho các chức năng không cần thiết này)

Tôi nghĩ rằng tính năng này nên được thêm lại ít nhất cho riêng lambdas. Có một giải pháp thay thế tốt không?


Cập nhật: Tôi đang sử dụng trình trợ giúp sau để mở rộng ý tưởng trong câu trả lời

def star(f):
  return lambda args: f(*args)

min(points, key=star(lambda x,y: (x*x + y*y))

Update2: Phiên bản sạch hơn chostar

import functools

def star(f):
    @functools.wraps(f):
    def f_inner(args):
        return f(*args)
    return f_inner

4
Sau đó có nhiều khả năng lambdabị xóa hoàn toàn khỏi ngôn ngữ để đảo ngược các thay đổi khiến nó khó sử dụng hơn, nhưng bạn có thể thử đăng lên ý tưởng python nếu bạn muốn bày tỏ mong muốn thấy tính năng được thêm lại.
Wooble

3
Tôi cũng không hiểu, nhưng có vẻ như BDFL phản đối lambdavới tinh thần giống như anh ta phản đối map, reducefilter.
Cuadue

3
lambdađã được dự kiến ​​xóa trong py3k vì nó về cơ bản là một điểm yếu về ngôn ngữ. Nhưng không ai có thể đồng ý về một giải pháp thay thế thích hợp cho việc xác định các chức năng ẩn danh, vì vậy cuối cùng Guido đã giơ cánh tay của mình trong thất bại và đó là điều đó.
roippi

5
các chức năng ẩn danh phải có trong bất kỳ ngôn ngữ thích hợp nào và tôi khá thích lambdas. Tôi sẽ phải đọc lý do của một cuộc tranh luận như vậy. (Ngoài ra, mặc dù mapfilterđược thay thế tốt nhất bằng comprehensions, tôi làm như reduce)
njzk2

13
Một trong những điều tôi không thích về Python 3 ...
timgeb

Câu trả lời:


33

Không, không có cách nào khác. Bạn đã che đậy tất cả. Cách để tiếp tục là nêu vấn đề này trong danh sách gửi thư ý tưởng Python , nhưng hãy chuẩn bị tranh luận rất nhiều ở đó để đạt được một số lực kéo.

Trên thực tế, không chỉ để nói "không có lối thoát", cách thứ ba có thể là triển khai thêm một cấp độ gọi lambda chỉ để mở các tham số - nhưng điều đó đồng thời sẽ không hiệu quả và khó đọc hơn hai đề xuất của bạn:

min(points, key=lambda p: (lambda x,y: (x*x + y*y))(*p))

cập nhật Python 3.8

Hiện tại, Python 3.8 alpha1 đã có sẵn và các biểu thức gán PEP 572- được triển khai.

Vì vậy, nếu một người sử dụng một thủ thuật để thực thi nhiều biểu thức bên trong lambda - tôi thường làm điều đó bằng cách tạo một bộ tuple và chỉ trả về thành phần cuối cùng của nó, bạn có thể thực hiện:

>>> a = lambda p:(x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
>>> a((3,4))
25

Nên nhớ rằng loại mã này hiếm khi dễ đọc hoặc thực tế hơn là có đầy đủ chức năng. Tuy nhiên, vẫn có những cách sử dụng - nếu có nhiều lớp lót khác nhau hoạt động trên điều này point, có thể đáng giá để có một tệp tin có tên và sử dụng biểu thức gán để "truyền" một cách hiệu quả trình tự đến với tệp tin có tên:

>>> from collections import namedtuple
>>> point = namedtuple("point", "x y")
>>> b = lambda s: (p:=point(*s), p.x ** 2 + p.y ** 2)[-1]

3
Và tất nhiên, tôi quên đề cập rằng cách "một và rõ ràng" để làm điều này là thực sự sử dụng hàm ba dòng bằng cách sử dụng defđược đề cập trong câu hỏi dưới nme some_name_to_think-of.
jsbueno

2
Câu trả lời này vẫn thấy một số du khách - với PEP 572 thực hiện, cần có cách để tạo ra các biến bên trong biểu thức lambda, như trong:key = lambda p: (x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
jsbueno

1
biểu thức gán !! Tôi (thật vui) ngạc nhiên khi họ đã đến được
javadba

24

Theo http://www.python.org/dev/peps/pep-3113/ việc giải nén tuple đã biến mất và 2to3sẽ dịch chúng như sau:

Vì các tham số tuple được lambdas sử dụng vì giới hạn biểu thức đơn, chúng cũng phải được hỗ trợ. Điều này được thực hiện bằng cách đặt đối số trình tự dự kiến ​​liên kết với một tham số duy nhất và sau đó lập chỉ mục trên tham số đó:

lambda (x, y): x + y

sẽ được dịch thành:

lambda x_y: x_y[0] + x_y[1]

Điều này khá giống với cách triển khai của bạn.


3
Tốt mà nó không được loại bỏ trong vòng lặp for hoặc comprehensions
Balki

12

Tôi không biết bất kỳ lựa chọn thay thế chung nào tốt cho hành vi giải nén các đối số trong Python 2. Dưới đây là một số gợi ý có thể hữu ích trong một số trường hợp:

  • nếu bạn không thể nghĩ ra một cái tên; sử dụng tên của tham số từ khóa:

    def key(p): # more specific name would be better
        x, y = p
        return x**2 + y**3
    
    result = min(points, key=key)
  • bạn có thể xem liệu a namedtuplecó làm cho mã của bạn dễ đọc hơn không nếu danh sách được sử dụng ở nhiều nơi:

    from collections import namedtuple
    from itertools import starmap
    
    points = [ (1,2), (2,3)]
    Point = namedtuple('Point', 'x y')
    points = list(starmap(Point, points))
    
    result = min(points, key=lambda p: p.x**2 + p.y**3)

Trong số tất cả các câu trả lời cho câu hỏi này cho đến nay, sử dụng một tệp tin có tên là cách hành động tốt nhất ở đây. Không chỉ bạn có thể giữ lambda của mình mà còn tôi thấy rằng phiên bản có tên là dễ đọc hơn vì sự khác biệt giữa lambda (x, y):lambda x, y:không rõ ràng ngay từ cái nhìn đầu tiên.
Burak Arslan

5

Trong khi các đối số hủy cấu trúc đã bị xóa trong Python3, nó không bị xóa khỏi phần hiểu. Có thể lạm dụng nó để có được hành vi tương tự trong Python 3. Về bản chất, chúng ta tận dụng lợi thế của thực tế là các đồng quy trình cho phép chúng ta chuyển các hàm từ trong ra ngoài và lợi nhuận không phải là một câu lệnh và do đó được phép trong lambdas.

Ví dụ:

points = [(1,2), (2,3)]
print(min(points, key=lambda y: next(x*x + y*y for x,y in (lambda a: (yield a))(y))))

So với câu trả lời được chấp nhận là sử dụng trình bao bọc, giải pháp này có thể phá hủy hoàn toàn các đối số trong khi trình bao bọc chỉ hủy mức đầu tiên. Đó là,

values = [(('A',1),'a'), (('B',0),'b')]
print(min(values, key=lambda y: next(b for (a,b),c in (lambda x: (yield x))(y))))

So với

values = [(('A',1),'a'), (('B',0),'b')]
print(min(points, key=lambda p: (lambda a,b: (lambda x,y: (y))(*a))(*p)))

Ngoài ra, người ta cũng có thể làm

values = [(('A',1),'a'), (('B',0),'b')]
print(min(points, key=lambda y: next(b for (a,b),c in [y])))

Hoặc tốt hơn một chút

print(min(values, key=lambda y: next(b for ((a,b),c) in (y,))))

Điều này chỉ để gợi ý rằng nó có thể được thực hiện, và không nên được coi là một khuyến nghị.


0

Dựa trên gợi ý của Cuadue và nhận xét của bạn về việc giải nén vẫn xuất hiện trong phần hiểu, bạn có thể sử dụng, sử dụng numpy.argmin:

result = points[numpy.argmin(x*x + y*y for x, y in points)]

0

Một tùy chọn khác là ghi nó vào một bộ tạo ra một bộ tuple trong đó khóa là phần tử đầu tiên. Các bộ giá trị được so sánh bắt đầu từ đầu đến cuối nên bộ giá trị có phần tử đầu tiên nhỏ nhất được trả về. Sau đó, bạn có thể lập chỉ mục vào kết quả để nhận giá trị.

min((x * x + y * y, (x, y)) for x, y in points)[1]

0

Cân nhắc xem bạn có cần giải nén bộ tuple ngay từ đầu hay không:

min(points, key=lambda p: sum(x**2 for x in p))

hoặc liệu bạn có cần cung cấp tên rõ ràng khi giải nén:

min(points, key=lambda p: abs(complex(*p))

0

Tôi nghĩ rằng cú pháp tốt hơn là x * x + y * y let x, y = point, lettừ khóa nên được lựa chọn cẩn thận hơn.

Lambda kép là phiên bản gần nhất. lambda point: (lambda x, y: x * x + y * y)(*point)

Trình trợ giúp hàm bậc cao sẽ hữu ích trong trường hợp chúng ta đặt tên riêng cho nó.

def destruct_tuple(f):
  return lambda args: f(*args)

destruct_tuple(lambda x, y: x * x + y * y)
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.