Получение имени переменной в виде строки

174

Этот поток обсуждает, как получить имя функции в виде строки в Python: Как получить имя функции в виде строки?

Как я могу сделать то же самое для переменной? В отличие от функций переменные Python не имеют __name__атрибута.

Другими словами, если у меня есть переменная, такая как:

foo = dict()
foo['bar'] = 2

Я ищу функцию / атрибут, например retrieve_name(), для создания DataFrame в 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] 
Амелио Васкес-Рейна
источник

Ответы:

20

Используя python-varnameпакет, вы можете легко получить имя переменных

https://github.com/pwwang/python-varname

В вашем случае вы можете сделать:

from varname import Wrapper

foo = Wrapper(dict())

# foo.name == 'foo'
# foo.value == {}
foo.value['bar'] = 2

Или вы можете также попытаться получить имя переменной напрямую:

from varname import nameof

foo = dict()

fooname = nameof(foo)
# fooname == 'foo'

Я автор этого пакета. Пожалуйста, дайте мне знать, если у вас есть какие-либо вопросы, или вы можете отправить вопросы на GitHub.

Панвен Ван
источник
2
ОК, наконец-то понял! Устанавливается с использованием следующего pip3 install python-varname==0.1.5; импортировано с использованиемfrom varname import nameof
enter_display_name_here
Каким-то образом функция не работает в цикле: test = {} print(varname.nameof(test)) for i in [0]: print(varname.nameof(test))первый отпечаток дает test, отпечаток в цикле повышаетсяVarnameRetrievingError: Callee's node cannot be detected.
Tillus
1
@Tillus Исправлено вv0.2.0
Панвен Ван
109

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

То, что вы хотите сделать, невозможно без рекурсивного обхода дерева именованных объектов ; имя является односторонней ссылкой на объект. Обычный или разнообразный объект Python не содержит ссылок на его имена. Представьте, что каждому целому числу, каждому дикту, каждому списку, каждому логическому значению требуется поддерживать список строк, которые представляют имена, ссылающиеся на него! Это был бы кошмар реализации, с небольшой пользой для программиста.

Kindall
источник
7
Спасибо. Но почему тогда Python делает это для функций? (т.е. один тип объектов Python)
Амелио Васкес-Рейна
1
@kindall: не забывайте модули: у них тоже есть канонические имена. Кроме того, не забывайте, что __name__всегда можно изменить, что означает, что «каноническое» имя не может быть именем, которое можно использовать для получения объекта (например, при динамическом создании классов).
nneonneo
8
Ваше утверждение «это просто невозможно» неверно, как показало @ scohe001 . База данных имен переменных Python, как и любая другая реляционная база данных, всегда позволяет искать связанные объекты в «обратном порядке» и возвращать первый найденный или полный набор допустимых имен переменных для любой данной переменной.
варенья
3
@hobs Вы технически правильны ... лучший вид правильных. На практике, однако, существует так много потенциальных имен для объекта, что это больше проблем, чем стоит пытаться получить их.
любезно
1
@kindall Я думаю, вы правы, если ваш порог "стоит того" O (1). Цикл scohe001 будет O (N).
варенье
82

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

Кто-то упомянул в этом ответе, что вам, возможно, придется пройтись по стеку и проверить все локальные и глобальные переменные, чтобы найти их foo, но если fooэто назначено в области, в которой вы вызываете эту retrieve_nameфункцию, вы можете использовать inspects, current frameчтобы получить все эти локальные переменные. ,

Мое объяснение может быть немного слишком многословным (возможно, мне следовало бы использовать меньше слов "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

scohe001
источник
1
@theodox Я абсолютно согласен, так как это, вероятно, будет действовать import hooks, ironpythonиjython
scohe001
5
Это не сработает. атомарные переменные не имеют своего имени в качестве атрибута. Так что если у вас есть a, b = 2, 2, retrieve_name(a)и retrieve_name(b)оба вернутся ['a', 'b']или['b', 'a']
Томас
1
@tomas На самом деле, это деталь реализации оптимизации cPython, в которой целые числа ниже 255 являются в основном одиночными, поэтому любые переменные, которым назначены эти значения, будут эффективно возвращаться trueдля isсравнения
Toote
1
есть ли мод этого, чтобы получить имя переданного var? Например def foo(bar): retrieve_name(bar), всегда будет возвращать бар, но что, если вы хотите foo(baz)вернуться baz) вместо bar?
SumNeuron
1
@SumNeuron, вам просто нужно изменить строку, которая назначает, callers_local_varsчтобы переместиться на одну область назад, чтобы она смотрела на область вызова foo. Измените строку на inspect.currentframe().f_back.f_back.f_locals.items()(обратите внимание на дополнительную f_back).
scohe001
37

