如何在python类中检测重复的方法名称?

How can I detect duplicate method names in a python class?

在编写单元测试时,我有时剪切粘贴一个测试,不记得更改方法名。这会导致覆盖上一个测试,有效地隐藏它并阻止它运行。例如;

1
2
3
4
5
6
7
8
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())

在这种情况下,只调用后一个测试。除了直接解析原始源代码之外,是否有一种以编程方式捕获此类错误的方法?


如果您在代码上运行pylint,它将在您覆盖另一个方法时通知您:

例如,我运行了以下命令:

1
2
3
4
5
6
class A(object):
    def blah(self):
        print("Hello World!")

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

在这个在线皮林检查。除了所有丢失的文档字符串等,我还得到了:

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


接下来是一个可怕的黑客,它使用了未记录的、特定于实现的Python特性。你不应该做这种事。

它已经在python 2.6.1和2.7.2上进行了测试;看起来不像上面写的那样可以与python 3.2一起使用,但是无论如何,您都可以在python3.x中正确地进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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)

用途:

1
2
3
4
with NoDupNames():
    class Foo(object):
        num = None
        num = 42

结果:

1
NameError: attribute 'num' already declared

工作原理:我们连接到系统跟踪挂钩。每次Python要执行一行时,我们都会被调用。这允许我们查看最后执行的语句所定义的名称。为了确保能够捕获重复的内容,我们实际上维护了自己的局部变量字典,并在每行之后清除了python。在类定义的末尾,我们将局部变量复制回python中。其中的一些tombourry用于处理嵌套类定义,并在一条语句中处理多个赋值。

不利的是,我们的"清除所有当地人!"方法意味着你不能这样做:

1
2
3
4
5
with NoDupNames():
    class Foo(object):
        a = 6
        b = 7
        c = a * b

因为据python所知,在执行c = a * b时,没有名称ab;我们一看到它们就清除了这些名称。而且,如果在一行(例如a = 0; a = 1中分配同一个变量两次,它就不会捕捉到这一点。但是,它适用于更典型的类定义。

此外,除了类定义之外,您不应该将任何内容放在NoDupNames上下文中。我不知道会发生什么,也许没什么不好的。但我没有尝试过,所以理论上宇宙可以被吸进它自己的黑洞。

这很可能是我写过的最邪恶的代码,但它确实很有趣!


下面是一个选项,用于在运行时使用decorator检测此问题,而不需要任何分析工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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())

尝试导入或运行的示例:

1
2
3
4
5
6
7
8
9
10
>>> 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

您不能在运行时轻松/干净地检测它,因为旧方法被简单地替换,并且必须在每个函数定义上使用修饰符。静态分析(pylint等)是最好的方法。

但是,如果方法被覆盖,则可以创建实现__setattr__和测试的元类。-just tested it and __setattr__of the metaclass is not called for stufactured in the class block.