2016-02-03 21 views
5

Tôi gặp sự cố khi tôi cần chuyển chỉ mục của mảng tới hàm mà tôi xác định nội tuyến. Hàm này sau đó được truyền vào như một tham số cho một hàm khác mà cuối cùng sẽ gọi nó là một hàm gọi lại.Hành vi phân luồng lẻ trong python

Vấn đề là khi mã được gọi, giá trị của chỉ mục là sai. Cuối cùng tôi đã giải quyết điều này bằng cách tạo ra một giải pháp xấu xí nhưng tôi quan tâm đến việc hiểu những gì đang xảy ra ở đây. Tôi tạo ra một ví dụ nhỏ để chứng minh vấn đề:

from __future__ import print_function 
import threading 


def works_as_expected(): 
    for i in range(10): 
     run_in_thread(lambda: print('the number is: {}'.format(i))) 

def not_as_expected(): 
    for i in range(10): 
     run_later_in_thread(lambda: print('the number is: {}'.format(i))) 

def run_in_thread(f): 
    threading.Thread(target=f).start() 

threads_to_run_later = [] 
def run_later_in_thread(f): 
    threads_to_run_later.append(threading.Thread(target=f)) 


print('this works as expected:\n') 
works_as_expected() 

print('\nthis does not work as expected:\n') 
not_as_expected() 
for t in threads_to_run_later: t.start() 

Đây là kết quả:

this works as expected: 

the number is: 0 
the number is: 1 
the number is: 2 
the number is: 3 
the number is: 4 
the number is: 6 
the number is: 7 
the number is: 7 
the number is: 8 
the number is: 9 

this does not work as expected: 

the number is: 9 
the number is: 9 
the number is: 9 
the number is: 9 
the number is: 9 
the number is: 9 
the number is: 9 
the number is: 9 
the number is: 9 
the number is: 9 

Ai đó có thể giải thích những gì đang xảy ra ở đây? Tôi cho rằng nó có liên quan đến phạm vi bao quanh hoặc một thứ gì đó, nhưng một câu trả lời với một tham chiếu giải thích góc tối (đối với tôi) của phạm vi python sẽ có giá trị đối với tôi.

Tôi đang chạy mã này trên trăn 2.7.11

+1

Tôi khuyên bạn nên kích hoạt trình gỡ rối [pdb] (https://docs.python.org/2/library/pdb.html) sẽ đủ, xóa phần làm việc như mong đợi của mã và chỉ cần thực hiện từng bước một , nó sẽ làm cho nó rõ ràng cho bạn rất nhanh về những gì là chính xác xảy ra –

+0

@ TymoteuszPaul Tôi nghĩ rằng bạn đang hiểu lầm điểm của câu hỏi của tôi. Không phải là tôi không hiểu mã đang làm gì. Tôi không hiểu tại sao nó lại làm. Tôi đang tìm một câu trả lời giúp tôi hiểu ngôn ngữ * thực sự hoạt động như thế nào để tôi có thể giải thích tốt hơn về mã. Cảm ơn lời đề nghị mặc dù, tôi khá thích pdb. – Stephen

Trả lời

2

Đây là kết quả của cách đóng và phạm vi hoạt động trong python.

Điều đang xảy ra là i bị ràng buộc trong phạm vi chức năng not_as_expected. Vì vậy, mặc dù bạn đang cung cấp chức năng lambda cho chủ đề, biến mà nó đang sử dụng đang được chia sẻ giữa mỗi lambda và mỗi chuỗi.

Hãy xem xét ví dụ sau:

def make_function(): 
    i = 1 
    def inside_function(): 
     print i 
    i = 2 
    return inside_function 

f = make_function() 
f() 

số gì làm bạn nghĩ rằng nó sẽ in? i = 1 trước khi chức năng được xác định hoặc sau i = 2?

Nó sẽ in giá trị hiện tại của i (ví dụ: 2). Không quan trọng giá trị của i là khi chức năng được thực hiện, nó sẽ luôn sử dụng giá trị hiện tại. Điều tương tự cũng xảy ra với các hàm lambda của bạn.

Ngay cả trong kết quả mong đợi của bạn bạn có thể nhìn thấy nó không luôn luôn làm việc đúng, nó bỏ qua 5 và hiển thị 7 hai lần. Điều xảy ra trong trường hợp đó là mỗi lambda là thường là chạy trước khi vòng lặp được lặp lại tiếp theo. Nhưng trong một số trường hợp (như 5) vòng lặp quản lý để có được thông qua hai lần lặp trước khi điều khiển được chuyển đến một trong các chủ đề khác, và số tăng i hai lần và một số bị bỏ qua. Trong các trường hợp khác (như 7) hai luồng quản lý để chạy trong khi vòng lặp vẫn trong cùng một lần lặp và vì i không thay đổi giữa hai luồng, cùng một giá trị được in.

Nếu bạn thay vì làm điều này:

def function_maker(i): 
    return lambda: print('the number is: {}'.format(i)) 

def not_as_expected(): 
    for i in range(10): 
     run_later_in_thread(function_maker(i)) 

Biến i bị ràng buộc bên trong function_maker cùng với lambda chức năng. Mỗi hàm lambda sẽ tham chiếu một biến khác, và nó sẽ hoạt động như mong đợi.

+0

Tuyệt vời. Tôi thậm chí không nhận thấy rằng * kết quả mong đợi * có sự bất thường '5' và' 7'. – Stephen

1

Việc đóng bằng Python chụp các biến số miễn phí, không phải giá trị hiện tại của chúng tại thời điểm tạo kết thúc. Ví dụ:

def make_closures(): 
    L = [] 

    # Captures variable L 
    def push(x): 
     L.append(x) 
     return len(L) 

    # Captures the same variable 
    def pop(): 
     return L.pop() 

    return push, pop 

pushA, popA = make_closures() 
pushB, popB = make_closures() 

pushA(10); pushB(20); pushA(30); pushB(40) 
print(popA(), popA(), popB(), popB()) 

sẽ hiển thị 30, 10, 40, 20: điều này xảy ra bởi vì các cặp đầu tiên của việc đóng cửa pushA, popA sẽ đề cập đến một danh sách L và cặp thứ hai pushB, popB sẽ đề cập đến một danh sách độc lập .

Điều quan trọng là trong mỗi cặp pushpop đóng cửa tham khảo danh sách tương tự, tức là họ chiếm được biếnL và không phải là giá trị của L tại thời điểm tạo ra. Nếu L bị đột biến bởi một đóng, người kia sẽ thấy các thay đổi.

Một sai lầm phổ biến là ví dụ để hy vọng rằng

L = [] 
for i in range(10): 
    L.append(lambda : i) 
for x in L: 
    print(x()) 

sẽ hiển thị số từ 0 đến 9 ... tất cả việc đóng cửa giấu tên ở đây bị bắt cùng một biến i sử dụng để lặp và tất cả trong số họ sẽ trả về cùng một giá trị khi được gọi.

Các Python chung thành ngữ để giải quyết vấn đề này là

L.append(lambda i=i: i) 

ví dụ: sử dụng thực tế là giá trị mặc định cho các thông số được đánh giá đồng thời các chức năng được tạo ra. Với cách tiếp cận này, mỗi đóng sẽ trả về một giá trị khác vì chúng trả về biến cục bộ riêng của chúng (một tham số có giá trị mặc định).

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