2012-05-04 32 views
33

Các mã sau spits ra 1 hai lần, tôi mong đợi để xem 0 và sau đó 1Python lambda của ràng buộc với các giá trị địa phương

def pv(v) : 
    print v 


def test() : 
    value = [] 
    value.append(0) 
    value.append(1) 
    x=[] 
    for v in value : 
    x.append(lambda : pv(v)) 
    return x 

x = test() 
for xx in x: 
    xx() 

tôi mong đợi lambdas python để ràng buộc vào tham khảo một biến địa phương được trỏ đến, đằng sau bối cảnh. Tuy nhiên điều đó dường như không đúng. Tôi đã gặp phải vấn đề này trong một hệ thống lớn, nơi lambda đang làm equavalent C++ hiện đại của một ràng buộc ('boost :: bind' chẳng hạn) trong trường hợp này bạn sẽ liên kết với một ptr thông minh hoặc bản sao contstruct một bản sao cho lambda.

Vì vậy, làm cách nào để liên kết biến cục bộ với hàm lambda và nó giữ nguyên tham chiếu chính xác khi được sử dụng? Tôi khá gobsmacked với hành vi vì tôi sẽ không mong đợi điều này từ một ngôn ngữ với một bộ thu rác.

Mã trong câu hỏi trông như sau (l3_e là biến mà gây ra vấn đề):

for category in cat : 
     for l2 in cat[category].entries : 
     for l3 in cat[category].entries[l2].entry["sub_entries"] : 
      l3_e = cat[category].entries[l2].entry["sub_entries"][l3] 
      url = "http://forums.heroesofnewerth.com/" + l3_e.entry["url"] 
      self.l4_processing_status[l3_e] = 0 
      l3_discovery_requests.append(Request(
      url, callback = lambda response : self.parse_l4(response,l3_e))) 
      print l3_e.entry["url"] 
    return l3_discovery_requests 
+2

Tôi đã nhìn thấy một số biến thể của câu hỏi này. ai đó cần tổng hợp tất cả, thay đổi tiêu đề thành nội dung đáng nhớ và sau đó thông báo cho google biết rằng – Shep

+1

ah, thông tin cho bạn, gần trùng lặp: [lexical-closures-in-python] (http://stackoverflow.com/q/233673/915501) – Shep

+0

Ngoài ra, đoạn mã đầu tiên minh họa điểm của bạn một cách hoàn hảo, tại sao lại dán vào đoạn thứ hai này? – Shep

Trả lời

59

Thay đổi x.append(lambda : pv(v)) để x.append(lambda v=v: pv(v)).

Bạn mong đợi "lambbas python liên kết với tham chiếu biến cục bộ trỏ tới, đằng sau cảnh", nhưng đó không phải là cách Python hoạt động. Python tra cứu tên biến tại thời điểm hàm được gọi, không phải khi nó được tạo. Sử dụng đối số mặc định hoạt động vì đối số mặc định được đánh giá khi hàm được tạo, không phải khi hàm được gọi.

Đây không phải là điều đặc biệt về lambdas. Xem xét:

x = "before foo defined" 
def foo(): 
    print x 
x = "after foo was defined" 
foo() 

in

after foo was defined 
+0

thú vị, bạn có thể xây dựng trên trực giác của cơ học xin vui lòng. –

+1

Một lựa chọn rõ ràng hơn là sử dụng 'functools.partial' hoặc sử dụng một hàm trợ giúp riêng biệt để tạo các đóng (' def make_closure (v): return lambda: pv (v) ', bạn có thể đặt nó vào' test'). – delnan

+0

vì vậy mã thực sự của tôi nên như 'lambda par1, par2 = l3_e: self.parse_l4 (par1, par2)'? –

12

đóng cửa của lambda giữ một tham chiếu đến biến đang được sử dụng, không phải giá trị của nó, vì vậy nếu giá trị của sau những thay đổi biến, giá trị trong việc đóng cửa cũng thay đổi . Nghĩa là giá trị của biến đóng được giải quyết khi hàm được gọi, không phải khi nó được tạo. (Hành vi Python ở đây không phải là bất thường trong thế giới lập trình chức năng, cho những gì nó có giá trị.)

Có hai giải pháp:

  1. Sử dụng một đối số mặc định, ràng buộc giá trị hiện tại của biến đến một địa phương tên tại thời điểm định nghĩa. lambda v=v: pv(v)

  2. Sử dụng lambda đôi và ngay lập tức gọi cuộc gọi đầu tiên. (lambda v: lambda: pv(v))(v)

Các vấn đề liên quan