0
0
Lập trình
TT

Hiểu Rõ Về Late Binding Trong Closures Của Python

Đăng vào 6 ngày trước

• 6 phút đọc

Hiểu Rõ Về Late Binding Trong Closures Của Python

Trong Python, một hàm không chỉ đơn thuần là danh sách các lệnh như chúng ta đã học trong Koan 5. Khi một hàm được định nghĩa bên trong một hàm khác, nó trở thành một closure, một đoạn mã nhỏ ghi nhớ môi trường bên ngoài của nó. Bộ nhớ này rất mạnh mẽ, nhưng không phải là một bức tranh của quá khứ. Nó giống như một cửa sổ, cho bạn thấy thế giới như nó đang tồn tại ngay bây giờ, không phải như nó đã như thế nào khi cửa sổ được mở lần đầu. Hiện tượng này được gọi là late binding.

Mục Lục

  1. Giới thiệu về Closure
  2. Ba Lệnh Của Bậc Thầy
  3. Người Thư Pháp Chăm Chỉ
  4. Người Thư Pháp Vô Danh
  5. Ghi Chú Cẩn Thận Của Bậc Thầy
  6. Cây Cọ Tùy Chỉnh
  7. Kết Luận

Giới thiệu về Closure

Hãy bắt đầu bằng cách quan sát biểu thức đơn giản nhất của một closure. Dưới đây là ví dụ về một hàm closure:

python Copy
def make_commission():
    item = "sun"
    def execute_commission():
        return item
    return execute_commission

draw_sun = make_commission()
print(draw_sun())  # Kết quả: sun

Ở đây, execute_commission là closure. Nó ghi nhớ biến item từ hàm cha make_commission. Khi chúng ta gọi draw_sun(), nó trả về "sun" như mong đợi. Closure có một bộ nhớ rõ ràng và duy nhất về mục đích của nó.

Ba Lệnh Của Bậc Thầy

Bây giờ, hãy quan sát kỹ thuật của người thư pháp lười biếng, như đã mô tả trong koan. Ông đợi cho đến khi tất cả các lệnh được đưa ra trước khi bắt đầu công việc của mình.

Nhiều người có thể mong đợi đầu ra sẽ là "sun", "moon", và sau đó là "cloud." Tuy nhiên, đoạn mã sẽ in ra "cloud" ba lần.

Đây chính là bản chất của late binding. Hàm execute_commission không nắm bắt giá trị của instruction vào thời điểm nó được tạo ra. Thay vào đó, nó giữ một tham chiếu đến biến instruction chính nó. Đến khi chúng ta thực sự thực thi hàm đầu tiên trong danh sách, vòng lặp for đã hoàn thành, và biến instruction đã có giá trị cuối cùng: "cloud."

Tất cả ba closure tham chiếu đến cùng một biến instruction trong phạm vi bên ngoài, mà đã hoàn thành hành trình của nó.

Người Thư Pháp Chăm Chỉ

Để sửa đổi hành vi này, chúng ta cần một cách để mỗi closure nắm giữ một bản sao duy nhất của lệnh tại thời điểm nó được tạo ra. Chúng ta phải dạy người thư pháp cẩn thận và ghi nhớ từng lệnh một cách riêng biệt.

Một phương pháp để đạt được điều này là sử dụng đối số mặc định. Như chúng ta đã học trong Koan 3, đối số mặc định được đánh giá một lần, tại thời điểm hàm được định nghĩa. Chúng ta có thể sửa đổi hàm make_commissions của mình để sử dụng kỹ thuật này:

python Copy
def make_commissions():
    for instruction in ["sun", "moon", "cloud"]:
        def execute_commission(item=instruction):
            return item
        print(execute_commission())

Lần này, đầu ra là như chúng ta đã dự định ban đầu. Bằng cách đặt đối số mặc định item=instruction, chúng ta buộc Python đánh giá instruction và gán giá trị hiện tại của nó cho tham số item cho mỗi hàm mới. Mỗi closure giờ đây giữ bản sao duy nhất của lệnh, thay vì chia sẻ một tham chiếu duy nhất.

Người Thư Pháp Vô Danh

