将变量名作为字符串获取


173

此线程讨论如何在Python中以字符串形式获取函数名称如何以字符串 形式获取函数名称?

如何对变量执行相同操作?与函数相反,Python变量没有__name__属性。

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

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

我正在寻找一个功能/属性,例如retrieve_name(),以便从此列表在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] 

Answers:


18

使用该python-varname包,您可以轻松检索变量的名称

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

就您而言,您可以执行以下操作:

from varname import Wrapper

foo = Wrapper(dict())

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

或者,您也可以尝试直接检索变量名称:

from varname import nameof

foo = dict()

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

我是这个软件包的作者。如果您有任何疑问,请告诉我,或者可以在github上提交问题。


2
好,终于知道了!使用以下方法安装pip3 install python-varname==0.1.5;使用from varname import nameof
enter_display_name_here

该功能以某种方式无法在循环中起作用:test = {} print(varname.nameof(test)) for i in [0]: print(varname.nameof(test))第一个打印给出test,循环中的打印上升VarnameRetrievingError: Callee's node cannot be detected.
Tillus

1
@Tillus固定在v0.2.0
泮文旺

106

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

如果不递归地遍历命名对象的树,你要做的就是不可能; 名称是对对象的单向引用。常见的或具有花园多样性的Python对象不包含对其名称的引用。想象一下,如果要维护代表表示引用它的名称的字符串列表,是否需要每个整数,每个字典,每个列表,每个布尔值!这将是实施的噩梦,对程序员几乎没有好处。


7
谢谢。但是,为什么Python会对函数这样做呢?(即一种Python对象)
Amelio Vazquez-Reina

1
@kindall:不要忘记模块:它们也有规范名称。另外,请不要忘记__name__可以随时对其进行修改,这意味着“规范”名称可能不是可用于获取对象的名称(例如,动态创建类时)。
nneonneo

8
您的陈述“根本不可能”是False,如@ scohe001 所示。Python的变量名数据库与任何其他关系数据库一样,您始终可以在“反向”中搜索相关对象,并为任何给定变量返回找到的第一个或全部有效变量名。
滚刀

3
@hobs从技术上来说您是正确的……最好的一种正确方法。但是,实际上,一个对象的潜在名称太多了,以致于比试图获得它们的价值更大。
kindall '16

1
@kindall我猜如果您的“值得”阈值为O(1),那么您是对的。scohe001的循环为O(N)。
滚刀

82

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

在该答复中提到有人说,你可能必须走栈和检查每个人的当地人和全局找到foo,但如果foo在你调用这个范围被分配retrieve_name的功能,你可以用inspectcurrent frame,让你所有的局部变量。

我的解释可能有点罗word(也许我应该少用“ 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_backcaller_local_vars初始化中添加额外的内容来完成。

在这里查看示例:ideone


1
@theodox我绝对同意,因为这将有可能是行动起来import hooksironpythonjython
scohe001

5
这行不通。原子变量没有名称作为属性。因此,如果您有a, b = 2, 2retrieve_name(a)并且retrieve_name(b)都会返回['a', 'b']['b', 'a']
tomas

1
@tomas实际上,这是cPython优化的实现细节,其中255以下的整数基本上是单例,因此分配了这些值的任何变量都将有效返回true以进行is比较
Toote

1
是否有这个mod以获得传递的var的名称?例如,def foo(bar): retrieve_name(bar)将始终返回bar,但是如果您想foo(baz)返回baz)而不是bar呢?
SumNeuron

1
@SumNeuron,您只需要修改分配的行即可将callers_local_vars作用域更进一步,因此它将查看正在调用的内容的范围foo。将行更改为inspect.currentframe().f_back.f_back.f_locals.items()(注意额外的f_back)。
scohe001 '19

36

使用Python 3.8可以简单地使用f字符串调试功能:

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

不错!而且,即使您想获取属性的名称而不是对象的名称,也可以f'{foo=}'.split('=')[0].split('.')[-1]
Mickael V.

30

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

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


不错的工作!虽然我尝试了retrieve_name(SomeClass.some_attribute)它不起作用。您可以在这方面提供进一步的帮助吗?
Nam G VU

这很难与布尔变量。我最终stop_on_error
遇到

26

我不认为这是可能的。考虑以下示例:

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

ab指向同一个对象,但对象无法知道指向它哪些变量。


2
当然,可以通过扩展引用计数来建立双向关系。这个答案(以及其他答案)甚至提供了一个实现。
Shayaan

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

它的用法如下:

name(variable=variable)

12
这不会返回所需变量的名称,而是“变量”。例如-using name(variable=variable)将输出['variable'],而using name(variable=another_variable)输出['another_variable'],而是输出['variable']
DalyaG

1
实际上,它按预期工作。您只需要用变量替换两个“变量”即可。它将返回一个包含第一个变量名称字符串的单元素列表。例如:>>> a = [] >>> b = a >>> name(a=b) ['a'] >>> name(b=a) ['b']`
Alguem Meugla

8

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

创建一个使用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

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

这种魔术的明显缺点是,它对源代码的结构进行了假设。当然,如果它在解释器中运行,它将根本无法工作。


您好,我只有三个问题:1.为什么选择“ 1”?2.为什么选择“ 4”?3.为什么为“ 0”?:)
Piotr Wasilewicz

7

我编写了包裹法术来稳健地执行这种魔术。你可以写:

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)

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

如果要编写自己的函数,可以这样做,以便可以检查在本地变量中定义的变量,然后检查全局变量。如果未找到任何内容,则可以在id()上进行比较,以查看变量是否指向内存中的相同位置。

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


如果名称在呼叫者框架中怎么办?然后,您将不得不做一些愚蠢的事情,例如遍历堆栈并检查每个人的堆栈,locals然后globals...如果实际上不存在该名称,则可能会输错名称。没有实际的有用收益,这是大量的工作。
nneonneo

整个问题很愚蠢……但是,如果他想做某件事,那是可能的。至于检查是否存在,您可以使用Globals()。setdefault(var,<type(var)的<new对象)在什么都没有的情况下创建某些东西。我不确定他想要的是什么,但是也许知道变量是在范围内保存的,他可能会发现更好的方法。
blakev

3

在Python中,defandclass关键字会将特定名称绑定到它们定义的对象(函数或类)。同样,模块被称为文件系统中的特定名称。在这三种情况下,都有一种明显的方法可以为所讨论的对象分配“规范”名称。

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

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

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

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

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



2

我认为用Python做到这一点是如此困难,因为一个简单的事实,就是您永远不会知道所使用的变量的名称。因此,在他的示例中,您可以执行以下操作:

代替:

list_of_dicts = [n_jobs, users, queues, priorities]

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

2

这是基于输入变量的内容执行此操作的另一种方法:

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

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

2

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

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

2

我有一种方法,虽然不是最有效的 ...但是有效!(并且它不涉及任何高级模块)。

基本上,它将变量的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

1

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


1

也许这可能是有用的:

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

该函数遍历全局作用域中值的ID列表(可以编辑名称空间),根据其ID查找所需/所需的var或函数的索引,然后从基于以下名称的全局名称列表中返回名称在获取的索引上。


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

0

对于常量,可以使用枚举,该枚举支持检索其名称。


0

我尝试从本地检查人员那里获取名称,但是它无法处理像a [1],b.val这样的var。之后,我有了一个新主意---从代码中获取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 ""

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.