我已经读了如何获得一个函数名作为字符串?。

如何对变量做同样的事情呢?与函数相反,Python变量没有__name__属性。

换句话说,如果我有一个变量,比如:

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

我正在寻找一个函数/属性,例如retrieve_name(),以便从这个列表中创建一个DataFrame in 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] 

Python中唯一具有规范名称的对象是模块、函数和类,当然,在定义了函数或类或导入了模块之后,不能保证这个规范名称在任何命名空间中都有任何意义。这些名称还可以在创建对象后修改,因此它们可能并不总是特别值得信赖。

如果不递归遍历命名对象的树,就不可能实现你想做的事情;名称是对对象的单向引用。普通或普通的Python对象不包含对其名称的引用。想象一下,如果每个整数、每个字典、每个列表、每个布尔值都需要维护一个表示引用它的名称的字符串列表!这将是实现的噩梦,对程序员没有什么好处。


我不相信这是可能的。考虑下面的例子:

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

a和b指向同一个对象,但是对象不知道哪些变量指向它。


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

如果你想写你自己的函数,你可以这样做,你可以检查一个局部变量定义,然后检查全局变量。如果没有找到,可以比较id(),看看变量是否指向内存中的相同位置。

如果你的变量在一个类中,你可以使用className.dict.keys()或vars(self)来查看你的变量是否已经被定义。


在Python中,def和class关键字将特定的名称绑定到它们定义的对象(函数或类)。类似地,模块通过在文件系统中调用某个特定的名称来命名。在这三种情况下,都有一种明显的方法可以将“规范”名称分配给有问题的对象。

然而,对于其他类型的对象,这样的规范名称可能根本不存在。例如,考虑列表中的元素。列表中的元素不是单独命名的,在程序中引用它们的唯一方法完全可能是在包含它们的列表上使用列表索引。如果将这样一个对象列表传递到函数中,则不可能为值分配有意义的标识符。

Python不会将赋值对象左边的名称保存到赋值对象中,因为:

这需要在多个冲突的对象中找出哪个名称是“规范的”, 对于那些从未赋值给显式变量名的对象来说,这是没有意义的, 这样效率会非常低, 实际上没有其他现存的语言能做到这一点。

例如,使用lambda定义的函数将始终具有"name" <lambda>,而不是特定的函数名。

最好的方法是简单地要求调用者传入一个(可选的)名称列表。如果输入“…”,“…”'太麻烦了,你可以接受一个包含逗号分隔的名字列表的字符串(像namedtuple那样)。


即使变量值不指向名称,您也可以访问每个赋值变量及其值的列表,所以我很惊讶,只有一个人建议在那里循环查找您的var名称。

有人在回答中提到,你可能需要遍历堆栈并检查每个人的局部变量和全局变量来找到foo,但如果foo被赋值在你调用retrieve_name函数的作用域内,你可以使用inspect的当前帧来获取所有这些局部变量。

我的解释可能有点太啰嗦了(也许我应该用“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,你只需要再往后退一步。这可以通过在caller_local_vars初始化中添加额外的.f_back来实现。

请看一个例子:ideone


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

它是这样使用的:

name(variable=variable)

这里有一种方法。我不建议在任何重要的事情上使用它,因为它会很脆。但这是可以做到的。

创建一个使用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的代码中的字符串。

这种魔法的明显缺点是,它对源代码的结构做出了假设。当然,如果它在解释器内部运行,它根本就不起作用。


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

它在代码的任何地方都有用。遍历反向堆栈,寻找第一个匹配项。


我认为在Python中很难做到这一点,因为一个简单的事实是,你永远不会不知道你正在使用的变量的名称。所以,在他的例子中,你可以这样做:

而不是:

list_of_dicts = [n_jobs, users, queues, priorities]

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

我写了包巫术健壮地做这种魔法。你可以这样写:

from sorcery import dict_of

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

并将其传递给dataframe构造函数。它相当于:

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

我试着从inspect locals中获取名称,但它不能处理var,比如[1],b.val。 之后,我有了一个新的想法——从代码中获取var名称,我尝试了succ! 代码如下:

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

如果目标是帮助您跟踪变量,您可以编写一个简单的函数来标记变量并返回其值和类型。例如,假设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)

这将在每次调用时打印到窗口中用于调试,并为书面报告生成一个字符串。唯一的缺点是每次调用函数时都必须输入两次变量。

