Получить номер строки, где определена переменная - PullRequest
0 голосов
/ 08 июня 2019

Я пытаюсь извлечь номер строки, в которой была создана конкретная переменная.Это звучит как простой вопрос, в конце концов, я мог бы пройтись по строкам кода и попытаться выполнить поиск или сделать регулярное выражение.Но это может быть неверный номер строки, и я бы предпочел, если возможно, проанализировать код 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.Использовать ast?dis?Я не против какого-либо предложения.

Это полезно для отладки.Атрибут class в моем случае - это многострочная строка, содержащая некоторый форматированный текст, и мне нужно сообщить об ошибке с именем файла и номером строки при возникновении проблем.Поэтому я предпочел бы знать, где определяется атрибут, а не просто иметь его значение.

Спасибо за вашу помощь,

Редактировать

На основе @bsbueno'sответ, я пытался придумать что-то простое, что не мешало бы моему первоначальному дизайну, протекало так, как это возможно.Я создаю простой метакласс, единственной задачей которого является регистрация этого атрибута.Обратите внимание, что я все еще «оборачиваю» атрибут в вызове функции в классе 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 Ответ

1 голос
/ 08 июня 2019

Хотя для функций и глобальных переменных это было бы более или менее просто.что странно, используя «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()
...