Подтвердить что ты не робот

Как я могу определить имена повторяющихся методов в классе python?

При написании модульных тестов я иногда вырезаю и вставляю тест и не помню, чтобы изменить имя метода. Это приводит к перезаписыванию предыдущего теста, который эффективно скрывает его и препятствует его запуску. Например:

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())

В этом случае будет вызван только последний тест. Есть ли способ программно поймать такую ​​ошибку, если не разобрать исходный исходный код напрямую?

4b9b3361

Ответ 1

Если вы запустите pylint над своим кодом, он сообщит вам, когда вы перезапишите другой метод:

Например, я запустил это:

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

    def blah(self):
        print("I give up!")

В эта онлайн-проверка pylint. Помимо всех недостающих docstrings и таких, я получаю следующее:

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

Ответ 2

Далее следует ужасный взлом, который использует недокументированные, специфичные для реализации функции Python. Вы никогда не должны когда-либо делать что-либо подобное.

Он был протестирован на Python 2.6.1 и 2.7.2; похоже, не работает с Python 3.2, но тогда вы можете сделать это правильно в 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)

Использование:

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

Результат:

NameError: attribute 'num' already declared

Как это работает: мы подключаемся к системному трассировочному крюку. Каждый раз, когда Python собирается выполнить строку, мы вызываемся. Это позволяет нам видеть, какие имена были определены последним выполненным оператором. Чтобы убедиться, что мы можем поймать дубликаты, мы фактически поддерживаем наш собственный словарь локальных переменных и очищаем Python после каждой строки. В конце определения класса мы скопируем наших локалей обратно в Python. Некоторый из других томов есть там, чтобы обрабатывать вложенные определения классов и обрабатывать несколько назначений в одном выражении.

Как недостаток, наши "ясные ВСЕ локальные жители!" подход означает, что вы не можете этого сделать:

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

Поскольку, насколько известно Python, нет имен a и b, когда выполняется c = a * b; мы очистили их, как только увидели их. Кроме того, если вы дважды назначаете одну и ту же переменную в одной строке (например, a = 0; a = 1), она не поймает ее. Однако он работает для более типичных определений классов.

Кроме того, вы не должны ставить что-либо помимо определений классов внутри контекста NoDupNames. Я не знаю, что произойдет; может быть, ничего плохого. Но я не пробовал, так что теоретически вселенная может всасываться в ее собственную плуголу.

Это, возможно, самый злой код, который я когда-либо писал, но это было весело!

Ответ 3

Вот один из способов определения этого во время выполнения с использованием декораторов без необходимости использования любого инструмента анализа:

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())

Пример попытки импорта или запуска:

>>> import testcases
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "testcases.py", line 13, in <module>
    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

Ответ 4

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

Однако вы можете создать метакласс, который реализует __setattr__, и проверяет, перезаписывается ли этот метод. - Просто протестировал его, а __setattr__ метакласса не вызвали определенных в блоке классов.