I am a Python newbie and found this very useful way to log my efforts as I program and try to cope with all the objects in Python. One flaw is that whatis() fails if it calls a function described outside the procedure where it is used. For example, int(i_f) was a valid function call only because the int function is known to Python. You could call whatis() using int(i_f**2), but if for some strange reason you choose to define a function called int_squared it must be declared inside the procedure where whatis() is used.


这是另一种基于输入变量内容的方法:

(它返回第一个与输入变量匹配的变量名,否则为None。可以修改它,以获得所有与输入变量具有相同内容的变量名)

def retrieve_name(x, Vars=vars()):
    for k in Vars:
        if isinstance(x, type(Vars[k])):
            if x is Vars[k]:
                return k
    return None

这个函数将输出变量名及其值:

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

***输入和函数调用:*** My_var = 10 print_this (my_var) * * *输出* *:* my_var: 10


您可以尝试以下方法来检索您定义的函数的名称(但不适用于内置函数):

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

在Python 3.8中,可以简单地使用f-string调试特性:

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

这种方法的一个缺点是,为了打印'foo',你必须自己添加f'{foo=}'。换句话说,你已经知道变量的名字了。换句话说,上面的代码片段与刚才完全相同

>>> 'foo'

也许这很有用:

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

该函数遍历全局作用域的值的ID列表(可以编辑命名空间),根据其ID找到想要/需要的var或函数的索引,然后根据获得的索引从全局名称列表中返回名称。


博士TL;

使用python-varname中的Wrapper helper:

from varname.helpers import Wrapper

foo = Wrapper(dict())

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

对于列表理解部分,您可以执行:

n_jobs = Wrapper(<original_value>) 
users = Wrapper(<original_value>) 
queues = Wrapper(<original_value>) 
priorities = Wrapper(<original_value>) 

list_of_dicts = [n_jobs, users, queues, priorities]
columns = [d.name for d in list_of_dicts]
# ['n_jobs', 'users', 'queues', 'priorities']
# REMEMBER that you have to access the <original_value> by d.value

我是python-varname包的作者。如果你有任何问题,请告诉我,或者你可以在Github上提交问题。

长话短说

这可能吗?

是也不是。

我们在运行时检索变量名,因此需要调用一个函数,使我们能够访问前面的帧以检索变量名。这就是为什么我们需要一个包装器。在该函数中,在运行时,我们将解析前一帧中的源代码/AST节点,以获得确切的变量名。

然而,前几帧中的源代码/AST节点并不总是可用的,或者它们可以被其他环境修改(例如:pytest的assert语句)。一个简单的例子是代码通过exec()运行。尽管我们仍然能够从字节码中检索一些信息,但这需要太多的工作,而且也容易出错。

怎么做呢?

首先,我们需要确定给定变量的坐标系。它并不总是直接的前一帧。例如,我们可以为函数使用另一个包装器:

from varname import varname

def func():
  return varname()

def wrapped():
  return func()

x = wrapped()

在上面的例子中,我们必须跳过wrapped内部的帧,以获得正确的帧x = wrapped(),以便能够定位x。参数frame和ignore的varname允许我们跳过这些中间帧中的一些。在README文件和包的API文档中查看更多细节。

然后,我们需要解析AST节点,以定位变量被赋值(函数调用)的位置。这并不总是一个简单的任务。有时可能存在复杂的AST节点,例如x = [wrapped()]。我们需要通过遍历AST树来确定正确的赋值。

它有多可靠?

一旦我们确定了分配节点,它就是可靠的。

Varname完全依赖于执行包来查找节点。确保执行检测的节点是正确的(另见此)。

它部分适用于其他AST魔法应用的环境,包括pytest, ipython, macropy, birdseye, reticulate with R等。无论是执行还是varname都不能100%地在这些环境中工作。

我们需要一个包裹来做吗?

又一次,是又不是。

如果您的场景很简单,那么@juan Isaza或@scohe001提供的代码可能就足以处理这样的情况:变量定义在前面的直接帧上,AST节点是一个简单的赋值。你只需要返回一帧,并检索那里的信息。

但是,如果场景变得复杂,或者我们需要采用不同的应用程序场景,您可能需要一个像python-varname这样的包来处理它们。这些情况可能包括:

当源代码不可用或AST节点不可访问时,提供更友好的消息 跳过中间帧(允许函数在其他中间帧中被包装或调用) 自动忽略来自内置函数或库的调用。例如:x = str(func()) 检索赋值左侧的多个变量名 等。

f弦呢?

