Ottenere il nome di una variabile come stringa


174

Questo thread illustra come ottenere il nome di una funzione come stringa in Python: Come ottenere un nome di funzione come stringa?

Come posso fare lo stesso per una variabile? A differenza delle funzioni, le variabili Python non hanno l' __name__attributo.

In altre parole, se ho una variabile come:

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

Sto cercando una funzione / attributo, ad esempio retrieve_name()per creare un DataFrame in Pandas da questo elenco , dove i nomi delle colonne sono dati dai nomi dei dizionari attuali:

# List of dictionaries for my DataFrame
list_of_dicts = [n_jobs, users, queues, priorities]
columns = [retrieve_name(d) for d in list_of_dicts] 

Risposte:


20

Utilizzando il python-varnamepacchetto, è possibile recuperare facilmente il nome delle variabili

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

Nel tuo caso, puoi fare:

from varname import Wrapper

foo = Wrapper(dict())

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

Oppure puoi anche provare a recuperare direttamente il nome della variabile:

from varname import nameof

foo = dict()

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

Sono l'autore di questo pacchetto. Per favore fatemi sapere se avete domande o se potete inviare problemi su Github.


2
OK, finalmente capito! Installato usando quanto segue pip3 install python-varname==0.1.5; importato utilizzandofrom varname import nameof
enter_display_name_here il

In qualche modo la funzione non funziona in un ciclo: test = {} print(varname.nameof(test)) for i in [0]: print(varname.nameof(test))la prima stampa dà test, la stampa nel ciclo aumentaVarnameRetrievingError: Callee's node cannot be detected.
Tillus

1
@Tillus Risolto av0.2.0
Panwen Wang il

109

Gli unici oggetti in Python che hanno nomi canonici sono moduli, funzioni e classi e, naturalmente, non vi è alcuna garanzia che questo nome canonico abbia un significato in qualsiasi spazio dei nomi dopo che la funzione o la classe è stata definita o il modulo importato. Questi nomi possono anche essere modificati dopo la creazione degli oggetti, quindi potrebbero non essere sempre particolarmente affidabili.

Quello che vuoi fare non è possibile senza camminare ricorsivamente sull'albero di oggetti nominati ; un nome è un riferimento unidirezionale a un oggetto. Un oggetto Python comune o da giardino non contiene riferimenti ai suoi nomi. Immagina se ogni numero intero, ogni dict, ogni elenco, ogni booleano fosse necessario per mantenere un elenco di stringhe che rappresentavano i nomi che si riferivano ad esso! Sarebbe un incubo di implementazione, con pochi vantaggi per il programmatore.


7
Grazie. Ma allora perché Python lo fa per le funzioni? (ovvero un tipo di oggetti Python)
Amelio Vazquez-Reina

1
@kindall: non dimenticare i moduli: hanno anche nomi canonici. Inoltre, non dimenticare che __name__può sempre essere modificato, il che significa che il nome "canonico" potrebbe non essere un nome che può essere utilizzato per ottenere l'oggetto (ad esempio durante la creazione dinamica di classi).
nneonneo,

8
La tua affermazione "semplicemente non è possibile" è falsa, come ha mostrato @ scohe001 . Il database dei nomi delle variabili di Python è proprio come qualsiasi altro DB relazionale, è sempre possibile cercare gli oggetti correlati in "reverse" e restituire il primo trovato o l'intero insieme di nomi di variabili validi per una determinata variabile.
Piani cottura

3
@hobs Sei tecnicamente corretto ... il miglior tipo di corretto. In pratica, tuttavia, ci sono così tanti nomi potenziali per un oggetto che è più un problema di quanto valga la pena cercare di ottenerli.
kindall

1
@kindall Immagino che tu abbia ragione se la tua soglia "ne vale la pena" è O (1). Il loop di scohe001 sarebbe O (N).
Piani cottura

82

Anche se i valori delle variabili non puntano al nome, hai accesso all'elenco di ogni variabile assegnata e al suo valore, quindi sono sorpreso che solo una persona abbia suggerito di fare un giro lì per cercare il tuo nome var.

