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

173

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

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

Bluu
источник
13
Другими словами, вы никогда не указали, что переопределяете метод? Предоставить это читателю, чтобы он сам это понял?
Bluu
2
Да, я знаю, что это похоже на склонную к ошибкам ситуацию, исходящую от скомпилированного языка, но вы просто должны принять это. На практике я не нашел, чтобы это было большой проблемой (Ruby в моем случае, не Python, но та же идея)
Ed S.
Конечно, сделано. И ответ Триптиха, и ответы Мкорпела просты, мне нравится это, но явный, чрезмерно неявный дух последнего и разумное предотвращение ошибок побеждает.
Bluu
1
Это не то же самое, но абстрактные базовые классы проверяют, все ли абстрактные методы были переопределены подклассом. Конечно, это не поможет, если вы переопределяете конкретные методы.
letmaik

Ответы:

208

Основываясь на этом и ответе 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!!!!!!!
mkorpela
источник
18
Потрясающие. Это поймало ошибку с ошибкой в ​​первый раз, когда я попробовал это. Престижность.
Кристофер Брунс
7
mfbutner: он не вызывается каждый раз, когда метод выполняется - только при создании метода.
mkorpela
3
Это также хорошо для строк документа! overridesможет скопировать строку документации переопределенного метода, если переопределенный метод не имеет своего собственного.
letmaik
5
@mkorpela, хе, этот твой код должен быть в lib системе по умолчанию python. Почему вы не включили это в систему пипсов? : P
5
@mkorpela: О, и я предлагаю сообщить разработчикам ядра Python об этом пакете, они могут рассмотреть возможность добавления декоратора overide в основную систему Python. :)
30

Вот реализация, которая не требует указания имени 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
FWC
источник
2
Немного волшебно, но делает типичное использование намного проще. Можете ли вы включить примеры использования?
Bluu
Каковы средние и наихудшие затраты на использование этого декоратора, возможно, выраженные в сравнении со встроенным декоратором, таким как @classmethod или @property?
larham1
4
@ larham1 Этот декоратор выполняется один раз, когда анализируется определение класса, а не при каждом вызове. Поэтому его стоимость выполнения не имеет значения по сравнению со временем выполнения программы.
Абган
Это будет намного лучше в Python 3.6 благодаря PEP 487 .
Нил Дж
Чтобы получить лучшее сообщение об ошибке: подтвердите любое (hasattr (cls, метод .__ name__) для cls в base_classes), «Переопределенный метод« {} »не найден в базовом классе.». Формат (метод .__ name__)
Иван Ковтун
14

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

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

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

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

Бер
источник
2
Можно добавить фактическую проверку путем проверки для overrideдекоратора.
Эрик Каплун
70
Но тогда, это Python, зачем писать так, как будто это Java? Потому что некоторые идеи в Java хороши и заслуживают распространения на другие языки?
Петр Доброгост
9
Потому что, когда вы переименовываете метод в суперклассе, было бы хорошо знать, что некоторые уровни подкласса 2 переопределяли его. Конечно, это легко проверить, но небольшая помощь от анализатора языка не помешает.
Абган
4
Потому что это хорошая идея. Тот факт, что у многих других языков есть особенность, не является аргументом - ни за, ни против.
sfkleach
6

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

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

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

Триптих
источник
57
Суть @Overrideв Java не в том, чтобы документировать, а в том, чтобы поймать ошибку, когда вы намеревались переопределить метод, но в итоге определили новый (например, потому что вы неправильно написали имя; в Java это также может произойти, потому что вы использовали неправильная подпись, но это не проблема в Python - но орфографическая ошибка все еще есть).
Павел Минаев
2
@ Павел Минаев: Да, но его все равно удобно иметь для документации, особенно если вы используете IDE / текстовый редактор, который не имеет автоматических индикаторов для переопределений (например, JDT в Eclipse показывает их аккуратно рядом с номерами строк).
Туукка Мустонен
2
@PavelMinaev Неправильно. Одним из основных пунктов @Overrideявляется документация в дополнение к проверке времени компиляции.
Сиами
6
@siamii Я думаю, что помощь в документации - это здорово, но во всей официальной документации по Java, которую я вижу, они указывают только на важность проверок времени компиляции. Пожалуйста, подтвердите ваше утверждение, что Павел "не прав".
Эндрю Меллингер
5

Импровизация на @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://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


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

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ошибки обнаруживаются раньше.

JamesThomasMoon1979
источник
Привет! Это выглядит интересно. Не могли бы вы подумать о том, чтобы сделать запрос на извлечение из моего проекта ipromise? Я добавил ответ.
Нил Г
@NeilG Я разветвил проект ipromise и немного кодировал. Похоже, вы по сути реализовали это внутри overrides.py. Я не уверен, что еще я могу значительно улучшить, кроме как изменить типы исключений с TypeErrorна NotImplementedError.
JamesThomasMoon1979
Привет! Спасибо, у меня нет проверок, что переопределенный объект действительно имеет тип types.MethodType. Это была хорошая идея в вашем ответе.
Нил Дж
2

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

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

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

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

Бикер х 2
источник
2

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

Пусть Aнаследует от Bи C, Bнаследуется от C.

Модуль ipromise проверяет, что:

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

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

  • У вас нет шаблона, A.fобъявляющего, что он переопределяет C.f, но B.fне объявляющего его переопределение.

  • У вас нет шаблона, A.fобъявляющего, что он переопределяет C.f, но B.fзаявляющего, что он переопределяет из некоторых D.f.

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

Нил Г
источник
0

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()
user3034016
источник
0

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

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

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

Майкл
источник
0

В Python 2.6+ и Python 3.2+ вы можете это сделать (на самом деле имитируйте это , Python не поддерживает перегрузку функций, а дочерний класс автоматически переопределяет метод parent). Мы можем использовать декораторы для этого. Но сначала обратите внимание, что 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, но нуждается во всех этих foo s, вам также нужно использовать @Override (хотя при использовании псевдонима «Overload» будет выглядеть лучше в таком случае)

mradul
источник