就像@Aivar Paalberg提供的答案。它绝对是快速和可靠的。但是,它不是在运行时,这意味着在输出名称之前必须知道它是foo。但是使用varname,你不需要知道变量是什么:

from varname import varname

def func():
  return varname()

# In external uses
x = func() # 'x'
y = func() # 'y'

最后

Python-varname不仅能够从赋值中检测变量名,而且还:

使用nameof直接检索变量名 检测下一个即时属性名,使用will 使用argname获取传递给函数的参数名/源

从它的文档中阅读更多。

然而,我想说的最后一句话是,尽量避免使用它。

因为您不能确保客户端代码将在源节点可用或AST节点可访问的环境中运行。当然,解析源代码、识别环境、检索AST节点并在需要时对它们进行评估都需要消耗资源。


下面的方法将不会返回变量的名称,但如果变量在全局作用域可用,则使用此方法可以轻松创建数据帧。

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

>> my_var = 5
>> my_var_name = [ k for k,v in locals().items() if v == my_var][0]
>> my_var_name 
'my_var'

如果myvar指向另一个变量,如果你得到一个错误,试试这个(由@mherzog建议)-

 >> my_var = 5
>> my_var_name = [ k for k,v in locals().items() if v is my_var][0]
>> my_var_name 
'my_var'

locals() -返回包含当前作用域局部变量的字典。 通过遍历这个字典,我们可以检查值等于定义变量的键,只需提取键就可以得到字符串格式的变量文本。

From(稍作改动后) https://www.tutorialspoint.com/How-to-get-a-variable-name-as-a-string-in-Python


我有一个方法,虽然不是最有效的…它的工作原理!(它不涉及任何花哨的模块)。

基本上,它将你的变量ID与globals()变量ID进行比较,然后返回匹配的名称。

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的答案的一个变体,如果更多变量具有相同的值,则返回多个结果。

def getVariableNames(variable):
    results = []
    globalVariables=globals().copy()
    for globalVariable in globalVariables:
        if id(variable) == id(globalVariables[globalVariable]):
            results.append(globalVariable)
    return results

a = 1
b = 1
getVariableNames(a)
# ['a', 'b']

每当我必须这样做时,主要是在与前端通信json模式和常量时,我定义了一个类,如下所示

class Param:
    def __init__(self, name, value):
        self.name = name
        self.value = value

然后用名称和值定义变量。

frame_folder_count = Param({'name':'frame_folder_count', 'value':10})

现在可以使用对象访问名称和值。

>>> frame_folder_count.name
'frame_folder_count'

当从变量值中查找变量名时, 你可能有几个变量等于相同的值, 例如var1 = 'hello'和var2 = 'hello'。

我的解决方案:

def find_var_name(val):

    dict_list = []
    global_dict = dict(globals())

    for k, v in global_dict.items():
        dict_list.append([k, v])
   
    return [item[0] for item in dict_list if item[1] == val]

var1 = 'hello'
var2 = 'hello'
find_var_name('hello')

输出

['var1', 'var2']

>>> def varname(v, scope=None):
        d = globals() if not scope else vars(scope); return [k for k in d if d[k] == v]
...
>>> d1 = {'a': 'ape'}; d2 = {'b': 'bear'}; d3 = {'c': 'cat'}
>>> ld = [d1, d2, d3]
>>> [varname(d) for d in ld]
[['d1'], ['d2'], ['d3']]
>>> d5 = d3
>>> [varname(d) for d in ld]
[['d1'], ['d2'], ['d3', 'd5']]
>>> def varname(v, scope=None):
        d = globals() if not scope else vars(scope); return [k for k in d if d[k] is v]
...
>>> [varname(d) for d in ld]
[['d1'], ['d2'], ['d3', 'd5']]

正如您在这里看到和注意到的,可以有多个具有相同值甚至地址的变量,因此使用包装器将名称与数据保存在一起是最好的。


iDilip的答案压缩版:

import inspect
def varname(x):
  return [k for k,v in inspect.currentframe().f_back.f_locals.items() if v is x][0]

hi = 123
print(varname(hi))

如果有两个值相同的变量,前面的一些情况会失败。所以提醒它很方便:

定义函数:

# Variable to string of variable name

def var_name(variable,i=0):

  results = []
  for name in globals():   
     if eval(name) == variable:
       results.append(name)

  if len(results) > 1:
    print('Warning:' )
    print('   var_name() has found',len(results), 'possible outcomes.')
    print('   Please choose the suitable parameter "i". Where "i" is the index')
    print('   that matches your choice from the list below.')
    print('  ',results) ; print('')

  return results[i]

Use:

var_1 = 10
var_name(var_1) # Output will be "var_1"

如果你有两个相同值的变量,比如var_1 = 8和var_2 = 8,那么就会出现一个警告。

var_1 = 8
var_2 = 8
var_name(var_2)  # Output will be "var_1" too but Warning will appear

获取实例变量的名称是完全可能的,只要它是类的属性。

这是我从Brett Slatkin的《Effective Python》中得到的。希望它能帮助到一些人:

类必须实现get、set和set_name dunder方法,它们是“描述符协议”的一部分。

当我运行它时,这是有效的:

class FieldThatKnowsItsName():
    def __init__(self):
        self.name = None
        self._value= None
        self.owner = None
 
    def __set_name__(self, owner, name):
        self.name = name
        self.owner = owner
        self.owner.fields[self.name] = self

    def __get__(self, instance, instance_type):
        return self

    def __set__(self, instance, value):
        self = value

class SuperTable:
    fields = {}
    field_1=FieldThatKnowsItsName()
    field_2=FieldThatKnowsItsName()

table = SuperTable()
print(table.field_1.name)
print(table.field_2.name)

然后,您可以根据需要添加方法或扩展数据类型。

作为奖励,set_name(self, owner, name) dunder也传递父实例,因此Field类实例可以向父实例注册自己。

这是我从Brett Slatkin的《Effective Python》中得到的。我花了一段时间才弄清楚如何实现。


你可以以kwargs的形式获取变量,并以字符串的形式返回:

var=2
def getVarName(**kwargs):
    return list(kwargs.keys())[0]

print (getVarName(var = var))

注:变量名必须等于自身。


如何对变量做同样的事情呢?与函数相反,Python变量没有__name__属性。

问题出现的原因是您对术语、语义或两者都感到困惑。

"variables" don't belong in the same category as "functions". A "variable" is not a thing that takes up space in memory while the code is running. It is just a name that exists in your source code - so that when you're writing the code, you can explain which thing you're talking about. Python uses names in the source code to refer to (i.e., give a name to) values. (In many languages, a variable is more like a name for a specific location in memory where the value will be stored. But Python's names actually name the thing in question.)

In Python, a function is a value. (In some languages, this is not the case; although there are bytes of memory used to represent the actual executable code, it isn't a discrete chunk of memory that your program logic gets to interact with directly.) In Python, every value is an object, meaning that you can assign names to it freely, pass it as an argument, return it from a function, etc. (In many languages, this is not the case.) Objects in Python have attributes, which are the things you access using the . syntax. Functions in Python have a __name__ attribute, which is assigned when the function is created. Specifically, when a def statement is executed (in most languages, creation of a function works quite differently), the name that appears after def is used as a value for the __name__ attribute, and also, independently, as a variable name that will get the function object assigned to it.

但大多数对象都没有这样的属性。

换句话说,如果我有一个变量,比如:

That's the thing: you don't "have" the variable in the sense that you're thinking of. You have the object that is named by that variable. Anything else depends on the information incidentally being stored in some other object - such as the locals() of the enclosing function. But it would be better to store the information yourself. Instead of relying on a variable name to carry information for you, explicitly build the mapping between the string name you want to use for the object, and the object itself.


如果您已经有了一个数据框架列表,如注释中所述,那么将该列表转换为字符串列表是很容易的。 不是从变量列表到字符串,而是用内置的exec函数结合f-strings(假设变量已经赋值,即vel, vol和area是现有pandas数据框架的变量名)走另一条路,将字符串列表到变量:

If:

dfstr_list = [
              'vel',
              'vol',
              'area'
             ]

这将遍历列表并使用每个dataframe写入excel,并定义sheetname:

for dfstr in dfstr_list:
    if dfstr in globals():
        exec(f"{dfstr}.to_excel(writer,sheet_name=dfstr)")

from __future__ import annotations
import inspect
import pandas as pd


# 변수 이름 가져오기
def getVariableName(variable: str) -> (str | Exception):
    callers_local_vars = inspect.currentframe().f_back.f_locals.items()

    df = pd.DataFrame(callers_local_vars)
    df.columns = ['함수명', '값']

    try:
        return df[(df['값'] == variable)].iloc[0]['함수명']
    except Exception as e:
        return e


test = 'Hello World!'

if __name__ == '__main__':
    print(getVariableName(test))