В Python, как мне указать, что я переопределяю метод?

В Java, например, @Override аннотация не только обеспечивает проверку переопределения во время компиляции, но и обеспечивает отличный самодокументирующийся код.

Я просто ищу документацию (хотя, если это индикатор какой-то проверки, такой как pylint, это бонус). Я могу добавить комментарий или строку документации где-нибудь, но каков идиоматический способ указать переопределение в Python?

15 ответов

Решение

ОБНОВЛЕНИЕ (23.05.2015): Исходя из этого и ответа fwc:s, я создал устанавливаемый пакет для pip https://github.com/mkorpela/overrides

Время от времени я в конечном итоге здесь, глядя на этот вопрос. Главным образом это происходит после того, как (снова) обнаружена та же ошибка в нашей кодовой базе: кто-то забыл некоторый класс реализации интерфейса, переименовывая метод в интерфейсе.

Ну, Python - это не Java, но Python обладает мощью - и явный лучше, чем неявный - и в реальном мире есть реальные конкретные случаи, где эта вещь мне помогла бы.

Итак, вот эскиз переопределений декоратора. Это проверит, что класс, заданный в качестве параметра, имеет то же имя метода (или что-то другое), что и декорируемый метод.

Если вы можете придумать лучшее решение, пожалуйста, опубликуйте его здесь!

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

Это работает следующим образом:

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


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

и если вы сделаете ошибочную версию, это вызовет ошибку утверждения во время загрузки класса:

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

>> AssertionError!!!!!!!

Начиная с Python 3.12 (дата выпуска осень 2023 г.) это можно сделать. Я бы посоветовал вам посмотреть этот сайт https://peps.python.org/pep-0698/ . Он очень хорошо объясняет, как украшать методы в Python, как в Java.

Вот пример кода, более подробную информацию вы можете найти на веб-сайте выше.

      from typing import override

class Parent:
    def foo(self) -> int:
        return 1

    def bar(self, x: str) -> str:
        return x

class Child(Parent):
    @override
    def foo(self) -> int:
        return 2

    @override
    def baz() -> int:  # Type check error: no matching signature in ancestor
        return 1

Вот реализация, которая не требует указания имени interface_class.

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

Если вы хотите это только для целей документирования, вы можете определить свой собственный декоратор переопределения:

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

Это на самом деле не что иное, как конфетка, если только вы не создадите override(f) таким образом, который фактически проверяет переопределение.

Но тогда, это Python, зачем писать так, как будто это Java?

Python не является Java. Конечно, нет такой вещи как проверка во время компиляции.

Я думаю, что комментариев в документации достаточно. Это позволяет любому пользователю вашего метода набирать help(obj.method) и увидеть, что метод является переопределением.

Вы также можете явно расширить интерфейс с class Foo(Interface), который позволит пользователям печатать help(Interface.method) чтобы получить представление о функциональности, которую должен обеспечить ваш метод.

Импровизация на @mkorpela отличный ответ, вот версия с

более точные проверки, присвоение имен и поднятие объектов Error