Qualcuno ha detto su quella risposta che si potrebbe avere a camminare locali e globali della pila e la verifica di tutti da trovare foo, ma se fooviene assegnato nel campo di applicazione in cui si sta chiamando questa retrieve_namefunzione, è possibile utilizzare inspect's current frameper ottenere tutte quelle variabili locali .

La mia spiegazione potrebbe essere un po 'troppo prolissa (forse avrei dovuto usare meno "parole"), ma ecco come apparirebbe nel codice ( Nota che se ci sono più variabili assegnate allo stesso valore, lo farai ottenere entrambi quei nomi di variabili ):

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)

Se stai chiamando questa funzione da un'altra funzione, qualcosa del tipo:

def foo(bar):
    return retrieve_name(bar)

foo(baz)

E vuoi bazinvece di bar, dovrai solo tornare indietro di un ambito. Questo può essere fatto con l'aggiunta di un extra .f_backnella caller_local_varsinizializzazione.

Vedi un esempio qui: ideone


1
@theodox Sono assolutamente d'accordo, in quanto questo sarà probabilmente agire con import hooks, ironpythonejython
scohe001

5
Questo non funzionerà. le variabili atomiche non hanno il loro nome come attributo. Quindi, se avete a, b = 2, 2, retrieve_name(a)e retrieve_name(b)tornerete entrambi ['a', 'b']or['b', 'a']
tomas

1
@tomas In realtà, questo è un dettaglio di implementazione di un'ottimizzazione di cPython in cui i numeri interi inferiori a 255 sono sostanzialmente singleton, quindi qualsiasi variabile assegnata a questi valori tornerà effettivamente trueper un isconfronto
Toote

1
c'è una mod di questo per ottenere il nome di una var passata? ad es. def foo(bar): retrieve_name(bar)restituirà sempre la barra, ma cosa succede se si desidera foo(baz)tornare baz) anziché bar?
SumNeuron

1
@SumNeuron dovresti solo modificare la linea che assegna callers_local_varsper tornare indietro di un ambito in modo che guarderà l'ambito di ciò che sta chiamando foo. Cambia la riga in modo che sia inspect.currentframe().f_back.f_back.f_locals.items()(nota il extra f_back).
scohe001,

37

Con Python 3.8 si può semplicemente usare la funzione di debug f-string:

>>> foo = dict()
>>> f'{foo=}'.split('=')[0]
'foo' 

Bello ! E solo se vuoi ottenere il nome di una proprietà anziché un oggetto, puoi farlof'{foo=}'.split('=')[0].split('.')[-1]
Mickael V.

30

Su python3, questa funzione otterrà il maggior numero esterno nello stack:

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]

È utile ovunque nel codice. Attraversa lo stack inverso alla ricerca della prima partita.


Bel lavoro! Anche se ci provo retrieve_name(SomeClass.some_attribute)non funziona. Puoi aiutarci ulteriormente?
Nam G VU,

Questo lotta con le variabili booleane. stop_on_error
Finisco

26

Non credo sia possibile. Considera il seguente esempio:

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

Il punto ae bpunta allo stesso oggetto, ma l'oggetto non può sapere quali variabili puntano ad esso.


2
Sicuramente la relazione può essere fatta in due modi estendendo il conteggio dei riferimenti . Questa risposta (e alcune altre) fornisce persino un'implementazione.
Shayaan,

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

È usato così:

name(variable=variable)

12
Questo non restituirà il nome della variabile desiderata, ma "variabili". Ad esempio: l'utilizzo name(variable=variable)produrrà output ['variable']e l'utilizzo nonname(variable=another_variable) produrrà , ma piuttosto . ['another_variable']['variable']
DalyaG,

