Lambda trong Python

Lambda trong Python
Photo by Michael Dziedzic from Unsplash

Chắc hẳn ai cũng đã từng có lần sử dụng lambda trong Python. Không hiếm những đoạn code như dưới đây được chia sẻ rộng rãi trong cộng đồng những người lập trình Python.

sorted_by_values = sorted(items, key=lambda i: i[1])

Vậy, lambda là gì? Nó khác gì với các hàm thông thường của Python?

Lambda là gì?

Một biểu thức lambda (lambda expression) sẽ định nghĩa một hàm lambda (lambda function). Một biểu thức lambda và hàm lambda sẽ trông như dưới đây. Về hình thức, biểu thức lambda giống như mô tả một “ánh xạ” giữa tham số đầu vào và kết quả đầu ra.

>>> square = lambda n: n**2
>>> square
<function <lambda> at 0x102cbcee0>

Trong ví dụ trên, biến square được gán một object (hàm lambda). Thử kiểm tra “type” của object đó:

>>> type(square)
<class 'function'>

Vậy là hàm lambda cũng là hàm và type của nó không khác gì các hàm khác. Và đương nhiên, có thể gọi hàm này như mọi hàm khác trong Python:

>>> square(3)
9

Thế nhưng, sự khác biệt là gì? Rõ ràng chúng ta hoàn toàn có thể định nghĩa một hàm theo cách “thông thường”.

def square(n):
    return n**2

Sự khác biệt này sẽ được trình bày trong những phần tiếp theo.

Lambda có thể định nghĩa “inline”

Ví dụ điển hình nhất là gọi lambda cùng với hàm sorted. Đây là một hàm có sẵn của Python, cho phép sắp xếp các phần tử theo nhiều tiêu chí khác nhau (mặc định là sắp xếp tăng dần sử dụng phép so sánh <).

>>> fruits = ['lemon', 'apple', 'lime', 'pear', 'watermelon', 'banana']
>>> sorted(fruits)
['apple', 'banana', 'lemon', 'lime', 'pear', 'watermelon']

Trong trường hợp muốn sắp xếp theo tiêu chí riêng, lập trình viên có thể truyền tham số key. Ví dụ, muốn sắp xếp các phần tử theo độ dài của chuỗi ký tự:

>>> fruits = ['lemon', 'apple', 'lime', 'pear', 'watermelon', 'banana']
>>> sorted(fruits, key=len)
['lime', 'pear', 'lemon', 'apple', 'banana', 'watermelon']

Tham số key có thể là bất cứ hàm nào. Về mặt kỹ thuật, không nhất thiết phải là hàm, bất cứ một callable object nào đều có thể truyền vào cho tham số này. Khi gọi hàm sorted, đối với mỗi phần tử, hàm được truyền vào sẽ được gọi và kết quả trả về sẽ được dùng để sắp xếp.

Sẽ có nhiều trường hợp chúng ta muốn sắp xếp theo những logic không có sẵn. Ví dụ, cần sắp xếp theo value (trong cặp key-value) với dữ liệu dạng dict chẳng hạn:

>>> counts = {"apple": 3, "pear": 2, "banana": 4, "lime": 1}
>>> counts.items()
dict_items([('apple', 3), ('pear', 2), ('banana', 4), ('lime', 1)])

Lúc này, lập trình viên có thể định nghĩa một hàm và trả về kết quả là tiêu chí cần sắp xếp, sau đó truyền vào hàm cho hàm sorted. Lưu ý rằng, hàm phải được định nghĩa trước khi gọi sorted:

>>> def by_value(item):
...     """Return the value from a given (key, value) tuple."""
...     return value[1]
...
... >>> sorted(counts.items(), key=by_value)
[('lime', 1), ('pear', 2), ('apple', 3), ('banana', 4)]

Trong trường hợp này, thay vì định nghĩa một hàm như trên, lập trình viên có thể sử dụng lambda, trông code sẽ đơn giản hơn rất nhiều:

>>> sorted(counts.items(), key=lambda item: item[1])
[('lime', 1), ('pear', 2), ('apple', 3), ('banana', 4)]

