Obtenir le nom d'une variable sous forme de chaîne

174

Ce fil explique comment obtenir le nom d'une fonction sous forme de chaîne en Python: Comment obtenir un nom de fonction sous forme de chaîne?

Comment puis-je faire de même pour une variable? Contrairement aux fonctions, les variables Python n'ont pas l' __name__attribut.

En d'autres termes, si j'ai une variable telle que:

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

Je recherche une fonction / attribut, par exemple retrieve_name()pour créer un DataFrame dans Pandas à partir de cette liste , où les noms de colonnes sont donnés par les noms des dictionnaires actuels:

# 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
la source

Réponses:

20

En utilisant le python-varnamepackage, vous pouvez facilement récupérer le nom des variables

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

Dans votre cas, vous pouvez faire:

from varname import Wrapper

foo = Wrapper(dict())

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

Ou vous pouvez également essayer de récupérer directement le nom de la variable:

from varname import nameof

foo = dict()

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

Je suis l'auteur de ce package. S'il vous plaît laissez-moi savoir si vous avez des questions ou vous pouvez soumettre des problèmes sur github.

Panwen Wang
la source
2
OK, enfin compris! Installé en utilisant ce qui suit pip3 install python-varname==0.1.5; importé en utilisantfrom varname import nameof
enter_display_name_here
D'une manière ou d'une autre, la fonction ne fonctionne pas en boucle: test = {} print(varname.nameof(test)) for i in [0]: print(varname.nameof(test))la première impression donne test, l'impression dans la boucle augmenteVarnameRetrievingError: Callee's node cannot be detected.
Tillus
1
@Tillus Fixé àv0.2.0
Panwen Wang le
109

Les seuls objets en Python qui ont des noms canoniques sont des modules, des fonctions et des classes, et bien sûr il n'y a aucune garantie que ce nom canonique ait une signification dans n'importe quel espace de noms après que la fonction ou la classe a été définie ou le module importé. Ces noms peuvent également être modifiés après la création des objets afin qu'ils ne soient pas toujours particulièrement fiables.

Ce que vous voulez faire n'est pas possible sans parcourir récursivement l'arborescence des objets nommés ; un nom est une référence unidirectionnelle à un objet. Un objet Python commun ou de jardin ne contient aucune référence à ses noms. Imaginez si chaque entier, chaque dict, chaque liste, chaque booléen avait besoin de maintenir une liste de chaînes qui représentaient les noms qui y faisaient référence! Ce serait un cauchemar d'implémentation, avec peu d'avantages pour le programmeur.

kindall
la source
7
Merci. Mais pourquoi Python fait-il cela pour les fonctions alors? (c'est-à-dire un type d'objets Python)
Amelio Vazquez-Reina
1
@kindall: n'oubliez pas les modules: ils ont aussi des noms canoniques. N'oubliez pas non plus qu'il __name__peut toujours être modifié, ce qui signifie que le nom «canonique» peut ne pas être un nom pouvant être utilisé pour obtenir l'objet (par exemple lors de la création dynamique de classes).
nneonneo
8
Votre déclaration "ce n'est tout simplement pas possible" est Faux, comme l'a montré @ scohe001 . La base de données de noms de variables de Python est comme n'importe quelle autre base de données relationnelle, vous pouvez toujours rechercher des objets liés en «inverse» et retourner le premier trouvé ou l'ensemble complet des noms de variable valides pour une variable donnée.
plaques de cuisson
3
@hobs Vous avez techniquement raison ... le meilleur type de correct. Dans la pratique, cependant, il y a tellement de noms potentiels pour un objet que cela pose plus de problèmes que d'essayer de les obtenir.
kindall
1
@kindall Je suppose que vous avez raison si votre seuil "en vaut la peine" est O (1). La boucle de scohe001 serait O (N).
plaques de cuisson
82

Même si les valeurs des variables ne pointent pas vers le nom, vous avez accès à la liste de chaque variable affectée et à sa valeur, donc je suis étonné qu'une seule personne ait suggéré de faire une boucle là-bas pour rechercher le nom de votre var.

