Отримання назви змінної у вигляді рядка


173

Цей потік обговорює, як отримати ім'я функції як рядка в 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] 

Відповіді:


19

Використовуючи 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
Панвен Ван

107

Єдині об'єкти в Python, які мають канонічні назви, - це модулі, функції та класи, і, звичайно, немає гарантії, що це канонічне ім'я має якесь значення в будь-якому просторі імен після того, як визначено функцію чи клас або імпортується модуль. Ці імена також можна змінювати після створення об'єктів, щоб вони не завжди були особливо надійними.

Те, що ви хочете зробити, неможливо без рекурсивного прогулянки по дереву названих об'єктів ; ім'я - це одностороннє посилання на об'єкт. Звичайний об’єкт Python або сорт саду не містить посилань на його назви. Уявіть, якби кожному цілому числу, кожному диктату, кожному списку, кожному булевому знаменню потрібно було підтримувати список рядків, що представляли імена, які посилалися на нього! Це було б кошмаром на реалізацію, не маючи користі для програміста.


7
Дякую. Але чому тоді Python робить це для функцій? (тобто один тип об’єктів Python)
Амеліо Васкес-Рейна

1
@kindall: не забувайте модулі: вони мають і канонічні назви. Крім того, не забувайте, що __name__це завжди можна змінити, тобто "канонічне" ім'я може бути не ім'ям, яке можна використовувати для отримання об'єкта (наприклад, при динамічному створенні класів).
nneonneo

8
Ваше твердження "це просто неможливо" є помилковим, як показало @ scohe001 . База даних імен змінних Python так само, як і будь-яка інша реляційна БД, ви завжди можете шукати пов'язані об'єкти в "зворотному" і повертати перше знайдене або весь набір дійсних імен змінних для будь-якої заданої змінної.
варильні панелі

3
@hobs Ви технічно правильні ... найкращий вид правильних. На практиці, однак, існує стільки потенційних імен для об'єкта, що це більше проблем, ніж варто намагатися їх отримати.
kindall

1
@kindall Я думаю, ви праві, якщо ваш поріг "вартий цього" - це O (1). Петля scohe001 буде O (N).
варильні панелі

82

Навіть якщо значення змінних не вказують на ім’я, у вас є доступ до списку кожної призначеної змінної та її значення, тому я вражений тим, що лише одна людина запропонувала прокрутити туди, щоб шукати ваше ім’я var.

Хтось згадав у цій відповіді, що вам, можливо, доведеться пройти стек і перевірити місцевих жителів і глобальних користувачів, щоб знайти 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


1
@theodox Я абсолютно згоден, так як це, швидше за все , діяти з import hooks, ironpythonіjython
scohe001

5
Це не спрацює. атомні змінні не мають свого імені як атрибута. Тож якщо у вас є a, b = 2, 2, retrieve_name(a)і retrieve_name(b)обидва повернуться ['a', 'b']або['b', 'a']
tomas

1
@tomas Власне, це деталь реалізації оптимізації cPython, в якій цілі числа нижче 255 - це в основному одинаки, тому будь-які змінні, призначені цим значенням, ефективно повернуться trueдля isпорівняння
Toote

1
чи є мод цього, щоб отримати ім'я переданого вару? наприклад 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-string:

>>> 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)це не працює. Чи можете ви допомогти далі у цьому?
Нам Г ВУ

Це бореться з булевими змінними. Я закінчуюstop_on_error
SumNeuron

26

Я не вірю, що це можливо. Розглянемо наступний приклад:

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

Значення aта bвказівка ​​на той самий об’єкт, але об'єкт не може знати, які змінні вказують на нього.


2
Безумовно, відносини можна зробити двосторонніми, розширивши підрахунок посилань . Ця відповідь (та деякі інші) навіть забезпечує реалізацію.
Шаяан

17
def name(**variables):
    return [x for x in variables]

Він використовується так:

name(variable=variable)

12
Це поверне не ім'я потрібної змінної, а "змінні". Наприклад - використовуючи name(variable=variable)вихід буде ['variable'], і використання name(variable=another_variable)буде НЕ вихід ['another_variable'], а ['variable'].
ДаляГ

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)

і передайте це конструктору фрейму даних. Це еквівалентно:

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

6
>>> locals()['foo']
{}
>>> globals()['foo']
{}

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

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


Що робити, якщо ім'я знаходиться у кадрі абонента? Тоді вам доведеться дурити такі речі, як прогулянка стеком і перевірка всіх localsі globals... і ви ризикуєте помилити ім'я, якщо жодного насправді не існує. Це тонна роботи без фактичного корисного прибутку.
nneonneo

ціле питання нерозумно ... але якщо це щось, що він хотів зробити, можливо. Що стосується перевірки існування, ви можете використовувати Globals (). Setdefault (var, <новий об'єкт типу (var)), щоб створити щось, коли нічого там немає. Я не зовсім впевнений, чого він цього хоче, але, можливо, дізнавшись, що змінні зберігаються за рамками, він міг би щось краще зрозуміти.
blakev

3

У Python, defіclass ключові слова прив'язують конкретне ім'я до об'єкта, який вони визначають (функція або клас). Аналогічно, модулі присвоюють ім’я завдяки тому, що називається чимось конкретним у файловій системі. У всіх трьох випадках існує очевидний спосіб присвоїти "канонічне" ім'я відповідному об'єкту.

Однак для інших видів об’єктів така канонічна назва може просто не існувати . Наприклад, розглянемо елементи списку. Елементи у списку не називаються індивідуально, і цілком можливо, що єдиний спосіб посилатися на них у програмі - це використовувати індекси списку, що містять список. Якщо такий перелік об'єктів передано у вашу функцію, ви не зможете призначити значущі ідентифікатори значенням.

Python не зберігає ім'я в лівій частині завдання в призначеному об'єкті, оскільки:

  1. Потрібно було б з’ясувати, яке ім’я було "канонічним" серед кількох конфліктуючих об'єктів,
  2. Це не має сенсу для об'єктів, які ніколи не присвоюються явній імені змінної,
  3. Це було б вкрай неефективно,
  4. Буквально жодна інша існуюча мова цього не робить.

Так, наприклад, функції, визначені за допомогою lambda, завжди матимуть "ім'я" <lambda>, а не конкретне ім'я функції.

Найкращим підходом було б просто попросити абонента передати (необов'язково) список імен. Якщо введення '...','...'символу занадто громіздке, ви можете прийняти, наприклад, одну рядок, що містить список імен, розділених комами (як namedtupleце робиться).


3
>> 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

Я думаю, що це так важко зробити в 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

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))]

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


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

0

Для констант можна використовувати enum, який підтримує отримання його імені.


0

Я намагаюся отримати ім'я від місцевих жителів, які оглядають, але це не може обробити var like [1], b.val. Після цього у мене з’явилася нова ідея --- отримати назву var із коду, і я спробую це успішно! код, як показано нижче:

#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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.