Вы не можете получить то, о чем просите.
Есть много способов вызова функции, когда вы даже не получите имен переменных для отдельных значений. Например, какие будут имена при использовании литеральных значений в вызове, так:
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)
, чтобы превратить это в обычный словарь.
Вместо этого вам придется думать о своей конкретной проблеме по-другому. Декораторы не должны воздействовать на вызывающий код, они действуют на декорированный объект. В языке есть много других мощных функций, которые могут помочь вам справиться с вызывающим контекстом, декораторы не таковы.