获取变量的名称作为字符串
- 2024-11-25 08:50:00
- admin 原创
- 172
问题描述:
我已经阅读过如何将函数名称获取为字符串?。
我如何对变量执行相同操作?与函数相反,Python 变量没有__name__
属性。
换句话说,如果我有一个变量,例如:
foo = dict()
foo['bar'] = 2
我正在寻找一个函数/属性,例如retrieve_name
:
>>> retrieve_name(foo)
'foo'
这是为了从此列表在 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]
解决方案 1:
使用 Python 3.8,我们可以简单地使用f 字符串调试功能:
>>> foo = dict()
>>> f'{foo=}'.split('=')[0]
'foo'
这种方法的一个缺点是,为了'foo'
打印,你必须自己添加f'{foo=}'
。换句话说,你必须已经知道变量的名称。换句话说,上面的代码片段与
>>> 'foo'
解决方案 2:
即使变量值没有指向名称,您也可以访问每个分配的变量及其值的列表,因此我很惊讶只有一个人建议循环遍历那里来查找您的变量名称。
有人在答案中提到,您可能必须遍历堆栈并检查每个人的本地变量和全局变量才能找到foo
,但如果foo
在调用此retrieve_name
函数的范围内分配了,则可以使用inspect
来currentframe
获取所有这些局部变量。
我的解释可能有点太冗长了(也许我应该少用一个“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
解决方案 3:
Python 中唯一具有规范名称的对象是模块、函数和类,当然,在定义函数或类或导入模块后,无法保证此规范名称在任何命名空间中都具有任何意义。这些名称在创建对象后也可能被修改,因此它们可能并不总是特别值得信赖。
如果不递归遍历命名对象树(或者至少像 @scohe001 的答案中那样迭代包含范围内的名称列表),您想要做的事情就不可能实现;名称是对对象的单向引用。普通或普通的 Python 对象不包含对其名称的引用。想象一下,如果每个整数、每个字典、每个列表、每个布尔值都需要维护一个表示引用它的名称的字符串列表!这将是一场实施噩梦,对程序员几乎没有好处。
解决方案 4:
总结
使用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
there 的原因。在该函数中,在运行时,我们将解析先前帧中的源代码/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
完全依赖于executing
包来查找节点。执行检测的节点确保是正确的(另请参阅此)。
它部分适用于其他 AST 魔法适用的环境,包括 pytest、ipython、macropy、birdseye、reticulate 与 R 等。在这些环境中,executing 和 varname 都不能 100% 正常工作。
我们需要一个包裹来做这件事吗?
嗯,又是既是,又是不是。
如果您的情况比较简单, @juan Isaza或@scohe001提供的代码可能足以让您处理在直接前一帧定义变量且 AST 节点是简单赋值的情况。您只需返回一帧并在那里检索信息即可。
但是,如果场景变得复杂,或者我们需要采用不同的应用场景,则可能需要像这样的包python-varname
来处理它们。这些场景可能包括:
当源代码不可用或 AST 节点无法访问时,显示更友好的消息
跳过中间帧(允许函数在其他中间帧中被包装或调用)
自动忽略来自内置函数或库的调用。例如:
x = str(func())
在赋值语句的左侧检索多个变量名
ETC。
怎么样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 节点并在需要时对其进行评估需要消耗资源。
解决方案 5:
在 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]
它在代码的任何地方都很有用。遍历反转堆栈寻找第一个匹配项。
解决方案 6:
我不相信这是可能的。请考虑以下示例:
>>> a = []
>>> b = a
>>> id(a)
140031712435664
>>> id(b)
140031712435664
和指向同一个对象,但是该对象无法知道a
哪些b
变量指向它。
解决方案 7:
>>> 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
解决方案 8:
def name(**variables):
return [x for x in variables]
它的使用方式如下:
name(variable=variable)
解决方案 9:
我编写了sorcery包来强大地实现这种魔法。你可以这样写:
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)
解决方案 10:
此函数将打印变量名称及其值:
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
解决方案 11:
这是一种方法。我不建议将此方法用于任何重要的事情,因为它会非常脆弱。但这是可以做到的。
创建一个使用该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(', ',', ')', '
']
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
。
这种魔法的明显缺点是它对源代码的结构做出了假设。当然,如果它在解释器中运行,它根本不起作用。
解决方案 12:
>>> locals()['foo']
{}
>>> globals()['foo']
{}
如果您想编写自己的函数,可以这样做:先检查局部变量中定义的变量,然后检查全局变量。如果没有找到任何内容,您可以比较 id() 以查看变量是否指向内存中的同一位置。
如果您的变量在类中,您可以使用 className.dict.keys ( ) 或 vars(self) 来查看您的变量是否已定义。
解决方案 13:
我有一个方法,虽然不是最有效的...但它有效!(并且它不涉及任何花哨的模块)。
基本上,它将你的变量的 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
解决方案 14:
在 Python 中,def
和class
关键字会将特定名称绑定到它们定义的对象(函数或类)。类似地,模块也会根据在文件系统中被称作某个特定名称而获得名称。在这三种情况下,都有一种明显的方式可以为相关对象分配一个“规范”名称。
然而,对于其他类型的对象,这种规范名称可能根本不存在。例如,考虑列表的元素。列表中的元素没有单独命名,在程序中引用它们的唯一方法很可能是使用包含列表上的列表索引。如果将这样的对象列表传递到函数中,则不可能为值分配有意义的标识符。
Python 不会将赋值左侧的名称保存到已赋值的对象中,因为:
这需要找出在多个冲突对象中哪个名称是“规范的”,
对于从未分配给明确变量名的对象来说,这是没有意义的,
这会非常低效,
确实,现存的其他语言都不能做到这一点。
因此,例如,使用 定义的函数lambda
将始终具有“名称” <lambda>
,而不是特定的函数名称。
最好的方法是简单地要求调用者传递一个(可选的)名称列表。如果输入'...','...'
太麻烦,您可以接受例如包含逗号分隔的名称列表的单个字符串(如namedtuple
does)。
解决方案 15:
我认为在 Python 中做到这一点非常困难,因为你永远不知道你使用的变量的名称。因此,在他的例子中,你可以这样做:
而不是:
list_of_dicts = [n_jobs, users, queues, priorities]
dict_of_dicts = {"n_jobs" : n_jobs, "users" : users, "queues" : queues, "priorities" : priorities}
解决方案 16:
许多答案只返回一个变量名。但如果多个变量具有相同的值,这种方法就不太适用。这是 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']
解决方案 17:
我如何对变量执行相同操作?与函数相反,Python 变量没有
__name__
属性。
出现问题是因为您对术语、语义或两者都感到困惑。
“变量”与“函数”不属于同一类别。“变量”不是在代码运行时占用内存空间的东西。它只是源代码中存在的名称- 这样在编写代码时,您可以解释您正在谈论哪个东西。Python 使用源代码中的名称来引用(即命名)值。(在许多语言中,变量更像是内存中存储值的特定位置的名称。但 Python 的名称实际上命名了所讨论的东西。)
在 Python 中,函数就是一个值。 (在某些语言中情况并非如此;尽管有用于表示实际可执行代码的内存字节,但它并不是程序逻辑直接与之交互的离散内存块。)在 Python 中,每个值都是一个对象,这意味着您可以自由地为其分配名称、将其作为参数传递、从函数返回它等等。 (在许多语言中情况并非如此。)Python 中的对象具有属性,即您可以使用语法访问的东西.
。Python 中的函数具有__name__
属性,该属性在创建函数时被分配。具体来说,当def
执行一条语句时(在大多数语言中,创建函数的方式完全不同),后面出现的名称def
将用作__name__
属性的值,并且还可以独立地用作变量名,该变量将获得分配给它的函数对象。
但大多数物体并不具备这样的属性。
举例来说:
陈述 | 将变量绑定到值 | 设置对象属性 |
---|---|---|
def f | f →<function> | <function>.__name__ = 'f' |
x = 0 | x →0 | 不适用 |
换句话说,如果我有一个变量,例如:
问题就在于此:您并不像您想象的那样“拥有”变量。您拥有的是由该变量命名的对象。其他任何事情都取决于偶然存储在其他某个对象中的信息 - 例如locals()
封闭函数的。但最好自己存储信息。不要依赖变量名来为您携带信息,而是明确构建您要用于对象的字符串名称与对象本身之间的映射。
解决方案 18:
根据输入变量的内容执行此操作的另一种方法:
(它返回与输入变量匹配的第一个变量的名称,否则返回 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
解决方案 19:
如果目标是帮助您跟踪变量,您可以编写一个简单的函数来标记变量并返回其值和类型。例如,假设 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='
'+20*'#'+'
This is the report:
'
report+= whatis('i_f ',i_f)+'
'
report+=whatis('i_n ',i_n)+'
'
report+=whatis('i_s ',i_s)+'
'
report+=whatis('i_l ',i_l)+'
'
report+=whatis('i_u ',i_u)+'
'
print(report)
每次调用时都会将结果打印到窗口中以供调试,同时还会生成一个字符串作为书面报告。唯一的缺点是每次调用该函数时都必须输入两次变量。
我是一名 Python 新手,我发现这种方法非常有用,可以记录我在编程和尝试处理 Python 中的所有对象时所做的努力。一个缺陷是,如果 whatis() 调用了在其使用过程之外描述的函数,它将失败。例如,int(i_f) 是一个有效的函数调用,因为Python 知道int函数。您可以使用 int(i_f**2) 调用 whatis(),但如果出于某种奇怪的原因,您选择定义一个名为 int_squared 的函数,则必须在使用 whatis() 的过程内声明它。
解决方案 20:
也许这可能会有用:
def Retriever(bar):
return (list(globals().keys()))[list(map(lambda x: id(x), list(globals().values()))).index(id(bar))]
该函数遍历全局范围(命名空间可以编辑)值的 ID 列表,根据其 ID 找到所需/必需的变量或函数的索引,然后根据获取的索引从全局名称列表中返回名称。
解决方案 21:
每当我必须这样做时,主要是在与前端通信 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'
解决方案 22:
>>> 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']]
正如您所看到的以及此处所指出的,可以有多个具有相同值甚至地址的变量,因此最好使用包装器将名称与数据保存在一起。
解决方案 23:
以下方法不会返回变量的名称,但如果变量在全局范围内可用,则使用此方法可以轻松创建数据框。
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
解决方案 24:
如果有两个变量具有相同的值,则前面的一些情况会失败。因此可以方便地提醒它:
定义函数:
# 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"
如果您有两个具有相同值的变量(如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
解决方案 25:
您可以获取变量kwargs
并将其作为字符串返回:
var=2
def getVarName(**kwargs):
return list(kwargs.keys())[0]
print (getVarName(var = var))
注意:变量名必须等于其本身。
解决方案 26:
我尝试从检查局部变量中获取名称,但它无法处理 a[1]、b.val 等变量。之后,我有了一个新的想法 --- 从代码中获取变量名称,我尝试成功!代码如下:
#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 ""
解决方案 27:
您可以尝试以下方法来检索您定义的函数的名称(但不适用于内置函数):
import re
def retrieve_name(func):
return re.match("<functions+(w+)s+at.*", str(func)).group(1)
def foo(x):
return x**2
print(retrieve_name(foo))
# foo
解决方案 28:
当从变量的值查找其名称时,
可能会有多个等于相同值的变量,
例如 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']
解决方案 29:
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))
解决方案 30:
完全可以获取实例变量的名称,只要它是类的属性。
我从 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》中得到了这个。花了一段时间才弄清楚如何实现。