def overrides(interface_class):
    """
    Function override annotation.
    Corollary to @abc.abstractmethod where the override is not of an
    abstractmethod.
    Modified from answer https://stackru.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


Вот как это выглядит на практике:

NotImplementedError "не реализовано в базовом классе"

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

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

приводит к более информативным NotImplementedError ошибка

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

полный стек

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 "ожидаемый реализованный тип"

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

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

приводит к более информативным 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'>

полный стек

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'>




Самое замечательное в ответе @mkorpela - проверка происходит во время фазы инициализации. Проверка не должна быть "запущена". Ссылаясь на предыдущие примеры, class B никогда не инициализируется (B()) пока что NotImplementedError все равно поднимет. Это означает overrides ошибки ловятся раньше.

Насколько я знаю, в Python нет особого способа указать переопределение. Вы просто определяете метод и включаете строку документации, как всегда.

Основываясь на отличном ответе @mkorpela, я написал похожий пакет (ipromise pypi github), который выполняет еще много проверок:

Предположим, что A наследует от B и C. А B наследует от C. ipromise проверяет, что

  • Если Af переопределяет Bf, Bf должен существовать, и A должен наследоваться от B. (Это проверка из пакета переопределений).

  • У вас нет шаблона Af, который объявляет, что он переопределяет Bf, а затем объявляет, что он переопределяет Cf. A должен сказать, что он переопределяет из Cf, поскольку B может решить прекратить переопределение этого метода, и это не должно привести к последующим обновлениям.

  • У вас нет шаблона Af, который объявляет, что он переопределяет Cf, но Bf не объявляет его переопределение.

  • У вас нет шаблона Af, который объявляет, что он переопределяет Cf, но Bf объявляет, что он переопределяет некоторый Df.

Он также имеет различные функции для маркировки и проверки реализации абстрактного метода.

Как и другие говорили, в отличие от Java, тег @Overide отсутствует, однако выше вы можете создать свой собственный, используя декораторы, однако я бы предложил использовать глобальный метод getattrib() вместо использования внутреннего dict, чтобы вы получили что-то вроде следующего:

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

Если вы хотите, вы можете поймать getattr() в своем собственном try catch и поднять свою собственную ошибку, но я думаю, что метод getattr лучше в этом случае.

Также это ловит все элементы, связанные с классом, включая методы класса и vairables

Вы можете использовать протоколы из PEP 544. В этом методе отношение интерфейс-реализация объявляется только на сайте использования.

Предполагая, что у вас уже есть реализация (назовем ее MyFoobar), вы определяете интерфейс (протокол), который имеет подписи всех методов и полей вашей реализации, назовем это IFoobar.

Затем на сайте использования вы объявляете привязку экземпляра реализации, чтобы иметь тип интерфейса, например myFoobar: IFoobar = MyFoobar(). Теперь, если вы используете поле / метод, отсутствующий в интерфейсе, Mypy будет жаловаться на сайте использования (даже если он будет работать во время выполнения!). Если вам не удалось реализовать метод из интерфейса в реализации, Mypy также пожалуется. Mypy не будет жаловаться, если вы реализуете что-то, чего нет в интерфейсе. Но это редкий случай, поскольку определение интерфейса компактно и легко просматривается. Вы не сможете использовать этот код, поскольку Mypy будет жаловаться.

Теперь это не касается случаев, когда у вас есть реализации как в суперклассе, так и в классе реализации, например, некоторые варианты использования ABC. Но overrideиспользуется в Java даже без реализации в интерфейсе . Это решение подходит для этого случая.

      from typing import Protocol

class A(Protocol):
    def b(self):
        ...
    def d(self):  # we forgot to implement this in C
        ...

class C:
    def b(self):
        return 0

bob: A = C()

Результат проверки типов:

      test.py:13: error: Incompatible types in assignment (expression has type "C", variable has type "A")
test.py:13: note: 'C' is missing following 'A' protocol member:
test.py:13: note:     d
Found 1 error in 1 file (checked 1 source file)

как и в python 3.6 и выше, функциональность, предоставляемая @override, может быть легко реализована с использованием протокола дескриптора python, а именно метода dunder set_name:

      class override:
    def __init__(self, func):
       self._func = func
       update_wrapper(self, func)

    def __get__(self, obj, obj_type):
        if obj is None:
            return self
        return self._func

    def __set_name__(self, obj_type, name):
        self.validate_override(obj_type, name)

    def validate_override(self, obj_type, name):
        for parent in obj_type.__bases__:
            func = parent.__dict__.get(name, None)
            if callable(func):
                return
        else:
            raise NotImplementedError(f"{obj_type.__name__} does not override {name}")

Обратите внимание, что здесь set_name вызывается после определения обернутого класса, и мы можем получить родительский класс обернутого класса, вызвав его базы методов dunder.

для каждого для его родительского класса мы хотели бы проверить, реализована ли обернутая функция в классе с помощью

  1. проверьте, что имя функции находится в dict класса
  2. это вызываемый

Использование i будет таким же простым, как:

      class AbstractShoppingCartService:
    def add_item(self, request: AddItemRequest) -> Cart:
        ...


class ShoppingCartService(AbstractShoppingCartService):
    @override
    def add_item(self, request: AddItemRequest) -> Cart:
        ...

Вот другое решение без аннотации.

У него немного другая цель. В то время как другие предлагаемые решения проверяют, действительно ли данный метод переопределяет родителя, этот проверяет, были ли переопределены все родительские методы.

Вам не нужно подниматьAssertionError, но может вывести предупреждение или отключить его в рабочей среде, проверив env в__init__и вернуться перед проверкой.

      class Parent:

    def a():
        pass

    def b():
        pass

class Child(Overrides, Parent):

    def a()

    # raises an error, as b() is not overridden


class Overrides:

    def __init__(self):
        # collect all defined methods of all base-classes
        bases = [b for b in self.__class__.__bases__ if b != Overrides]
        required_methods = set()
        for base in bases:
            required_methods = required_methods.union(set([f for f in dir(base) if not f.startswith('_')]))
        
        # check for each method in each base class (in required_methods)
        # if the class, that inherits `Overrides` implements them all
        missing = []
        # me is the fully qualified name of the CLASS, which inherits 
        # `Overrides`
        me = self.__class__.__qualname__
        for required_method in required_methods:

            # The method can be either defined in the parent or the child 
            # class. To check it, we get a reference to the method via 
            # getattr
            try:
                found = getattr(self, required_method)
            except AttributeError:
                # this should not happen, as getattr returns the method in 
                # the parent class if it is not defined in the cild class.
                # It has to be in a parent class, as the required_methods 
                # is a union of all base-class methods.
                missing.append(required_method)
                continue
            
            # here is, where the magic happens.
            # found is a reference to a method, and found.__qualname__ is
            # the full-name of the METHOD. Remember, that me is the full
            # name of the class. 
            # We want to check, where the method is defined. If it is 
            # defined in an parent class, we did no override it, thus it 
            # is missing. 
            # If we did not override, the __qualname__ is Parent.method
            # If we did override it, the __qualname__ is Child.method
            # With this fact, we can determine if the class, which uses
            # `Override` did implement it.
            if not found.__qualname__.startswith(me + '.'):
                missing.append(required_method)

        # Maybe a warning would be enough here
        if missing != []:
            raise AssertionError(f'{me} did not override these methods: {missing}')

В Python 2.6+ и Python 3.2+ вы можете это сделать (насамом деле имитируйте это, Python не поддерживает перегрузку функций, а дочерний класс автоматически переопределяет родительский метод). Для этого мы можем использовать декораторы. Но сначала обратите внимание, что Python@decorators и Java @Annotationsэто совершенно разные вещи. Предыдущий - это оболочка с конкретным кодом, а второй - флаг для компилятора.

Для этого сначала сделайте 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)

выход:

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

Обратите внимание, что вы должны использовать здесь декоратор со скобками

Следует помнить, что, поскольку Python не имеет прямой перегрузки функций, даже если класс B не наследуется от класса A, но нуждается во всех этих foos, чем также вам нужно использовать @Override (хотя в этом случае использование псевдонима Overload будет выглядеть лучше)

Декоратор, который я сделал, не только проверял, является ли имя переопределяющего атрибута в каком-либо суперклассе класса, в котором находится атрибут, без необходимости указывать суперкласс, этот декоратор также проверяет, чтобы переопределяющий атрибут был того же типа, что и переопределенный атрибут. Методы класса обрабатываются как методы, а статические методы обрабатываются как функции. Этот декоратор работает с вызываемыми объектами, методами класса, статическими методами и свойствами.

Исходный код см.: https://github.com/fireuser909/override

Этот декоратор работает только для классов, которые являются экземплярами override.OverridesMeta, но если ваш класс является экземпляром настраиваемого метакласса, используйте функцию create_custom_overrides_meta для создания метакласса, совместимого с декоратором override. Для тестов запустите модуль override.__init__.

Hear является самым простым и работает в Jython с классами 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()
Другие вопросы по тегам