Python:将字典中的变量加载到命名空间中
- 2025-03-19 08:56:00
- admin 原创
- 58
问题描述:
我想使用函数中定义的一组局部变量,在函数之外。所以我传递了x=locals()
返回值。
我怎样才能将该字典中定义的所有变量加载到函数外部的命名空间中,这样我就不用使用 来访问值x['variable']
,而只需使用 即可variable
。
解决方案 1:
您不需要创建自己的对象,而是可以使用argparse.Namespace
:
from argparse import Namespace
ns = Namespace(**mydict)
执行相反的操作:
mydict = vars(ns)
解决方案 2:
考虑一下Bunch
替代方案:
class Bunch(object):
def __init__(self, adict):
self.__dict__.update(adict)
因此,如果你有一本字典d
,并且想要使用语法x.foo
而不是笨拙的语法来访问(读取)它的值d['foo']
,那么只需这样做
x = Bunch(d)
这在函数内部和外部都有效——而且比注入更干净、更安全!记住 Python 之禅的最后一句话……:d
`globals()`
>>> import this
The Zen of Python, by Tim Peters
...
Namespaces are one honking great idea -- let's do more of those!
解决方案 3:
只要知道自己在做什么,将一个局部空间中的变量导入另一个局部空间是完全有效的。我多次看到这样的代码被以有用的方式使用。只需小心不要污染公共全局空间。
您可以执行以下操作:
adict = { 'x' : 'I am x', 'y' : ' I am y' }
locals().update(adict)
blah(x)
blah(y)
解决方案 4:
将变量导入本地命名空间是一个有效的问题,并且经常在模板框架中使用。
从函数返回所有局部变量:
return locals()
然后导入如下:
r = fce()
for key in r.keys():
exec(key + " = r['" + key + "']")
解决方案 5:
Bunch 的答案不错,但缺少递归和适当的__repr__
内置__eq__
函数来模拟您已经可以使用字典执行的操作。此外,递归的关键不仅在于对字典进行递归,还在于对列表进行递归,以便列表中的字典也会被转换。
我希望这两个选项能够满足您的需求(您可能需要调整类型检查以__elt()
适应更复杂的对象;这些主要在 json 导入上进行测试,因此核心类型非常简单)。
Bunch方法(按照前面的答案) - 对象接受一个字典并以递归方式转换它。
repr(obj)
将返回Bunch({...})
可以重新解释为等效对象的内容。
class Bunch(object):
def __init__(self, adict):
"""Create a namespace object from a dict, recursively"""
self.__dict__.update({k: self.__elt(v) for k, v in adict.items()})
def __elt(self, elt):
"""Recurse into elt to create leaf namespace objects"""
if type(elt) is dict:
return type(self)(elt)
if type(elt) in (list, tuple):
return [self.__elt(i) for i in elt]
return elt
def __repr__(self):
"""Return repr(self)."""
return "%s(%s)" % (type(self).__name__, repr(self.__dict__))
def __eq__(self, other):
if hasattr(other, '__dict__'):
return self.__dict__ == other.__dict__
return NotImplemented
# Use this to allow comparing with dicts:
#return self.__dict__ == (other.__dict__ if hasattr(other, '__dict__') else other)
SimpleNamespace方法- 因为
types.SimpleNamespace
已经实现了__repr__
和__eq__
,所以您只需要实现一个递归__init__
方法:
import types
class RecursiveNamespace(types.SimpleNamespace):
# def __init__(self, /, **kwargs): # better, but Python 3.8+
def __init__(self, **kwargs):
"""Create a SimpleNamespace recursively"""
self.__dict__.update({k: self.__elt(v) for k, v in kwargs.items()})
def __elt(self, elt):
"""Recurse into elt to create leaf namespace objects"""
if type(elt) is dict:
return type(self)(**elt)
if type(elt) in (list, tuple):
return [self.__elt(i) for i in elt]
return elt
# Optional, allow comparison with dicts:
#def __eq__(self, other):
# return self.__dict__ == (other.__dict__ if hasattr(other, '__dict__') else other)
RecursiveNamespace类采用关键字参数,这些参数当然可以来自取消引用的字典(例如**mydict
)
现在让我们对它们进行测试(argparse.Namespace
为了比较而添加,尽管它的嵌套字典是手动转换的):
from argparse import Namespace
from itertools import combinations
adict = {'foo': 'bar', 'baz': [{'aaa': 'bbb', 'ccc': 'ddd'}]}
a = Bunch(adict)
b = RecursiveNamespace(**adict)
c = Namespace(**adict)
c.baz[0] = Namespace(**c.baz[0])
for n in ['a', 'b', 'c']:
print(f'{n}:', str(globals()[n]))
for na, nb in combinations(['a', 'b', 'c'], 2):
print(f'{na} == {nb}:', str(globals()[na] == globals()[nb]))
结果是:
a: Bunch({'foo': 'bar', 'baz': [Bunch({'aaa': 'bbb', 'ccc': 'ddd'})]})
b: RecursiveNamespace(foo='bar', baz=[RecursiveNamespace(aaa='bbb', ccc='ddd')])
c: Namespace(foo='bar', baz=[Namespace(aaa='bbb', ccc='ddd')])
a == b: True
a == c: True
b == c: False
虽然它们是不同的类,但由于它们(a
和b
)都已初始化为等效命名空间,并且它们的__eq__
方法仅比较命名空间(self.__dict__
),因此比较两个命名空间对象将返回True
。对于与进行比较的情况argparse.Namespace
,出于某种原因,仅Bunch
有效,我不确定为什么(如果您知道,请发表评论,我没有进一步研究,因为types.SimpleNameSpace
它是内置实现)。
您可能还会注意到,我使用递归而不是使用类名 - 这有两个优点:首先,可以重命名类而无需更新递归调用;其次,如果类是子类,我们将使用子类名称进行递归。这也是( )type(self)(...)
中使用的名称。__repr__
`type(self).__name__`
编辑2021-11-27:
修改了
Bunch.__eq__
方法,使其能够安全地应对类型不匹配的情况。添加/修改了可选
__eq__
方法(注释掉)以允许与原始方法进行比较dict
(argparse.Namespace(**dict)
请注意,后者不是递归的,但仍然可以与其他类进行比较,因为子级结构无论如何都可以很好地比较)。
解决方案 6:
为了扩展托马斯的想法,以下内容保留支持列表到类型转换的任何受支持的可迭代对象的类型。
import types
import sys
class RecursiveNamespace(types.SimpleNamespace):
def __init__(self, accepted_iter_types = [], **kwargs):
self.supported_types = [list, tuple, set] + accepted_iter_types
for key, val in kwargs.items():
if type(val) == dict:
setattr(self, key, RecursiveNamespace(**val))
elif hasattr(val, '__iter__'): # object is iterable
setattr(self, key, self.__make_iterable(val))
else:
setattr(self, key, val)
def __make_iterable(self, val):
if(type(val) not in self.supported_types): # not a supoorted iterable type
return val
lst = [self.__recurse_in(v) for v in val]
try:
ret = type(val)(lst) # the type is assumed to support list-to-type conversion
except Exception as e:
print(f"Failed to make iterable object of type {type(val)}", e, out=sys.stderr)
return ret
def __recurse_in(self, val):
if type(val) == dict:
return RecursiveNamespace(**val)
elif(hasattr(val, '__iter__')): # if it's iterable
return self.__make_iterable(val)
else:
return val
def __getitem__(self, key):
return self.__dict__[key]
if __name__ == '__main__':
data = {'a': 1,
'b': (2,3),
'c': [4,5],
'd': set([6,'7',8]),
'e': {
'e_1': 9,
'e_2': {
'e_2_1': 10,
'e_2_2': (11,)
},
'e_3': [12,13]}
}
rname = RecursiveNamespace(**data)
print(rname)
print('%20s :'%(type(rname.a)), rname.a)
print('%20s :'%(type(rname.b)), rname.b)
print('%20s :'%(type(rname.c)), rname.c)
print('%20s :'%(type(rname.d)), rname.d)
print('%20s :'%(type(rname.e.e_2.e_2_2)), rname.e.e_2.e_2_2)
输出:
<class 'int'> : 1
<class 'tuple'> : (2, 3)
<class 'list'> : [4, 5]
<class 'set'> : {8, '7', 6}
<class 'tuple'> : (11,)
解决方案 7:
使用以下代码片段(PY2)从我的 dict(yaml)配置中创建递归命名空间:
class NameSpace(object):
def __setattr__(self, key, value):
raise AttributeError('Please don\'t modify config dict')
def dump_to_namespace(ns, d):
for k, v in d.iteritems():
if isinstance(v, dict):
leaf_ns = NameSpace()
ns.__dict__[k] = leaf_ns
dump_to_namespace(leaf_ns, v)
else:
ns.__dict__[k] = v
config = NameSpace()
dump_to_namespace(config, config_dict)
解决方案 8:
总是有这个选项,我不知道这是否是最好的方法,但它确实有效。假设 type(x) = dict
for key, val in x.items(): # unpack the keys from the dictionary to individual variables
exec (key + '=val')
扫码咨询,免费领取项目管理大礼包!