Trong Python, làm cách nào để chỉ ra tôi đang ghi đè một phương thức?


173

Ví dụ, trong Java, @Overridechú thích không chỉ cung cấp kiểm tra thời gian biên dịch của một ghi đè mà còn tạo ra mã tự ghi tài liệu tuyệt vời.

Tôi chỉ tìm kiếm tài liệu (mặc dù nếu đó là một chỉ báo cho một số người kiểm tra như pylint, thì đó là một phần thưởng). Tôi có thể thêm một nhận xét hoặc chuỗi ký tự ở đâu đó, nhưng cách thành ngữ để chỉ ra một ghi đè trong Python là gì?


13
Nói cách khác, bạn không bao giờ chỉ ra rằng bạn đang ghi đè một phương thức? Để nó cho người đọc tự tìm ra điều đó?
Bluu

2
Vâng, tôi biết có vẻ như một tình huống dễ bị lỗi đến từ một ngôn ngữ được biên dịch, nhưng bạn chỉ cần chấp nhận nó. Trong thực tế tôi không thấy nó có vấn đề gì nhiều (Ruby trong trường hợp của tôi, không phải Python, nhưng cùng một ý tưởng)
Ed S.

Chắc chắn hoàn thành. Cả câu trả lời của Triptych và câu trả lời của mkorpela đều đơn giản, tôi thích điều đó, nhưng tinh thần rõ ràng quá mức của cái sau và ngăn chặn những sai lầm một cách thông minh sẽ chiến thắng.
Bluu

1
Nó không trực tiếp giống nhau, nhưng các lớp cơ sở trừu tượng kiểm tra xem tất cả các phương thức trừu tượng đã bị ghi đè bởi một lớp con. Tất nhiên điều này không giúp ích gì nếu bạn ghi đè các phương pháp cụ thể.
letmaik

Câu trả lời:


205

Dựa trên câu trả lời này và fwc: s Tôi đã tạo một gói có thể cài đặt pip https://github.com/mkorpela/overrides

Thỉnh thoảng tôi mới đến đây xem câu hỏi này. Chủ yếu điều này xảy ra sau khi (một lần nữa) gặp lỗi tương tự trong cơ sở mã của chúng tôi: Ai đó đã quên một số lớp triển khai "giao diện" trong khi đổi tên một phương thức trong "giao diện" ..

Vâng, Python không phải là Java nhưng Python có sức mạnh - và rõ ràng là tốt hơn ngầm định - và có những trường hợp cụ thể thực sự trong thế giới thực, nơi điều này sẽ giúp tôi.

Vì vậy, đây là một bản phác thảo của trang trí ghi đè. Điều này sẽ kiểm tra xem lớp được cung cấp dưới dạng tham số có cùng tên phương thức (hoặc một cái gì đó) như phương thức đang được trang trí.

Nếu bạn có thể nghĩ ra một giải pháp tốt hơn xin vui lòng gửi nó ở đây!

def overrides(interface_class):
    def overrider(method):
        assert(method.__name__ in dir(interface_class))
        return method
    return overrider

Nó hoạt động như sau:

class MySuperInterface(object):
    def my_method(self):
        print 'hello world!'


class ConcreteImplementer(MySuperInterface):
    @overrides(MySuperInterface)
    def my_method(self):
        print 'hello kitty!'

và nếu bạn làm một phiên bản bị lỗi, nó sẽ xuất hiện một lỗi xác nhận trong khi tải lớp:

class ConcreteFaultyImplementer(MySuperInterface):
    @overrides(MySuperInterface)
    def your_method(self):
        print 'bye bye!'

>> AssertionError!!!!!!!

17
Tuyệt vời. Điều này đã bắt gặp một lỗi chính tả trong lần đầu tiên tôi thử nó. Thanh danh.
Christopher Bruns

7
mfbutner: nó không được gọi mỗi khi phương thức được thực thi - chỉ khi phương thức được tạo.
mkorpela