Nguyên tắc của late binding cũng áp dụng cho lambda functions. Như chúng ta đã học trong Koan 5, lambdas chỉ là các hàm vô danh. Hiệu ứng "người thư pháp lười biếng" rất phổ biến khi lambdas được tạo ra trong một vòng lặp.

Dưới đây là vấn đề gốc, viết bằng lambda:

python Copy
for instruction in ["sun", "moon", "cloud"]:
    draw = lambda: instruction
    print(draw())

Cũng giống như hàm được định nghĩa bằng def, đoạn mã này sẽ quay lại "cloud," bởi vì lambda đóng lên biến instruction, không phải giá trị của nó. Để sửa đổi điều này, chúng ta sử dụng một đối số mặc định cho hàm lambda:

python Copy
for instruction in ["sun", "moon", "cloud"]:
    draw = lambda item=instruction: item
    print(draw())

Chúng ta có thể làm điều này ngắn gọn và pythonic hơn bằng cách sử dụng list comprehension để tạo tất cả các commission trong một lần:

python Copy
[print(lambda item=instruction: item) for instruction in ["sun", "moon", "cloud"]]

Ghi Chú Cẩn Thận Của Bậc Thầy

Nếu chúng ta không thể sử dụng một đối số mặc định? Có cách nào khác để buộc người thư pháp ghi nhận lệnh ngay lập tức? Giải pháp nằm ở việc tạo ra một phạm vi mới cho mỗi lệnh, buộc mỗi closure "lắng nghe" một giọng nói độc nhất.

Chúng ta có thể đạt được điều này bằng cách tạo một hàm factory mà nhận lệnh như một đối số, do đó tạo ra một môi trường tách biệt cho mỗi closure:

python Copy
def create_executor(instruction):
    def execute_commission():
        return instruction
    return execute_commission

for instruction in ["sun", "moon", "cloud"]:
    draw = create_executor(instruction)
    print(draw())

Đoạn mã này cũng cho ra đầu ra chính xác. Ở mỗi vòng lặp, chúng ta gọi create_executor(instruction). Hàm này nhận giá trị hiện tại của instruction. Sau đó, nó tạo ra một closure mới, execute_commission, mà đóng lên item. Bởi vì item là một biến cục bộ trong create_executor, giá trị của nó bị tách biệt khỏi các thay đổi của vòng lặp bên ngoài (như chúng ta đã học trong Koan 4 về quy tắc LEGB của Python). Mỗi lần gọi create_executor tạo ra một bộ nhớ riêng biệt cho closure mà nó trả về. Giống như việc bậc thầy đã ghi một ghi chú nhỏ, cụ thể cho mỗi bức tranh, mà người thư pháp không thể quên.

Cây Cọ Tùy Chỉnh

Có những cách khác ngắn gọn hơn hoặc chức năng hơn để đạt được cùng một kết quả. Lựa chọn phương pháp thường phụ thuộc vào sự rõ ràng và phong cách mà bạn ưa thích. Chúng ta có thể sử dụng functools.partial để "đặt trước" đối số cho một hàm. Điều này giống như việc chuẩn bị một cây cọ đặc biệt cho mỗi nhiệm vụ, với lệnh đã được khắc vào tay cầm của nó.

Kết Luận

Giáo lý của Bậc Thầy rất đơn giản: người thư pháp không nhớ những gì đã từng nói, chỉ nhớ lệnh cuối cùng được nói ra khi bức tranh cuối cùng bắt đầu.

Trong mã của bạn, các hàm giống như người thư pháp hay quên. Closure không nhớ những giá trị mà biến đã giữ khi nó được tạo ra, chỉ có giá trị cuối cùng được nhắc lại khi hàm được chạy.

Cảm ơn bạn đã đọc Python Koans! Nếu bạn thích bài viết này, hãy xem xét việc chia sẻ với bạn bè hoặc đăng ký phía dưới:

Python Koans | Vivis Dev | Substack

Bài học Python được gói gọn trong các koan. Những câu đố nhỏ, những chân lý sâu sắc. Không phải là một chuỗi hướng dẫn thông thường. Nhấp vào để đọc Python Koans, bởi Vivis Dev, một ấn phẩm Substack với hàng trăm người đăng ký.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào