Bạn có thể giải thích các bao đóng (vì chúng liên quan đến Python) không?


83

Tôi đã đọc rất nhiều về sự đóng cửa và tôi nghĩ rằng tôi hiểu chúng, nhưng không che giấu bức tranh cho bản thân và những người khác, tôi hy vọng ai đó có thể giải thích sự đóng cửa ngắn gọn và rõ ràng nhất có thể. Tôi đang tìm kiếm một lời giải thích đơn giản có thể giúp tôi hiểu vị trí và lý do tôi muốn sử dụng chúng.

Câu trả lời:


95

Đóng cửa khi đóng cửa

Đối tượng là dữ liệu với các phương thức được đính kèm, các bao đóng là các hàm với dữ liệu được đính kèm.

def make_counter():
    i = 0
    def counter(): # counter() is a closure
        nonlocal i
        i += 1
        return i
    return counter

c1 = make_counter()
c2 = make_counter()

print (c1(), c1(), c2(), c2())
# -> 1 2 1 2

6
Lưu ý rằng nonlocalđã được bổ sung trong python 3, python 2.x không có đầy đủ-on, read-write đóng cửa (tức là bạn có thể đọc đóng qua các biến, nhưng không thay đổi giá trị của họ)
James Porter

6
@JamesPorter: lưu ý: bạn có thể mô phỏng nonlocaltừ khóa trong Python 2 bằng cách sử dụng một đối tượng có thể thay đổi, ví dụ: L = [0] \n def counter(): L[0] += 1; return L[0]bạn không thể thay đổi tên (liên kết nó với một đối tượng khác) trong trường hợp này nhưng bạn có thể thay đổi chính đối tượng có thể thay đổi mà tên đó tham chiếu đến. Danh sách này là bắt buộc vì các số nguyên là bất biến trong Python.
jfs 21/09/13

1
@JFSebastian: đúng. mà luôn cảm thấy như một bẩn, bẩn Hack dù :)
James Porter

46

Thật đơn giản: Một hàm tham chiếu đến các biến từ một phạm vi chứa, có khả năng sau khi luồng điều khiển đã rời khỏi phạm vi đó. Phần cuối cùng rất hữu ích:

>>> def makeConstantAdder(x):
...     constant = x
...     def adder(y):
...         return y + constant
...     return adder
... 
>>> f = makeConstantAdder(12)
>>> f(3)
15
>>> g = makeConstantAdder(4)
>>> g(3)
7

Lưu ý rằng 12 và 4 đã lần lượt "biến mất" bên trong f và g, đặc điểm này là thứ khiến f và g đóng đúng cách.


Không cần phải làm constant = x; bạn chỉ có thể thực hiện return y + xtrong hàm lồng nhau (hoặc nhận đối số với tên constant), và nó sẽ hoạt động tốt; các đối số được nắm bắt bởi sự đóng cửa không khác gì so với các đối số địa phương không đối số.
ShadowRanger

15

Tôi thích định nghĩa thô sơ, ngắn gọn này :

Một hàm có thể tham chiếu đến các môi trường không còn hoạt động.

Tôi muốn thêm

Một bao đóng cho phép bạn liên kết các biến vào một hàm mà không cần chuyển chúng dưới dạng tham số .

Trình trang trí chấp nhận các tham số là cách sử dụng phổ biến cho các bao đóng. Đóng cửa là một cơ chế thực hiện phổ biến cho loại "nhà máy chức năng". Tôi thường chọn sử dụng các kết thúc trong Mô hình Chiến lược khi chiến lược được dữ liệu sửa đổi tại thời điểm chạy.

Trong một ngôn ngữ cho phép định nghĩa khối ẩn danh - ví dụ: Ruby, C # - các bao đóng có thể được sử dụng để triển khai (số lượng bao nhiêu) các cấu trúc điều khiển mới. Việc thiếu các khối ẩn danh là một trong những hạn chế của các bao đóng trong Python .


15

Thành thật mà nói, tôi hiểu rất rõ về sự đóng cửa ngoại trừ việc tôi chưa bao giờ rõ ràng về thứ chính xác là "sự đóng cửa" và cái gì là "sự đóng cửa" về nó. Tôi khuyên bạn nên từ bỏ việc tìm kiếm bất kỳ logic nào đằng sau việc lựa chọn thuật ngữ.

Dù sao, đây là lời giải thích của tôi:

def foo():
   x = 3
   def bar():
      print x
   x = 5
   return bar

bar = foo()
bar()   # print 5

Một ý tưởng chính ở đây là đối tượng hàm được trả về từ foo vẫn giữ một hook đối với var 'x' cục bộ ngay cả khi 'x' đã vượt ra khỏi phạm vi và không còn tồn tại. Cái móc này là của chính var, không chỉ giá trị mà var có vào thời điểm đó, vì vậy khi thanh được gọi, nó sẽ in ra 5 chứ không phải 3.

Cũng cần nói rõ rằng Python 2.x có giới hạn đóng: không có cách nào tôi có thể sửa đổi 'x' bên trong 'bar' bởi vì viết 'x = bla' sẽ khai báo một 'x' cục bộ trong thanh chứ không phải gán cho 'x' của foo . Đây là một tác dụng phụ của việc khai báo gán = của Python. Để giải quyết vấn đề này, Python 3.0 giới thiệu từ khóa phi địa phương:

def foo():
   x = 3
   def bar():
      print x
   def ack():
      nonlocal x
      x = 7
   x = 5
   return (bar, ack)

bar, ack = foo()
ack()   # modify x of the call to foo
bar()   # print 7

7

Tôi chưa bao giờ nghe nói về các giao dịch được sử dụng trong cùng ngữ cảnh giải thích việc đóng là gì và thực sự không có bất kỳ ngữ nghĩa giao dịch nào ở đây.

Nó được gọi là bao đóng vì nó "đóng trên" biến bên ngoài (hằng số) - tức là, nó không chỉ là một hàm mà còn là một vùng bao quanh môi trường nơi hàm được tạo ra.

Trong ví dụ sau, việc gọi bao đóng g sau khi thay đổi x cũng sẽ thay đổi giá trị của x trong g, vì g đóng trên x:

x = 0

def f():
    def g(): 
        return x * 2
    return g


closure = f()
print(closure()) # 0
x = 2
print(closure()) # 4

Ngoài ra, khi nó đứng, g()tính toán x * 2nhưng không trả về bất kỳ thứ gì. Điều đó nên được return x * 2. Tuy nhiên, +1 để giải thích cho từ "đóng cửa".
Bruno Le Floch

3

Đây là một trường hợp sử dụng điển hình cho các bao đóng - gọi lại cho các phần tử GUI (đây sẽ là một sự thay thế cho việc phân lớp con của lớp nút). Ví dụ: bạn có thể xây dựng một hàm sẽ được gọi để đáp ứng với một lần nhấn nút và "đóng" trên các biến có liên quan trong phạm vi chính cần thiết để xử lý lần nhấp. Bằng cách này, bạn có thể kết nối các giao diện khá phức tạp từ cùng một hàm khởi tạo, xây dựng tất cả các phụ thuộc vào bao đóng.


2

Trong Python, một bao đóng là một ví dụ của một hàm có các biến liên kết với nó là bất biến.

Trên thực tế, mô hình dữ liệu giải thích điều này trong phần mô tả __closure__thuộc tính của các hàm :

Không có hoặc nhiều ô chứa liên kết cho các biến tự do của hàm. Chỉ đọc

Để chứng minh điều này:

def enclosure(foo):
    def closure(bar):
        print(foo, bar)
    return closure

closure_instance = enclosure('foo')

Rõ ràng, chúng ta biết rằng bây giờ chúng ta có một hàm được trỏ đến từ tên biến closure_instance. Rõ ràng, nếu chúng ta gọi nó bằng một đối tượng, barthì nó sẽ in ra chuỗi 'foo'và bất kể biểu thức chuỗi barlà gì.

Trên thực tế, chuỗi 'foo' được liên kết với phiên bản của hàm và chúng ta có thể đọc trực tiếp nó tại đây, bằng cách truy cập cell_contentsthuộc tính của ô đầu tiên (và duy nhất) trong bộ của __closure__thuộc tính:

>>> closure_instance.__closure__[0].cell_contents
'foo'

Ngoài ra, các đối tượng ô được mô tả trong tài liệu API C:

Đối tượng "ô" được sử dụng để triển khai các biến được tham chiếu bởi nhiều phạm vi

Và chúng tôi có thể chứng minh cách sử dụng của hàm đóng, lưu ý rằng 'foo'nó bị mắc kẹt trong hàm và không thay đổi:

>>> closure_instance('bar')
foo bar
>>> closure_instance('baz')
foo baz
>>> closure_instance('quux')
foo quux

Và không gì có thể thay đổi nó:

>>> closure_instance.__closure__ = None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute

Chức năng một phần

Ví dụ được đưa ra sử dụng hàm đóng làm một phần, nhưng nếu đây là mục tiêu duy nhất của chúng ta, thì mục tiêu tương tự có thể được thực hiện với functools.partial

>>> from __future__ import print_function # use this if you're in Python 2.
>>> partial_function = functools.partial(print, 'foo')
>>> partial_function('bar')
foo bar
>>> partial_function('baz')
foo baz
>>> partial_function('quux')
foo quux

Có nhiều cách đóng phức tạp hơn sẽ không phù hợp với ví dụ hàm một phần và tôi sẽ trình bày thêm khi thời gian cho phép.


2
# A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory.

# Defining a closure

# This is an outer function.
def outer_function(message):
    # This is an inner nested function.
    def inner_function():
        print(message)
    return inner_function

