En Python, ¿cómo indico que estoy anulando un método?

12 minutos de lectura

avatar de usuario
Azul

En Java, por ejemplo, el @Override La anotación no solo proporciona una verificación en tiempo de compilación de una anulación, sino que también es un excelente código de autodocumentación.

Solo estoy buscando documentación (aunque si es un indicador para algún verificador como pylint, eso es una ventaja). Puedo agregar un comentario o una cadena de documentos en algún lugar, pero ¿cuál es la forma idiomática de indicar una anulación en Python?

  • En otras palabras, ¿nunca indica que está anulando un método? ¿Dejar que el lector lo averigüe por sí mismo?

    – Azul

    22 de julio de 2009 a las 23:49

  • Sí, sé que parece una situación propensa a errores proveniente de un lenguaje compilado, pero solo tienes que aceptarlo. En la práctica, no he encontrado que sea un gran problema (Ruby en mi caso, no Python, pero la misma idea)

    – Ed S.

    23 de julio de 2009 a las 17:12

  • Claro, hecho. Tanto la respuesta de Triptych como la de mkorpela son simples, me gusta eso, pero gana el espíritu explícito sobre implícito de este último y la prevención inteligible de errores.

    – Azul

    17/10/2013 a las 16:07

  • No es directamente lo mismo, pero clases base abstractas compruebe si todos los métodos abstractos han sido anulados por una subclase. Por supuesto, esto no ayuda si está anulando métodos concretos.

    – letmaik

    29 de mayo de 2014 a las 10:27

avatar de usuario
mkorpela

Basado en esto y en la respuesta de fwc: s, creé un paquete instalable pip https://github.com/mkorpela/overrides

De vez en cuando termino aquí mirando esta pregunta. Principalmente, esto sucede después de (nuevamente) ver el mismo error en nuestra base de código: alguien olvidó alguna clase de implementación de “interfaz” al cambiar el nombre de un método en la “interfaz”.

Bueno, Python no es Java, pero Python tiene poder, y lo explícito es mejor que lo implícito, y hay casos concretos reales en el mundo real en los que esto me habría ayudado.

Así que aquí hay un boceto del decorador de anulaciones. Esto verificará que la clase dada como parámetro tenga el mismo nombre de método (o algo parecido) que el método que se está decorando.

Si se le ocurre una solución mejor, publíquela aquí.

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

Funciona de la siguiente manera:

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


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

y si hace una versión defectuosa, generará un error de afirmación durante la carga de la clase:

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

>> AssertionError!!!!!!!

  • Impresionante. Esto detectó un error de ortografía la primera vez que lo probé. Prestigio.

    – Christopher Bruns

    12 de enero de 2012 a las 23:15

  • mfbutner: no se llama cada vez que se ejecuta el método, solo cuando se crea el método.

    – mkorpela

    22 de enero de 2014 a las 10:42

  • ¡Esto también es bueno para cadenas de documentos! overrides podría copiar la cadena de documentación del método anulado si el método anulado no tiene uno propio.

    – letmaik

    29 mayo 2014 a las 10:20

  • @mkorpela, Je, este código tuyo debería estar en el sistema lib predeterminado de Python. ¿Por qué no pones esto en el sistema pip? :PAGS

    usuario2081554

    20 de mayo de 2015 a las 12:16


  • @mkorpela: Ah, y sugiero informar a los desarrolladores del núcleo de python sobre este paquete, es posible que deseen considerar agregar un decorador anulado al sistema del núcleo de python. 🙂

    usuario2081554

    23 mayo 2015 a las 14:00


Aquí hay una implementación que no requiere la especificación del nombre de clase_interfaz.

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

  • Un poco mágico pero hace que el uso típico sea mucho más fácil. ¿Puedes incluir ejemplos de uso?

    – Azul

    17/10/2013 a las 16:06

  • ¿Cuáles son los costos promedio y en el peor de los casos de usar este decorador, tal vez expresados ​​como una comparación con un decorador incorporado como @classmethod o @property?

    – larham1

    17 de diciembre de 2013 a las 21:23

  • @larham1 Este decorador se ejecuta una vez, cuando se analiza la definición de clase, no en cada llamada. Por lo tanto, su costo de ejecución es irrelevante, en comparación con el tiempo de ejecución del programa.

    – Abgan

    6 de abril de 2014 a las 9:10

  • Esto será mucho mejor en Python 3.6 gracias a PEP 487.

    – Neil G.

    19 de julio de 2016 a las 2:05


  • Para obtener un mejor mensaje de error: afirmar cualquier (hasattr (cls, método.__nombre__) para cls en clases_base), ‘El método anulado “{}” no se encontró en la clase base.’.formato (método.__nombre__)

    – Iván Kovtun

    16 de agosto de 2019 a las 23:53


Si desea esto solo con fines de documentación, puede definir su propio decorador de anulación:

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

Esto no es más que un atractivo para la vista, a menos que cree override(f) de tal manera que en realidad verifique si hay una anulación.

Pero entonces, esto es Python, ¿por qué escribirlo como si fuera Java?

  • Se podría agregar la validación real a través de la inspección a la override decorador.

    – Erik Kaplún

    29 de julio de 2011 a las 22:53


  • Pero entonces, esto es Python, ¿por qué escribirlo como si fuera Java? ¿Porque algunas ideas en Java son buenas y vale la pena extenderlas a otros lenguajes?

    –Piotr Dobrogost

    17 de febrero de 2013 a las 18:53


  • Porque cuando cambia el nombre de un método en una superclase, sería bueno saber que algunos niveles de subclase 2 hacia abajo lo estaban anulando. Claro, es fácil de verificar, pero un poco de ayuda del analizador de lenguaje no estaría de más.

    – Abgan

    9 de mayo de 2013 a las 13:01

  • Porque es una buena idea. El hecho de que una variedad de otros idiomas tengan la función no es un argumento, ni a favor ni en contra.

    – sfkleach

    16 de junio de 2017 a las 7:45

