2011-07-02 32 views
37

Có vẻ như tôi tình cờ gặp một địa ngục metaclass ngay cả khi tôi không muốn làm gì với nó.Ba thừa kế gây ra xung đột metaclass ... Đôi khi

Tôi đang viết một ứng dụng bằng Qt4 bằng PySide. Tôi muốn tách riêng phần sự kiện theo định nghĩa giao diện người dùng, được tạo ra từ các tệp thiết kế Qt. Do đó tôi tạo ra một "bộ điều khiển" các lớp học, nhưng để giảm bớt cuộc sống của tôi tôi nhiều kế thừa chúng anyways. Ví dụ:

class BaseController(QObject): 
    def setupEvents(self, parent): 
     self.window = parent 

class MainController(BaseController): 
    pass 

class MainWindow(QMainWindow, Ui_MainWindow, MainController): 
    def __init__(self, parent=None): 
     super(MainWindow, self).__init__(parent) 

     self.setupUi(self) 
     self.setupEvents(self) 

Điều này hoạt động như mong đợi. Nó cũng có thừa kế từ (QDialog, Ui_Dialog, BaseController). Nhưng khi tôi phân lớp BaseController và cố gắng kế thừa từ lớp con nói (ở vị trí của BaseController), tôi nhận được một lỗi:

TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Làm rõ: Cả QMainWindowQDialog kế thừa từ QObject. BaseController cũng phải kế thừa từ nó vì tính chất đặc thù của hệ thống sự kiện Qt. Các lớp Ui_ chỉ kế thừa từ lớp đối tượng Python đơn giản. Tôi đã tìm kiếm các giải pháp, nhưng tất cả chúng đều liên quan đến các trường hợp cố ý sử dụng metaclasses. Vì vậy, tôi phải làm điều gì đó khủng khiếp sai.

EDIT: Mô tả của tôi có thể rõ ràng hơn bằng cách thêm biểu đồ.

dụ làm việc:

QObject 
|  \___________________ 
|   object  | 
QMainWindow  |   BaseController 
|  /---Ui_MainWindow | 
|  |     MainController 
MainWindow-----------------/ 

Một ví dụ làm việc: ví dụ

QObject 
|  \___________________ 
|   object  | 
QDialog   |   BaseController 
|  /---Ui_OtherWindow | 
|  |     | 
OtherWindow----------------/ 

Không làm việc:

QObject 
|  \___________________ 
|   object  | 
QDialog   |   BaseController 
|  /---Ui_OtherWindow | 
|  |     OtherController 
OtherWindow----------------/ 
+0

Tôi không giỏi về metaclasses Python, nhưng tôi nghĩ * vấn đề có thể là khi sắp xếp các lớp cha trong định nghĩa lớp 'MainWindow' của bạn. Chỉ cần đoán thôi. – Tony

+0

Điều làm tôi bối rối nhất là MainWindow hoạt động, trong khi đặt QDialog, Ui_Dialog và controller trong cùng một trình tự: lớp kế thừa từ QObject, lớp kế thừa từ đối tượng, lớp kế thừa từ QObject - vì một số lý do không thành công. – Red

+0

Tôi không biết về python, nhưng trong C++/Qt, nhiều thừa kế từ QObject bị nghiêm cấm. Tôi tự hỏi nếu bạn đang gặp phải vấn đề tương tự, và nó chỉ xảy ra để làm việc trong một số trường hợp của bạn. –

Trả lời

32

Các thông báo lỗi chỉ ra rằng bạn có hai metaclasses mâu thuẫn ở đâu đó trong hệ thống cấp bậc của bạn . Bạn cần kiểm tra từng lớp học của bạn và các lớp QT để tìm ra xung đột.

Dưới đây là một số mã ví dụ đơn giản mà thiết lập tình huống tương tự:

class MetaA(type): 
    pass 
class MetaB(type): 
    pass 
class A: 
    __metaclass__ = MetaA 
class B: 
    __metaclass__ = MetaB 

Chúng ta không thể phân lớp cả những lớp học trực tiếp, vì trăn sẽ không biết mà metaclass sử dụng:

>>> class Broken(A, B): pass 
... 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: Error when calling the metaclass bases 
    metaclass conflict: the metaclass of a derived class must be a (non-strict) 
    subclass of the metaclasses of all its bases 

