Получение имени переменной в виде строки
Я уже читал, Как получить имя функции в виде строки?.
Как я могу сделать то же самое для переменной? В отличие от функций, переменные Python не имеют атрибута __name__
.
Другими словами, если у меня есть переменная, такая как:
foo = dict()
foo['bar'] = 2
Я ищу функцию / атрибут, например, retrieve_name
, где:
>>> retrieve_name(foo)
'foo'
Это делается для того, чтобы создать фрейм данных в Pandas из этого списка, где имена столбцов задаются именами реальных словарей:
# List of dictionaries for my DataFrame
list_of_dicts = [n_jobs, users, queues, priorities]
columns = [retrieve_name(d) for d in list_of_dicts]
Переведено автоматически
Ответ 1
В Python 3.8 можно просто использовать функцию отладки f-string:
>>> foo = dict()
>>> f'{foo=}'.split('=')[0]
'foo'
Одним из недостатков этого метода является то, что для 'foo'
печати вам нужно добавить f'{foo=}'
себя. Другими словами, вы уже должны знать имя переменной. Другими словами, приведенный выше фрагмент кода точно такой же, как просто
>>> 'foo'
Ответ 2
Даже если значения переменных не указывают на обратное имя, у вас есть доступ к списку каждой присвоенной переменной и ее значению, поэтому я поражен, что только один человек предложил просмотреть там, чтобы найти ваше имя переменной.
Кто-то упомянул в этом ответе, что вам, возможно, придется обойти стек и проверить все локальные и глобальные значения, чтобы найти foo
, но если foo
назначено в области видимости, где вы вызываете эту retrieve_name
функцию, вы можете использовать inspect
's currentframe
, чтобы получить все эти локальные переменные.
Мое объяснение может быть немного слишком многословным (возможно, мне следовало использовать на "foo" меньше слов), но вот как это будет выглядеть в коде (Обратите внимание, что если нескольким переменным присвоено одно и то же значение, вы получите оба этих имени переменных):
import inspect
x, y, z = 1, 2, 3
def retrieve_name(var):
callers_local_vars = inspect.currentframe().f_back.f_locals.items()
return [var_name for var_name, var_val in callers_local_vars if var_val is var]
print(retrieve_name(y))
Если вы вызываете эту функцию из другой функции, что-то вроде:
def foo(bar):
return retrieve_name(bar)
foo(baz)
Если вы хотите baz
вместо bar
, вам просто нужно вернуться к области видимости дальше. Это можно сделать, добавив дополнительный .f_back
в caller_local_vars
инициализации.
Смотрите пример здесь: ideone
Ответ 3
Единственными объектами в Python, имеющими канонические имена, являются модули, функции и классы, и, конечно, нет никакой гарантии, что это каноническое имя имеет какое-либо значение в любом пространстве имен после определения функции или класса или импорта модуля. Эти имена также могут быть изменены после создания объектов, поэтому они не всегда могут вызывать особого доверия.
То, что вы хотите сделать, невозможно без рекурсивного обхода дерева именованных объектов (или, по крайней мере, повторения списка имен в содержащей области видимости, как в ответе @scohe001); имя - это односторонняя ссылка на объект. Обычный объект Python или разновидность сада не содержит ссылок на свои имена. Представьте, что каждое целое число, каждый dict, каждый список, каждое логическое значение должны поддерживать список строк, представляющих имена, которые ссылаются на него! Это было бы кошмаром реализации, приносящим мало пользы программисту.
Ответ 4
TL;DR
Используйте Wrapper
помощник из python-varname
:
from varname.helpers import Wrapper
foo = Wrapper(dict())
# foo.name == 'foo'
# foo.value == {}
foo.value['bar'] = 2
Для части понимания списка вы можете сделать:
n_jobs = Wrapper(<original_value>)
users = Wrapper(<original_value>)
queues = Wrapper(<original_value>)
priorities = Wrapper(<original_value>)
list_of_dicts = [n_jobs, users, queues, priorities]
columns = [d.name for d in list_of_dicts]
# ['n_jobs', 'users', 'queues', 'priorities']
# REMEMBER that you have to access the <original_value> by d.value
Я являюсь автором python-varname
пакета. Пожалуйста, дайте мне знать, если у вас возникнут какие-либо вопросы, или вы можете отправить вопросы на Github.
Длинный ответ
Возможно ли это вообще?
Да и Нет.
Мы извлекаем имена переменных во время выполнения, поэтому нам нужно вызвать функцию, которая позволит нам получить доступ к предыдущим фреймам для извлечения имен переменных. Вот почему нам нужен Wrapper
там. В этой функции во время выполнения мы анализируем исходный код / узлы AST в предыдущих кадрах, чтобы получить точное имя переменной.
Однако исходный код / узлы AST в предыдущих фреймах не всегда доступны, или они могут быть изменены другими средами (например, оператором pytest
's assert
). Одним из простых примеров является то, что коды выполняются через exec()
. Хотя мы по-прежнему можем извлекать некоторую информацию из байт-кода, это требует слишком больших усилий и к тому же чревато ошибками.
Как это сделать?
Прежде всего, нам нужно определить, в каком фрейме задана переменная. Это не всегда просто прямой предыдущий фрейм. Например, у нас может быть другая оболочка для функции:
from varname import varname
def func():
return varname()
def wrapped():
return func()
x = wrapped()
В приведенном выше примере мы должны пропустить фрейм внутри, wrapped
чтобы перейти к нужному фрейму, x = wrapped()
чтобы мы могли найти x
. Аргументы frame
и ignore
of varname
позволяют нам пропустить некоторые из этих промежуточных фреймов. Смотрите больше деталей в файле README и документах API пакета.
Затем нам нужно проанализировать узел AST, чтобы определить, где переменной присваивается значение (вызов функции). Это не всегда простое присвоение. Иногда могут быть сложные узлы AST, например, x = [wrapped()]
. Нам нужно определить правильное назначение, пройдясь по дереву AST.
Насколько это надежно?
Как только мы определяем узел назначения, это становится надежным.
varname
все зависит от executing
пакета для поиска узла. Гарантируется, что узел, который обнаруживает выполнение, является правильным (см. Также Это).
Он частично работает со средами, где применяются другие AST-технологии, включая pytest, ipython, macropy, birdseye, reticulate с R и т.д. Ни executing, ни varname не работают на 100% с этими средами.
Нужен ли нам пакет для этого?
Ну, опять же, да и нет.
Если ваш сценарий прост, кода, предоставленного @juan Isaza или @scohe001, вероятно, будет достаточно для работы со случаем, когда переменная определена непосредственно в предыдущем кадре, а узел AST является простым назначением. Вам просто нужно вернуться на один кадр назад и получить оттуда информацию.
Однако, если сценарий усложняется или нам нужно использовать другие сценарии приложения, вам, вероятно, понадобится пакет типа python-varname
, для их обработки. Эти сценарии могут включать в себя to:
- предоставлять более понятные сообщения, когда исходный код недоступен или узлы AST недоступны
- пропускать промежуточные кадры (позволяет обернуть функцию или вызвать ее в других промежуточных кадрах)
- автоматически игнорирует вызовы встроенных функций или библиотек. Например:
x = str(func())
- извлеките несколько имен переменных в левой части присваивания
- и т.д.
Как насчет f-string
?
Нравится ответ, предоставленный @Aivar Paalberg. Это определенно быстро и надежно. Однако это происходит не во время выполнения, а это означает, что вы должны знать, что это foo
прежде чем печатать имя. Но с varname
вам не нужно знать, что эта переменная поступает:
from varname import varname
def func():
return varname()
# In external uses
x = func() # 'x'
y = func() # 'y'
Наконец
python-varname
способен не только определять имя переменной из присваивания, но и:
- Извлекайте имена переменных напрямую, используя
nameof
- Определите следующее немедленное имя атрибута, используя
will
- Извлекать имена аргументов / источники, передаваемые функции с помощью
argname
Подробнее читайте в документации.
Однако последнее слово, которое я хочу сказать, это то, что старайтесь избегать его использования, когда можете.
Потому что вы не можете быть уверены, что клиентский код будет выполняться в среде, где доступен исходный узел или доступен AST-узел. И, конечно, это требует ресурсов для анализа исходного кода, идентификации среды, извлечения узлов AST и их оценки при необходимости.