Thích hợp hơn: hàm lambda hay hàm lồng nhau ( def
)?
Có một lợi thế khi sử dụng lambda thay vì một hàm thông thường: chúng được tạo trong một biểu thức.
Có một số nhược điểm:
- không tên (chỉ
'<lambda>'
)
- không có docstrings
- không có chú thích
- không có câu lệnh phức tạp
Chúng cũng là cùng một loại đối tượng. Vì những lý do đó, tôi thường thích tạo các hàm bằng def
từ khóa thay vì bằng lambdas.
Điểm đầu tiên - chúng là cùng một loại đối tượng
Một lambda dẫn đến cùng một loại đối tượng như một hàm thông thường
>>> l = lambda: 0
>>> type(l)
<class 'function'>
>>> def foo(): return 0
...
>>> type(foo)
<class 'function'>
>>> type(foo) is type(l)
True
Vì lambdas là các hàm nên chúng là các đối tượng hạng nhất.
Cả lambdas và hàm:
- có thể được chuyển xung quanh như một đối số (giống như một hàm thông thường)
- khi được tạo bên trong một chức năng bên ngoài, trở thành một lớp đóng trên các địa phương của chức năng bên ngoài đó
Nhưng lambdas, theo mặc định, thiếu một số thứ mà các hàm nhận được thông qua cú pháp định nghĩa hàm đầy đủ.
Của một lamba __name__
là'<lambda>'
Lambdas xét cho cùng là các hàm ẩn danh, vì vậy chúng không biết tên của chính mình.
>>> l.__name__
'<lambda>'
>>> foo.__name__
'foo'
Do đó, lambda không thể được lập trình tra cứu trong không gian tên của chúng.
Điều này hạn chế một số điều. Ví dụ: foo
có thể được tra cứu bằng mã số tuần tự, trong khi l
không thể:
>>> import pickle
>>> pickle.loads(pickle.dumps(l))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <function <lambda> at 0x7fbbc0464e18>:
attribute lookup <lambda> on __main__ failed
Chúng tôi có thể tra cứu foo
tốt - vì nó biết tên riêng của nó:
>>> pickle.loads(pickle.dumps(foo))
<function foo at 0x7fbbbee79268>
Lambdas không có chú thích và không có chuỗi tài liệu
Về cơ bản, lambdas không được ghi lại. Hãy viết lại foo
để được tài liệu tốt hơn:
def foo() -> int:
"""a nullary function, returns 0 every time"""
return 0
Bây giờ, foo có tài liệu:
>>> foo.__annotations__
{'return': <class 'int'>}
>>> help(foo)
Help on function foo in module __main__:
foo() -> int
a nullary function, returns 0 every time
Trong khi đó, chúng tôi không có cơ chế giống nhau để cung cấp cùng một thông tin cho lambdas:
>>> help(l)
Help on function <lambda> in module __main__:
<lambda> lambda (...)
Nhưng chúng ta có thể hack chúng trên:
>>> l.__doc__ = 'nullary -> 0'
>>> l.__annotations__ = {'return': int}
>>> help(l)
Help on function <lambda> in module __main__:
<lambda> lambda ) -> in
nullary -> 0
Nhưng có thể có một số lỗi làm xáo trộn đầu ra của trợ giúp.
Lambdas chỉ có thể trả về một biểu thức
Lambdas không thể trả về các câu lệnh phức tạp, chỉ có các biểu thức.
>>> lambda: if True: 0
File "<stdin>", line 1
lambda: if True: 0
^
SyntaxError: invalid syntax
Các biểu thức có thể khá phức tạp và nếu bạn cố gắng rất nhiều, bạn có thể hoàn thành điều tương tự với lambda, nhưng sự phức tạp thêm vào sẽ gây hại nhiều hơn cho việc viết mã rõ ràng.
Chúng tôi sử dụng Python để rõ ràng và dễ bảo trì. Việc lạm dụng lambdas có thể chống lại điều đó.
Ưu điểm duy nhất cho lambdas: có thể được tạo trong một biểu thức duy nhất
Đây là điều duy nhất có thể xảy ra. Vì bạn có thể tạo lambda với một biểu thức, bạn có thể tạo nó bên trong một lời gọi hàm.
Việc tạo một hàm bên trong một lệnh gọi hàm tránh được việc tra cứu tên (rẻ tiền) so với việc tra cứu tên được tạo ở nơi khác.
Tuy nhiên, vì Python được đánh giá nghiêm ngặt, không có lợi ích hiệu suất nào khác để làm như vậy ngoài việc tránh tra cứu tên.
Đối với một biểu thức rất đơn giản, tôi có thể chọn lambda.
Tôi cũng có xu hướng sử dụng lambdas khi thực hiện Python tương tác, để tránh nhiều dòng khi một dòng sẽ làm. Tôi sử dụng loại định dạng mã sau khi tôi muốn chuyển đối số vào một phương thức khởi tạo khi gọi timeit.repeat
:
import timeit
def return_nullary_lambda(return_value=0):
return lambda: return_value
def return_nullary_function(return_value=0):
def nullary_fn():
return return_value
return nullary_fn
Và bây giờ:
>>> min(timeit.repeat(lambda: return_nullary_lambda(1)))
0.24312214995734394
>>> min(timeit.repeat(lambda: return_nullary_function(1)))
0.24894469301216304
Tôi tin rằng sự khác biệt nhỏ về thời gian ở trên có thể là do việc tra cứu tên trong return_nullary_function
- lưu ý rằng nó rất không đáng kể.
Phần kết luận
Lambdas rất phù hợp cho các tình huống không chính thức, nơi bạn muốn giảm thiểu các dòng mã để tạo ra một điểm độc đáo.
Lambdas là không tốt cho các tình huống chính thức hơn, nơi bạn cần sự rõ ràng cho những người chỉnh sửa mã, những người sẽ đến sau, đặc biệt là trong những trường hợp chúng không tầm thường.
Chúng ta biết rằng chúng ta phải đặt cho các đối tượng của mình những cái tên hay. Làm thế nào chúng ta có thể làm như vậy khi đối tượng không có tên?
Vì tất cả những lý do này, tôi thường thích tạo các hàm với def
thay vì với lambda
.
lambda
, nhưng tôi không đồng ý rằng đây là "rất hiếm", người ta thường cho các chức năng chìa khóa đểsorted
hoặcitertools.groupby
vv, ví dụsorted(['a1', 'b0'], key= lambda x: int(x[1]))