Lỗi nào đang cố gắng cho chúng ta biết là chúng ta cần giải quyết xung đột giữa hai metaclasses bằng cách giới thiệu một metaclass thứ ba là một phân lớp của tất cả các metaclasses từ các lớp cơ sở.

Tôi không chắc đó là bất kỳ rõ ràng hơn so với thông báo lỗi chính nó, nhưng về cơ bản, bạn khắc phục nó bằng cách làm này:

class MetaAB(MetaA, MetaB): 
    pass 

class Fixed(A, B): 
    __metaclass__ = MetaAB 

Mã này tại biên dịch và chạy một cách chính xác. Tất nhiên, trong tình huống thực tế, metaclass giải quyết mâu thuẫn của bạn sẽ phải quyết định hành vi metaclass cha mẹ để áp dụng, mà bạn sẽ phải tìm ra cho chính mình từ yêu cầu của ứng dụng của bạn.

Xin lưu ý rằng lớp được kế thừa của bạn chỉ nhận được một trong hai metaclass.Các phương thức __init__, đôi khi thực hiện tất cả công việc, vì vậy trong nhiều trường hợp, bạn sẽ phải thêm một số __init__ gọi theo cả hai cách để giúp chúng hoạt động.

+0

Câu trả lời chính xác chắc chắn, cảm ơn vì đã giúp tôi hiểu điều đó. Tôi đã học được một chút về phương pháp lập trình (tức là thích ứng với đối tượng hơn là kế thừa lớp của nó ...) nhưng vẫn có thể sử dụng kiến ​​thức này khi tôi đối phó với metaclasses nơi tôi cần chúng. Cảm ơn một lần nữa. – Red

7

Chúng tôi sử dụng một cái gì đó như thế này:

class CooperativeMeta(type): 
    def __new__(cls, name, bases, members): 
     #collect up the metaclasses 
     metas = [type(base) for base in bases] 

     # prune repeated or conflicting entries 
     metas = [meta for index, meta in enumerate(metas) 
      if not [later for later in metas[index+1:] 
       if issubclass(later, meta)]] 

     # whip up the actual combined meta class derive off all of these 
     meta = type(name, tuple(metas), dict(combined_metas = metas)) 

     # make the actual object 
     return meta(name, bases, members) 

    def __init__(self, name, bases, members): 
     for meta in self.combined_metas: 
      meta.__init__(self, name, bases, members) 

Giả tốt, hiện đại vệ sinh metaclass-thi (nơi metaclasses lớp con type, và bất cứ điều gì có thể được thực hiện trong __init__ được thực hiện có) này cho phép nhiều metaclasses để có được cùng.

Metaclasses thực sự và nhất thiết phải thực hiện hầu hết công việc của chúng trong __new__, sẽ khó kết hợp. Bạn có thể lẻn một trong số họ vào đây bằng cách đảm bảo rằng lớp của nó là phần tử đầu tiên trong đa thừa kế.

Để sử dụng, bạn chỉ cần khai báo:

__metaclass__ = CooperativeMeta 

cho những lớp học nơi metaclasses khác nhau đến với nhau.

Trong trường hợp này, ví dụ:

class A: 
    __metaclass__ = MetaA 
class B: 
    __metaclass__ = MetaB 
class Fixed(A, B): 
    __metaclass__ = CooperativeMeta 

Đây là nhiều lần nhiều khả năng để làm việc một cách chính xác trên bảng cho MetaA khác nhau và MetaB hơn là chỉ kế thừa chúng lại với nhau để đóng trình biên dịch lên.

Hy vọng rằng các nhận xét sẽ giải thích mã. Chỉ có một dòng khó khăn, và nó là về việc loại bỏ các cuộc gọi dư thừa cho bất kỳ __metaclass__ nào được thừa kế từ những nơi khác nhau và cho phép các lớp không có metaclass rõ ràng để chơi độc đáo với những người khác. Nếu nó có vẻ quá mức, bạn có thể bỏ qua nó và trong mã của bạn, chỉ cần đặt các lớp cơ sở một cách cẩn thận.

Điều đó làm cho giải pháp ba dòng và khá rõ ràng.

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