Tại sao Python không cho phép lambdas nhiều dòng?


50

Ai đó có thể giải thích lý do cụ thể tại sao BDFL chọn tạo một dòng lambdas Python không?

Điều này là tốt:

lambda x: x**x

Điều này dẫn đến một lỗi:

lambda x:
    x**x

Tôi hiểu rằng việc tạo ra nhiều dòng lambda bằng cách nào đó sẽ "làm phiền" các quy tắc thụt lề thông thường và sẽ yêu cầu thêm nhiều ngoại lệ, nhưng điều đó có đáng để mang lại lợi ích không?

Nhìn vào JavaScript chẳng hạn. Làm thế nào một người có thể sống mà không có các chức năng ẩn danh? Chúng không thể thiếu. Không phải Pythonistas muốn thoát khỏi việc phải đặt tên cho mọi hàm đa dòng chỉ để vượt qua nó như là một đối số?


3
Xem xét rằng bạn lưu ý những lý do cụ thể tại sao Guido không cho phép lambdas đa biểu hiện và sau đó loại bỏ chúng, tôi sẽ cho rằng bạn đang tìm kiếm xác nhận thay vì một câu trả lời thực sự.
Jason Baker

3
Ngoài việc lưu bảy ký tự, làm thế nào là tốt hơn một def? Bây giờ nó có chính xác cấu trúc hình ảnh tương tự.
gièm pha

Câu trả lời:


42

Guido van van Rossum tự trả lời:

Nhưng các giải pháp như vậy thường thiếu "Pythonicity" - đặc điểm khó nắm bắt đó của một tính năng Python tốt. Không thể diễn tả Pythonicity như một ràng buộc cứng. Ngay cả Zen của Python cũng không chuyển thành một bài kiểm tra đơn giản về Pythonicity ...

Trong ví dụ trên, thật dễ dàng tìm thấy gót chân Achilles của giải pháp được đề xuất: dấu hai chấm, trong khi thực sự không rõ ràng về mặt cú pháp (một trong những "ràng buộc câu đố"), hoàn toàn tùy ý và không giống với bất kỳ thứ gì khác trong Python ...

Nhưng tôi cũng từ chối điều đó, bởi vì cuối cùng (và đây là lúc tôi thừa nhận đã vô tình gây hiểu lầm cho người nộp) Tôi thấy bất kỳ giải pháp nào không thể chấp nhận được mà nhúng một khối dựa trên thụt vào giữa một biểu thức. Vì tôi tìm thấy cú pháp thay thế cho nhóm câu lệnh (ví dụ: dấu ngoặc nhọn hoặc từ khóa bắt đầu / kết thúc) không thể chấp nhận được, điều này khá nhiều làm cho lambda nhiều dòng trở thành một câu đố không thể giải được.

http://www.artima.com/weblogs/viewpost.jsp?thread=147353

Về cơ bản, ông nói rằng mặc dù có thể có một giải pháp, nhưng nó không phù hợp với cách Python.


2
+1 cảm ơn về liên kết luồng - nhưng tôi vẫn đánh giá cao lambdas nhiều dòng - chúng là vô giá - hãy nhìn vào JavaScript, PHP cũng có chúng.
treecoder

2
@greengit Bạn chỉ có thể sử dụng một def lồng nhau. Nó không giống như các chức năng ẩn danh, nhưng chúng đủ gần.
jsternberg

2
defs lồng nhau không giúp ích gì khi truyền các hàm làm đối số - đó là lý do số một tại sao tôi thích những con cừu nhiều dòng
treecoder

1
@greengit - Tôi nghĩ rằng bạn nên chấp nhận điều này với GvR hơn là đăng bình luận của bạn ở đây.
Jason Baker

9
@greengit: Bạn có biết rằng bạn có thể truyền hàm này dưới dạng đối số cho hàm khác không? Bạn không thể viết nó nội tuyến, nhưng không có kỹ thuật lập trình không có sẵn cho bạn.
btilly

24

Thật tuyệt khi làm lambda nhiều dòng trong python: xem

>>> f = lambda x: (
...   x**x)
>>> f
<function <lambda> at 0x7f95d8f85488>
>>> f(3)
27

