为什么使用 PyCharm 调试没有按预期进入断点?

前言

最近调试一段复杂代码的时候遇到一个问题,我在某处打了断点,并认为按照预期应该会运行到指定的断点,但遗憾的是并没有。几经排查,发现了一处隐藏的“坑”。

用简单的代码复现

简单起见,用下面这段代码来复现遇到的问题:

class Person(object):

    def __init__(self, id):
        self.id = id
        self._person = None
        self._name = None

    @property
    def name(self):
        if self._person is None:
            self._person = db_get_person(self.id)
        return self._person['name']

    def __repr__(self):
        return ''.format(self.name)

def db_get_person(id):
    return {
        'name': 'Jack'
    }

if __name__ == '__main__':
    p = Person('Jack')
    print(p.name)

这段代码定义了一个 Person 类型,构造函数接收 id 参数。它有一个 name 属性,在首次访问时会调用 db_get_person() (示例就直接返回了)函数从数据库根据 id 加载这个人的信息,并将之缓存,后续再访问时就返回缓存值。另外,还定义了 __repr__ 魔法方法,为了在打印 Person 对象时能够更加友好。

让我们在第 1824 行打上断点并开始调试,首先会运行到第 24 行。当点击继续运行下一个断点时,会发现程序直接运行结束了,而没有如预期进行。

为什么 PyCharm 没有运行到指定断点?

file

仔细看下运行到第 24 行的调试界面,在 Variables 面板中会自动显示出当前作用域的所有变量。其中就有变量 p,而它显示为了 ,这说明此时 PyCharm 已经自动运行了 Person.__repr__,而此魔法方法调用了 self.name。当运行第 24 行的 p.name 时,已不是首次运行。

看到这里就真相大白了,PyCharm 的 Variables 面板中的变量会被自动运行 Person.__repr__。在此阶段中,该方法涉及到的逻辑如果有断点是不会被暂停的,换句话说也就不会运行到指定的断点。

PyCharm 调试界面中除了 Variables 面板可以查看变量外,Watches 面板也能够执行任意表达式来观察。如果使用不慎,也可能遇到相同问题。比如:断点运行到第 24 行时,在 Watches 面板中添加变量 p.name

该如何解决?

既然明白了原因,那解决的思路就是不让 PyCharm 自动运行到特定的代码。

方法一:取消第 24 行断点

由于取消了第 24 行断点,调试逻辑不会在第 24 行停留,那么 Variables 面板就不会显示变量 p,也就不会调用 Person.__repr__。在运行第 24 行的 p.name 时就是首次运行,自然就能运行到第 18 行断点。

方法二:临时注释掉 Person.repr

由于注释了 Person.__repr__,调试停留在第 24 行时,尽管 Variables 面板显示了变量 p,并不会通过 Person.__repr__ 调用 p.name。这样,也能够确保运行第 24 行的 p.name 是首次运行,自然就能运行到第 18 行断点。

总结

使用 PyCharm 调试具有缓存逻辑的代码时,要注意 VariablesWatches 面板中的变量是否已被自动执行对应的逻辑,从而导致没有进入到预期的断点。

你可能感兴趣的:(为什么使用 PyCharm 调试没有按预期进入断点?)