将变量名称转换为字符串?

115

我想将一个Python变量名转换为字符串形式,如下所示。有什么好主意吗?

var = {}
print ???  # Would like to see 'var'
something_else = 3
print ???  # Would print 'something_else'

1
你能详细说明一下你想要实现什么吗?看起来你正在以错误的方式寻求解决问题的方法。此外,有一个类似的问题,但是关于Ruby的https://dev59.com/uXVD5IYBdhLWcg3wL4iM - dbr
1
我查看了这个问题,因为我想使用RPy2将一个Python变量传递给一个同名的R变量。一个无用但接近我的应用程序的例子:def to_R( py_var ): r.assign( 'py_var', py_var ) 将(Py)py_var中的数据分配到R环境中并具有相同的名称。对我来说,做以下操作会很有用,r.assign( py_var.stringof, py_var ),就像D一样,因为传递给我的fn to_r的变量名不一定是py_var - Matthew Turner
4
当断言(Assert)语句失败时,一种利用方法是提供与特定变量名称有关的具体信息,同时保持代码的通用性。例如:AssertionError: 'something_else' must be a number greater than 5。你可以对需要大于5的每个变量进行测试,并在 AssertionError 消息中获取变量名称。 - nu everest
谢天谢地,有一个关于如何在Ruby中实现这个的问题! - eric
23个回答

65

简而言之:不可能。请查看结论部分。


有一种使用情境,您可能需要这样做。我并不意味着没有更好的方法或达到相同的功能。

这将非常有用,以便在错误、调试模式和其他类似情况下“转储”任意字典列表。

所需的是 eval() 函数的反向操作:

get_indentifier_name_missing_function()

需要一个以标识符名称('variable','dictionary'等)为参数的函数,并返回包含该标识符名称的字符串。


考虑以下现状:

random_function(argument_data)

如果一个人把标识符名称('function','variable','dictionary'等)argument_data 传递给另一个标识符名称的 random_function(),那么实际上是将一个标识符(例如:<argument_data object at 0xb1ce10>)传递给另一个标识符(例如:<function random_function at 0xafff78>):

<function random_function at 0xafff78>(<argument_data object at 0xb1ce10>)

据我所知,只有内存地址被传递给函数:

<function at 0xafff78>(<object at 0xb1ce10>)
因此,为了使该函数具有参数标识符的名称,需要将字符串作为参数传递给 random_function()
random_function('argument_data')

在 random_function() 函数内部

def random_function(first_argument):

在这种情况下,使用已提供的字符串 'argument_data'

  1. 作为“标识符名称”(用于显示、日志记录、字符串拆分/连接等)

  2. 将其传递给 eval() 函数以获得对实际标识符的引用,从而获得对真实数据的引用:

print("Currently working on", first_argument)
some_internal_var = eval(first_argument)
print("here comes the data: " + str(some_internal_var))

很不幸,这并不适用于所有情况。它仅在random_function()能够将'argument_data'字符串解析为实际标识符时才起作用。也就是说,如果argument_data标识符名称在random_function()的命名空间中可用。

但并非总是如此:

# main1.py
import some_module1

argument_data = 'my data'

some_module1.random_function('argument_data')


# some_module1.py
def random_function(first_argument):
    print("Currently working on", first_argument)
    some_internal_var = eval(first_argument)
    print("here comes the data: " + str(some_internal_var))
######

预期结果应该是:

Currently working on: argument_data
here comes the data: my data

由于argument_data标识符名称在random_function()的命名空间中不可用,因此会产生以下结果:

Currently working on argument_data
Traceback (most recent call last):
  File "~/main1.py", line 6, in <module>
    some_module1.random_function('argument_data')
  File "~/some_module1.py", line 4, in random_function
    some_internal_var = eval(first_argument)
  File "<string>", line 1, in <module>
NameError: name 'argument_data' is not defined

现在,考虑使用假想的 get_indentifier_name_missing_function() 函数,它将按照上述描述的方式运行。

以下是一个 Python 3.0 的示例代码:

# main2.py
import some_module2
some_dictionary_1       = { 'definition_1':'text_1',
                            'definition_2':'text_2',
                            'etc':'etc.' }
some_other_dictionary_2 = { 'key_3':'value_3',
                            'key_4':'value_4', 
                            'etc':'etc.' }
#
# more such stuff
#
some_other_dictionary_n = { 'random_n':'random_n',
                            'etc':'etc.' }

for each_one_of_my_dictionaries in ( some_dictionary_1,
                                     some_other_dictionary_2,
                                     ...,
                                     some_other_dictionary_n ):
    some_module2.some_function(each_one_of_my_dictionaries)