giới hạn lambda thực sự là thực tế rằng lambda phải là một biểu thức duy nhất ; nó không thể chứa từ khóa (như python2's printhoặc return).

GvR chọn làm như vậy để giới hạn kích thước của lambda, vì chúng thường được sử dụng làm tham số. Nếu bạn muốn một chức năng thực sự, sử dụngdef


1
multi line là về phần chèn của ký tự '\ n': D python không có đa tuyên bố lambda. Bạn thực sự muốn sử dụng def. Hãy suy nghĩ về nó: bạn thực sự cần một cuộc gọi như là một tham số của chức năng của bạn? Và người dùng của chức năng đó không được phép vượt qua cuộc gọi mặc định của bạn ? Làm thế nào họ có thể vượt qua nó nếu bạn không đưa nó cho họ?
Vito De Tullio

btw, bạn có thể cung cấp một ví dụ về nhu cầu của bạn về một chức năng không đồng nhất?
Vito De Tullio

1
Vâng, tôi thấy giới hạn của một biểu hiện duy nhất thực sự bực bội. Đúng, rằng nếu họ cho phép lambdas đa biểu hiện, mọi người chắc chắn sẽ bắt đầu lạm dụng nó nhưng cách khác là omho quá hạn chế.
rbaleksandar

10

Tôi biết điều này là siêu cũ, nhưng đặt ở đây như một tài liệu tham khảo.

Một cách khác để sử dụng lambda có thể là sử dụng deftheo cách không thông thường. Mục tiêu là để truyền một defchức năng, có thể được thực hiện chỉ trong một trường hợp - một người trang trí. Lưu ý rằng với việc thực hiện def resultnày không tạo ra một hàm, nó tạo ra kết quả của reduce(), kết quả là a dict.

Ổ cắm không biết xấu hổ : Tôi làm rất nhiều thứ ở đây .

>>> xs = [('a', 1), ('b', 2), ('a', 3), ('b', 4)]
>>> foldl = lambda xs, initial: lambda f: reduce(f, xs, initial)
>>> @foldl(xs, {})
... def result(acc, (k, v)):
...     acc.setdefault(k, 0)
...     acc[k] += v
...     return acc
...
>>> result
{'a': 4, 'b': 6} 

Lưu ý rằng lambdas đa câu lệnh có thể được thực hiện, nhưng chỉ với mã thực sự, thực sự xấu xí. Tuy nhiên, điều thú vị là cách phạm vi hoạt động với việc triển khai này (lưu ý việc sử dụng nhiều namebiến và bóng của messagebiến.

>>> from __future__ import print_function
>>> bind = lambda x, f=(lambda x: x): f(x)
>>> main = lambda: bind(
...     print('Enter your name.'), lambda _: bind(
...     raw_input('> '), lambda name: bind(
...     'Hello {}!'.format(name), lambda message: bind(
...     print(message), lambda _: bind(
...     'Bye {}!'.format(name), lambda message: bind(
...     print(message)
... ))))))
>>> main()
Enter your name.
> foo
Hello foo!
Bye foo!

+1 cho cách tiếp cận đơn nguyên
jozefg

Monads cũng được gọi là thenables hoặc tương lai / lời hứa hoặc thậm chí gọi lại trong JavaScript BTW.
aoeu256

3

Việc hack cùng một lambda đa tuyên bố không tệ như pyrospade đưa ra: chắc chắn chúng ta có thể tạo ra một loạt các hàm đơn nguyên bằng cách sử dụng liên kết, như trong Haskell, nhưng vì chúng ta ở trong thế giới không trong sạch của Python, chúng ta cũng có thể sử dụng tác dụng phụ để đạt được điều tương tự.

Tôi bao gồm một vài cách để làm điều này trên blog của tôi .

Ví dụ, Python đảm bảo đánh giá các phần tử của một bộ theo thứ tự, vì vậy chúng ta có thể sử dụng ,giống như một mệnh lệnh ;. Chúng ta có thể thay thế nhiều câu lệnh, like print, bằng biểu thức, like sys.stdout.write.

Do đó, sau đây là tương đương:

def print_in_tag_def(tag, text):
    print "<" + tag + ">"
    print text
    print "</" + tag + ">"

import sys
print_ = sys.stdout.write
print_in_tag_lambda = lambda tag, text: (print_("<" + tag + ">"),
                                         print_(text),
                                         print_("</" + tag + ">"),
                                         None)[-1]

Lưu ý rằng tôi đã thêm một Noneở cuối và trích xuất nó bằng cách sử dụng [-1]; cái này đặt giá trị trả về một cách rõ ràng. Chúng tôi không phải làm điều này, nhưng không có nó, chúng tôi sẽ nhận được một (None, None, None)giá trị hoàn trả thú vị, điều mà chúng tôi có thể hoặc không quan tâm.

Vì vậy, chúng ta có thể sắp xếp các hành động IO. Còn các biến cục bộ thì sao?

Python =tạo thành một câu lệnh, vì vậy chúng ta cần tìm một biểu thức tương đương. Một cách là để thay đổi nội dung của cơ sở hạ tầng, được thông qua như là một đối số. Ví dụ:

def stateful_def():
    foo = 10
    bar = foo * foo
    foo = 2
    return foo + bar

stateful_lambda = (lambda state: lambda *_: (state.setdefault('foo', 10),
                                             state.setdefault('bar', state.get('foo') * state.get('foo')),
                                             state.pop('foo'),
                                             state.setdefault('foo', 2),
                                             state.get('foo') + state.get('bar'))[-1])({})

Có một vài thủ thuật đang được sử dụng trong stateful_lambda:

  • Đối *_số cho phép lambda của chúng tôi nhận bất kỳ số lượng đối số. Vì điều này cho phép không có đối số, chúng tôi phục hồi quy ước gọi của stateful_def.
    • Gọi một đối số _chỉ là một quy ước có nội dung "Tôi sẽ không sử dụng biến này"
  • Chúng ta có một hàm ("trình bao bọc") trả về một hàm ("chính") khác: lambda state: lambda *_: ...
    • Nhờ phạm vi từ vựng , đối số của hàm thứ nhất sẽ nằm trong phạm vi cho hàm thứ hai
    • Chấp nhận một số đối số bây giờ và trả về một chức năng khác để chấp nhận phần còn lại sau đó được gọi là currying
  • Chúng tôi gọi ngay hàm "bao bọc", chuyển cho nó một từ điển trống: (lambda state: ...)({})
    • Điều này cho phép chúng ta gán một biến statecho một giá trị {}mà không cần sử dụng câu lệnh gán (ví dụ state = {}:)
  • Chúng tôi coi các khóa và giá trị statelà tên biến và giá trị ràng buộc
    • Điều này ít cồng kềnh hơn so với sử dụng lambdas ngay lập tức
    • Điều này cho phép chúng ta thay đổi giá trị của các biến
    • Chúng tôi sử dụng state.setdefault(a, b)thay vì a = bstate.get(a)thay vìa
  • Chúng tôi sử dụng một bộ dữ liệu để kết hợp các tác dụng phụ của chúng tôi, như trước đây
  • Chúng tôi sử dụng [-1]để trích xuất giá trị cuối cùng, hoạt động như một returntuyên bố

Tất nhiên điều này khá cồng kềnh, nhưng chúng ta có thể tạo một API đẹp hơn với các hàm trợ giúp:

# Keeps arguments and values close together for immediately-called functions
callWith = lambda x, f: f(x)

# Returns the `get` and `setdefault` methods of a new dictionary
mkEnv = lambda *_: callWith({},
                            lambda d: (d.get,
                                       lambda k, v: (d.pop(k), d.setdefault(k, v))))

# A helper for providing a function with a fresh `get` and `setdefault`
inEnv = lambda f: callWith(mkEnv(), f)

# Delays the execution of a function
delay = lambda f x: lambda *_: f(x)

# Uses `get` and `set`(default) to mutate values
stateful_lambda = delay(inEnv, lambda get, set: (set('foo', 10),
                                                 set('bar', get('foo') * get('foo')),
                                                 set('foo', 2),
                                                 get('foo') + get('bar'))[-1])

bạn đang đùa đấy, điều này trông giống như một cơn ác mộng lol
Alexander Mills

1
@AlexanderMills Heh, điều này đã không có ý định như một ví dụ thực tế, nhiều hơn một sự bác bỏ của pyrospade của lambdas-in-lambdas-in-lambdas tiếp cận, để hiển thị những thứ không xấu. Trên thực tế, điều này có thể được đơn giản hóa hơn nữa khi chúng ta có python.org/dev/peps/pep-0572
Warbo

1

Tôi mặc dù tôi có thể đóng góp, sử dụng một bộ ngắt dòng:

x = lambda x,y: x-y if x<y \ 
                     else y-x if y<x \
                     else 0

Đừng quên điều rất hay là python có thể viết oneliners, như trong ví dụ:

a=b=0; c=b+a; d = a+b**2 #etc etc

lambda rất mạnh, nhưng nó không có nghĩa là thay thế toàn bộ 1 chức năng, ý tôi là bạn có thể hack nó như thế nào (ví dụ mượn từ đồng nghiệp ở trên):

makeTag = lambda tagName: "<{}>".format(tagName)
closeTag = lambda tagName: makeTag("/"+str(tagName))
openTag = lambda tagName: makeTag(tagName)
writeHMTLline = lambda tag,content: ""+opetTag(tag)+str(content)+closeTag(tag)

Nhưng bạn có thực sự muốn làm điều đó như thế này? Nó hầu như không thể đọc được sau một thời gian, nó giống như bắt đầu từ sợi dây bắt đầu với kết thúc được làm sáng tỏ. dây thừng được làm sáng tỏ

Lambdas được coi là các chức năng duy nhất, trong bản đồ, lọc và giảm các chức năng trong Lập trình định hướng chức năng (trong số những thứ khác). Ví dụ: lấy các giá trị ký tự của các giá trị là số nguyên và chia hết cho 2

chrDev2 = lambda INT: chr(INT) if isinstance(INT,int) and INT%2==0 else INT
someStringList = map( chrDev2, range(30) )
>>> ['\x00', 1, '\x02', 3, '\x04', 5, '\x06', 7, '\x08', 9, '\n', 11, '\x0c', 13, '\x0e', 15, '\x10', 17, '\x12', 19, '\x14', 21, '\x16', 23, '\x18', 25, '\x1a', 27, '\x1c', 29]

Bạn có thể sử dụng nó như chức năng thể hiện chức năng bằng cách hủy bỏ chức năng phức tạp (hoặc nhiều hơn và một vài lambdas, và đặt nó vào một lambda khác:

def someAnon(*args): return sum(list(args))
defAnon = lambda list: [ x*someAnon(*list) for x in list]

nhưng Python có hỗ trợ expresions theo cách khác: -lets nói rằng bạn có một số hàm được gọi superAwesomeFunctionvà hàm đó có thể thực hiện một số công cụ siêu tuyệt vời, bạn có thể gán nó cho một biến bằng cách không gọi nó, như thế này:

SAF = superAwesomeFunction # there is no () at the end, 

Vì vậy, bây giờ khi bạn gọi SAF, bạn sẽ gọi superAwgieFunction hoặc phương thức. Nếu bạn tìm kiếm máng thư mục Lib của bạn, bạn có thể thấy rằng hầu hết các __builtin__mô-đun python được viết theo cách đó. Điều này được thực hiện bởi vì đôi khi bạn sẽ cần một số chức năng thực hiện nhiệm vụ cụ thể không đủ cần thiết để người dùng có thể sử dụng nhưng nó cần thiết cho một số chức năng. Vì vậy, sau đó bạn có một lựa chọn là bạn không thể có 2 hàm với tên "superAw đũaFunction", bạn có thể có "superAw đũaFunctionDagingBasicStuf" và "realSuperAwemmeFunction" và thay vì chỉ đặt "realSuperAwemmeFeft".

Bạn có thể tìm vị trí của các mô-đun đã nhập bằng cách nhập vào bảng điều khiển importedModule.__file__(ví dụ thực tế import os;os.__file__) và chỉ cần theo thư mục đó để gửi tệp có tên là importModule.py và mở nó trong trình chỉnh sửa và tìm cách bạn có thể tối đa hóa "kiến thức" của riêng mình.

Tôi hy vọng điều này sẽ giúp bạn và có thể các đồng nghiệp khác gặp rắc rối.

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.