В Python 3.8 можно просто использовать функцию отладки f-строки:

>>> foo = dict()
>>> f'{foo=}'.split('=')[0]
'foo' 
Айвар Паальберг
источник
Ницца ! И просто если вы хотите получить название свойства вместо объекта, который вы можетеf'{foo=}'.split('=')[0].split('.')[-1]
Микаэль В.
30

На python3 эта функция получит самое внешнее имя в стеке:

import inspect


def retrieve_name(var):
        """
        Gets the name of var. Does it from the out most frame inner-wards.
        :param var: variable to get name from.
        :return: string
        """
        for fi in reversed(inspect.stack()):
            names = [var_name for var_name, var_val in fi.frame.f_locals.items() if var_val is var]
            if len(names) > 0:
                return names[0]

Это полезно в любом месте кода. Пройдет обратный стек в поисках первого совпадения.

Хуан Исаза
источник
Хорошая работа! Хотя я стараюсь, retrieve_name(SomeClass.some_attribute)это не работает. Можете ли вы помочь в этом?
Nam G VU
Это борется с булевыми переменными. Я заканчиваю сstop_on_error
SumNeuron
26

Я не верю, что это возможно. Рассмотрим следующий пример:

>>> a = []
>>> b = a
>>> id(a)
140031712435664
>>> id(b)
140031712435664

aИ bуказывают на тот же объект, но объект не может знать , какие переменные указывают на него.

korylprince
источник
2
Конечно, отношения могут быть сделаны двусторонними путем расширения подсчета ссылок . Этот ответ (и некоторые другие) даже обеспечивает реализацию.
Шаян
17
def name(**variables):
    return [x for x in variables]

Используется так:

