Obtener el nombre de una variable como una cadena

173

Este hilo discute cómo obtener el nombre de una función como una cadena en Python: ¿Cómo obtener el nombre de una función como una cadena?

¿Cómo puedo hacer lo mismo para una variable? A diferencia de las funciones, las variables de Python no tienen el __name__atributo.

En otras palabras, si tengo una variable como:

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

Estoy buscando una función / atributo, por ejemplo retrieve_name(), para crear un DataFrame en Pandas a partir de esta lista , donde los nombres de columna están dados por los nombres de los diccionarios reales:

# 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
fuente

Respuestas:

19

Usando el python-varnamepaquete, puede recuperar fácilmente el nombre de las variables

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

En su caso, puede hacer:

from varname import Wrapper

foo = Wrapper(dict())

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

O también puede intentar recuperar el nombre de la variable directamente:

from varname import nameof

foo = dict()

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

Soy el autor de este paquete. Avíseme si tiene alguna pregunta o puede enviar problemas en github.

Panwen Wang
fuente
2
OK, finalmente lo tengo! Instalado usando lo siguiente pip3 install python-varname==0.1.5; importado usandofrom varname import nameof
enter_display_name_here
De alguna manera, la función no funciona en un bucle: test = {} print(varname.nameof(test)) for i in [0]: print(varname.nameof(test))la primera impresión da test, la impresión en el bucle aumentaVarnameRetrievingError: Callee's node cannot be detected.
Tillus
1
@Tillus fijado env0.2.0
Panwen Wang
107

Los únicos objetos en Python que tienen nombres canónicos son módulos, funciones y clases, y, por supuesto, no hay garantía de que este nombre canónico tenga algún significado en ningún espacio de nombres después de que la función o clase haya sido definida o importado el módulo. Estos nombres también se pueden modificar después de crear los objetos, por lo que no siempre son particularmente confiables.

Lo que desea hacer no es posible sin recorrer recursivamente el árbol de objetos con nombre ; un nombre es una referencia unidireccional a un objeto. Un objeto Python común o de variedad de jardín no contiene referencias a sus nombres. ¡Imagínese si cada entero, cada dict, cada lista, cada booleano necesitara mantener una lista de cadenas que representaran nombres que se referían a él! Sería una pesadilla de implementación, con poco beneficio para el programador.

un poco
fuente
77
Gracias. Pero, ¿por qué Python hace esto para las funciones entonces? (es decir, un tipo de objetos Python)
Amelio Vazquez-Reina
1
@kindall: no olvides los módulos: también tienen nombres canónicos. Además, no olvide que __name__siempre se puede modificar, lo que significa que el nombre "canónico" puede no ser un nombre que pueda usarse para obtener el objeto (por ejemplo, al crear clases dinámicamente).
nneonneo
8
Su afirmación "simplemente no es posible" es falsa, como mostró @ scohe001 . La base de datos de nombres de variables de Python es como cualquier otra base de datos relacional, siempre puede buscar objetos relacionados en "reversa" y devolver el primer conjunto encontrado o el conjunto completo de nombres de variables válidos para cualquier variable dada.
encimeras
3
@hobs Eres técnicamente correcto ... el mejor tipo de correcto. En la práctica, sin embargo, hay tantos nombres potenciales para un objeto que es más problemático de lo que vale la pena tratar de obtenerlos.
poco
1
@kindall Creo que tienes razón si tu umbral "vale la pena" es O (1). El bucle de scohe001 sería O (N).
placas el
82

Incluso si los valores de las variables no apuntan al nombre, tiene acceso a la lista de todas las variables asignadas y su valor, por lo que me sorprende que solo una persona sugiera recorrer allí para buscar su nombre de var.

Alguien mencionó en esa respuesta que es posible que tenga que caminar por la pila y verificar los locales y globales de todos para encontrar foo, pero si fooestá asignado en el alcance donde está llamando a esta retrieve_namefunción, puede usar inspect's' current framepara obtener todas esas variables locales .

Mi explicación puede ser un poco demasiado prolija (tal vez debería haber usado un "foo" menos palabras), pero así es como se vería en el código ( Tenga en cuenta que si hay más de una variable asignada al mismo valor, lo hará obtener ambos nombres 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 está llamando a esta función desde otra función, algo como:

def foo(bar):
    return retrieve_name(bar)

foo(baz)

Y desea el en bazlugar de bar, solo tendrá que retroceder un alcance más. Esto se puede hacer agregando un extra .f_backen la caller_local_varsinicialización.

Vea un ejemplo aquí: ideone

scohe001
fuente
1
@theodox estoy totalmente de acuerdo, ya que esto probablemente actuará con import hooks, ironpythonyjython
scohe001
55
Esto no funcionara. Las variables atómicas no tienen su nombre como atributo. Entonces, si lo ha hecho a, b = 2, 2, retrieve_name(a)y retrieve_name(b)ambos regresarán ['a', 'b']o['b', 'a']
tomas
1
@tomas En realidad, es un detalle de implementación de una optimización de CPython en el que los números enteros por debajo de 255 son básicamente únicos, por lo que cualquier variables asignadas esos valores volverán efectiva truepara una iscomparación
Toote
1
¿hay una modificación de esto para obtener el nombre de una var aprobada? por ejemplo def foo(bar): retrieve_name(bar), siempre devolverá la barra, pero ¿qué pasa si desea foo(baz)volver baz) en lugar de bar?
SumNeuron el
1
@SumNeuron, solo tendría que modificar la línea que se asigna callers_local_varspara ir un alcance más atrás para que vea el alcance de lo que sea que llame foo. Cambie la línea para ser inspect.currentframe().f_back.f_back.f_locals.items()(observe el extra f_back).
scohe001
37

