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,vadict.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 (最近一次调用最后) in () 3 print(pos) 4 print(locals( )) ----> 5 func("pos":23) in func(x) 1 def func(x): 2 locals().update(x) -- --> 3 print(pos) 4 print(locals()) 5 func("pos":23) NameError: name 'pos' is not defined The docs explicitly warn not to update the return value of 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

虽然它们是不同的类,因为它们(ab)都已初始化为等效的命名空间,并且它们的 __eq__ 方法只比较命名空间(self.__dict__),比较两个命名空间对象返回 @987654341 @。对于与argparse.Namespace 进行比较的情况,出于某种原因,只有Bunch 有效,我不确定为什么(如果您知道,请发表评论,因为types.SimpleNameSpace 是一个内置实现,所以我没有进一步研究)。

您可能还注意到,我使用type(self)(...) 而不是使用类名进行递归 - 这有两个优点:首先可以重命名类而无需更新递归调用,其次如果类是子类,我们将使用子类名称递归。这也是__repr__ (type(self).__name__) 中使用的名称。

编辑 2021-11-27:

    修改了Bunch.__eq__ 方法,使其能够防止类型不匹配。

    添加/修改了可选的 __eq__ 方法(已注释掉)以允许与原始 dictargparse.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:将字典中的变量加载到命名空间中的主要内容,如果未能解决你的问题,请参考以下文章

python中怎样查看已加载的命名空间中所有变量和函数

python中怎样查看已加载的命名空间中所有变

python基础之了解函数多一点

python命名空间和作用域

Python中的命名空间是啥?

python函数函数