Khác với hàm thông thường, lambda có thể được định nghĩa “inline”. Nghĩa là khi cần sử dụng, chúng ta có thể định nghĩa hàm lambda và truyền nó đi ngay lập tức. Trong khi đó, một hàm định nghĩa với def sẽ phải sử dụng một khối code riêng. Do đó, sử dụng lambda sẽ giúp code ngắn gọn hơn.

Hạn chế của lambda

Tuy có lợi thế về sự ngắn gọn, lambda cũng có những hạn chế nhất định. Lambda function là một hàm vô danh, nghĩa là nó không có tên. Bắt buộc phải gán lambda function vào một biến để có thể sử dụng.

>>> square = lambda n: n**2
>>> help(square)
Help on function <lambda> in module __main__:

<lambda> lambda n

Lambda expression bị giới hạn chỉ trong 1 dòng code. Không thể code logic phức tạp, nhiều dòng với lambda expression. Cũng vì giới hạn này, lambda expression không sử dụng return, kết quả của tính toán trong lambda function sẽ luôn được trả về.

Vì những đặc trưng trên, lambda có những ưu/nhược điểm nhất định khi so sánh với hàm thông thường của Python.

  • Có thể định nghĩa và truyền đi luôn (ưu điểm)
  • Tự động trả về kết quả (ưu điểm)
  • Chỉ dùng 1 dòng code (ưu/nhược điểm tùy trường hợp)
  • Không có tên và không thể viết docstring (nhược điểm)
  • Cú pháp đặc biệt, khác với phần còn lại của Python (nhược điểm)

Khi nào nên sử dụng lambda

Theo PEP 8, không nên sử dụng lambda function mà được gán cho một biến. Trong trường hợp đó, nên sử dụng hàm thông thường với def.

Với hàm, lập trình viên có thể viết docstring giúp hàm dễ hiểu hơn, ví dụ:

def square(n):
    """Return the square of the given number."""
    return n**2

Mọi lambda function đều có thể thay thế bằng các hàm thông thường. Vì được định nghĩa trong một khối riêng, các hàm này (hầu hết) đều dễ đọc, dễ hiểu hơn. Tuy nhiên, lambda vẫn tồn tại ắt là có lý do.

Lambda expressions thường được sử dụng trong những trường hợp cần truyền hàm cho một hàm khác. Như trong những phần trước, lambda kết hợp hoàn hảo với sorted. Không chỉ sorted, một số hàm dựng sẵn của Python như map, filter cũng có thể dùng lambda.

>>> numbers = [2, 1, 3, 4, 7, 11, 18, 29]
>>> odds = filter(lambda n: n % 2, numbers)
>>> list(odds)
[1, 3, 7, 11, 29]
>>> squares = map(lambda n: n**2, numbers)
>>> list(squares)
[4, 1, 9, 16, 49, 121, 324, 841]

Hàm của Python cũng là một object, và có thể truyền vào một hàm khác được. Nhưng sử dụng lambda trong trường hợp này sẽ cho code ngắn gọn và vẫn đảm bảo tính dễ đọc, dễ hiểu. Lưu ý rằng, khi sử dụng lambda, tham số thường được đặt tên rất ngắn gọn mới đảm bảo tính dễ đọc, dễ hiểu. Với lambda, nếu dùng nhiều tham số hoặc đặt tên tham số đầy đủ như hàm thông thường nhiều khi lại phản tác dụng, khiến nó khó đọc hơn.

Không có một công thức “chuẩn” nào mô tả trường hợp nào thì nên dùng lambda, trường hợp nào không. Nói chung, việc sử dụng lambda mang tính cá nhân là chủ yếu. Sự khác biệt chủ yếu đến từ hình thức, cách trình bày code. Về mặt hiệu năng, không có sự khác biệt giữa hai cách làm này. Cá nhân tôi khi gặp những tình huống tương tự như trên, tôi sẽ chọn lambda expression.

Tôi xin lỗi nếu bài viết có bất kỳ typo nào. Nếu bạn nhận thấy điều gì bất thường, xin hãy cho tôi biết.

Nếu có bất điều gì muốn nói, bạn có thể liên hệ với tôi qua các mạng xã hội, tạo discussion hoặc report issue trên Github.