# some_module2.py
def some_function(a_dictionary_object):
    for _key, _value in a_dictionary_object.items():
        print( get_indentifier_name_missing_function(a_dictionary_object)    +
               "    " +
               str(_key) +
               "  =  " +
               str(_value) )
######

预期的结果应该是:

some_dictionary_1    definition_1  =  text_1
some_dictionary_1    definition_2  =  text_2
some_dictionary_1    etc  =  etc.
some_other_dictionary_2    key_3  =  value_3
some_other_dictionary_2    key_4  =  value_4
some_other_dictionary_2    etc  =  etc.
......
......
......
some_other_dictionary_n    random_n  =  random_n
some_other_dictionary_n    etc  =  etc.

不幸的是,get_identifier_name_missing_function() 函数无法看到“原始”的标识符名称 (some_dictionary_some_other_dictionary_2some_other_dictionary_n),它只能看到 a_dictionary_object 这个标识符名称。

因此,实际结果应该是:

a_dictionary_object    definition_1  =  text_1
a_dictionary_object    definition_2  =  text_2
a_dictionary_object    etc  =  etc.
a_dictionary_object    key_3  =  value_3
a_dictionary_object    key_4  =  value_4
a_dictionary_object    etc  =  etc.
......
......
......
a_dictionary_object    random_n  =  random_n
a_dictionary_object    etc  =  etc.

因此,在这种情况下,eval()函数的反向操作并不那么有用。


目前,需要执行以下操作:

# main2.py same as above, except:

    for each_one_of_my_dictionaries_names in ( 'some_dictionary_1',
                                               'some_other_dictionary_2',
                                               '...',
                                               'some_other_dictionary_n' ):
        some_module2.some_function( { each_one_of_my_dictionaries_names :
                                     eval(each_one_of_my_dictionaries_names) } )
    
    
    # some_module2.py
    def some_function(a_dictionary_name_object_container):
        for _dictionary_name, _dictionary_object in a_dictionary_name_object_container.items():
            for _key, _value in _dictionary_object.items():
                print( str(_dictionary_name) +
                       "    " +
                       str(_key) +
                       "  =  " +
                       str(_value) )
    ######

总结:

  • Python只传递内存地址作为参数传递给函数。
  • 仅当名称标识符在当前命名空间可用时,通过eval()函数引用表示标识符名称的字符串才能被引用回实际标识符。
  • 假设反向eval()函数,对于在调用代码中未直接“看到”标识符名称的情况将没有用处。例如,在任何已调用的函数内部。
  • 目前,需要将以下内容传递给函数:
    1. 表示标识符名称的字符串
    2. 实际标识符(内存地址)

可以通过同时传递'string'eval('string')来实现这一点。我认为这是最“通用”的解决方案,可以在任意函数、模块、命名空间中解决这个鸡蛋问题,而不使用边角情况的解决方案。唯一的缺点是使用eval()函数可能很容易导致不安全的代码。必须注意不要将eval()函数与几乎任何东西一起使用,特别是未经过滤的外部输入数据。


9
这篇文章很出色,是我来这里寻找的高质量内容。 - Brendan
25
如果获取一个对象的名称需要在StackOverflow上得到一个五页的答案,那么Python中肯定出了点问题。:/ - Seymour
3
可以简单概括一下吗? - Nimitz14
6
TLDR概括地说,无法实现OP所要求的内容。然而,@Alice等类似回答建议使用locals()或globals(),这些回答实际上实现了OP要求的内容。因此,这个TLDR是误导性的,不应该被接受作为答案。 - Robyc
1
有许多语言提供这种功能 - 即使是将C/C++编译为机器语言,也可以通过定义来实现;Python声称具有易于使用的编程入口,但缺少人们学习和查看其程序正在执行的流行工具; 与C/C++一样,“编译时”函数可以完美地完成工作,但似乎所有建议的尝试在globals()或等效项中定位变量名的方法只能部分成功,不能 100% 的时间工作。匹配是在多个变量可能引用的对象上完成的。 - Codemeister
显示剩余2条评论

56

使用 python-varname 包 (python3) 完全可以实现:

from varname import nameof

s = 'Hey!'

print (nameof(s))

输出:

s

安装:

pip3 install varname

或者在这里获取软件包:

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


