¿Cómo puedo detectar nombres de métodos duplicados en una clase de python?

Al escribir pruebas unitarias, a veces corto y pego una prueba y no recuerdo cambiar el nombre del método. Esto resulta en sobrescribir la prueba anterior, ocultándola de manera efectiva y evitando que se ejecute. Por ejemplo;

class WidgetTestCase(unittest.TestCase): def test_foo_should_do_some_behavior(self): self.assertEquals(42, self.widget.foo()) def test_foo_should_do_some_behavior(self): self.widget.bar() self.assertEquals(314, self.widget.foo()) 

En este caso, solo la última prueba sería llamada. ¿Hay alguna forma de detectar este tipo de error mediante progtwigción, sin analizar directamente el código fuente en bruto?

Si ejecuta pylint sobre su código, le informará cuando haya sobrescrito otro método:

Por ejemplo, corrí esto:

 class A(object): def blah(self): print("Hello World!") def blah(self): print("I give up!") 

En este comprobador de pylint en línea . Además de todas las cadenas de documentación que faltan y eso, me sale esto:

 E: 5:A.blah: method already defined line 2 

Lo que sigue es un truco horrible que usa características de Python no documentadas y específicas de la implementación. Nunca debes hacer algo como esto.

Ha sido probado en Python 2.6.1 y 2.7.2; Parece que no funciona con Python 3.2 como está escrito, pero de todos modos, puedes hacerlo correctamente en Python 3.x.

 import sys class NoDupNames(object): def __init__(self): self.namespaces = [] def __call__(self, frame, event, arg): if event == "call": if frame.f_code.co_flags == 66: self.namespaces.append({}) elif event in ("line", "return") and self.namespaces: for key in frame.f_locals.iterkeys(): if key in self.namespaces[-1]: raise NameError("attribute '%s' already declared" % key) self.namespaces[-1].update(frame.f_locals) frame.f_locals.clear() if event == "return": frame.f_locals.update(self.namespaces.pop()) return self def __enter__(self): self.oldtrace = sys.gettrace() sys.settrace(self) def __exit__(self, type, value, traceback): sys.settrace(self.oldtrace) 

Uso:

 with NoDupNames(): class Foo(object): num = None num = 42 

Resultado:

 NameError: attribute 'num' already declared 

Cómo funciona: Nos enganchamos al gancho de rastreo del sistema. Cada vez que Python está a punto de ejecutar una línea, nos llaman. Esto nos permite ver qué nombres fueron definidos por la última instrucción ejecutada. Para asegurarnos de que podamos detectar duplicados, en realidad mantenemos nuestro propio diccionario de variables locales y eliminamos Python’s después de cada línea. Al final de la definición de la clase, volvemos a copiar nuestros locales en Python’s. Algo de la otra tontería está ahí para manejar definiciones de clase anidadas y para manejar múltiples asignaciones en una sola statement.

Como inconveniente, nuestro “claro TODOS los locales”! enfoque significa que no puedes hacer esto:

 with NoDupNames(): class Foo(object): a = 6 b = 7 c = a * b 

Porque hasta donde Python sabe, no hay nombres a y b cuando c = a * b se ejecuta; Los eliminamos tan pronto como los vimos. Además, si asigna la misma variable dos veces en una sola línea (por ejemplo, a = 0; a = 1 ), no lo detectará. Sin embargo, funciona para definiciones de clase más típicas.

Además, no debe poner nada más que definiciones de clase dentro de un contexto NoDupNames . No sé qué pasará; tal vez nada malo Pero no lo he probado, por lo que en teoría el universo podría ser absorbido por su propio agujero.

Este es posiblemente el código más malvado que he escrito, ¡pero seguro que fue divertido!

Aquí hay una opción sobre cómo detectar esto en tiempo de ejecución usando decoradores sin la necesidad de ninguna herramienta de análisis:

 def one_def_only(): names = set() def assert_first_def(func): assert func.__name__ not in names, func.__name__ + ' defined twice' names.add(func.__name__) return func return assert_first_def class WidgetTestCase(unittest.TestCase): assert_first_def = one_def_only() @assert_first_def def test_foo_should_do_some_behavior(self): self.assertEquals(42, self.widget.foo()) @assert_first_def def test_foo_should_do_some_behavior(self): self.widget.bar() self.assertEquals(314, self.widget.foo()) 

Ejemplo de un bash de importar o ejecutar:

 >>> import testcases Traceback (most recent call last): File "", line 1, in  File "testcases.py", line 13, in  class WidgetTestCase(unittest.TestCase): File "testcases.py", line 20, in WidgetTestCase @assert_first_def File "testcases.py", line 7, in assert_first_def assert func.__name__ not in names, func.__name__ + ' defined twice' AssertionError: test_foo_should_do_some_behavior defined twice 

No puede detectarlo fácil y limpiamente durante el tiempo de ejecución, ya que el método anterior simplemente se reemplaza y debería usarse un decorador en cada definición de función. El análisis estático (pylint, etc.) es la mejor manera de hacerlo.

Sin embargo, es posible que pueda crear una metaclase que implemente __setattr__ y pruebe si se está sobrescribiendo un método. – Solo lo probé y no se llama a __setattr__ de la metaclase para cosas definidas en el bloque de clase.