Con Python 3.8 uno simplemente puede usar la función de depuración de f-string:

>>> foo = dict()
>>> f'{foo=}'.split('=')[0]
'foo' 
Aivar Paalberg
fuente
Agradable ! Y solo si desea obtener el nombre de una propiedad en lugar de un objeto, puede hacerlof'{foo=}'.split('=')[0].split('.')[-1]
Mickael V.
30

En python3, esta función obtendrá el nombre más externo en la pila:

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]

Es útil en cualquier parte del código. Atraviesa la pila invertida buscando el primer partido.

juan Isaza
fuente
¡Buen trabajo! Aunque lo intento retrieve_name(SomeClass.some_attribute)no funciona. ¿Puedes ayudar más en eso?
Nam G VU
Esto lucha con las variables booleanas. Termino constop_on_error
SumNeuron
26

No creo que esto sea posible. Considere el siguiente ejemplo:

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

El ay bapunta al mismo objeto, pero el objeto no puede saber qué variables apuntan a él.

korylprince
fuente
2
Seguramente la relación se puede hacer de dos vías al extender el recuento de referencias . Esta respuesta (y algunas otras) incluso proporciona una implementación.
Shayaan
17
def name(**variables):
    return [x for x in variables]

Se usa así:

name(variable=variable)
fdvfcges
fuente
12
Esto no devolverá el nombre de la variable deseada, sino "variables". Por ejemplo: usar name(variable=variable)generará salida ['variable']y usar noname(variable=another_variable) generará salida, sino más bien . ['another_variable']['variable']
DalyaG
1
En realidad funciona como se esperaba. Solo tiene que reemplazar ambas «variables» por su variable. Devolverá una lista de un elemento con una cadena del nombre de la primera variable. Por ejemplo: >>> a = [] >>> b = a >>> name(a=b) ['a'] >>> name(b=a) ['b']`
Alguem Meugla
8

Aquí hay un enfoque. No recomendaría esto para nada importante, porque será bastante frágil. Pero puede hacerse.

Cree una función que use el inspectmódulo para encontrar el código fuente que lo llamó. Luego, puede analizar el código fuente para identificar los nombres de las variables que desea recuperar. Por ejemplo, aquí hay una función llamada autodictque toma una lista de variables y devuelve los nombres de las variables de asignación de diccionario a sus valores. P.ej:

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

Daría:

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

Inspeccionar el código fuente en sí es mejor que buscar a través de locals()o globals()porque este último enfoque no le dice cuáles de las variables son las que desea.

En cualquier caso, aquí está el código:

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

La acción ocurre en la línea con inspect.getouterframes, que devuelve la cadena dentro del código que llamó autodict.

La desventaja obvia de este tipo de magia es que hace suposiciones sobre cómo está estructurado el código fuente. Y, por supuesto, no funcionará si se ejecuta dentro del intérprete.

Zachary Ernst
fuente
Hola, solo tengo tres preguntas: 1. ¿Por qué "1"? 2. ¿Por qué "4"? 3. ¿Por qué "0"? :)
Piotr Wasilewicz
7

Escribí el paquete de brujería para hacer este tipo de magia de manera robusta. Puedes escribir:

from sorcery import dict_of

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

y pasar eso al constructor del marco de datos. Es equivalente a:

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

Si desea escribir su propia función, puede hacerlo de manera que pueda verificar una variable definida en locales y luego verificar las variables globales. Si no se encuentra nada, puede comparar en id () para ver si la variable apunta a la misma ubicación en la memoria.

Si su variable está en una clase, puede usar className. dict .keys () o vars (self) para ver si su variable ha sido definida.

blakev
fuente
¿Qué pasa si el nombre está en un marco de llamada? Entonces tendrías que hacer cosas tontas como caminar por la pila y revisar a todos localsy globals... y te arriesgas a equivocarte si no existe ninguno. Es un montón de trabajo sin ganancia útil real.
nneonneo
toda la pregunta es tonta ... pero si es algo que quería hacer, es posible. En cuanto a verificar la existencia, podría usar Globals (). Setdefault (var, <nuevo objeto de tipo (var)) para crear algo cuando no hay nada allí. No estoy exactamente seguro de para qué lo quiere, pero tal vez aprendiendo que las variables se mantienen por alcance, podría descubrir algo mejor.
blakev
3

En Python, los defy classlas palabras clave se unirán un nombre específico al objeto que definen (función o clase). Del mismo modo, los módulos reciben un nombre en virtud de ser llamados algo específico en el sistema de archivos. En los tres casos, hay una forma obvia de asignar un nombre "canónico" al objeto en cuestión.

Sin embargo, para otros tipos de objetos, tal nombre canónico simplemente puede no existir . Por ejemplo, considere los elementos de una lista. Los elementos en la lista no se nombran individualmente, y es completamente posible que la única forma de referirse a ellos en un programa sea mediante el uso de índices de lista en la lista que lo contiene. Si dicha lista de objetos se pasó a su función, no podría asignar identificadores significativos a los valores.

Python no guarda el nombre en el lado izquierdo de una tarea en el objeto asignado porque:

  1. Requeriría averiguar qué nombre era "canónico" entre múltiples objetos en conflicto,
  2. No tendría sentido para los objetos que nunca se asignan a un nombre de variable explícito,
  3. Sería extremadamente ineficiente
  4. Literalmente, ningún otro idioma en existencia hace eso.

Entonces, por ejemplo, las funciones definidas usando lambdasiempre tendrán el "nombre" <lambda>, en lugar de un nombre de función específico.

El mejor enfoque sería simplemente pedirle a la persona que llama que pase una lista (opcional) de nombres. Si escribir '...','...'es demasiado engorroso, puede aceptar, por ejemplo, una sola cadena que contenga una lista de nombres separados por comas (como lo namedtuplehace).

nneonneo
fuente
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 (): devuelve un diccionario que contiene las variables locales del ámbito actual. iterando a través de este diccionario podemos verificar la clave que tiene un valor igual a la variable definida, solo extrayendo la clave nos dará el texto de la variable en formato de cadena.

desde (después de un poco de cambios) https://www.tutorialspoint.com/How-to-get-a-variable-name-as-a-string-in-Python

Anshu Pandey
fuente
2

Creo que es muy difícil hacer esto en Python debido al simple hecho de que nunca sabrás el nombre de la variable que estás usando. Entonces, en su ejemplo, podrías hacer:

En vez de:

list_of_dicts = [n_jobs, users, queues, priorities]

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

Solo otra forma de hacerlo en función del contenido de la variable de entrada:

(devuelve el nombre de la primera variable que coincide con la variable de entrada; de lo contrario, ninguna. Se puede modificar para obtener todos los nombres de variables que tienen el mismo contenido que la variable de entrada)

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
fuente
2

Esta función imprimirá el nombre de la variable con su valor:

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
fuente
2

Tengo un método, y aunque no es el más eficiente ... ¡funciona! (y no involucra ningún módulo sofisticado).

Básicamente se compara su ID de variable a variables globales () identificadores de variables' , a continuación, devuelve el nombre del partido.

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
fuente
1

Si el objetivo es ayudarlo a realizar un seguimiento de sus variables, puede escribir una función simple que etiquete la variable y devuelva su valor y tipo. Por ejemplo, suponga que i_f = 3.01 y lo redondea a un número entero llamado i_n para usar en un código, y luego necesita una cadena i_s que irá a un informe.

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)

Esto se imprime en la ventana en cada llamada con fines de depuración y también produce una cadena para el informe escrito. El único inconveniente es que debe escribir la variable dos veces cada vez que llama a la función.

Soy un novato en Python y encontré esta forma muy útil de registrar mis esfuerzos mientras programo e intento hacer frente a todos los objetos en Python. Una falla es que whatis () falla si llama a una función descrita fuera del procedimiento donde se usa. Por ejemplo, int (i_f) era una llamada de función válida solo porque Python conoce la función int . Puede llamar a whatis () usando int (i_f ** 2), pero si por alguna extraña razón elige definir una función llamada int_squared, debe declararse dentro del procedimiento donde se usa whatis ().

Guy Vandegrift
fuente
1

Quizás esto podría ser útil:

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

La función pasa por la lista de ID de valores del ámbito global (el espacio de nombres podría editarse), encuentra el índice de la variable o función deseada / requerida en función de su ID, y luego devuelve el nombre de la lista de nombres globales basados en el índice adquirido.

Reitsch-Z2
fuente
1

El siguiente método no devolverá el nombre de la variable, pero con este método puede crear fácilmente un marco de datos si la variable está disponible en el ámbito global.

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
usuario5828964
fuente
0

Para las constantes, puede usar una enumeración, que admite recuperar su nombre.

Dana
fuente
0

Intento obtener el nombre de inspeccionar a los locales, pero no puede procesar varios me gusta a [1], b.val. Después de eso, tuve una nueva idea: ¡obtener el nombre var del código, y lo intento succ! código como a continuación:

#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
fuente
0

Puede intentar lo siguiente para recuperar el nombre de una función que definió (aunque no funciona para las funciones integradas):

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
fuente