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

如何对变量做同样的事情呢?与函数相反,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 3.8中,可以简单地使用f-string调试特性:

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

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

>>> 'foo'

其他回答

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

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

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

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

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

博士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节点并在需要时对它们进行评估都需要消耗资源。

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

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

许多答案只返回一个变量名。但如果有多个变量具有相同的值,这就不能很好地工作。下面是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']