2015-12-11 14 views
16

Tôi tò mò liệu có một cách trong Python để buộc (từ lớp cha) cho một phương thức cha mẹ được gọi từ một lớp con khi nó đang bị ghi đè.Lực lượng lớp con gọi phương thức cha mẹ khi ghi đè nó

Ví dụ:

class Parent(object): 

    def __init__(self): 
     self.someValue=1 

    def start(self): 
     ''' Force method to be called''' 
     self.someValue+=1 

Việc thực hiện chính xác những gì tôi muốn lớp trẻ con tôi làm sẽ là:

class ChildCorrect(Parent): 

    def start(self): 
     Parent.start(self) 
     self.someValue+=1 

Tuy nhiên, có một cách để buộc các nhà phát triển mà phát triển các lớp học trẻ em để gọi cụ thể (không chỉ ghi đè) phương thức 'bắt đầu' được xác định trong lớp Cha mẹ trong trường hợp họ quên gọi nó:

class ChildIncorrect(Parent): 

    def start(self): 
     '''Parent method not called, but correctly overridden''' 
     self.someValue+=1 

Ngoài ra, nếu điều này không được coi là thực hành tốt nhất, điều gì sẽ là một lựa chọn thay thế?

+0

Phương pháp ghi đè có tự do * khi * nó gọi là siêu không? I E. nó sẽ không sao nếu phương pháp quan trọng bị buộc phải gọi siêu ở đầu hoặc cuối của việc thực hiện nó? (Có lẽ, không được phép hạn chế điều đó trong trường hợp chung.) Nếu có, một metaclass có thể được sử dụng để bọc bất kỳ phương pháp ghi đè nào và ngầm gọi siêu; phương pháp ghi đè sau đó sẽ không được phép gọi siêu chính nó (mà bạn có thể không thực thi được). –

+0

Điều gì về việc gợi ý rằng người dùng nên gọi phương thức siêu lớp, tương tự như cách thiếu phương pháp triển khai phương pháp trừu tượng được chọn với một linter? – pnovotnak

Trả lời

14

thế nào (không) để làm điều đó