avatar de usuario
JamesThomasMoon

Improvisando en @mkorpela gran respuesta, aquí hay una versión con

verificaciones más precisas, nombres y objetos de error elevados

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

Esto es lo que parece en la práctica:

NotImplementedErrorno implementado en la clase base

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

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

resultados más descriptivos NotImplementedError error

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

completa pila

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

NotImplementedErrortipo implementado esperado

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

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

resultados más descriptivos NotImplementedError error

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

completa pila

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


Lo mejor de la respuesta de @mkorpela es que la verificación ocurre durante alguna fase de inicialización. No es necesario “ejecutar” la comprobación. Haciendo referencia a los ejemplos anteriores, class B nunca se inicializa (B()) sin embargo, el NotImplementedError seguirá subiendo. Esto significa overrides los errores se detectan antes.

Python no es Java. Por supuesto, no existe tal cosa como la verificación en tiempo de compilación.

Creo que un comentario en la cadena de documentación es suficiente. Esto permite que cualquier usuario de su método escriba help(obj.method) y vea que el método es una anulación.

También puede extender explícitamente una interfaz con class Foo(Interface)que permitirá a los usuarios escribir help(Interface.method) para tener una idea de la funcionalidad que su método pretende proporcionar.

  • El verdadero punto de @Override en Java no es para documentar, es para detectar un error cuando intentó anular un método, pero terminó definiendo uno nuevo (por ejemplo, porque escribió mal un nombre; en Java, también puede suceder porque usó la firma incorrecta, pero esto no es un problema en Python, pero el error de ortografía todavía lo es).

    – Pavel Minaev

    22 de julio de 2009 a las 19:35

  • @ Pavel Minaev: Cierto, pero aún es conveniente tener documentación, especialmente si está utilizando un IDE / editor de texto que no tiene indicadores automáticos para anulaciones (JDT de Eclipse los muestra claramente junto con los números de línea, por ejemplo).

    – Tuukka Mustonen

    3 de agosto de 2011 a las 12:19

  • @PavelMinaev Incorrecto. Uno de los principales puntos de @Override es documentación además de verificar el tiempo de compilación.

    – siamii

    20 de noviembre de 2011 a las 9:15

  • @siamii Creo que una ayuda para la documentación es excelente, pero en toda la documentación oficial de Java que veo, solo indican la importancia de las verificaciones de tiempo de compilación. Justifique su afirmación de que Pavel está “equivocado”.

    – Andrew Mellinger

    2 de junio de 2014 a las 11:13

avatar de usuario
Disputa x 2

Como otros han dicho, a diferencia de Java, no hay una etiqueta @Overide; sin embargo, arriba puede crear la suya propia usando decoradores; sin embargo, sugeriría usar el método global getattrib() en lugar de usar el dict interno para obtener algo como lo siguiente:

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

Si quisiera, podría capturar getattr() en su propio intento de captura y generar su propio error, pero creo que el método getattr es mejor en este caso.

Además, esto captura todos los elementos vinculados a una clase, incluidos los métodos de clase y las variables.

  • El verdadero punto de @Override en Java no es para documentar, es para detectar un error cuando intentó anular un método, pero terminó definiendo uno nuevo (por ejemplo, porque escribió mal un nombre; en Java, también puede suceder porque usó la firma incorrecta, pero esto no es un problema en Python, pero el error de ortografía todavía lo es).

    – Pavel Minaev

    22 de julio de 2009 a las 19:35

  • @ Pavel Minaev: Cierto, pero aún es conveniente tener documentación, especialmente si está utilizando un IDE / editor de texto que no tiene indicadores automáticos para anulaciones (JDT de Eclipse los muestra claramente junto con los números de línea, por ejemplo).

    – Tuukka Mustonen

    3 de agosto de 2011 a las 12:19

  • @PavelMinaev Incorrecto. Uno de los principales puntos de @Override es documentación además de verificar el tiempo de compilación.

    – siamii

    20 de noviembre de 2011 a las 9:15

  • @siamii Creo que una ayuda para la documentación es excelente, pero en toda la documentación oficial de Java que veo, solo indican la importancia de las verificaciones de tiempo de compilación. Justifique su afirmación de que Pavel está “equivocado”.

    – Andrew Mellinger

    2 de junio de 2014 a las 11:13

avatar de usuario
Neurona

Basado en la gran respuesta de @mkorpela, he escrito un paquete similar (lo prometo pypi github) que hace muchas más comprobaciones:

Suponer A hereda de B y C, B hereda de C.

Módulo lo prometo comprueba que:

  • Si A.f anula B.f, B.f debe existir, y A debe heredar de B. (Este es el cheque del paquete overrides).

  • no tienes el patrón A.f declara que anula B.fque luego declara que anula C.f. A debería decir que anula desde C.f ya que B podría decidir dejar de anular este método, y eso no debería dar lugar a actualizaciones posteriores.

  • no tienes el patrón A.f declara que anula C.fpero B.f no declara su anulación.

  • no tienes el patrón A.f declara que anula C.fpero B.f declara que anula de algunos D.f.

También tiene varias características para marcar y verificar implementando un método abstracto.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad