Python:将字典中的变量加载到命名空间中
Posted
技术标签:
【中文标题】Python:将字典中的变量加载到命名空间中【英文标题】:Python: load variables in a dict into namespace 【发布时间】:2011-02-05 12:55:26 【问题描述】:我想在函数之外使用函数中定义的一堆局部变量。所以我在返回值中传递了x=locals()
。
如何将该字典中定义的所有变量加载到函数外部的命名空间中,这样我就可以简单地使用variable
,而不是使用x['variable']
访问值。
【问题讨论】:
这听起来是个糟糕的主意。 没有'from module import *'的概念那么可怕,因为大概你对字典中的内容有更多的了解。 @JohnLaRooy 为什么这是个糟糕的主意? @JohnLaRooy 点符号是编写代码的绝佳方式。我很好奇,你有理由更喜欢字典而不是命名空间吗? 你试过了吗:from argparse import Namespace; ns = Namespace(**mydict)
?我很好奇你为什么不接受这个答案。
【参考方案1】:
考虑Bunch
替代方案:
class Bunch(object):
def __init__(self, adict):
self.__dict__.update(adict)
因此,如果您有一本字典 d
并希望使用语法 x.foo
而不是笨拙的 d['foo']
访问(读取)其值,那么就这样做
x = Bunch(d)
这对内部和外部函数都有效——它非常比将d
注入globals()
更清洁、更安全!记住 Python 之禅的最后一行...:
>>> import this
The Zen of Python, by Tim Peters
...
Namespaces are one honking great idea -- let's do more of those!
【讨论】:
但这看起来很优雅:globals().update(locals())
! (我孩子,我孩子)
我在简单的绘图脚本中使用这个 globals().update(locals()),当我以交互方式加载模块或从命令行以一次性方式运行时,我有一些简单的区别大大地。我想我可以把 if name == 'main': 放在文件的顶部,然后就没有函数了……但这似乎很不雅。无论如何,如果有更好的方法将值从函数内部传递到外部模块,我会很感兴趣。
喜欢这个,但它不是递归的。嵌套字典的任何解决方案?
@CleverGuy 只要你不使用update
,你就可以递归地编写它:而是将k,v
与adict.items()
和if type(v) is dict: self.__dict__[k]=Bunch(v)
配对,然后将else: self.__dict__[k]=v
用于其他所有内容.您可以像命名空间一样访问结果,包括为嵌套空间分配新值。
怎么了:from argparse import Namespace; ns = Namespace(**mydict)
?【参考方案2】:
您可以使用argparse.Namespace
,而不是创建自己的对象:
from argparse import Namespace
ns = Namespace(**mydict)
反之:
mydict = vars(ns)
【讨论】:
最后,一些安全、可读并且已经在标准库中实现的东西。不过,我想知道为什么这个honking great idea
驻留在 argparse
模块中。
现在可以使用types.SimpleNamespace
这是一个超级解决方案,值得点赞。见repl.it/repls/NavyFlusteredSearchengine 看看它是多么容易
在“反向”部分有一个微妙的警告:在执行mydict = vars(ns)
时,新字典和原始命名空间都将指向同一个对象(更改其中一个对象也会更改另一个对象)。使用mydict = vars(ns).copy()
来避免这种情况。
您如何看待关于bunch
的公认答案?我很好奇。【参考方案3】:
这是导入变量的完全有效的情况 一个本地空间进入另一个本地空间,只要 一个人知道他/她在做什么。 我已经多次看到这样的代码以有用的方式使用。 只需要注意不要污染公共的全局空间。
您可以执行以下操作:
adict = 'x' : 'I am x', 'y' : ' I am y'
locals().update(adict)
blah(x)
blah(y)
【讨论】:
def func(x): locals().update(x) print(pos) print(locals()) func("pos":23) -------- -------------------------------------------------- ----------------- NameError Traceback (最近一次调用最后)locals()
.
喜欢这个答案,但可能会误导读者认为这是推荐的方法
怎么了:from argparse import Namespace; ns = Namespace(**mydict)
?【参考方案4】:
将变量导入本地命名空间是一个有效的问题,经常在模板框架中使用。
从函数返回所有局部变量:
return locals()
然后导入如下:
r = fce()
for key in r.keys():
exec(key + " = r['" + key + "']")
【讨论】:
工作完美,但速度很慢......不过对于简单的测试程序来说不是问题。 怎么了:from argparse import Namespace; ns = Namespace(**mydict)
?
没有 CharlieParker 查看 orodbhen 发布的答案。【参考方案5】:
Bunch 答案还可以,但缺少递归和适当的 __repr__
和 __eq__
内置函数来模拟您已经可以使用 dict 执行的操作。另外,递归的关键不仅在于对字典进行递归,还在于对列表进行递归,以便列表中的字典也被转换。
我希望这两个选项能满足您的需求(对于更复杂的对象,您可能需要调整 __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__
),比较两个命名空间对象返回 @987654341 @。对于与argparse.Namespace
进行比较的情况,出于某种原因,只有Bunch
有效,我不确定为什么(如果您知道,请发表评论,因为types.SimpleNameSpace
是一个内置实现,所以我没有进一步研究)。
您可能还注意到,我使用type(self)(...)
而不是使用类名进行递归 - 这有两个优点:首先可以重命名类而无需更新递归调用,其次如果类是子类,我们将使用子类名称递归。这也是__repr__
(type(self).__name__
) 中使用的名称。
编辑 2021-11-27:
修改了Bunch.__eq__
方法,使其能够防止类型不匹配。
添加/修改了可选的 __eq__
方法(已注释掉)以允许与原始 dict
和 argparse.Namespace(**dict)
进行比较(请注意,后者不是递归的,但仍可与其他方法进行比较作为子级结构的类无论如何都会比较好)。
【讨论】:
怎么了:from argparse import Namespace; ns = Namespace(**mydict)
?
试试我的例子。 Namespace(**dict)
不是递归的。你确实让我意识到,通过一个简单的改变,我也可以让这两个类与dict
进行比较。要将Namespace()
与dicts 进行比较,可以使用相同的`__eq__` 方法。
我也不能解释为什么SimpleNamespace
不能和argparse.Namespace
比较,而Bunch
可以,我得看看CPython中的实际实现。【参考方案6】:
使用以下 sn-p (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)
【讨论】:
【参考方案7】:总是有这个选项,我不知道这是最好的方法,但它确实有效。假设 type(x) = dict
for key, val in x.items(): # unpack the keys from the dictionary to individual variables
exec (key + '=val')
【讨论】:
这很好用,直到实习生发现你这样做并认为“exec”是自 Python 以来最好的事情。以防万一有实习生读到这篇文章,不要这样做。这会损害代码的可读性,使其难以调试,速度非常慢,并且可能导致我什至无法正确预测和解释的一大堆问题。 如果你走这条路,直接更新本地人。例如locals().update(x)
.以上是关于Python:将字典中的变量加载到命名空间中的主要内容,如果未能解决你的问题,请参考以下文章