获取定义了变量的行号

时间:2019-06-08 13:41:44

标签: python debugging inspect

我正在尝试提取创建特定变量的行号。这听起来像是一个简单的问题,毕竟,我可以遍历代码行并尝试搜索或执行正则表达式。但这可能是错误的行号,如果可能的话,我希望按原样解析Python代码并获得真实的行号。

这就是我所拥有的:

class MyClass:

    """
    MyClass.

    Notice that we do override the name class attribute.

    class MyClass:

        name = "a real name"

    """

    name = "the true name"

    def __iinit__(self):
        pass

由于inspect.getsourcelines,我可以得到该类的行(在一个文件中可以定义多个类,并且它们可能都具有不同的name属性)。我需要的是找到为该类定义name类属性的行号。使用astdis?我不反对任何建议。

  

这对于调试很有用。在我的情况下,class属性是包含某些格式文本的多行字符串,发生问题时,我需要报告错误的文件名和行号。因此,我宁愿知道该属性在哪里定义,而不仅仅是知道它的值。

感谢您的帮助,

编辑

基于@bsbueno的回答,我试图提出一些简单的,不会干扰我原始设计的东西,它可能会按照原样流动。我正在创建一个简单的元类,其唯一的工作就是注册该属性。请注意,我仍在Child类的函数调用中“包装”属性,因此此解决方案并不理想,并且比在单独的命名空间中定义register函数要好得多。总而言之,我所做的并不是最好的选择,但是它提供了另一个示例,可能对其他人有用,所以我希望:

import sys

class MetaRegister(type):

    def __prepare__(name, bases, **kwds):
        return {"register": MetaRegister.register}

    @staticmethod
    def register(value):
        frame = sys._getframe().f_back
        file = frame.f_globals["__file__"]
        line = frame.f_lineno
        print(f"Calling register {file}:{line}: {value!r}")
        return value

class Parent(metaclass=MetaRegister):

    option = None

class Child(Parent):

    option = register(3)

在创建其他类(__prepare__Parent)之前,将调用元类的Child方法。它将在其名称空间中添加一些内容(在本例中为register函数)。它直接在Child的类主体中使用。 register函数(实际上是元类本身的静态方法)没有做很多事情,只是在第一次调用它的地方进行打印。这对我来说已经足够了,尽管正如我已经说过的那样,这是一种解决方案,感觉好像没有解决太多问题。公开意见!

1 个答案:

答案 0 :(得分:1)

对于函数和全局变量,这或多或少是直接的。很难理解,对于类属性,使用“ dis”,这可能更令人费解-因为类主体首先被编译为代码对象,该类对象在定义类时运行一次,而所有类则全部“扔掉”已正确创建。

那时,您只有类'__dict__,根本没有创建顺序或创建位置的提示。

此外,使用dis可以通过搜索STORE_NAME操作码来查找变量归因于何处,同样存在一个问题,就是不知道其中的 是运行-但这不比在源代码本身上对模式进行文本搜索更为可靠。

执行此操作的唯一可靠方法是,如果使用特殊描述符而不是使用常规变量,则这些描述符可以检查进行调用以设置其值的位置,并在注册表中进行注释。

因此,您所需要做的就是将要跟踪的变量放在一个特殊的对象中,该对象可以有一个短名称-最终代码如下所示:

from variable_registry import R

class MyClass:

   R.name = "the true name"

   def __init__(self):
        print(R.name)

如果需要实例变量,这将需要特殊处理-但仍然可以通过更复杂的机制来完成。对于类和全局变量,在非并行执行中,您最好像局部变量一样。

import sys

class Registry:
    def __init__(self):
        self.__dict__["_registry"] = {}

    def __setattr__(self, name, value):
        frame = sys._getframe().f_back
        file = frame.f_globals["__file__"]
        self.__dict__["_registry"][name] = file, frame.f_lineno, value
        super().__setattr__(name, value)


R = Registry()