Không, không có cách nào an toàn để buộc người dùng phải gọi siêu. Hãy để chúng tôi xem xét một vài tùy chọn sẽ đạt được mục tiêu đó hoặc một mục tiêu tương tự và thảo luận tại sao đó là một ý tưởng tồi.Trong phần tiếp theo, tôi cũng sẽ thảo luận về những gì hợp lý (đối với cộng đồng Python) để đối phó với tình huống này.

  1. Một metaclass có thể kiểm tra, tại thời điểm lớp con được xác định, cho dù phương pháp ghi đè phương pháp đích (= có cùng tên với phương pháp đích) gọi siêu với đối số thích hợp.

    Điều này yêu cầu hành vi thực hiện cụ thể sâu sắc, chẳng hạn như sử dụng mô-đun dis của CPython. Không có hoàn cảnh nào mà tôi có thể tưởng tượng đây là một ý tưởng hay - nó phụ thuộc vào phiên bản CPython cụ thể và trên thực tế là bạn đang sử dụng CPython.

  2. Một metaclass có thể hợp tác với baseclass. Trong trường hợp này, baseclass thông báo cho metaclass khi phương thức kế thừa được gọi và metaclass kết thúc tốt đẹp bất kỳ phương thức ghi đè nào với một đoạn mã bảo vệ.

    Mã bảo vệ kiểm tra xem phương thức kế thừa có được gọi trong khi thực thi phương pháp ghi đè hay không. Điều này có nhược điểm là một kênh thông báo riêng biệt cho mỗi phương pháp cần "tính năng" này là cần thiết, và ngoài ra thread-an toàn và tái nhập cảnh sẽ là một mối quan tâm. Ngoài ra, phương thức ghi đè đã thực hiện xong tại thời điểm bạn nhận thấy rằng nó không được gọi là phương thức kế thừa, có thể là xấu tùy thuộc vào kịch bản của bạn.

    Nó cũng mở câu hỏi: Phải làm gì nếu phương pháp ghi đè không được gọi là phương pháp được kế thừa? Việc đưa ra một ngoại lệ có thể bị bất ngờ bởi mã sử dụng phương thức (hoặc thậm chí tệ hơn, nó có thể giả định rằng phương thức này không có tác dụng gì cả, điều đó không đúng).

    Ngoài ra thông tin phản hồi trễ nhà phát triển sẽ ghi đè lớp học (nếu họ nhận được phản hồi đó!) Thì không tốt.

  3. Một metaclass có thể tạo ra một đoạn mã bảo vệ cho mỗi phương pháp overriden gọi phương thức kế thừa tự động trước hoặc sau khi phương thức ghi đè đã được thực thi. Nhược điểm ở đây là các nhà phát triển sẽ không nghĩ rằng phương pháp kế thừa được gọi tự động và không có cách nào để dừng cuộc gọi đó (ví dụ: nếu điều kiện tiên quyết đặc biệt cho lớp con không được đáp ứng) hoặc kiểm soát khi ở phương thức ghi đè của riêng họ phương thức kế thừa được gọi.

    Điều này vi phạm một loạt các nguyên tắc hợp lý khi mã hóa python (tránh những điều ngạc nhiên, rõ ràng là tốt hơn là ngầm, và có thể nhiều hơn).

  4. Kết hợp điểm 2 và 3. Sử dụng hợp tác cơ sở và metaclass từ điểm 2, mã bảo vệ từ điểm 3 có thể được mở rộng để tự động gọi siêu iff phương thức ghi đè không được gọi là siêu chính họ.

    Một lần nữa, điều này là bất ngờ, nhưng nó giải quyết các vấn đề với các cuộc gọi siêu trùng lặp và cách xử lý một phương thức không gọi là siêu.

    Tuy nhiên, vẫn còn các sự cố còn lại. Mặc dù thread-safety có thể được cố định với thread-locals, vẫn không có cách nào cho một phương thức ghi đè để hủy bỏ cuộc gọi đến siêu khi điều kiện tiên quyết không được đáp ứng, ngoại trừ việc đưa ra một ngoại lệ có thể không mong muốn trong mọi trường hợp. Ngoài ra, siêu chỉ có thể được gọi tự động sau phương pháp ghi đè, không phải trước đây, trong đó, một lần nữa, trong một số trường hợp là không mong muốn.

Ngoài ra, không ai trong số này giúp chống lại ràng buộc thuộc tính trong suốt cuộc đời của đối tượng và lớp, mặc dù điều này có thể được giúp đỡ bằng cách sử dụng mô tả và/hoặc kéo dài metaclass để chăm sóc nó.

Tại sao không để làm điều đó

Ngoài ra, nếu điều này không được coi là tốt nhất, những gì sẽ là một sự thay thế?

Phương pháp hay nhất với Python là giả định rằng bạn là một trong số những người lớn đồng ý. Điều đó có nghĩa, rằng không ai đang tích cực cố gắng làm những điều khó chịu với mã của bạn, trừ khi bạn cho phép họ. Trong một hệ sinh thái như vậy, nó sẽ có ý nghĩa để tack một .. warning:: trong tài liệu của phương pháp hoặc lớp học, để bất cứ ai thừa kế từ lớp đó biết những gì họ phải làm.

Ngoài ra, việc gọi phương thức siêu tại một điểm thích hợp có ý nghĩa trong nhiều ngữ cảnh mà các nhà phát triển sử dụng lớp cơ sở sẽ xem xét nó và chỉ quên nó một cách vô tình. Trong trường hợp này, việc sử dụng metaclass từ điểm thứ ba ở trên cũng không giúp được - người dùng sẽ phải nhớ không phải là để gọi siêu, điều này có thể là vấn đề của chính nó, đặc biệt đối với những người lập trình có kinh nghiệm.