3
Điều này cũng tốt cho chuỗi doc! overridescó thể sao chép chuỗi doc của phương thức được ghi đè nếu phương thức ghi đè không có một phương thức riêng.
letmaik

5
@mkorpela, Heh mã này của bạn phải nằm trong hệ thống lib mặc định của python. Tại sao bạn không đặt cái này trong hệ thống pip? : P

5
@mkorpela: Ồ và tôi đề nghị thông báo cho các nhà phát triển lõi python tuân theo gói này, họ có thể muốn xem xét việc bổ sung trang trí chồng lên hệ thống python lõi. :)

30

Đây là một triển khai không yêu cầu đặc điểm kỹ thuật của tên interface_group.

import inspect
import re

def overrides(method):
    # actually can't do this because a method is really just a function while inside a class def'n  
    #assert(inspect.ismethod(method))

    stack = inspect.stack()
    base_classes = re.search(r'class.+\((.+)\)\s*\:', stack[2][4][0]).group(1)

    # handle multiple inheritance
    base_classes = [s.strip() for s in base_classes.split(',')]
    if not base_classes:
        raise ValueError('overrides decorator: unable to determine base class') 

    # stack[0]=overrides, stack[1]=inside class def'n, stack[2]=outside class def'n
    derived_class_locals = stack[2][0].f_locals

    # replace each class name in base_classes with the actual class type
    for i, base_class in enumerate(base_classes):

        if '.' not in base_class:
            base_classes[i] = derived_class_locals[base_class]

        else:
            components = base_class.split('.')

            # obj is either a module or a class
            obj = derived_class_locals[components[0]]

            for c in components[1:]:
                assert(inspect.ismodule(obj) or inspect.isclass(obj))
                obj = getattr(obj, c)

            base_classes[i] = obj


    assert( any( hasattr(cls, method.__name__) for cls in base_classes ) )
    return method

2
Một chút ma thuật nhưng làm cho việc sử dụng điển hình dễ dàng hơn rất nhiều. Bạn có thể bao gồm các ví dụ sử dụng?
Bluu

chi phí trung bình và trường hợp xấu nhất khi sử dụng công cụ trang trí này, có lẽ được thể hiện dưới dạng so sánh với một công cụ trang trí tích hợp như @ classmethod hoặc @property?
larham1

4
@ larham1 Trình trang trí này được thực thi một lần, khi định nghĩa lớp được phân tích, không phải trên mỗi cuộc gọi. Do đó, chi phí thực hiện là không liên quan, khi so sánh với thời gian chạy chương trình.
Abgan

Điều này sẽ đẹp hơn nhiều trong Python 3.6 nhờ PEP 487 .
Neil G

Để nhận được thông báo lỗi tốt hơn: khẳng định bất kỳ (hasattr (cls, phương thức .__ name__) cho cls trong base_groupes), 'Phương thức Overriden "{}" không được tìm thấy trong lớp cơ sở.'. Định dạng (phương thức .__ name__)
Ivan Kovtun

14

Nếu bạn muốn điều này chỉ cho mục đích tài liệu, bạn có thể xác định trang trí ghi đè của riêng bạn:

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

Điều này thực sự không có gì ngoài kẹo mắt, trừ khi bạn tạo ghi đè (f) theo cách thực sự kiểm tra ghi đè.

Nhưng sau đó, đây là Python, tại sao lại viết nó giống như Java?


2
Người ta có thể thêm xác nhận thực tế thông qua kiểm tra để overridetrang trí.
Erik Kaplun

69
Nhưng sau đó, đây là Python, tại sao lại viết nó giống như Java? Bởi vì một số ý tưởng trong Java là tốt và có giá trị mở rộng sang các ngôn ngữ khác?
Piotr Dobrogost

