326

本主题讨论如何在 Python 中将函数名作为字符串获取: 如何将函数名作为字符串获取?

我怎样才能对变量做同样的事情?与函数相反,Python 变量没有__name__属性。

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

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

我正在寻找一个函数/属性,例如retrieve_name()为了从这个 list 在 Pandas 中创建一个 DataFrame,其中列名由实际字典的名称给出:

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

27 回答 27

210

使用 Python 3.8 可以简单地使用f-string调试功能:

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

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

>>> 'foo'

请在此处查看可能适用于回答该问题的其他答案。

于 2019-10-18T12:42:01.393 回答
129

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

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

于 2013-08-25T03:08:35.000 回答
124

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

有人在那个答案中提到,您可能必须遍历堆栈并检查每个人的本地变量和全局变量才能找到foo,但是如果foo在您调用此retrieve_name函数的范围内分配,您可以使用inspect'scurrent frame来获取所有这些局部变量.

我的解释可能有点罗嗦(也许我应该少用“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,你只需要进一步返回一个范围。.f_back这可以通过在caller_local_vars初始化中添加额外的来完成。

在此处查看示例:ideone

于 2013-08-25T03:55:13.477 回答
72

TL;博士

使用Wrapper来自的助手python-varname

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 上提交问题。

长答案

甚至可能吗?

是和否。

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

但是,之前帧中的源代码/AST 节点并不总是可用,或者它们可能被其他环境修改(例如:pytestassert语句)。一个简单的例子是代码通过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完全取决于executing包来寻找节点。确保执行检测的节点是正确的(另请参见this)。

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

我们需要一个包来做吗?

嗯,是的,不是的,再一次。

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

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

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

怎么样f-string

就像@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 节点并在需要时评估它们会耗费资源。

于 2019-12-16T20:52:48.173 回答
40

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

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

于 2016-11-10T20:00:46.067 回答
31

我不相信这是可能的。考虑以下示例:

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

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

于 2013-08-25T03:10:34.470 回答
15
def name(**variables):
    return [x for x in variables]

它是这样使用的:

name(variable=variable)
于 2013-10-05T19:45:26.537 回答
15
>> 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() - 返回包含当前作用域的局部变量的字典。通过遍历这个字典,我们可以检查值等于定义变量的键,只需提取键就会给我们字符串格式的变量文本。

来自(经过一些更改) https://www.tutorialspoint.com/How-to-get-a-variable-name-as-a-string-in-Python

于 2020-01-18T19:32:53.717 回答
12

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

from sorcery import dict_of

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

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

columns = dict(n_jobs=n_jobs, users=users, queues=queues, priorities=priorities)
于 2018-07-15T11:34:04.363 回答
9

这是一种方法。我不会推荐任何重要的东西,因为它会很脆弱。但这是可以完成的。

创建一个使用该inspect模块的函数来查找调用它的源代码。然后您可以解析源代码以识别您想要检索的变量名称。例如,这里有一个函数autodict,它接受一个变量列表并返回一个字典,将变量名映射到它们的值。例如:

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

会给:

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

检查源代码本身比搜索locals()or更好,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

该操作发生在 with 的行中inspect.getouterframes,它返回调用 的代码中的字符串autodict

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

于 2015-06-18T17:43:10.890 回答
8

此函数将打印变量名称及其值:

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
于 2019-03-05T09:23:16.337 回答
7
>>> locals()['foo']
{}
>>> globals()['foo']
{}

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

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

于 2013-08-25T03:16:41.163 回答
5

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

基本上,它将您的变量 IDglobals() 变量的 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
于 2020-03-24T07:52:44.867 回答
4

在 Python 中,defandclass关键字将特定名称绑定到它们定义的对象(函数或类)。类似地,模块通过在文件系统中被称为特定的东西而被命名。在所有这三种情况下,有一种明显的方法可以为相关对象分配“规范”名称。

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

Python 不会将分配左侧的名称保存到分配的对象中,因为:

  1. 它需要在多个冲突对象中找出哪个名称是“规范的”,
  2. 对于从未分配给显式变量名的对象来说,这是没有意义的,
  3. 这将是极其低效的,
  4. 从字面上看,没有其他语言可以做到这一点。

因此,例如,使用定义的函数lambda将始终具有“名称” <lambda>,而不是特定的函数名称。

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

于 2013-08-25T03:21:19.813 回答
4

许多答案只返回一个变量名。但是,如果多个变量具有相同的值,那将无法正常工作。这是 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']
于 2020-07-30T10:58:22.340 回答
3

根据输入变量的内容执行此操作的另一种方法:

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

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
于 2019-01-29T14:42:28.203 回答
3

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

代替:

list_of_dicts = [n_jobs, users, queues, priorities]

dict_of_dicts = {"n_jobs" : n_jobs, "users" : users, "queues" : queues, "priorities" : priorities}
于 2017-09-28T13:56:51.127 回答
2

也许这可能有用:

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

该函数遍历全局范围内的值的 ID 列表(可以编辑命名空间),根据其 ID 找到所需/需要的 var 或函数的索引,然后从基于全局名称的列表中返回名称在获得的索引上。

于 2019-11-27T23:21:23.403 回答
2

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

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

我是一个 Python 新手,发现这种方法非常有用,可以在我编程时记录我的工作并尝试处理 Python 中的所有对象。一个缺陷是,如果 whatis() 调用在使用它的过程之外描述的函数,它就会失败。例如,int(i_f) 是一个有效的函数调用,只是因为Python 知道int函数。您可以使用 int(i_f**2) 调用 whatis(),但如果出于某种奇怪的原因您选择定义一个名为 int_squared 的函数,则必须在使用 whatis() 的过程中声明它。

于 2019-01-04T04:22:45.067 回答
2

每当我必须这样做时,主要是在与前端通信 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'
于 2021-01-12T06:26:20.617 回答
2
>>> 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']]

正如您在此处看到并指出的那样,可以有多个具有相同值甚至地址的变量,因此最好使用包装器将名称与数据一起保存。

于 2021-05-06T13:52:44.337 回答
1

以下方法不会返回变量的名称,但如果变量在全局范围内可用,则使用此方法可以轻松创建数据框。

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
于 2020-01-13T18:02:26.727 回答
0

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))
于 2021-10-08T13:08:30.817 回答
0

我尝试从检查本地人那里获取名称,但它无法处理 var like a[1]、b.val。之后,我有了一个新的想法——从代码中获取var name,我试了一下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 ""
于 2018-12-08T16:33:30.150 回答
0

如果有两个具有相同值的变量,则前面的一些案例将失败。所以提醒它很方便:

定义函数:

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

利用:

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

如果您有 2 个具有相同值的变量,例如var_1 = 8and var_2 = 8,则会出现警告。

var_1 = 8
var_2 = 8
var_name(var_2)  # Output will be "var_1" too but Warning will appear
于 2021-11-14T03:25:18.657 回答
0

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

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
于 2019-08-15T01:09:44.433 回答
0

从变量的值中查找变量的名称时,
您可能有多个变量等于相同的值,
例如 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']
于 2021-03-27T15:56:22.790 回答