1
如果我想要一个函数,它接受会变化的输入并打印出输入变量的名称怎么办?例如:def somefunction(a): nameof(a)。然后somefunction(dtar)将打印dtar,somefunction(rat)将打印rat,等等。 - rsc05
我一直在网上搜寻这样的答案。谢谢@synthase! - Jake
@rsc05,我认为你需要的是 from varname import argname,就像他们在 GitHub 页面上展示的例子一样。 - Naman Bansal
这在Django中非常有用,因为很多时候你需要将模型字段名称作为字符串传递,但是字符串不能轻易重命名。使用这个方法,可以利用重构工具简单地重命名模型字段,而且所有相关的内容都会被更新。 - ProblemsLoop
这在Django中非常有用,因为很多时候你需要将模型字段名作为字符串传递,但字符串不能轻易重命名。使用这个方法,你可以利用重构工具简单地重命名模型字段,所有相关内容都会被更新。 - undefined

14
我搜索了这个问题,因为我想要一个Python程序来打印程序中一些变量的赋值语句。例如,它可能会打印“foo = 3, bar = 21, baz = 432”。print函数需要以字符串形式提供变量名。我本可以用字符串“foo”、“bar”和“baz”来提供我的代码,但那感觉像是在重复自己。在阅读了之前的答案后,我开发了下面的解决方案。
globals()函数的行为类似于具有变量名称(以字符串形式)作为键的字典。我想从globals()中检索与每个变量的值相对应的键。方法globals().items()返回一个元组列表;在每个元组中,第一个项目是变量名(作为字符串),第二个是变量值。我的variablename()函数通过该列表搜索变量名,以找到与我需要以字符串形式命名的变量的值相对应的变量名。
函数itertools.ifilter()通过使用函数 lambda x: var is globals () [x [0]] 测试globals().items()列表中的每个元组进行搜索。在该函数中,x是被测试的元组; x [0]是变量名(作为字符串),x [1]是值。lambda函数测试是否被测试变量的值与传递给variablename()的变量的值相同。实际上,通过使用is操作符,lambda函数测试被测试变量的名称是否绑定到与传递给variablename()的变量完全相同的对象。如果是,则元组通过测试并由ifilter()返回。
实际上,itertools.ifilter()函数返回一个迭代器,只有在正确调用时才会返回任何结果。为了正确调用它,我将其放在列表理解中[tpl[0] for tpl ... globals().items())]。列表理解仅保存变量名tpl[0],忽略变量值。创建的列表包含一个或多个名称(作为字符串),这些名称绑定到传递给variablename()的变量的值。
在下面所示的 variablename() 的用法中,所需的字符串作为列表中的一个元素返回。在许多情况下,它将是列表中唯一的项目。然而,如果另一个变量名被赋予相同的值,那么列表将会更长。
>>> def variablename(var):
...     import itertools
...     return [tpl[0] for tpl in 
...     itertools.ifilter(lambda x: var is x[1], globals().items())]
... 
>>> var = {}
>>> variablename(var)
['var']
>>> something_else = 3
>>> variablename(something_else)
['something_else']
>>> yet_another = 3
>>> variablename(something_else)
['yet_another', 'something_else']

itertools.ifilter()在Python 3中被移除,因为现在内置的filter()函数提供了相同的功能。


14
只要它是一个变量且不是第二类,这对我来说就可以工作:
def print_var_name(variable):
 for name in globals():
     if eval(name) == variable:
        print name
foo = 123
print_var_name(foo)
>>>foo

对于类成员,会发生以下情况:

class xyz:
     def __init__(self):
         pass
member = xyz()
print_var_name(member)
>>>member

为课程答题(例如):

abc = xyz
print_var_name(abc)
>>>abc
>>>xyz

因此,对于类,它会提供名称和属性。


请编辑您的帖子以解决格式问题。很难理解您的意思。 - yhw42
17
阅读一篇回答问题而不对提问的原因进行发泄的文章确实很愉快。 - user5920660
3
请注意,同一个值可以绑定到多个名称。在这种情况下,上述代码将打印多个名称。无法知道哪个名称是“正确”的。 - steveha
使用 is variable 而不是 == variable ... 更安全。 - Erik Aronesty
不幸的是,这在Python 3中似乎不再起作用了。将print name更改为print(name)非常明显,但它仍然会出错。@Erik Aronesty的修复方法可以使其正常工作,但@steveha指出的问题仍然存在。 - JC_CL

7

这是不可能的。

在Python中,实际上并不存在所谓的“变量”。Python真正拥有的是可以绑定对象的“名称”。对象无论被绑定到哪个名称上都不会受到影响。它可能被绑定到几十个不同的名称上,也可能没有被绑定到任何名称上。

考虑以下示例:

foo = 1
bar = 1
baz = 1

现在,假设你有一个整数对象,其值为1,并且你想要倒推并找到它的名称。你会打印什么?有三个不同的名称与该对象绑定,并且所有名称都是同样有效的。
在Python中,名称是访问对象的一种方式,因此没有直接使用名称的方法。可能有一些巧妙的方法来修改Python字节码或其他内容以获取名称的值,但这最多只是一种花招。
如果你知道你想要print foo打印"foo",那么你最好一开始就执行print "foo"
编辑:我已经稍微修改了措辞,使其更加清晰。另外,下面是一个更好的例子:
foo = 1
bar = foo
baz = foo

