Як я можу вказати, що в Python я переважаю метод?


173

Наприклад, у Java, @Overrideанотація не лише забезпечує перевірку перезапису в часі компіляції, але забезпечує відмінний код самодокументування.

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


13
Іншими словами, ви ніколи не вказуєте на те, що ви переосмислили метод? Залишити це читачеві, щоб розібратися в цьому сам?
Блу

2
Так, я знаю, схоже на ситуацію, схильну до помилок, що виходить зі складеної мови, але ви просто повинні це прийняти. На практиці я не виявив, що це велика проблема (у моєму випадку Рубі, не Python, але така ж ідея)
Ed S.

Звичайно, зробили. І відповіді Триптиха, і відповіді Мкорпела прості, мені це подобається, але явний надто неявний дух останнього та розумне запобігання помилкам перемагає.
Блу

1
Це не однаково, але абстрактні базові класи перевіряють, чи всі абстрактні методи були замінені підкласом. Звичайно, це не допоможе, якщо ви перекреслюєте конкретні методи.
letmaik

Відповіді:


206

Виходячи з цього і відповіді 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!!!!!!!

17
Дивовижно. Уперше я спробував помилку помилкового написання. Кудос.
Крістофер Брунс

7
mfbutner: він не викликається кожен раз, коли метод виконується - лише тоді, коли метод створений.
mkorpela

3
Це також приємно для doc-струн! overridesможе скопіювати docstring методу, що перекрито, якщо метод переосмислення не має свого власного.
letmaik

5
@mkorpela, Heh цей ваш код повинен бути в системі lib python за замовчуванням. Чому ви не ставите це в систему піп? : P

5
@mkorpela: О, і я пропоную повідомити розробникам ядра python про цей пакет, вони, можливо, захочуть розглянути питання про додавання додаткового декоратора до основної системи 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

2
Трохи магічне, але значно полегшує типове використання. Чи можете ви включити приклади використання?
Блу

які середні та найгірші витрати на використання цього декоратора, можливо, виражені порівнянням із вбудованим декоратором, наприклад @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. Звичайно, немає такої речі, як перевірка часу збирання.

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

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


57
Справжній сенс @OverrideJava не в документі - це помилка, коли ви мали намір замінити метод, але в кінцевому підсумку визначили новий (наприклад, тому, що ви неправильно написали ім'я; в Java це може статися і тому, що ви використовували неправильний підпис, але це не проблема в Python - але орфографічна помилка все ж є).
Павло Мінаєв

2
@ Павел Мінаєв: Щоправда, але це все ще зручно мати документацію, особливо якщо ви використовуєте редактор IDE / текст, який не має автоматичних індикаторів для переопределення (наприклад, JDT Eclipse показує їх акуратно поряд із номерами рядків).
Tuukka Mustonen

2
@PavelMinaev Неправильно. Один з головних моментів - @Overrideце документація, крім складання перевірки часу.
Сіамій

6
@siamii Я думаю, що допомога в документації є чудовою, але в усіх офіційних документах Java, які я бачу, вони лише вказують на важливість перевірки часу компіляції. Обґрунтуйте, будь ласка, свою заяву, що Павло "неправий".
Ендрю Меллінгер

5

Імпровізація на @mkorpela чудова відповідь , ось версія з

точніші перевірки, іменування та підняті помилки

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помилки виявляються швидше.


Гей! Це виглядає цікаво. Чи можете ви подумати про те, щоб зробити запит на тягнення до мого ipromise проекту? Я додав відповідь.
Ніл Г

@NeilG Я відправив проект ipromise і трохи зашифрував . Схоже, ви по суті це реалізували overrides.py. Я не впевнений, що ще можу значно покращити, окрім зміни типів виключень з TypeErrorна NotImplementedError.
JamesThomasMoon1979

Гей! Дякую, я не маю перевірок, чи перетворений об’єкт насправді має тип types.MethodType. Це була гарна ідея у вашій відповіді.
Ніл Г

2

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

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

Якщо ви хотіли, щоб ви могли зловити getattr () у вашому власному, спробуйте вловити помилку, але я думаю, що метод getattr в цьому випадку кращий.

Також цей вміст вловлює всі елементи, прив’язані до класу, включаючи методи класу та параметри


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

Слухати найпростіше і працювати в класах 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()

0

Мало того, що декоратор, який я зробив, перевірив, чи є ім'я атрибуту, що перекриває, якийсь надклас класу, в якому є атрибут, не вказуючи надклас, цей декоратор також перевіряє, чи повинен атрибут, що перекриває, той самий тип, що і переосмислений атрибут. Методи класу трактуються як методи, а статичні методи - як функції. Цей декоратор працює на дзвінки, класичні методи, статичні методи та властивості.

Для вихідного коду дивіться: https://github.com/fireuser909/override

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


0

У 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 (хоча використання псевдоніма «Перевантаження» буде виглядати в цьому випадку краще)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.