Nó cũng vi phạm nguyên tắc ít ngạc nhiên nhất và "rõ ràng là tốt hơn là ngầm" (không ai mong đợi phương pháp được thừa kế được gọi là ngầm!). Điều này, một lần nữa, sẽ phải được ghi lại tốt, trong trường hợp này bạn cũng có thể sử dụng để chỉ không có siêu được gọi tự động và chỉ ghi lại rằng nó làm cho thậm chí còn ý nghĩa hơn bình thường để gọi phương thức được kế thừa.

-1

Tất cả những gì bạn cần là super.

Với nó, mã của bạn có thể trông như thế này:

class Parent(object): 
    def __init__(self): 
     self.someValue = 1 

    def start(self): 
     self.someValue += 1 


class Child(Parent): 
    def start(self): 
     # put code here 
     super().start() 
     # or here 
+2

Cảm ơn bạn đã trả lời! Tuy nhiên, những gì tôi đang đề cập đến là liệu có bất cứ điều gì mà tôi có thể làm với phương thức được định nghĩa trong lớp Parent để buộc nó được gọi ngay cả khi nó bị ghi đè. Điều gì sẽ xảy ra nếu một nhà phát triển sẽ không gọi điều này: siêu (Trẻ em, tự) .bắt đầu() – Spektor

+0

Ugh, tôi hiểu lầm bạn. Vì vậy, tôi nghĩ rằng nó không thể trong Python bằng cách xây dựng thừa kế - bạn có thể ghi đè lên mỗi chức năng, do đó, không có đảm bảo rằng bất kỳ chức năng của bạn sẽ được gọi. Mặc dù, Python có quy ước rằng các phương thức/thuộc tính mà tên bắt đầu bằng dấu gạch dưới đơn (ví dụ _my_method) được "bảo vệ" và bạn tự chịu rủi ro khi sử dụng chúng. – andrzej3393

1

Tuy nhiên, có một cách để buộc các nhà phát triển mà phát triển các lớp học trẻ em cụ thể gọi số (không chỉ ghi đè) phương pháp 'start' được xác định trong lớp Cha mẹ trong trường hợp họ quên gọi nó là

Trong Python bạn không thể kiểm soát cách người khác ghi đè các chức năng của bạn.

+1

Kiểm tra xem bạn có thể sử dụng 'metaclass' cho nhu cầu này không. –

+1

Một metaclass sẽ không được giúp đỡ. Trong bối cảnh này, điều hữu ích nhất mà nó có thể làm là phát hiện ra rằng hàm đã bị ghi đè. Điều này là không đủ mặc dù.Bạn sẽ phải kiểm tra xem nó có gọi siêu thích hợp (không thể thực hiện được mà không dựa vào hành vi được thực hiện đã định nghĩa như module '' dis'' trong CPython hoặc thực sự gọi phương thức và truy tìm cuộc gọi) và không nhận lại được chỉ định trong suốt thời gian của phiên bản. Truy tìm cuộc gọi khi phương pháp đang được gọi có thể được thực hiện với một metaclass, nhưng chắc chắn là quá mức cần thiết. –

2

Rất ít trong Python là về việc buộc các lập trình viên khác mã hóa một cách nhất định. Ngay cả trong thư viện chuẩn, bạn sẽ tìm thấy cảnh báo such as this:

Nếu subclass ghi đè hàm tạo, nó phải đảm bảo gọi hàm tạo lớp cơ sở trước khi thực hiện bất kỳ thứ gì khác cho chuỗi.