# Now lets call the outer function and return value bound to name 'temp'
temp = outer_function("Hello")
# On calling temp, 'message' will be still be remembered although we had finished executing outer_function()
temp()
# Technique by which some data('message') that remembers values in enclosing scopes 
# even if they are not present in memory is called closures

# Output: Hello

Các tiêu chí để Closures đáp ứng là:

  1. Chúng ta phải có hàm lồng nhau.
  2. Hàm lồng nhau phải tham chiếu đến giá trị được xác định trong hàm bao quanh.
  3. Hàm bao gồm phải trả về hàm lồng nhau.

# Example 2
def make_multiplier_of(n): # Outer function
    def multiplier(x): # Inner nested function
        return x * n
    return multiplier
# Multiplier of 3
times3 = make_multiplier_of(3)
# Multiplier of 5
times5 = make_multiplier_of(5)
print(times5(3)) # 15
print(times3(2)) #  6

1

Đây là một ví dụ về các bao đóng Python3

def closure(x):
    def counter():
        nonlocal x
        x += 1
        return x
    return counter;

counter1 = closure(100);
counter2 = closure(200);

print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 2 " + str(counter2()))
print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 2 " + str(counter2()))

# result

i from closure 1 101
i from closure 1 102
i from closure 2 201
i from closure 1 103
i from closure 1 104
i from closure 1 105
i from closure 2 202

0

Đối với tôi, "bao đóng" là những hàm có khả năng ghi nhớ môi trường mà chúng được tạo ra. Chức năng này, cho phép bạn sử dụng các biến hoặc phương thức trong trình đóng, theo cách khác, bạn sẽ không thể sử dụng vì chúng không còn tồn tại nữa hoặc chúng nằm ngoài tầm với do phạm vi. Hãy xem mã này bằng ruby:

def makefunction (x)
  def multiply (a,b)
    puts a*b
  end
  return lambda {|n| multiply(n,x)} # => returning a closure
end

func = makefunction(2) # => we capture the closure
func.call(6)    # => Result equal "12"  

nó hoạt động ngay cả khi cả hai, phương thức "nhân" và biến "x", không còn tồn tại. Tất cả chỉ vì khả năng đóng cửa để ghi nhớ.


0

tất cả chúng ta đã sử dụng Decorator trong python. Chúng là những ví dụ tuyệt vời để hiển thị các hàm đóng trong python là gì.

class Test():
    def decorator(func):
        def wrapper(*args):
            b = args[1] + 5
            return func(b)
        return wrapper

@decorator
def foo(val):
    print val + 2

obj = Test()
obj.foo(5)

ở đây giá trị cuối cùng là 12

Ở đây, hàm wrapper có thể truy cập đối tượng func vì wrapper là "bao đóng từ vựng", nó có thể truy cập các thuộc tính cha của nó. Đó là lý do tại sao, nó có thể truy cập đối tượng func.


0

Tôi muốn chia sẻ ví dụ của tôi và giải thích về việc đóng cửa. Tôi đã tạo một ví dụ về python và hai hình để minh họa các trạng thái ngăn xếp.

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n’ * margin_top, a * n, 
            ' ‘ * padding, msg, ' ‘ * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)
…
f('hello')
g(‘good bye!')

Đầu ra của mã này sẽ như sau:

*****      hello      #####

      good bye!    ♥♥♥

Đây là hai hình để hiển thị ngăn xếp và bao đóng được gắn với đối tượng hàm.

khi hàm được trả về từ nhà sản xuất

khi hàm được gọi sau

Khi hàm được gọi thông qua một tham số hoặc một biến phi địa phương, mã cần các ràng buộc biến cục bộ như margin_top, padding cũng như a, b, n. Để đảm bảo mã hàm hoạt động, nên có thể truy cập khung ngăn xếp của hàm maker đã biến mất từ ​​lâu, khung này được sao lưu trong bao đóng mà chúng ta có thể tìm thấy cùng với đối tượng hàm của 'message.


-2

Lời giải thích tốt nhất mà tôi từng thấy về việc đóng cửa là giải thích cơ chế. Nó đã đi một cái gì đó như thế này:

Hãy tưởng tượng ngăn xếp chương trình của bạn như một cây suy biến trong đó mỗi nút chỉ có một nút con và nút lá đơn là bối cảnh của thủ tục hiện đang thực thi của bạn.

Bây giờ hãy nới lỏng ràng buộc rằng mỗi nút chỉ có thể có một nút con.

Nếu bạn làm điều này, bạn có thể có một cấu trúc ('output') có thể trả về từ một thủ tục mà không loại bỏ ngữ cảnh cục bộ (tức là nó không bật nó ra khỏi ngăn xếp khi bạn quay lại). Lần tiếp theo thủ tục được gọi, lời gọi sẽ lấy khung (cây) ngăn xếp cũ và tiếp tục thực hiện ở nơi nó đã dừng.


Đó KHÔNG phải là lời giải thích về việc đóng cửa.
Jules

Bạn đang mô tả sự liên tục, không phải sự đóng lại.
Matthew Olenik
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.