1
In realtà funziona come previsto. Devi solo sostituire entrambe le «variabili» con la tua variabile. Restituirà un elenco di un elemento con una stringa del nome della prima variabile. Ad esempio: >>> a = [] >>> b = a >>> name(a=b) ['a'] >>> name(b=a) ['b']`
Alguem Meugla,

8

Ecco un approccio. Non lo consiglierei a niente di importante, perché sarà piuttosto fragile. Ma si può fare.

Creare una funzione che utilizza il inspectmodulo per trovare il codice sorgente che lo ha chiamato. Quindi è possibile analizzare il codice sorgente per identificare i nomi delle variabili che si desidera recuperare. Ad esempio, ecco una funzione chiamata autodictche accetta un elenco di variabili e restituisce un dizionario che associa i nomi delle variabili ai loro valori. Per esempio:

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

Darebbe:

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

Ispezionare il codice sorgente stesso è meglio che cercare attraverso locals()o globals()perché quest'ultimo approccio non ti dice quali delle variabili sono quelle che desideri.

Ad ogni modo, ecco il codice:

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

L'azione avviene nella riga con inspect.getouterframes, che restituisce la stringa all'interno del codice che ha chiamato autodict.

L'ovvio svantaggio di questo tipo di magia è che fa ipotesi su come è strutturato il codice sorgente. E, naturalmente, non funzionerà affatto se viene eseguito all'interno dell'interprete.


Ciao, ho solo tre domande: 1. Perché "1"? 2. Perché "4"? 3. Perché "0"? :)
Piotr Wasilewicz,

7

Ho scritto la stregoneria del pacchetto per fare questo tipo di magia in modo robusto. Tu puoi scrivere:

from sorcery import dict_of

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

e passalo al costruttore del frame di dati. È equivalente a:

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

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

Se si desidera scrivere la propria funzione, è possibile che sia possibile verificare la presenza di una variabile definita in locali, quindi controllare i globali. Se non viene trovato nulla, puoi confrontare su id () per vedere se la variabile punta nella stessa posizione in memoria.

Se la tua variabile è in una classe, puoi usare className. dict .keys () o vars (self) per vedere se la tua variabile è stata definita.


Cosa succede se il nome si trova in una cornice del chiamante? Quindi dovresti fare cose stupide come camminare nello stack e controllare quello di tutti localse globals... e rischi di sbagliare il nome se non esiste davvero. È un sacco di lavoro per nessun guadagno utile reale.
nneonneo,

l'intera domanda è sciocca ... ma se è qualcosa che voleva fare, è possibile. Per quanto riguarda il controllo dell'esistenza potresti usare Globals (). Setdefault (var, <nuovo oggetto di tipo (var)) per creare qualcosa quando non c'è nulla. Non sono esattamente sicuro di cosa lo voglia, ma forse imparando che le variabili sono mantenute in base alla portata, potrebbe capire qualcosa di meglio.
Blakev,

3

In Python, le parole chiave defe classassoceranno un nome specifico all'oggetto che definiscono (funzione o classe). Allo stesso modo, ai moduli viene dato un nome in virtù del fatto che sono chiamati qualcosa di specifico nel filesystem. In tutti e tre i casi, esiste un modo ovvio di assegnare un nome "canonico" all'oggetto in questione.

Tuttavia, per altri tipi di oggetti, un tale nome canonico potrebbe semplicemente non esistere . Ad esempio, considera gli elementi di un elenco. Gli elementi nell'elenco non sono nominati individualmente ed è del tutto possibile che l'unico modo per fare riferimento ad essi in un programma sia utilizzare gli indici dell'elenco nell'elenco contenente. Se un tale elenco di oggetti è stato passato alla funzione, non è possibile assegnare ai valori identificatori significativi.

Python non salva il nome sul lato sinistro di un compito nell'oggetto assegnato perché:

  1. Richiederebbe di capire quale nome fosse "canonico" tra più oggetti in conflitto,
  2. Non avrebbe senso per gli oggetti che non sono mai assegnati a un nome di variabile esplicito,
  3. Sarebbe estremamente inefficiente,
  4. Letteralmente nessun'altra lingua esistente lo fa.

Quindi, ad esempio, le funzioni definite usando lambdaavranno sempre il "nome" <lambda>, piuttosto che un nome di funzione specifico.

L'approccio migliore sarebbe semplicemente chiedere al chiamante di passare un elenco (facoltativo) di nomi. Se la digitazione di '...','...'è troppo ingombrante, è possibile accettare ad esempio una singola stringa contenente un elenco di nomi separato da virgole (come namedtuplefa).


2

Penso che sia così difficile farlo in Python a causa del semplice fatto che non conoscerai mai il nome della variabile che stai utilizzando. Quindi, nel suo esempio, potresti fare:

Invece di:

list_of_dicts = [n_jobs, users, queues, priorities]

dict_of_dicts = {"n_jobs" : n_jobs, "users" : users, "queues" : queues, "priorities" : priorities}

2

solo un altro modo per farlo in base al contenuto della variabile di input:

(restituisce il nome della prima variabile che corrisponde alla variabile di input, altrimenti None. È possibile modificarlo per ottenere tutti i nomi di variabili che hanno lo stesso contenuto della variabile di input)

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

Questa funzione stampa il nome della variabile con il suo valore:

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
>> my_var = 5
>> my_var_name = [ k for k,v in locals().items() if v == my_var][0]
>> my_var_name 
'my_var'

locals () - Restituisce un dizionario contenente le variabili locali dell'ambito corrente. iterando attraverso questo dizionario possiamo verificare la chiave che ha un valore uguale alla variabile definita, semplicemente estraendo la chiave ci darà il testo della variabile in formato stringa.

da (dopo alcune modifiche) https://www.tutorialspoint.com/How-to-get-a-variable-name-as-a-string-in-Python


2

Ho un metodo, e sebbene non sia il più efficiente ... funziona! (e non prevede alcun modulo di fantasia).

Fondamentalmente si confronta il tuo ID di variabile per globali () ID variabili , quindi restituisce il nome della partita.

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

Se l'obiettivo è aiutarti a tenere traccia delle tue variabili, puoi scrivere una semplice funzione che etichetta la variabile e ne restituisce il valore e il tipo. Ad esempio, supponiamo i_f = 3.01 e lo arrotondi a un numero intero chiamato i_n da utilizzare in un codice, quindi hai bisogno di una stringa i_s che andrà in un rapporto.

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)

Questo stampa alla finestra ad ogni chiamata per scopi di debug e produce anche una stringa per il rapporto scritto. L'unico aspetto negativo è che devi digitare la variabile due volte ogni volta che chiami la funzione.

Sono un principiante di Python e ho trovato questo modo molto utile per registrare i miei sforzi mentre programmo e provo a far fronte a tutti gli oggetti in Python. Un difetto è che whatis () fallisce se chiama una funzione descritta al di fuori della procedura in cui viene usata. Ad esempio, int (i_f) era una chiamata di funzione valida solo perché la funzione int è nota a Python. Puoi chiamare whatis () usando int (i_f ** 2), ma se per qualche strana ragione scegli di definire una funzione chiamata int_squared, deve essere dichiarata all'interno della procedura in cui viene utilizzato whatis ().


1

Forse questo potrebbe essere utile:

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

La funzione passa attraverso l'elenco di ID di valori dall'ambito globale (lo spazio dei nomi può essere modificato), trova l'indice della var desiderata o richiesta in base al suo ID e quindi restituisce il nome dall'elenco di nomi globali basati sull'indice acquisito.


1

Il seguente metodo non restituirà il nome della variabile ma utilizzando questo metodo è possibile creare facilmente un frame di dati se la variabile è disponibile nell'ambito globale.

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

Per le costanti, puoi usare un enum, che supporta il recupero del suo nome.


0

Cerco di ottenere il nome dall'ispezione dei locali, ma non riesco a elaborare var like a [1], b.val. Dopo di ciò, ho avuto una nuova idea --- ottengo il nome var dal codice e lo provo succ! codice come sotto:

#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

È possibile provare quanto segue per recuperare il nome di una funzione definita (tuttavia non funziona per le funzioni integrate):

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.