name(variable=variable)
fdvfcges
источник
12
Это не вернет имя нужной переменной, а «переменные». Например - использование name(variable=variable)будет выводить ['variable'], а использование неname(variable=another_variable) будет выводить, а скорее . ['another_variable']['variable']
DalyaG
1
На самом деле это работает, как ожидалось. Вы просто должны заменить обе «переменные» вашей переменной. Он вернет одноэлементный список со строкой имени первой переменной. Например: >>> a = [] >>> b = a >>> name(a=b) ['a'] >>> name(b=a) ['b']`
Alguem Meugla
8

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

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

x = 'foo'
y = 'bar'
d = autodict(x, y)
print d

Даст:

{'x': 'foo', 'y': 'bar'}

Проверка самого исходного кода лучше, чем поиск с помощью locals()или, globals()поскольку последний подход не говорит вам, какие из переменных вам нужны.

В любом случае, вот код:

def autodict(*args):
    get_rid_of = ['autodict(', ',', ')', '\n']
    calling_code = inspect.getouterframes(inspect.currentframe())[1][4][0]
    calling_code = calling_code[calling_code.index('autodict'):]
    for garbage in get_rid_of:
        calling_code = calling_code.replace(garbage, '')
    var_names, var_values = calling_code.split(), args
    dyn_dict = {var_name: var_value for var_name, var_value in
                zip(var_names, var_values)}
    return dyn_dict

Действие происходит в строке с inspect.getouterframes, которая возвращает строку в коде, который вызвал autodict.

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

Захари Эрнст
источник
Здравствуйте, у меня есть только три вопроса: 1. Почему «1»? 2. Почему «4»? 3. Почему "0"? :)
Петр Васильевич
7

Я написал пакет волшебства, чтобы творить магию такого рода. Ты можешь написать:

from sorcery import dict_of

columns = dict_of(n_jobs, users, queues, priorities)

и передать это конструктору dataframe. Это эквивалентно:

columns = dict(n_jobs=n_jobs, users=users, queues=queues, priorities=priorities)
Алекс Холл
источник
6
>>> locals()['foo']
{}
>>> globals()['foo']
{}

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

Если ваша переменная находится в классе, вы можете использовать className. dict .keys () или vars (self), чтобы увидеть, была ли определена ваша переменная.

blakev
источник
Что, если имя находится в кадре вызывающего абонента? Тогда вам придется пойти на глупые вещи, такие как обход стека и проверка всех localsи globals... и вы рискуете ошибиться, если ничего не существует. Это тонна работы без реальной выгоды.
nneonneo
весь вопрос глупый ... но если это то, что он хотел сделать, это возможно. Для проверки существования вы можете использовать Globals (). Setdefault (var, <новый объект типа (var)), чтобы создать что-то, когда ничего нет. Я не совсем уверен, для чего он этого хочет, но, возможно, узнав, что переменные сохраняются по объему, он мог бы придумать что-нибудь получше.
Blakev
3

В Python defиclass ключевые слова будут связывать имя , характерным для конкретного объекта они определяют (функция или класс). Аналогичным образом, модулям присваивается имя в силу того, что они называются чем-то конкретным в файловой системе. Во всех трех случаях существует очевидный способ присвоения «канонического» имени рассматриваемому объекту.

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

Python не сохраняет имя в левой части назначения в назначенном объекте, потому что:

  1. Это потребовало бы выяснения, какое имя было «каноническим» среди множества конфликтующих объектов,
  2. Это не имело бы смысла для объектов, которые никогда не назначаются явному имени переменной,
  3. Это было бы крайне неэффективно,
  4. Буквально ни один другой существующий язык не делает этого.

Так, например, функции, определенные с использованием lambdaвсегда будут иметь «имя»<lambda> , а не конкретное имя функции.

Наилучшим подходом было бы просто попросить звонящего передать (необязательный) список имен. Если ввод '...','...'слишком громоздок, вы можете принять, например, одну строку, содержащую список имен через запятую (как, например, namedtupleделает).

nneonneo
источник
2

Я думаю, что это трудно сделать в Python из-за того простого факта, что вы никогда не узнаете имя используемой переменной. Итак, в его примере вы могли бы сделать:

Вместо того:

list_of_dicts = [n_jobs, users, queues, priorities]

dict_of_dicts = {"n_jobs" : n_jobs, "users" : users, "queues" : queues, "priorities" : priorities}
Джо верблюд
источник
2

просто еще один способ сделать это на основе содержимого входной переменной:

(он возвращает имя первой переменной, которая соответствует входной переменной, в противном случае None. Можно изменить его, чтобы получить все имена переменных, которые имеют то же содержимое, что и входная переменная)

def retrieve_name(x, Vars=vars()):
    for k in Vars:
        if type(x) == type(Vars[k]):
            if x is Vars[k]:
                return k
    return None
сес
источник
2

Эта функция напечатает имя переменной со значением:

import inspect

def print_this(var):
    callers_local_vars = inspect.currentframe().f_back.f_locals.items()
    print(str([k for k, v in callers_local_vars if v is var][0])+': '+str(var))
***Input & Function call:***
my_var = 10

print_this(my_var)

***Output**:*
my_var: 10
iDilip
источник
2
>> my_var = 5
>> my_var_name = [ k for k,v in locals().items() if v == my_var][0]
>> my_var_name 
'my_var'

locals () - возвращает словарь, содержащий локальные переменные текущей области. перебирая этот словарь, мы можем проверить ключ, который имеет значение, равное определенной переменной, простое извлечение ключа даст нам текст переменной в строковом формате.

от (после небольшого изменения) https://www.tutorialspoint.com/How-to-get-a-variable-name-as-a-string-in-Python

Аншу Пандей
источник
2

У меня есть метод, и пока он не самый эффективный ... он работает! (и это не включает в себя какие-либо модные модули).

В основном это сравнивает идентификатор переменного для глобал () идентификаторы переменных , а затем возвращает имя Спичечного.

def getVariableName(variable, globalVariables=globals().copy()):
    """ Get Variable Name as String by comparing its ID to globals() Variables' IDs

        args:
            variable(var): Variable to find name for (Obviously this variable has to exist)

        kwargs:
            globalVariables(dict): Copy of the globals() dict (Adding to Kwargs allows this function to work properly when imported from another .py)
    """
    for globalVariable in globalVariables:
        if id(variable) == id(globalVariables[globalVariable]): # If our Variable's ID matches this Global Variable's ID...
            return globalVariable # Return its name from the Globals() dict
Амр Шараки
источник
1

Если цель состоит в том, чтобы помочь вам отслеживать ваши переменные, вы можете написать простую функцию, которая помечает переменную и возвращает ее значение и тип. Например, предположим, что i_f = 3,01, и вы округлите его до целого числа i_n для использования в коде, а затем потребуется строка i_s, которая войдет в отчет.

def whatis(string, x):
    print(string+' value=',repr(x),type(x))
    return string+' value='+repr(x)+repr(type(x))
i_f=3.01
i_n=int(i_f)
i_s=str(i_n)
i_l=[i_f, i_n, i_s]
i_u=(i_f, i_n, i_s)

## make report that identifies all types
report='\n'+20*'#'+'\nThis is the report:\n'
report+= whatis('i_f ',i_f)+'\n'
report+=whatis('i_n ',i_n)+'\n'
report+=whatis('i_s ',i_s)+'\n'
report+=whatis('i_l ',i_l)+'\n'
report+=whatis('i_u ',i_u)+'\n'
print(report)

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

Я новичок в Python и нашел этот очень полезный способ записывать свои усилия во время программирования и пытаться справиться со всеми объектами в Python. Один недостаток заключается в том, что whatis () завершается ошибкой, если вызывает функцию, описанную вне процедуры, в которой он используется. Например, int (i_f) был допустимым вызовом функции только потому, что функция int известна Python. Вы можете вызвать whatis (), используя int (i_f ** 2), но если по какой-то странной причине вы решите определить функцию с именем int_squared, она должна быть объявлена ​​внутри процедуры, где используется whatis ().

Гай Вандегрифт
источник
1

Может быть, это может быть полезно:

def Retriever(bar):
    return (list(globals().keys()))[list(map(lambda x: id(x), list(globals().values()))).index(id(bar))]

Функция просматривает список идентификаторов значений из глобальной области (пространство имен может быть отредактировано), находит индекс требуемой / требуемой переменной или функции на основе ее идентификатора, а затем возвращает имя из списка глобальных имен на основе по приобретенному индексу.

Reitsch-Z2
источник
1

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

class CustomDict(dict):
    def __add__(self, other):
        return CustomDict({**self, **other})

class GlobalBase(type):
    def __getattr__(cls, key):
        return CustomDict({key: globals()[key]})

    def __getitem__(cls, keys):
        return CustomDict({key: globals()[key] for key in keys})

class G(metaclass=GlobalBase):
    pass

x, y, z = 0, 1, 2

print('method 1:', G['x', 'y', 'z']) # Outcome: method 1: {'x': 0, 'y': 1, 'z': 2}
print('method 2:', G.x + G.y + G.z) # Outcome: method 2: {'x': 0, 'y': 1, 'z': 2}

A = [0, 1]
B = [1, 2]
pd.DataFrame(G.A + G.B) # It will return a data frame with A and B columns
user5828964
источник
0

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

Dana
источник
0

Я пытаюсь получить имя от осмотра местных жителей, но он не может обработать var like a [1], b.val. После этого у меня появилась новая идея - получить имя var из кода, и я попробую его succ! код как ниже:

#direct get from called function code
def retrieve_name_ex(var):
    stacks = inspect.stack()
    try:
        func = stacks[0].function
        code = stacks[1].code_context[0]
        s = code.index(func)
        s = code.index("(", s + len(func)) + 1
        e = code.index(")", s)
        return code[s:e].strip()
    except:
        return ""
Райан
источник
0

Вы можете попробовать следующее, чтобы получить имя функции, которую вы определили (хотя она не работает для встроенных функций):

import re
def retrieve_name(func):
    return re.match("<function\s+(\w+)\s+at.*", str(func)).group(1)

def foo(x):
    return x**2

print(retrieve_name(foo))
# foo
Сандипан Дей
источник