Pobieranie nazwy zmiennej w postaci ciągu

173

W tym wątku omówiono, jak uzyskać nazwę funkcji jako ciąg znaków w Pythonie: Jak uzyskać nazwę funkcji w postaci ciągu?

Jak mogę zrobić to samo dla zmiennej? W przeciwieństwie do funkcji zmienne Pythona nie mają __name__atrybutu.

Innymi słowy, jeśli mam zmienną taką jak:

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

Szukam funkcji / atrybutu np. retrieve_name()W celu stworzenia DataFrame w Pandas z tej listy , gdzie nazwy kolumn podane są przez nazwy rzeczywistych słowników:

# List of dictionaries for my DataFrame
list_of_dicts = [n_jobs, users, queues, priorities]
columns = [retrieve_name(d) for d in list_of_dicts] 
Amelio Vazquez-Reina
źródło

Odpowiedzi:

19

Korzystając z python-varnamepakietu, możesz łatwo pobrać nazwy zmiennych

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

W Twoim przypadku możesz:

from varname import Wrapper

foo = Wrapper(dict())

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

Możesz też spróbować bezpośrednio pobrać nazwę zmiennej:

from varname import nameof

foo = dict()

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

Jestem autorem tego pakietu. Daj mi znać, jeśli masz jakieś pytania lub możesz zgłaszać problemy na github.

Panwen Wang
źródło
2
OK, w końcu to rozumiem! Zainstalowany przy użyciu następujących pip3 install python-varname==0.1.5; zaimportowane za pomocąfrom varname import nameof
enter_display_name_here
Jakoś funkcja nie działa w pętli: test = {} print(varname.nameof(test)) for i in [0]: print(varname.nameof(test))pierwszy wydruk daje test, wydruk w pętli podnosi sięVarnameRetrievingError: Callee's node cannot be detected.
Tillus
1
@Tillus Naprawiono ov0.2.0
Panwen Wang
107

Jedynymi obiektami w Pythonie, które mają nazwy kanoniczne, są moduły, funkcje i klasy, i oczywiście nie ma gwarancji, że ta nazwa kanoniczna ma jakiekolwiek znaczenie w jakiejkolwiek przestrzeni nazw po zdefiniowaniu funkcji lub klasy lub zaimportowaniu modułu. Nazwy te można również modyfikować po utworzeniu obiektów, więc nie zawsze są one szczególnie godne zaufania.

To, co chcesz zrobić, nie jest możliwe bez rekurencyjnego chodzenia po drzewie nazwanych obiektów ; nazwa to jednostronne odniesienie do obiektu. Typowy lub odmienny obiekt w Pythonie nie zawiera odniesień do swoich nazw. Wyobraź sobie, że każda liczba całkowita, każda dykta, każda lista, każda wartość logiczna musiałaby utrzymywać listę łańcuchów reprezentujących nazwy, które się do niej odnoszą! Byłby to koszmar implementacyjny, z niewielką korzyścią dla programisty.

kindall
źródło
7
Dzięki. Ale dlaczego więc Python robi to dla funkcji? (tj. jeden typ obiektów Pythona)
Amelio Vazquez-Reina
1
@kindall: nie zapomnij o modułach: mają też nazwy kanoniczne. Nie zapominaj też, że __name__zawsze można ją zmodyfikować, co oznacza, że ​​nazwa „kanoniczna” może nie być nazwą, której można użyć do pobrania obiektu (np. Podczas dynamicznego tworzenia klas).
nneonneo
8
Twoje stwierdzenie „to po prostu niemożliwe” jest fałszywe, jak pokazał @ scohe001 . Baza danych nazw zmiennych Pythona jest taka sama, jak każda inna relacyjna baza danych. Zawsze możesz wyszukiwać powiązane obiekty w „odwrotnej kolejności” i zwracać pierwsze znalezione lub cały zestaw poprawnych nazw zmiennych dla dowolnej danej zmiennej.
płyty kuchenne
3
@hobs Technicznie jesteś poprawny ... najlepszy rodzaj poprawności. W praktyce jednak potencjalnych nazw dla obiektu jest tak wiele, że jest to więcej kłopotu, niż warto je zdobyć.
kindall
1
@kindall Myślę, że masz rację, jeśli twój próg „warto” wynosi O (1). Pętla scohe001 byłaby O (N).
płyty grzejne
82

Nawet jeśli wartości zmiennych nie wskazują z powrotem na nazwę, masz dostęp do listy każdej przypisanej zmiennej i jej wartości, więc jestem zdumiony, że tylko jedna osoba zasugerowała przeglądanie tam w celu wyszukania nazwy twojej zmiennej.