9
Bởi vì khi bạn đổi tên một phương thức trong một siêu lớp, sẽ rất tuyệt khi biết rằng một số lớp con 2 cấp xuống đã ghi đè lên nó. Chắc chắn, thật dễ dàng để kiểm tra, nhưng một chút trợ giúp từ trình phân tích cú pháp ngôn ngữ sẽ không gây hại.
Abgan

4
Bởi vì đó là một ý tưởng tốt. Thực tế là một loạt các ngôn ngữ khác có tính năng này là không có đối số - cho dù là chống lại.
sfkleach

6

Python không phải là Java. Tất nhiên không có thứ gì thực sự là kiểm tra thời gian biên dịch.

Tôi nghĩ rằng một nhận xét trong docopes là rất nhiều. Điều này cho phép bất kỳ người dùng phương thức nào của bạn nhập help(obj.method)và thấy rằng phương thức đó là ghi đè.

Bạn cũng có thể mở rộng một giao diện một cách rõ ràng class Foo(Interface), điều này sẽ cho phép người dùng nhập help(Interface.method)để có ý tưởng về chức năng mà phương thức của bạn dự định cung cấp.


56
Điểm thực sự @Overridetrong Java không phải là tài liệu - đó là một lỗi khi bạn định ghi đè một phương thức, nhưng cuối cùng lại xác định một phương thức mới (ví dụ vì bạn đã viết sai tên; trong Java, nó cũng có thể xảy ra do bạn đã sử dụng chữ ký sai, nhưng đây không phải là vấn đề trong Python - nhưng lỗi chính tả vẫn là).
Pavel Minaev

2
@ Pavel Minaev: Đúng, nhưng vẫn thuận tiện để có tài liệu, đặc biệt nếu bạn đang sử dụng trình soạn thảo IDE / văn bản không có chỉ báo tự động để ghi đè (ví dụ JDT hiển thị chúng gọn gàng bên cạnh số dòng).
Tuukka Mustonen

2
@PavelMinaev Sai. Một trong những điểm chính của @Overridetài liệu là ngoài việc kiểm tra thời gian biên dịch.
siamii

6
@siamii Tôi nghĩ rằng một sự trợ giúp cho tài liệu là tuyệt vời, nhưng trong tất cả các tài liệu Java chính thức mà tôi thấy, chúng chỉ cho thấy tầm quan trọng của việc kiểm tra thời gian biên dịch. Vui lòng chứng minh cho tuyên bố của bạn rằng Pavel "sai".
Andrew Mellinger

5

Cải thiện trên @mkorpela câu trả lời tuyệt vời , đây là một phiên bản với

kiểm tra chính xác hơn, đặt tên và nâng cao các đối tượng Lỗi

def overrides(interface_class):
    """
    Function override annotation.
    Corollary to @abc.abstractmethod where the override is not of an
    abstractmethod.
    Modified from answer https://stackoverflow.com/a/8313042/471376
    """
    def confirm_override(method):
        if method.__name__ not in dir(interface_class):
            raise NotImplementedError('function "%s" is an @override but that'
                                      ' function is not implemented in base'
                                      ' class %s'
                                      % (method.__name__,
                                         interface_class)
                                      )

        def func():
            pass

        attr = getattr(interface_class, method.__name__)
        if type(attr) is not type(func):
            raise NotImplementedError('function "%s" is an @override'
                                      ' but that is implemented as type %s'
                                      ' in base class %s, expected implemented'
                                      ' type %s'
                                      % (method.__name__,
                                         type(attr),
                                         interface_class,
                                         type(func))
                                      )
        return method
    return confirm_override


Đây là những gì nó trông giống như trong thực tế:

NotImplementedError" không được thực hiện trong lớp cơ sở "

class A(object):
    # ERROR: `a` is not a implemented!
    pass

class B(A):
    @overrides(A)
    def a(self):
        pass

dẫn đến NotImplementedErrorlỗi mô tả nhiều hơn