实际上,Python会重复使用具有常见值(如0或1)的整数对象,因此第一个示例应将相同的对象绑定到所有三个名称。但是这个示例非常清晰:相同的对象绑定到了foo、bar和baz。


4
物体没有名字,名字有物体。 - John La Rooy
有一种方法可以使用这些名称,即globals()字典,但不清楚如何使用它来完成OP所要求的操作。 - Todd
当然可以。而且相当简单(无论如何,它解决了OP的问题)。只是没有人应该想要这样做。 - SilentGhost
你说这不可能。但它是可能的。看看其他答案。 @SilentGhost:能否解释一下为什么“没有人应该想要这样做”?我猜这取决于应用程序。你的论点是什么? - Robyc
同意。尝试了“globals()”方法后,显然拥有一个字典是实现我的目标的更好方式(正如我在之前的评论中所述)。再次强调,如果实际上可以做到,你不应该写“这是不可能的”,对此我很抱歉。你可以写“在我能想到的所有情况下都不建议使用” :) - Robyc
显示剩余5条评论

6

从技术上讲,这些信息对您是可用的,但正如其他人所问,您如何以明智的方式利用它呢?

>>> x = 52
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', 
'x': 52, '__doc__': None, '__package__': None}

这表明变量名以字符串形式存在于globals()字典中。
>>> globals().keys()[2]
'x'

在这种情况下,它恰好是第三个键,但没有可靠的方法知道给定变量名将最终出现在哪里。
>>> for k in globals().keys():
...   if not k.startswith("_"):
...     print k
...
x
>>>

您可以按照以下方式过滤系统变量,但仍会显示您自己的所有项目。运行上述代码将创建另一个名为“k”的变量,该变量将更改字典中“x”的位置。
但也许这对您有所帮助。如果您告诉我们需要此功能的原因,可能会提供更有用的信息。

5
Chris,你的评论似乎与我的答案或原问题无关。你在责骂谁?为什么?你不知道他是在写生产代码还是只是为了自己的学习而尝试语言特性。 - Todd

3

通过使用解包操作符

>>> def tostr(**kwargs):
    return kwargs

>>> var = {}
>>> something_else = 3
>>> tostr(var = var,something_else=something_else)
{'var' = {},'something_else'=3}

1
谢谢@restrepo,这正是我需要创建标准save_df_to_file()函数的东西。为了实现这一点,我对您的tostr()函数进行了一些小改动:` def variabletostr(**df): variablename = list(df.keys())[0] return variablenamevariabletostr(df=0)`顺便说一下:我将此代码作为新帖子添加到了该线程中。 - Arend

2

我认为这是一个很酷的解决方案,也许是最好的解决方案。但是你有没有想过如何处理你的函数可能返回的模棱两可的结果?正如"is" operator behaves unexpectedly with integers所示,Python会缓存低整数和具有相同值的字符串,因此你的变量名函数可能会以高概率提供模棱两可的结果。 在我的情况下,我想创建一个装饰器,通过我传递给它的变量名添加一个新的变量到类中:

def inject(klass, dependency):
klass.__dict__["__"+variablename(dependency)]=dependency

如果你的方法返回的结果含糊不清,我怎么知道我添加的变量的名称?
var any_var="myvarcontent"
var myvar="myvarcontent"
@inject(myvar)
class myclasss():
    def myclass_method(self):
        print self.__myvar    #I can not be sure, that this variable will be set...

也许如果我检查本地列表,我至少可以从列表中删除“dependency”变量,但这不是一个可靠的结果。

2

这里有一个简洁的变体,可以让您指定任何目录。 使用目录查找任何内容的问题在于多个变量可能具有相同的值。因此,此代码返回可能变量的列表。

def varname( var, dir=locals()):
  return [ key for key, val in dir.items() if id( val) == id( var)]

2

在有限的范围内是可能的。答案类似于@tamtam提供的解决方案。 给定的示例假设以下情况 -

  • 您正在根据其值搜索变量
  • 变量具有唯一的值
  • 该值位于全局命名空间中

示例:

testVar         = "unique value"
varNameAsString = [k for k,v in globals().items() if v == "unique value"]
#
# the variable "varNameAsString" will contain all the variable name that matches
# the value "unique value"
# for this example, it will be a list of a single entry "testVar"
#
print(varNameAsString)

输出:['testVar']

您可以将此示例扩展到任何其他变量/数据类型


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接