Ktoś wspomniał w tej odpowiedzi, że być może będziesz musiał przejść stos i sprawdzić lokalne i globalne wszystkich, aby znaleźć foo, ale jeśli foojest przypisany w zakresie, w którym wywołujesz tę retrieve_namefunkcję, możesz użyć inspects, current frameaby uzyskać wszystkie te zmienne lokalne .

Moje wyjaśnienie może być trochę zbyt rozwlekłe (może powinienem był użyć "foo" mniej słów), ale oto jak będzie wyglądać w kodzie ( zwróć uwagę, że jeśli jest więcej niż jedna zmienna przypisana do tej samej wartości, pobierz obie te nazwy zmiennych ):

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)

Jeśli wywołujesz tę funkcję z innej funkcji, na przykład:

def foo(bar):
    return retrieve_name(bar)

foo(baz)

I chcesz bazzamiast tego bar, wystarczy cofnąć się o jeden zakres dalej. Można to zrobić, dodając dodatkowy element .f_backpodczas caller_local_varsinicjalizacji.

Zobacz przykład tutaj: ideone

scohe001
źródło
1
@theodox I absolutnie zgadzam, jak to będzie prawdopodobnie działać z import hooks, ironpythonijython
scohe001
5
To nie zadziała. zmienne atomowe nie mają nazwy jako atrybutu. Więc jeśli masz a, b = 2, 2, retrieve_name(a)i retrieve_name(b)będzie zarówno zwrot ['a', 'b']lub['b', 'a']
Tomas
1
@tomas Właściwie jest to szczegół implementacji optymalizacji cPythona, w którym liczby całkowite poniżej 255 są w zasadzie singletonami, więc wszelkie zmienne przypisane do tych wartości zostaną skutecznie zwrócone truedo isporównania
Toote
1
czy istnieje mod tego, aby uzyskać nazwę przekazanej zmiennej? np. def foo(bar): retrieve_name(bar)zawsze zwróci pasek, ale co jeśli chcesz foo(baz)zwrócić baz) zamiast bar?
SumNeuron
1
@SumNeuron musiałbyś tylko zmodyfikować linię, która przypisuje, callers_local_varsaby cofnąć się o jeden zakres, aby spojrzał na zakres tego, co wywołuje foo. Zmień linię na inspect.currentframe().f_back.f_back.f_locals.items()(zwróć uwagę na dodatek f_back).
scohe001
37

W Pythonie 3.8 można po prostu użyć funkcji debugowania f-stringów:

>>> foo = dict()
>>> f'{foo=}'.split('=')[0]
'foo' 
Aivar Paalberg
źródło
Miły ! A jeśli chcesz otrzymać nazwę nieruchomości zamiast obiektu, możeszf'{foo=}'.split('=')[0].split('.')[-1]
Mickael V.
30

W python3 ta funkcja uzyska najbardziej zewnętrzną nazwę na stosie:

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]

Przydaje się w dowolnym miejscu kodu. Przechodzi przez odwrócony stos w poszukiwaniu pierwszego dopasowania.

juan Isaza
źródło
Dobra robota! Chociaż próbuję, retrieve_name(SomeClass.some_attribute)to nie działa. Czy możesz w tym pomóc?
Nam G VU
To zmaga się ze zmiennymi boolowskimi. I skończyć zstop_on_error
SumNeuron
26

Nie wierzę, że to jest możliwe. Rozważmy następujący przykład:

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

aI bpunkt do tego samego obiektu, ale obiekt nie może wiedzieć, co zmienne wskazują na niego.

Korylprince
źródło
2
Z pewnością związek można nawiązać dwukierunkowo, rozszerzając liczenie referencji . Ta odpowiedź (i kilka innych) zapewnia nawet implementację.
Shayaan
17
def name(**variables):
    return [x for x in variables]

Jest używany w ten sposób:

name(variable=variable)
fdvfcges
źródło
12
To nie zwróci nazwy żądanej zmiennej, ale „zmienne”. Na przykład - za pomocą name(variable=variable)wyjścia będzie ['variable'], a przy użyciu name(variable=another_variable)będzie nie wyjście ['another_variable'], ale raczej ['variable'].
DalyaG
1
Właściwie działa zgodnie z oczekiwaniami. Musisz tylko zastąpić obie «zmienne» swoją zmienną. Zwróci listę jednoelementową z ciągiem znaków z nazwą pierwszej zmiennej. Na przykład: >>> a = [] >>> b = a >>> name(a=b) ['a'] >>> name(b=a) ['b']`
Alguem Meugla
8

Oto jedno podejście. Nie polecałbym tego do niczego ważnego, ponieważ będzie dość kruchy. Ale da się to zrobić.

Utwórz funkcję, która używa inspectmodułu, aby znaleźć kod źródłowy, który ją wywołał. Następnie możesz przeanalizować kod źródłowy, aby zidentyfikować nazwy zmiennych, które chcesz pobrać. Na przykład, oto funkcja o nazwie, autodictktóra pobiera listę zmiennych i zwraca słownik mapujący nazwy zmiennych na ich wartości. Na przykład:

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

Dałby:

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

Sprawdzanie samego kodu źródłowego jest lepsze niż przeszukiwanie locals()lub, globals()ponieważ to drugie podejście nie mówi ci, które zmienne są tymi, które chcesz.

W każdym razie oto kod:

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

Akcja ma miejsce w wierszu z inspect.getouterframes, który zwraca ciąg w wywołanym kodzie autodict.

Oczywistą wadą tego rodzaju magii jest to, że przyjmuje założenia dotyczące struktury kodu źródłowego. I oczywiście to w ogóle nie zadziała, jeśli jest uruchomione wewnątrz tłumacza.

Zachary Ernst
źródło
Witam, mam tylko trzy pytania: 1. Dlaczego „1”? 2. Dlaczego „4”? 3. Dlaczego „0”? :)
Piotr Wasilewicz
7

Napisałem magię pakietu, aby solidnie robić tego rodzaju magię. Możesz pisać:

from sorcery import dict_of

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

i przekaż to do konstruktora dataframe. Jest to równoważne z:

columns = dict(n_jobs=n_jobs, users=users, queues=queues, priorities=priorities)
Alex Hall
źródło
6
>>> locals()['foo']
{}
>>> globals()['foo']
{}

Gdybyś chciał napisać własną funkcję, mógłbyś to zrobić w taki sposób, że mógłbyś sprawdzić zmienną zdefiniowaną w lokalnych, a następnie sprawdzić globalne. Jeśli nic nie zostanie znalezione, możesz porównać za pomocą id (), aby sprawdzić, czy zmienna wskazuje na to samo miejsce w pamięci.

Jeśli twoja zmienna należy do klasy, możesz użyć className. dict .keys () lub vars (self), aby sprawdzić, czy zmienna została zdefiniowana.

blakev
źródło
A co, jeśli nazwa znajduje się w ramce dzwoniącego? Wtedy musiałbyś robić głupie rzeczy, takie jak chodzenie po stosie, sprawdzanie wszystkich localsi globals... i ryzykujesz błędną nazwę, jeśli tak naprawdę nie istnieje. To mnóstwo pracy, która nie przynosi żadnych korzyści.
nneonneo,
całe pytanie jest głupie ... ale jeśli jest to coś, co chciał zrobić, to jest to możliwe. Jeśli chodzi o sprawdzenie istnienia, możesz użyć Globals (). Setdefault (var, <nowy obiekt typu (var)), aby stworzyć coś, gdy nic tam nie ma. Nie jestem do końca pewien, do czego on tego chce, ale może dowiadując się, że zmienne są przechowywane według zakresu, mógłby wymyślić coś lepszego.
blakev,
3

W Pythonie deficlass słowa kluczowe będą wiązać określoną nazwę z definiowanym obiektem (funkcją lub klasą). Podobnie, moduły otrzymują nazwy, ponieważ są nazywane czymś specyficznym w systemie plików. We wszystkich trzech przypadkach istnieje oczywisty sposób przypisania „kanonicznej” nazwy do danego obiektu.

Jednak w przypadku innych rodzajów obiektów taka nazwa kanoniczna może po prostu nie istnieć . Weźmy na przykład pod uwagę elementy listy. Elementy na liście nie są nazywane indywidualnie i jest całkowicie możliwe, że jedynym sposobem odniesienia się do nich w programie jest użycie indeksów listy na liście zawierającej. Jeśli taka lista obiektów została przekazana do twojej funkcji, prawdopodobnie nie mógłbyś przypisać znaczących identyfikatorów do wartości.

Python nie zapisuje nazwy po lewej stronie przypisania do przypisanego obiektu, ponieważ:

  1. Wymagałoby to ustalenia, która nazwa była „kanoniczna” wśród wielu sprzecznych obiektów,
  2. Nie miałoby to sensu dla obiektów, które nigdy nie są przypisane do jawnej nazwy zmiennej,
  3. Byłoby to wyjątkowo nieefektywne,
  4. Dosłownie żaden inny istniejący język tego nie robi.

Na przykład funkcje zdefiniowane za pomocą lambdazawsze będą miały „nazwę” <lambda>zamiast określonej nazwy funkcji.

Najlepszym podejściem byłoby po prostu poproszenie dzwoniącego o przekazanie (opcjonalnej) listy nazwisk. Jeśli wpisanie '...','...'jest zbyt kłopotliwe, możesz zaakceptować np. Pojedynczy łańcuch zawierający listę nazw oddzielonych przecinkami (tak jak namedtuplerobi).

nneonneo
źródło
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 () - Zwraca słownik zawierający zmienne lokalne bieżącego zakresu. iterując przez ten słownik możemy sprawdzić klucz, który ma wartość równą zdefiniowanej zmiennej, po prostu wyodrębnienie klucza da nam tekst zmiennej w formacie łańcuchowym.

z (po drobnych zmianach) https://www.tutorialspoint.com/How-to-get-a-variable-name-as-a-string-in-Python

Anshu Pandey
źródło
2

Myślę, że zrobienie tego w Pythonie jest tak trudne z powodu prostego faktu, że nigdy nie poznasz nazwy używanej zmiennej. Na jego przykładzie możesz więc zrobić:

Zamiast:

list_of_dicts = [n_jobs, users, queues, priorities]

dict_of_dicts = {"n_jobs" : n_jobs, "users" : users, "queues" : queues, "priorities" : priorities}
Joe Camel
źródło
2

po prostu inny sposób na zrobienie tego w oparciu o zawartość zmiennej wejściowej:

(zwraca nazwę pierwszej zmiennej, która pasuje do zmiennej wejściowej, w przeciwnym razie None. Można ją zmodyfikować, aby uzyskać wszystkie nazwy zmiennych, które mają taką samą zawartość jak zmienna wejściowa)

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
ses
źródło
2

Ta funkcja wypisze nazwę zmiennej wraz z jej wartością:

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
źródło
2

Mam metodę i chociaż nie jest najbardziej wydajna ... to działa! (i nie obejmuje żadnych wyszukanych modułów).

Zasadniczo porównuje swojego identyfikatora zmiennej do globalnych () identyfikatory zmiennych , a następnie zwraca nazwę użytkownika meczu.

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
Amr Sharaki
źródło
1

Jeśli celem jest pomoc w śledzeniu zmiennych, możesz napisać prostą funkcję, która etykietuje zmienną i zwraca jej wartość oraz typ. Na przykład załóżmy, że i_f = 3.01 i zaokrągliłeś to do liczby całkowitej o nazwie i_n do użycia w kodzie, a następnie potrzebujesz ciągu i_s, który zostanie umieszczony w raporcie.

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)

To drukuje do okna przy każdym wywołaniu w celu debugowania, a także dostarcza ciąg dla pisemnego raportu. Jedynym minusem jest to, że musisz dwukrotnie wpisać zmienną za każdym razem, gdy wywołujesz funkcję.

Jestem nowicjuszem w Pythonie i znalazłem ten bardzo przydatny sposób rejestrowania moich wysiłków podczas programowania i próbowania radzenia sobie ze wszystkimi obiektami w Pythonie. Jedną z wad jest to, że whatis () zawodzi, jeśli wywołuje funkcję opisaną poza procedurą, w której jest używana. Na przykład int (i_f) był prawidłowym wywołaniem funkcji tylko dlatego, że funkcja int jest znana Pythonowi. Możesz wywołać whatis () używając int (i_f ** 2), ale jeśli z jakiegoś dziwnego powodu zdecydujesz się zdefiniować funkcję o nazwie int_squared, musi ona zostać zadeklarowana wewnątrz procedury, w której użyto whatis ().

Guy Vandegrift
źródło
1

Może to mogłoby się przydać:

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

Funkcja przeszukuje listę identyfikatorów wartości z zakresu globalnego (przestrzeń nazw można edytować), znajduje indeks poszukiwanej / wymaganej zmiennej lub funkcji na podstawie jej identyfikatora, a następnie zwraca nazwę z listy nazw globalnych na podstawie na pozyskanym indeksie.

Reitsch-Z2
źródło
1

Poniższa metoda nie zwróci nazwy zmiennej, ale za pomocą tej metody można łatwo utworzyć ramkę danych, jeśli zmienna jest dostępna w zasięgu globalnym.

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
źródło
0

W przypadku stałych można użyć wyliczenia, które obsługuje pobieranie jego nazwy.

Dana
źródło
0

Próbuję uzyskać nazwę od inspect locals, ale nie może przetwarzać var ​​like a [1], b.val. Potem przyszedł mi do głowy nowy pomysł - pobierz nazwę var z kodu i spróbuję! kod jak poniżej:

#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 ""
Ryan
źródło
0

Możesz spróbować wykonać następujące czynności, aby pobrać nazwę zdefiniowanej funkcji (nie działa to jednak w przypadku funkcji wbudowanych):

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
Sandipan Dey
źródło