2011-07-27 49 views
5

Tôi có đoạn mã sau:Nhiều thừa kế và sử dụng một phương pháp của một trong các lớp cơ sở

class A(object): 
    def __init__(self): 
     self.name = "A" 
     super(A, self).__init__() 

    def Update(self): 
     print "Update A" 
     self.PickTarget() 

    def PickTarget(self): 
     print "PickTarget A" 

class B(object): 
    def __init__(self): 
     self.name = "B" 
     super(B, self).__init__() 

    def Update(self): 
     print "Update B" 
     self.PickTarget() 

    def PickTarget(self): 
     print "PickTarget B" 

class C(A, B): 
    def __init__(self): 
     super(C, self).__init__() 

    def Update(self, useA): 
     if useA: 
      A.Update(self) 
     else: 
      B.Update(self) 

c = C() 

c.Update(useA = True) 
# prints: 
# Update A 
# PickTarget A 

c.Update(useA = False) 
# prints: 
# Update B 
# PickTarget A 

Tại sao gọi C.Update với useA=False vẫn gọi vào A.PickTarget? Làm thế nào tôi có thể làm cho công việc này như thế nào tôi muốn nó làm việc (tức là B.Update luôn luôn gọi vào B.PickTarget)? Tôi chắc chắn điều này đã được hỏi trước nhưng tìm kiếm của tôi không có gì cả - có lẽ vì tôi không biết phải tìm kiếm gì.

+0

