以 Python 代码片段为例理解 __getattribute__ 和 pdb.set_trace()

understanding __getattribute__ and pdb.set_trace() with a Python code snippet as an example

我已经阅读了 this on SO 这样的问题,我相信我理解使用 __getattribute__ 的危险。不过最近接了一个别人的项目,需要做一些修改。我认为了解一个项目的最好方法是跟踪它——所以我插入了 pdb.set_trace() 然后我按下了 "n\r\n"——但是程序没有执行到下一行并等待新的input 而是继续执行直到结束。经过搜索,我认为是 __getattribute__ 的误用导致了问题,但我不知道为什么。我将代码简化为以下内容:

class TestAttribute(object):
    """docstring for TestAttribute"""
    def __init__(self, is_testing=False):
        super(TestAttribute, self).__init__()
        self.is_testing = is_testing

    def __getattribute__(self, name):
        # print(name)
        try:
            # the line below will trigger the recursion error
            if self.is_testing:
                name = name.upper()
            return super(TestAttribute, self).__getattribute__(name)
        except AttributeError:
            return None
        except Exception:
            # this line is added by me to see the output
            import traceback; traceback.print_exc();
            return None

    def __getitem__(self, name):
        return self.__getattribute__(name)

    def __setitem__(self, name, val):
        return self.__setattr__(name, val)

    def __setattr__(self, name, val):
        # so this func will be called in __init__ and will
        # enter __getattribute__
        if self.is_testing:
            name = name.lower()
        super(TestAttribute, self).__setattr__(name, val)


if __name__ == '__main__':
    ttt = TestAttribute()
    import pdb; pdb.set_trace()
    ttt.k = 1
    print('test done')
    print('test done again')
    print('test done again')
    print('test done again')

输出如下:

Traceback (most recent call last):
  File "test_getattribute.py", line 10, in __getattribute__
Traceback (most recent call last):
  File "test_getattribute.py", line 10, in __getattribute__
    if self.is_testing:
  File "test_getattribute.py", line 16, in __getattribute__
    import traceback; traceback.print_exc();
  File "/usr/lib/python2.7/traceback.py", line 232, in print_exc
    print_exception(etype, value, tb, limit, file)
  File "/usr/lib/python2.7/traceback.py", line 125, in print_exception
    print_tb(tb, limit, file)
  File "/usr/lib/python2.7/traceback.py", line 69, in print_tb
    line = linecache.getline(filename, lineno, f.f_globals)
  File "/home/jgu/repos/.venv/lib/python2.7/linecache.py", line 14, in getline
    lines = getlines(filename, module_globals)
  File "/home/jgu/repos/.venv/lib/python2.7/linecache.py", line 40, in getlines
    return updatecache(filename, module_globals)
RuntimeError: maximum recursion depth exceeded
> /home/jgu/repos/dat_cs/test_getattribute.py(34)<module>()
-> ttt.k = 1
(Pdb) n
Traceback (most recent call last):
  File "test_getattribute.py", line 10, in __getattribute__
    if self.is_testing:
  File "test_getattribute.py", line 7, in __getattribute__
    def __getattribute__(self, name):
  File "/usr/lib/python2.7/bdb.py", line 50, in trace_dispatch
    return self.dispatch_call(frame, arg)
  File "/usr/lib/python2.7/bdb.py", line 76, in dispatch_call
    if not (self.stop_here(frame) or self.break_anywhere(frame)):
  File "/usr/lib/python2.7/bdb.py", line 147, in break_anywhere
    return self.canonic(frame.f_code.co_filename) in self.breaks
  File "/usr/lib/python2.7/bdb.py", line 29, in canonic
    if filename == "<" + filename[1:-1] + ">":
RuntimeError: maximum recursion depth exceeded in cmp
test done
test done again
test done again
test done again

如您所见,我只按了"n\r\n",执行一直持续到程序结束。

还有一个小问题,如果我 运行 没有 pdb,我会看到这个输出:

Traceback (most recent call last):
  File "test_getattribute.py", line 10, in __getattribute__
Traceback (most recent call last):
  File "test_getattribute.py", line 10, in __getattribute__
    if self.is_testing:
  File "test_getattribute.py", line 16, in __getattribute__
    import traceback; traceback.print_exc();
  File "/usr/lib/python2.7/traceback.py", line 232, in print_exc
    print_exception(etype, value, tb, limit, file)
  File "/usr/lib/python2.7/traceback.py", line 125, in print_exception
    print_tb(tb, limit, file)
  File "/usr/lib/python2.7/traceback.py", line 69, in print_tb
    line = linecache.getline(filename, lineno, f.f_globals)
  File "/home/jgu/repos/.venv/lib/python2.7/linecache.py", line 14, in getline
    lines = getlines(filename, module_globals)
  File "/home/jgu/repos/.venv/lib/python2.7/linecache.py", line 40, in getlines
    return updatecache(filename, module_globals)
RuntimeError: maximum recursion depth exceeded
Traceback (most recent call last):
Traceback (most recent call last):
  File "test_getattribute.py", line 10, in __getattribute__
    if self.is_testing:
  File "test_getattribute.py", line 16, in __getattribute__
    import traceback; traceback.print_exc();
  File "/usr/lib/python2.7/traceback.py", line 232, in print_exc
    print_exception(etype, value, tb, limit, file)
  File "/usr/lib/python2.7/traceback.py", line 125, in print_exception
    print_tb(tb, limit, file)
  File "/usr/lib/python2.7/traceback.py", line 67, in print_tb
    '  File "%s", line %d, in %s' % (filename, lineno, name))
RuntimeError: <unprintable RuntimeError object>
test done
test done again
test done again
test done again

所以第二个错误没有打印正确,这是为什么?

编辑: 我不是在问为什么会出现递归错误。我相信我对那部分很清楚——所以请先理解我的问题。谢谢

pdb 使用 sys.settrace 设置跟踪函数来完成它的工作。传播到跟踪函数之外的任何异常都会禁用它。 RuntimeError 发生在 trace 函数内部,一旦发生,基本上会关闭 pdb。