Как получить имена переменных при вызове функции - PullRequest
0 голосов
/ 05 сентября 2018

Я собираюсь написать декоратор, который оценивает фактические имена (не их значения) переменных, которые передаются в вызов функции.

Ниже вы найдете скелет кода, который делает его более понятным, что я хочу сделать.

import functools

def check_func(func):
    # how to get variable names of function call
    # s.t. a call like func(arg1, arg2, arg3)
    # returns a dictionary {'a':'arg1', 'b':'arg2', 'c':'arg3'} ?
    pass

def my_decorator(func):
    @functools.wraps(func)
    def call_func(*args, **kwargs):
        check_func(func)
        return func(*args, **kwargs)

    return call_func

@my_decorator
def my_function(a, b, c):
    pass

arg1='foo'
arg2=1
arg3=[1,2,3]
my_function(arg1,arg2,arg3)

1 Ответ

0 голосов
/ 05 сентября 2018

Вы не можете получить то, о чем просите.

Есть много способов вызова функции, когда вы даже не получите имен переменных для отдельных значений. Например, какие будут имена при использовании литеральных значений в вызове, так:

my_function('foo', 10 - 9, [1] + [2, 3])

или когда вы используете список со значениями для расширения аргумента с *:

args = ['foo', 1, [1, 2, 3]]
my_function(*args)

Или когда вы используете functools.partial() объект для привязки некоторых значений аргумента к вызываемому объекту:

from functools import partial
func_partial = partial(my_function, arg1, arg2)
func_partial(arg3)

Передаются функции объекты (значения), а не переменные. Выражения, состоящие только из имен, могли использоваться для создания объектов, но эти объекты не зависят от переменных.

Объекты Python могут иметь много разных ссылок , так как тот факт, что вызов использовал arg1, не означает, что в другом месте не будет других ссылок на объект, которые были бы более интересны для вашего код.

Вы можете попытаться проанализировать код, который вызвал функцию (модуль inspect может дать вам доступ к стеку вызовов ), но тогда это предполагает, что исходный код доступен. Вызывающий код может использовать расширение C, или интерпретатор имеет доступ только к файлам .pyc байт-кода, но не к исходному исходному коду. Вам все равно придется проследить и проанализировать выражение вызова (не всегда это просто, функции тоже являются объектами и могут храниться в контейнерах и извлекаться позже для динамического вызова) и оттуда находят переменные , если есть какие-либо на все .

Для тривиального случая, когда для вызова использовались только прямые имена позиционных аргументов, а весь вызов был ограничен одной строкой исходного кода, вы можете использовать комбинацию inspect.stack() и ast модуль для разбора источника на что-то достаточно полезное для анализа:

import inspect, ast

class CallArgumentNameFinder(ast.NodeVisitor):
    def __init__(self, functionname):
        self.name = functionname
        self.params = []
        self.kwargs = {}

    def visit_Call(self, node):
        if not isinstance(node.func, ast.Name):
            return  # not a name(...) call
        if node.func.id != self.name:
            return  # different name being called
        self.params = [n.id for n in node.args if isinstance(n, ast.Name)]
        self.kwargs = {
            kw.arg: kw.value.id for kw in node.keywords
            if isinstance(kw.value, ast.Name)
        }

def check_func(func):
    caller = inspect.stack()[2]  # caller of our caller
    try:
        tree = ast.parse(caller.code_context[0])
    except SyntaxError:
        # not a complete Python statement
        return None
    visitor = CallArgumentNameFinder(func.__name__)
    visitor.visit(tree)
    return inspect.signature(func).bind_partial(
        *visitor.params, **visitor.kwargs)

Опять же, для акцента: это работает только с самыми основными вызовами, где вызов состоит только из одной строки, а вызываемое имя совпадает с именем функции. Это можно расширить, но это требует много работы.

Для вашего конкретного примера получается <BoundArguments (a='arg1', b='arg2', c='arg3')>, поэтому inspect.BoundArguments экземпляр . Используйте .arguments, чтобы получить OrderedDict отображение с парами имя-значение, или dict(....arguments), чтобы превратить это в обычный словарь.

Вместо этого вам придется думать о своей конкретной проблеме по-другому. Декораторы не должны воздействовать на вызывающий код, они действуют на декорированный объект. В языке есть много других мощных функций, которые могут помочь вам справиться с вызывающим контекстом, декораторы не таковы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...