Mặc dù tôi vẫn nghĩ rằng điều này có thể đòi hỏi một thiết kế lại, tôi sẽ chỉ ra rằng mangling tên cung cấp một giải pháp khá linh hoạt cho vấn đề này. Xem [chỉnh sửa của tôi] (http://stackoverflow.com/questions/6849519/multiple-inheritance-and-using-a-method-of-one-of-the-base-classes/6849613#6849613). – senderle

Trả lời

7

Đó là vì A là trước B trong các lớp cơ sở của C.

Bạn cần sử dụng B.PickTarget(self) thay vì self.PickTarget() trong B.Update(self) để nhận hành vi này. Nếu không, hãy chuyển đổi AB trong định nghĩa C.

Edit:

Nếu hành vi dự định là cho B luôn gọi các phương thức trong B và cho A luôn gọi các phương thức trong A, nó đúng sử dụng A.method(self) thay vì self.method(), như hình thức thứ hai doesn' t ngụ ý rằng method là trong A.

Bạn nên thiết kế lại lớp học của mình. A cần có phương pháp di chuyển robot ngẫu nhiên và xác định đó là hành vi cơ bản khác. B phải là một lớp con của A và phải có phương thức di chuyển gọi số super(B, self).move() nếu nó không có đường dẫn và nếu không di chuyển trên đường dẫn. Đây là cách thích hợp để ghi đè lên một phương thức trên một điều kiện.

+0

Không có cách nào mà điều này có thể được thực hiện mà không sửa đổi A và B? Đây chỉ là mã ví dụ - trong mã thực, A và B cũng được sử dụng độc lập. (Mã thực tế là một số AI đơn giản cho robot - A làm cho bot di chuyển ngẫu nhiên, B làm cho bot theo một con đường, C kiểm tra nếu bot có đường dẫn - nếu không, nó gọi A.Update, nếu có, nó gọi B Nếu có cách tốt hơn để đối phó với điều này, tôi là tất cả tai.) – combatdave

+0

Như tôi đã nói, bạn chỉ có thể chuyển 'A' và' B' trong định nghĩa 'C'. Ngoài ra, có vẻ như hành vi dự định của bạn là 'B' để luôn gọi các phương thức trong' B' và 'A' để luôn gọi các phương thức trong' A' trong trường hợp đó là __correct__ để sử dụng 'A.method (self)' thay thế của 'self.method()', vì dạng thứ hai không ngụ ý rằng 'phương thức' nằm trong' A'. – agf

+0

Tôi hiểu! Bây giờ điều này bắt đầu có ý nghĩa. Làm thế nào để làm việc này để truy cập các thuộc tính trong lớp? Ví dụ: truy cập self.name bên trong A.Update - nó có phải là self.name hoặc A.name không? – combatdave

2

Bất cứ khi nào một phương thức của đối tượng được gọi, python sử dụng "Thứ tự phân giải phương thức" (MRO) để xác định phiên bản của phương thức cần gọi. Trong trường hợp này, mặc dù bạn đã gọi rõ ràng là A.Update(), A.Update() không rõ ràng gọi A.PickTarget. Nó chỉ gọi self.PickTarget(). Vì đây là đối tượng C, tương đương với C.PickTarget(self). C.PickTarget() được kế thừa và C MRO ra lệnh trong trường hợp này là A.PickTarget là phiên bản PickTarget để sử dụng.

Bạn có thể ghé qua MRO của C như thế này:

>>> C.__mro__ 
(<class '__main__.C'>, <class 'foo.A'>, <class 'foo.B'>, <type 'object'>) 

Có một bài viết siêu thông tin trên MRO here.


Về làm thế nào để có được hành vi mà bạn muốn - tốt, có rất nhiều cách khác nhau khá rõ ràng, và cùng một lúc, không tốt người (mà tôi có thể nghĩ đến). Tôi không nghĩ đây thực sự là một thiết kế tốt. Điểm chính của đa thừa kế là bạn có thể trộn và kết hợp các phương thức trực giao trong C, nhưng bạn đang cố gắng nhồi nhét nhiều phiên bản của các phương thức gần giống nhau, hai từ A và hai từ B, vào một lớp. Nếu bạn cho chúng tôi biết thêm về những gì bạn đang làm, có lẽ chúng tôi có thể đề xuất một giải pháp tốt hơn. (Bạn cũng đang thay đổi chữ ký của phương pháp của bạn trong khi vẫn giữ cùng tên. Điều đó có vẻ tinh nghịch với tôi.)

Nếu bạn chắc chắn rằng bạn muốn làm điều này, tuy nhiên, một cách tiếp cận khác mà bạn có thể xem xét là name mangling, được thiết kế chính xác cho các trường hợp như thế này. Đây thực hiện chính xác những gì bạn muốn:

class A(object): 
    def __init__(self): 
     self.name = "A" 
     super(A, self).__init__() 

    def Update(self): 
     print "Update A" 
     self.__PickTarget() 

    def PickTarget(self): 
     print "PickTarget A" 

    __PickTarget = PickTarget 

class B(object): 
    def __init__(self): 
     self.name = "B" 
     super(B, self).__init__() 

    def Update(self): 
     print "Update B" 
     self.__PickTarget() 

    def PickTarget(self): 
     print "PickTarget B" 

    __PickTarget = PickTarget 

class C(A, B): 
    def __init__(self): 
     super(C, self).__init__() 

    def Update(self, useA): 
     if useA: 
      A.Update(self) 
     else: 
      B.Update(self) 

Output:

>>> from mangling import A, B, C 
>>> c = C() 
>>> c.Update(useA = True) 
Update A 
PickTarget A 
>>> c.Update(useA = False) 
Update B 
PickTarget B 
+0

Cảm ơn lời giải thích là tại sao điều này xảy ra. Tôi đã thử đặt self.PickTarget trong C.Update() thành A.PickTarget hoặc B.PickTarget, nhưng điều này ném "TypeError: phương thức unbound Target PickTarget() phải được gọi với một đối tượng là đối số đầu tiên (không có gì thay thế)" – combatdave

+0

@combatdave, tôi không hoàn toàn hiểu ý bạn là gì bằng cách "đặt self.PickTarget trong C.Update() thành A.PickTarget hoặc B.PickTarget". Bạn có nghĩa là bạn đang làm 'self.PickTarget = A.PickTarget'? Bởi vì đó là siêu sai. 'A.PickTarget' là một phương thức không liên kết, vì vậy bây giờ nó không bị ràng buộc vào' self' nữa, và bạn phải truyền 'self' một cách rõ ràng. – senderle

+0

Vâng, tôi đã làm điều đó - tôi đã hiểu nó sai;) Cảm ơn bài đăng - điều này đã giúp tôi hiểu điều gì đang xảy ra, nhưng bài đăng của @agf đã khắc phục được sự cố của tôi :) – combatdave

0

Đây không phải là giải pháp nhưng nếu bạn thay đổi phương thức cập nhật C để:

def update(self, useA): 
    if useA: 
     A().update() 
    else: 
     B().update() 

Nó không làm việc như bạn mong đợi.

Bạn nên đọc về Lệnh phân giải phương thức của Python để làm việc với những thứ như thế này đúng cách.

+1

Đó là việc tạo một thể hiện mới của lớp cơ sở để gọi Update() bật, điều này không hữu ích. – combatdave

+0

Sau đó thử chuyển A cho B trong định nghĩa C. EDIT: Ngoài ra, nếu bạn thử in C.name, bạn sẽ thấy những gì đang xảy ra. – toqueteos

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