2011-01-02 26 views
7

Tôi đang cố gắng xây dựng một máy tính với PyQt4 và kết nối các tín hiệu 'nhấp()' từ các nút không như dự kiến. Im tạo các nút của tôi cho các số bên trong vòng lặp for nơi tôi cố gắng kết nối chúng sau đó.Kết nối các khe và tín hiệu trong PyQt4 trong vòng lặp

def __init__(self):  
    for i in range(0,10): 
     self._numberButtons += [QPushButton(str(i), self)] 
     self.connect(self._numberButtons[i], SIGNAL('clicked()'), lambda : self._number(i)) 

def _number(self, x): 
    print(x) 

Khi tôi nhấp vào tất cả các nút in ra '9'. Tại sao vậy và làm thế nào tôi có thể sửa lỗi này?

Trả lời

12

Đây chỉ là cách phạm vi, tra cứu tên và đóng cửa được xác định bằng Python.

Python chỉ giới thiệu các ràng buộc mới trong không gian tên thông qua gán và thông qua danh sách tham số của hàm. Do đó, i không thực sự được xác định trong không gian tên của lambda, nhưng trong không gian tên là __init__(). Tra cứu tên cho i trong lambda do đó kết thúc trong không gian tên __init__(), trong đó i cuối cùng bị ràng buộc là 9. Điều này được gọi là "đóng cửa".

Bạn có thể làm việc xung quanh những ngữ nghĩa được thừa nhận không thực sự trực quan (nhưng được xác định rõ) bằng cách chuyển i làm đối số từ khóa với giá trị mặc định. Như đã nói, tên trong danh sách tham số giới thiệu các ràng buộc mới trong không gian tên địa phương, vì vậy i bên trong lambda sau đó trở thành độc lập từ i trong .__init__():

self._numberButtons[i].clicked.connect(lambda i=i: self._number(i)) 

Một dễ đọc hơn, ít ma thuật khác là functools.partial:

self._numberButtons[i].clicked.connect(partial(self._number, i)) 

Tôi đang sử dụng cú pháp tín hiệu và vị trí kiểu mới ở đây chỉ để thuận tiện, cú pháp kiểu cũ hoạt động giống nhau.

+0

Sử dụng 'functools.partial' cho điều này là một ý tưởng rất hay. +1 – delnan

+0

Cảm ơn bạn. Tôi sẽ đi với giải pháp functools.partial. – lukad

2

Bạn đang tạo bao đóng. Đóng cửa thực sự nắm bắt một biến, không phải là giá trị của một biến. Ở cuối __init__, i là phần tử cuối cùng của range(0, 10), tức là 9. Tất cả các lambda bạn tạo ra trong phạm vi này tham chiếu đến i này và chỉ khi chúng được gọi, chúng nhận được giá trị i tại thời điểm chúng được viện dẫn (tuy nhiên, yêu cầu riêng biệt của __init__ tạo lambdas đề cập đến các biến riêng biệt!).

Có hai cách phổ biến để tránh điều này:

  1. Sử dụng một tham số mặc định: lambda i=i: self._number(i). Công việc này bởi vì các tham số mặc định ràng buộc một giá trị tại thời gian định nghĩa hàm.
  2. Xác định chức năng trợ giúp helper = lambda i: (lambda: self._number(i)) và sử dụng helper(i) trong vòng lặp. Điều này hoạt động bởi vì "bên ngoài" i được đánh giá tại thời điểm i bị ràng buộc và - như đã đề cập trước đó - lần đóng cửa tiếp theo được tạo trong lần tiếp theo helper sẽ tham chiếu đến một biến khác.
0

Sử dụng cách Qt thay vào đó, sử dụng QSignalMapper.

+1

Xin đừng. 'QSignalMapper' là một phần còn lại của C++ 98, không có hàm lambdas hoặc một phần và cách giải quyết như vậy là một điều ác cần thiết.Trong Python tuy nhiên 'QSignalMapper' chỉ đơn giản là thừa và thực sự cồng kềnh so với' functools.partial() 'hoặc lambda. Các giải pháp với 'partial()' hoặc 'lambda' ngắn hơn và thanh lịch hơn' QSignalMapper'. – lunaryorn

+0

Cũng tốt về sự lựa chọn, tôi sẽ chọn 'QSignalMapper' cho khả năng tương thích Qt/C++. – ismail

+1

Tất nhiên đó là về sự lựa chọn, nhưng tôi chỉ đơn giản là không hiểu sự lựa chọn của bạn và tôi không thể làm theo nó. Tôi đang sử dụng Python cho các ứng dụng Qt chính xác vì nó * không * C++, và nếu tôi từ bỏ tất cả các tính năng đặc biệt của Python để duy trì hoặc để đạt được khả năng tương thích C++, tôi cũng có thể sử dụng C++;) – lunaryorn

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