function "a" is an @override but that function is not implemented in base class <class '__main__.A'>

đầy đủ ngăn xếp

Traceback (most recent call last):
  
  File "C:/Users/user1/project.py", line 135, in <module>
    class B(A):
  File "C:/Users/user1/project.py", line 136, in B
    @overrides(A)
  File "C:/Users/user1/project.py", line 110, in confirm_override
    interface_class)
NotImplementedError: function "a" is an @override but that function is not implemented in base class <class '__main__.A'>


NotImplementedError" loại thực hiện dự kiến "

class A(object):
    # ERROR: `a` is not a function!
    a = ''

class B(A):
    @overrides(A)
    def a(self):
        pass

dẫn đến NotImplementedErrorlỗi mô tả nhiều hơn

function "a" is an @override but that is implemented as type <class 'str'> in base class <class '__main__.A'>, expected implemented type <class 'function'>

đầy đủ ngăn xếp

Traceback (most recent call last):
  
  File "C:/Users/user1/project.py", line 135, in <module>
    class B(A):
  File "C:/Users/user1/project.py", line 136, in B
    @overrides(A)
  File "C:/Users/user1/project.py", line 125, in confirm_override
    type(func))
NotImplementedError: function "a" is an @override but that is implemented as type <class 'str'> in base class <class '__main__.A'>, expected implemented type <class 'function'>




Điều tuyệt vời về câu trả lời @mkorpela là kiểm tra xảy ra trong một số giai đoạn khởi tạo. Kiểm tra không cần phải được "chạy". Đề cập đến các ví dụ trước, class Bkhông bao giờ được khởi tạo ( B()) nhưng vẫn NotImplementedErrorsẽ tăng. Điều này có nghĩa là overrideslỗi được bắt sớm hơn.


Chào! Điều này có vẻ thú vị. Bạn có thể xem xét thực hiện một yêu cầu kéo trong dự án ipromise của tôi? Tôi đã thêm một câu trả lời.
Neil G

@NeilG Tôi đã rẽ nhánh dự án ipromise và mã hóa một chút. Có vẻ như về cơ bản bạn đã thực hiện điều này trong overrides.py. Tôi không chắc những gì khác tôi có thể cải thiện đáng kể ngoại trừ thay đổi các loại ngoại lệ từ TypeErrorsang NotImplementedError.
JamesThomasMoon1979

Chào! Cảm ơn, tôi không kiểm tra xem đối tượng bị ghi đè có thực sự có loại không types.MethodType. Đó là một ý tưởng tốt trong câu trả lời của bạn.
Neil G

2

Giống như những người khác đã nói không giống như Java, không có thẻ @Overide, tuy nhiên, ở trên bạn có thể tự tạo trang trí bằng cách sử dụng trang trí tuy nhiên tôi sẽ đề xuất sử dụng phương thức toàn cầu getattrib () thay vì sử dụng chính tả bên trong để bạn có được thứ gì đó như sau:

def Override(superClass):
    def method(func)
        getattr(superClass,method.__name__)
    return method

Nếu bạn muốn, bạn có thể bắt getattr () trong thử của riêng bạn, hãy bắt lỗi của bạn nhưng tôi nghĩ phương pháp getattr tốt hơn trong trường hợp này.

Ngoài ra, điều này bắt tất cả các mục bị ràng buộc với một lớp bao gồm các phương thức lớp và vai


2

Dựa trên câu trả lời tuyệt vời của @ mkorpela, tôi đã viết một gói tương tự ( ipromise pypi github ) thực hiện nhiều kiểm tra khác:

Giả sử Athừa kế từ BC, Bthừa kế từ C.

