以字符串形式获取变量名

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

如何对变量做同样的事情呢?与函数相反,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]
530145 次浏览

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

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

我不相信这是可能的。考虑下面的例子:

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

ab指向同一个对象,但是对象不能知道哪些变量指向它。

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

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

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

在Python中,defclass关键字将把特定的名称绑定到它们定义的对象(函数或类)。类似地,模块通过在文件系统中调用某个特定的名称来命名。在这三种情况下,都有一种明显的方法可以将“规范”名称分配给有问题的对象。

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

Python不会将赋值对象左边的名称保存到赋值对象中,因为:

  1. 这需要在多个冲突的对象中找出哪个名称是“规范的”,
  2. 对于那些从未赋值给显式变量名的对象来说,这是没有意义的,
  3. 这样效率会非常低,
  4. 实际上没有其他现存的语言能做到这一点。

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

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

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

有人在回答中提到,你可能必须遍历堆栈并检查每个人的局部变量和全局变量才能找到foo,但如果foo在你调用这个retrieve_name函数的作用域中赋值,你可以使用inspectcurrent frame来获得所有这些局部变量。

我的解释可能有点太啰嗦了(也许我应该用一个" food "更少的词),但下面是它在代码(请注意,如果有多个变量赋给相同的值,您将得到两个变量名)中的样子:

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,你只需要进一步返回一个范围。这可以通过在caller_local_vars初始化中添加额外的.f_back来实现。

参见下面的例子:ideone

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

它是这样使用的:

name(variable=variable)

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

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

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

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

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

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

而不是:

list_of_dicts = [n_jobs, users, queues, priorities]


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

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

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)

我尝试从检查局部变量中获取名称,但它不能像[1]一样处理var, b.val。 之后,我有了一个新的想法——从代码中获取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 ""

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

这是另一种基于输入变量内容的方法:

(它返回第一个与输入变量匹配的变量名,否则为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

这个函数将输出变量名及其值:

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

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

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

在Python 3.8中,可以简单地使用f-string调试特性:

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

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

>>> 'foo'

请在这里看到其他可能适用于回答这个问题的答案。

也许这很有用:

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

该函数遍历全局作用域的值的ID列表(可以编辑命名空间),根据其ID找到想要/需要的var或函数的索引,然后根据获得的索引从全局名称列表中返回名称。

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

长话短说

这可能吗?

是也不是。

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

然而,前几帧中的源代码/AST节点并不总是可用的,或者它们可以被其他环境修改(例如:pytestassert语句)。一个简单的例子是代码通过exec()运行。尽管我们仍然能够从字节码中检索一些信息,但这需要太多的工作,而且也容易出错。

怎么做呢?

首先,我们需要确定给定变量的坐标系。它并不总是直接的前一帧。例如,我们可以为函数使用另一个包装器:

from varname import varname


def func():
return varname()


def wrapped():
return func()


x = wrapped()

在上面的例子中,我们必须跳过wrapped内的帧来进入正确的帧x = wrapped(),以便我们能够定位xvarname的参数frameignore允许我们跳过其中一些中间帧。在README文件和包的API文档中查看更多细节。

然后,我们需要解析AST节点,以定位变量被赋值(函数调用)的位置。这并不总是一个简单的任务。有时可能存在复杂的AST节点,例如x = [wrapped()]。我们需要通过遍历AST树来确定正确的赋值。

它有多可靠?

一旦我们确定了分配节点,它就是可靠的。

varname完全依赖于executing包来查找节点。确保执行检测的节点是正确的(另见)。

它部分适用于其他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节点并在需要时对它们进行评估都需要消耗资源。

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

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
>> 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() -返回包含当前作用域局部变量的字典。 通过遍历这个字典,我们可以检查值等于已定义变量的键,只要提取键就会以字符串格式给出变量的文本

from(经过位变化) https://www.tutorialspoint.com/How-to-get-a-variable-name-as-a-string-in-Python < / p >

我有一个方法虽然不是最有效的…它的工作原理!(它不涉及任何花哨的模块)。

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

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

每当我必须这样做时,主要是在与前端通信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'

当从变量值中查找变量名时,
你可以有几个变量等于相同的值,
例如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']
>>> 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']]

如你所见,and是注意到这里,可以有多个变量具有相同的值甚至地址,所以使用包装器保持名称与数据是最好的。

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))

如果有两个值相同的变量,前面的一些情况会失败。所以提醒它很方便:

定义函数:

# 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 = 8var_2 = 8,则会出现警告。

var_1 = 8
var_2 = 8
var_name(var_2)  # Output will be "var_1" too but Warning will appear

获取实例变量的名称是完全可能的,只要它是类的属性。

这是我从Brett Slatkin的《Effective Python》中得到的。希望它能帮助到一些人:

该类必须实现得到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》中得到的。我花了一段时间才弄清楚如何实现。

你可以获取变量kwargs,并将其作为字符串返回:

var=2
def getVarName(**kwargs):
return list(kwargs.keys())[0]


print (getVarName(var = var))

注:变量名必须等于自身。

如何对变量做同样的事情呢?与函数相反,Python变量没有__name__属性。

问题出现的原因是您对术语、语义或两者都感到困惑。

“variables"不属于同一类别的“功能”。一个“variable"不是在代码运行时占用内存空间的东西。它是只是一个名字,存在在你的源代码中 -所以当你在写代码时,你可以解释你在谈论的东西。Python在源代码中使用名称来引用(即给出一个名称)。(在许多语言中,变量更像是存储值的特定内存中的位置的名称。但Python的名字实际上就是问题所在。)

在Python中,函数是一个值。(在某些语言中,情况并非如此;虽然有字节的内存用来表示实际的可执行代码,但它不是一个离散的内存块,你的程序逻辑可以直接与之交互。)在Python中,每个值都是对象,这意味着你可以自由地为它分配名称,将它作为参数传递,从函数中返回它,等等(在许多语言中,情况并非如此)。Python中的对象具有属性,这是使用.语法访问的对象。Python中的函数有__name__属性,该属性在函数创建时被赋值。具体来说,当def语句被执行时(在大多数语言中,创建函数的工作方式完全不同),出现在def后面的名称被用作__name__属性的值,而__name__0则单独用作变量名,用于获取分配给它的函数对象。

但是大多数对象没有这样的属性。

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

这就是问题所在:你不 "have"你所考虑的变量。你有的对象,由该变量命名。其他任何东西都取决于顺便存储在其他对象中的信息——比如封闭函数的locals()。但最好还是自己存储这些信息。与其依赖变量名来为您传递信息,不如显式地构建希望用于对象的字符串名与对象本身之间的映射

如果你已经有了一个数据框架列表,就像注释中说的那样,那么把这个列表变成一个字符串列表是很容易的。 而不是从变量列表到字符串,相反,一个字符串列表到内置执行函数结合f-strings的变量(假设变量已经被赋值,即velvolarea是现有pandas数据框架的变量名):

如果:

dfstr_list = [
'vel',
'vol',
'area'
]

这将遍历列表并使用每个dataframe写入excel,并定义sheetname:

for dfstr in dfstr_list:
if dfstr in globals():
exec(f"{dfstr}.to_excel(writer,sheet_name=dfstr)")