Mặc dù có thể sử dụng metaclass để sửa đổi hành vi của lớp con, điều tốt nhất có thể làm trong trường hợp này là thay thế các phương pháp mới bằng một phương pháp khác và phương pháp gốc. Tuy nhiên, điều đó sẽ có vấn đề nếu lập trình viên sau đó đã làm gọi phương thức cha mẹ trong mã mới, cộng với nó sẽ dẫn đến thói quen xấu không gọi phương thức cha mẹ khi người ta nên.

Nói cách khác, Python không được xây dựng theo cách đó.

+0

Cảm ơn lời giải thích; đây là những gì tôi đang hướng tới để hiểu và tôi chắc chắn không muốn thực hiện một cái gì đó không phải là pythonic. Tôi nghĩ rằng có lẽ, có thể sẽ có một loại phương pháp ẩn mà tôi có thể sử dụng để thực thi điều này. – Spektor

2

Với một số hòm thư metaclass, bạn có thể phát hiện khi chạy cho dù bắt đầu phụ huynh đã được gọi hay chưa. Tuy nhiên, tôi sẽ chắc chắn NOT recommand sử dụng mã này trong các tình huống thực tế, có thể là lỗi theo nhiều cách và nó không phải là pythonic để thực thi những hạn chế như vậy.

class ParentMetaClass(type): 

    parent_called = False 

    def __new__(cls, name, bases, attrs): 
     print cls, name, bases, attrs 
     original_start = attrs.get('start') 
     if original_start: 
      if name == 'Parent': 
       def patched_start(*args, **kwargs): 
        original_start(*args, **kwargs) 
        cls.parent_called = True 

      else: 
       def patched_start(*args, **kwargs): 
        original_start(*args, **kwargs) 
        if not cls.parent_called: 
         raise ValueError('Parent start not called') 
        cls.parent_called = False 

      attrs['start'] = patched_start 

     return super(ParentMetaClass, cls).__new__(cls, name, bases, attrs) 


class Parent(object): 
    __metaclass__ = ParentMetaClass 

    def start(self): 
     print 'Parent start called.' 


class GoodChild(Parent): 
    def start(self): 
     super(GoodChild, self).start() 
     print 'I am a good child.' 


class BadChild(Parent): 
    def start(self): 
     print 'I am a bad child, I will raise a ValueError.' 
5

Nếu phân cấp lớp là dưới sự kiểm soát của bạn, bạn có thể sử dụng những gì Gang of Four (Gamma, et al) Design Patterns cuốn sách gọi là Phương pháp Mẫu Pattern:

class MyBase: 
    def MyMethod(self): 
     # place any code that must always be called here... 
     print "Base class pre-code" 

     # call an internal method that contains the subclass-specific code 
     self._DoMyMethod() 

     # ...and then any additional code that should always be performed 
     # here. 
     print "base class post-code!" 

    def _DoMyMethod(self): 
     print "BASE!" 


class MyDerived(MyBase): 
    def _DoMyMethod(self): 
     print "DERIVED!" 


b = MyBase() 
d = MyDerived() 

b.MyMethod() 
d.MyMethod() 

kết quả đầu ra:

Base class pre-code 
BASE! 
base class post-code! 
Base class pre-code 
DERIVED! 
base class post-code! 
+0

Và sau đó ghi lại rằng phương thức riêng tư là phương thức ghi đè. 1 cho kỹ thuật này, nhưng tôi khuyên không nên dùng nó. ;) –

+0

@EthanFurman Hm, tại sao? Có cả trước và sau mã mà cần phải chạy có vẻ như một trường hợp sử dụng hợp lý với tôi (không cần thiết cho câu hỏi ban đầu). Có thành ngữ đẹp hơn để sử dụng ở đây không? –

+0

@ JonasWielicki: Đây là một trường hợp sử dụng hợp lý, nhưng đó không phải là những gì mà OP cần. Để phù hợp với trường hợp của OP, nó vẫn sẽ cần phải được ghi lại, trong trường hợp này bạn cũng có thể chỉ cần ghi lại rằng phương thức cha cần được gọi. –

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