Quelqu'un a mentionné sur cette réponse que vous devrez peut-être parcourir la pile et vérifier les sections locales et globales de tout le monde pour trouver foo, mais si fooest attribué dans la portée où vous appelez cette retrieve_namefonction, vous pouvez utiliser inspects current framepour obtenir toutes ces variables locales .

Mon explication est peut-être un peu trop verbeuse (j'aurais peut-être dû utiliser un "toto" moins de mots), mais voici à quoi cela ressemblerait dans le code ( notez que s'il y a plus d'une variable assignée à la même valeur, vous obtenir ces deux noms de variables ):

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)

Si vous appelez cette fonction à partir d'une autre fonction, quelque chose comme:

def foo(bar):
    return retrieve_name(bar)

foo(baz)

Et vous voulez le bazau lieu de bar, il vous suffira de remonter un peu plus loin. Cela peut être fait en ajoutant un supplément .f_backlors de l' caller_local_varsinitialisation.

Voir un exemple ici: ideone

scohe001
la source
1
@theodox Je suis absolument d' accord, car cela va probablement agir avec import hooks, ironpythonetjython
scohe001
5
Cela ne fonctionnera pas. les variables atomiques n'ont pas leur nom comme attribut. Donc, si vous avez a, b = 2, 2, retrieve_name(a)et retrieve_name(b)reviendrez tous les deux ['a', 'b']ou['b', 'a']
tomas
1
@tomas En fait, c'est un détail d'implémentation d'une optimisation de cPython dans laquelle les entiers inférieurs à 255 sont essentiellement des singletons, donc toutes les variables affectées à ces valeurs seront effectivement trueis
renvoyées
1
y a-t-il un mod de ceci pour obtenir le nom d'un var passé? par exemple def foo(bar): retrieve_name(bar)retournera toujours bar, mais que faire si vous voulez foo(baz)retourner baz) au lieu de bar?
SumNeuron
1
@SumNeuron vous auriez juste à modifier la ligne qui attribue callers_local_varspour aller plus loin en arrière afin de regarder la portée de tout ce qui appelle foo. Changez la ligne pour être inspect.currentframe().f_back.f_back.f_locals.items()(notez le supplément f_back).
scohe001
37

Avec Python 3.8, on peut simplement utiliser la fonction de débogage f-string:

>>> foo = dict()
>>> f'{foo=}'.split('=')[0]
'foo' 
Aivar Paalberg
la source
Agréable ! Et juste si vous voulez obtenir le nom d'une propriété à la place d'un objet, vous pouvezf'{foo=}'.split('=')[0].split('.')[-1]
Mickael V.
30

Sur python3, cette fonction obtiendra le nom le plus externe de la pile:

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]

C'est utile n'importe où sur le code. Traverse la pile inversée à la recherche du premier match.

Juan Isaza
la source
Bon travail! Bien que j'essaye, retrieve_name(SomeClass.some_attribute)cela ne fonctionne pas. Pouvez-vous aider davantage à ce sujet?
Nam G VU
Cela se débat avec les variables booléennes. Je me retrouve avecstop_on_error
SumNeuron
26

Je ne crois pas que ce soit possible. Prenons l'exemple suivant:

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

Les aet bpointent vers le même objet, mais l'objet ne peut pas savoir quelles variables pointent vers lui.

korylprince
la source
2
La relation peut certainement être bidirectionnelle en étendant le comptage de références . Cette réponse (et quelques autres) fournit même une implémentation.
Shayaan
17
def name(**variables):
    return [x for x in variables]

Il est utilisé comme ceci:

name(variable=variable)
fdvfcges
la source
12
Cela ne renverra pas le nom de la variable souhaitée, mais "variables". Par exemple - en utilisant la name(variable=variable)volonté de sortie ['variable'], et l' utilisation name(variable=another_variable)sera pas sortie , ['another_variable']mais plutôt ['variable'].
DalyaG
1
En fait, cela fonctionne comme prévu. Il vous suffit de remplacer les deux «variables» par votre variable. Il renverra une liste à un élément avec une chaîne du nom de la première variable. Par exemple: >>> a = [] >>> b = a >>> name(a=b) ['a'] >>> name(b=a) ['b']`
Alguem Meugla
8

Voici une approche. Je ne recommanderais pas cela pour quelque chose d'important, car ce sera assez fragile. Mais cela peut être fait.

Créez une fonction qui utilise le inspectmodule pour trouver le code source qui l'a appelé. Ensuite, vous pouvez analyser le code source pour identifier les noms de variables que vous souhaitez récupérer. Par exemple, voici une fonction appelée autodictqui prend une liste de variables et renvoie un dictionnaire mappant les noms de variables à leurs valeurs. Par exemple:

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

Donnerait:

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

Inspecter le code source lui-même est préférable à une recherche dans le locals()ou globals()parce que cette dernière approche ne vous dit pas quelles variables sont celles que vous souhaitez.

En tout cas, voici le code:

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'action se produit dans la ligne avec inspect.getouterframes, qui renvoie la chaîne dans le code qui a appelé autodict.

L'inconvénient évident de ce type de magie est qu'elle émet des hypothèses sur la structure du code source. Et bien sûr, cela ne fonctionnera pas du tout s'il est exécuté dans l'interpréteur.

Zachary Ernst
la source
Bonjour, je n'ai que trois questions: 1. Pourquoi "1"? 2. Pourquoi "4"? 3. Pourquoi "0"? :)
Piotr Wasilewicz
7

J'ai écrit le paquet sorcellerie pour faire ce genre de magie avec robustesse. Tu peux écrire:

from sorcery import dict_of

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

et transmettez-le au constructeur de dataframe. C'est équivalent à:

columns = dict(n_jobs=n_jobs, users=users, queues=queues, priorities=priorities)
Alex Hall
la source
6
>>> locals()['foo']
{}
>>> globals()['foo']
{}

Si vous vouliez écrire votre propre fonction, cela pourrait être fait de telle sorte que vous puissiez vérifier une variable définie dans les locaux puis vérifier les globaux. Si rien n'est trouvé, vous pouvez comparer sur id () pour voir si la variable pointe vers le même emplacement en mémoire.

Si votre variable est dans une classe, vous pouvez utiliser className. dict .keys () ou vars (self) pour voir si votre variable a été définie.

blakev
la source
Et si le nom est dans un cadre d'appelant? Ensuite, vous devrez faire des choses stupides comme marcher dans la pile et vérifier tout le monde localset globals... et vous risquez de vous tromper si aucun nom n'existe réellement. C'est une tonne de travail sans gain utile réel.
nneonneo
toute la question est idiote ... mais si c'est quelque chose qu'il voulait faire, c'est possible. En ce qui concerne la vérification de l'existence, vous pouvez utiliser Globals (). Setdefault (var, <nouvel objet de type (var)) pour créer quelque chose quand il n'y a rien. Je ne sais pas exactement pourquoi il le veut, mais peut-être qu'en apprenant que les variables sont maintenues par portée, il pourrait trouver quelque chose de mieux.
blakev
3

En Python, les mots def- classclés et lieront un nom spécifique à l'objet qu'ils définissent (fonction ou classe). De même, les modules reçoivent un nom parce qu'ils sont appelés quelque chose de spécifique dans le système de fichiers. Dans les trois cas, il existe une manière évidente d'attribuer un nom «canonique» à l'objet en question.

Cependant, pour d'autres types d'objets, un tel nom canonique peut simplement ne pas exister . Par exemple, considérez les éléments d'une liste. Les éléments de la liste ne sont pas nommés individuellement et il est tout à fait possible que la seule façon de s'y référer dans un programme soit d'utiliser des index de liste sur la liste contenante. Si une telle liste d'objets était transmise à votre fonction, vous ne pourriez pas attribuer d'identificateurs significatifs aux valeurs.

Python n'enregistre pas le nom sur le côté gauche d'une affectation dans l'objet affecté car:

  1. Il faudrait déterminer quel nom était "canonique" parmi plusieurs objets en conflit,
  2. Cela n'aurait aucun sens pour les objets qui ne sont jamais affectés à un nom de variable explicite,
  3. Ce serait extrêmement inefficace,
  4. Aucune autre langue existante ne fait cela.

Ainsi, par exemple, les fonctions définies avec lambdaauront toujours le "nom" <lambda>, plutôt qu'un nom de fonction spécifique.

La meilleure approche serait simplement de demander à l'appelant de transmettre une liste (facultative) de noms. Si taper le '...','...'est trop compliqué, vous pouvez accepter par exemple une seule chaîne contenant une liste de noms séparés par des virgules (comme le namedtuplefait).

nneonneo
la source
2

Je pense que c'est tellement difficile de faire cela en Python à cause du simple fait que vous ne saurez jamais le nom de la variable que vous utilisez. Donc, dans son exemple, vous pourriez faire:

Au lieu de:

list_of_dicts = [n_jobs, users, queues, priorities]

dict_of_dicts = {"n_jobs" : n_jobs, "users" : users, "queues" : queues, "priorities" : priorities}
Joe Camel
la source
2

juste une autre façon de le faire en fonction du contenu de la variable d'entrée:

(il renvoie le nom de la première variable qui correspond à la variable d'entrée, sinon None. On peut le modifier pour obtenir tous les noms de variables qui ont le même contenu que la variable d'entrée)

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
la source
2

Cette fonction affichera le nom de la variable avec sa valeur:

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
la source
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 () - Renvoie un dictionnaire contenant les variables locales de la portée actuelle. en itérant dans ce dictionnaire, nous pouvons vérifier la clé qui a une valeur égale à la variable définie, le simple fait d'extraire la clé nous donnera le texte de la variable au format chaîne.

depuis (après quelques changements) https://www.tutorialspoint.com/How-to-get-a-variable-name-as-a-string-in-Python

Anshu Pandey
la source
2

J'ai une méthode, et même si ce n'est pas la plus efficace ... ça marche! (et cela n'implique aucun module sophistiqué).

Fondamentalement , il compare votre ID de variable à globals () les IDs de variables , puis retourne le nom du match.

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
la source
1

Si l'objectif est de vous aider à garder une trace de vos variables, vous pouvez écrire une fonction simple qui étiquette la variable et renvoie sa valeur et son type. Par exemple, supposons que i_f = 3.01 et que vous l'arrondissiez à un entier appelé i_n à utiliser dans un code, puis que vous ayez besoin d'une chaîne i_s qui ira dans un rapport.

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)

Cela s'imprime dans la fenêtre à chaque appel à des fins de débogage et génère également une chaîne pour le rapport écrit. Le seul inconvénient est que vous devez taper la variable deux fois chaque fois que vous appelez la fonction.

Je suis un débutant en Python et j'ai trouvé ce moyen très utile de consigner mes efforts pendant que je programme et que j'essaie de gérer tous les objets en Python. Un défaut est que whatis () échoue s'il appelle une fonction décrite en dehors de la procédure où elle est utilisée. Par exemple, int (i_f) était un appel de fonction valide uniquement parce que la fonction int est connue de Python. Vous pouvez appeler whatis () en utilisant int (i_f ** 2), mais si pour une raison étrange vous choisissez de définir une fonction appelée int_squared, elle doit être déclarée dans la procédure où whatis () est utilisé.

Guy Vandegrift
la source
1

Peut-être que cela pourrait être utile:

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

La fonction parcourt la liste des ID de valeurs de la portée globale (l'espace de noms peut être modifié), trouve l'index de la variable ou de la fonction voulue / requise en fonction de son ID, puis renvoie le nom de la liste des noms globaux en fonction sur l'index acquis.

Reitsch-Z2
la source
1

La méthode suivante ne retournera pas le nom de la variable, mais en utilisant cette méthode, vous pouvez créer facilement un bloc de données si la variable est disponible dans la portée 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
user5828964
la source
0

Pour les constantes, vous pouvez utiliser une énumération, qui prend en charge la récupération de son nom.

Dana
la source
0

J'essaie d'obtenir le nom des locaux inspectés, mais il ne peut pas traiter var aime a [1], b.val. Après cela, j'ai eu une nouvelle idée - obtenir le nom de var à partir du code, et je l'essaye avec succès! code comme ci-dessous:

#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
la source
0

Vous pouvez essayer ce qui suit pour récupérer le nom d'une fonction que vous avez définie (ne fonctionne pas pour les fonctions intégrées cependant):

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
la source