Mô-đun kiểm tra ipromise rằng:

  • Nếu A.fghi đè B.f, B.fphải tồn tại và Aphải kế thừa từ B. (Đây là kiểm tra từ gói ghi đè).

  • Bạn không có mô hình A.ftuyên bố rằng nó ghi đè B.f, sau đó tuyên bố rằng nó ghi đè C.f. Anên nói rằng nó ghi đè từ C.fđó Bcó thể quyết định dừng ghi đè phương thức này và điều đó sẽ không dẫn đến cập nhật xuôi dòng.

  • Bạn không có mẫu A.fkhai báo rằng nó ghi đè C.f, nhưng B.fkhông khai báo ghi đè của nó.

  • Bạn không có mô hình A.ftuyên bố rằng nó ghi đè C.f, nhưng B.ftuyên bố rằng nó ghi đè từ một số D.f.

Nó cũng có nhiều tính năng khác nhau để đánh dấu và kiểm tra thực hiện một phương pháp trừu tượng.


0

Nghe đơn giản nhất và hoạt động theo Jython với các lớp Java:

class MyClass(SomeJavaClass):
     def __init__(self):
         setattr(self, "name_of_method_to_override", __method_override__)

     def __method_override__(self, some_args):
         some_thing_to_do()

0

Trình trang trí tôi không chỉ kiểm tra xem tên của thuộc tính ghi đè là bất kỳ siêu lớp nào của thuộc tính mà không phải chỉ định siêu lớp, trình trang trí này cũng kiểm tra để đảm bảo thuộc tính ghi đè phải cùng loại với thuộc tính ghi đè thuộc tính. Các phương thức lớp được xử lý như các phương thức và Phương thức tĩnh được xử lý như các hàm. Trình trang trí này hoạt động cho các phương thức gọi, phương thức lớp, phương thức tĩnh và thuộc tính.

Để biết mã nguồn, hãy xem: https://github.com/fireuser909/override

Trình trang trí này chỉ hoạt động cho các lớp là các thể hiện của ghi đè.OverridesMeta nhưng nếu lớp của bạn là một thể hiện của siêu dữ liệu tùy chỉnh, hãy sử dụng hàm created_custom_overrides_meta để tạo một siêu dữ liệu tương thích với trình trang trí ghi đè. Để kiểm tra, hãy chạy mô-đun ghi đè .__ init__.


0

Trong Python 2.6+ và Python 3.2+ bạn có thể làm điều đó ( Thực tế mô phỏng nó , Python không hỗ trợ nạp chồng hàm và lớp con tự động ghi đè phương thức của cha mẹ). Chúng ta có thể sử dụng trang trí cho việc này. Nhưng trước tiên, hãy lưu ý rằng Python @decoratorsvà Java@Annotations là những thứ hoàn toàn khác nhau. Cái trước là một trình bao bọc với mã cụ thể trong khi cái trước là cờ cho trình biên dịch.

Đối với điều này, đầu tiên làm pip install multipledispatch

from multipledispatch import dispatch as Override
# using alias 'Override' just to give you some feel :)

class A:
    def foo(self):
        print('foo in A')

    # More methods here


class B(A):
    @Override()
    def foo(self):
        print('foo in B')
    
    @Override(int)
    def foo(self,a):
        print('foo in B; arg =',a)
        
    @Override(str,float)
    def foo(self,a,b):
        print('foo in B; arg =',(a,b))
        
a=A()
b=B()
a.foo()
b.foo()
b.foo(4)
b.foo('Wheee',3.14)

đầu ra:

foo in A
foo in B
foo in B; arg = 4
foo in B; arg = ('Wheee', 3.14)

Lưu ý rằng bạn phải sử dụng trang trí ở đây với dấu ngoặc đơn

Một điều cần nhớ là vì Python không có chức năng nạp chồng trực tiếp, nên ngay cả khi Lớp B không kế thừa từ Lớp A nhưng cần tất cả những thứ đó foo, bạn cũng cần sử dụng @Override (mặc dù sử dụng bí danh 'Quá tải' sẽ trông tốt hơn trong trường hợp đó)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.