在 Python 中轻松地将变量从/向命名空间/字典转储

Posted

技术标签:

【中文标题】在 Python 中轻松地将变量从/向命名空间/字典转储【英文标题】:Easily dumping variables from/to namespaces/dictionaries in Python 【发布时间】:2013-01-31 22:50:45 【问题描述】:

假设我在 Python 中有几个变量或对象,abc,...

我怎样才能轻松地将这些变量转储到 Python 中的命名空间并在以后恢复它们? (例如,argparse 以同样的方式将各种变量包装到命名空间中)。

以下是我希望如何将内容转储到命名空间和从命名空间转储的两个示例:

将局部变量转储到命名空间中

function (bar):
   # We start with a, b and c
   a = 10
   b = 20
   c = "hello world"

   # We can dump anything we want into e, by just passing things as arguments:
   e = dump_into_namespace(a, b, c) 
   del a, b, c

   print (e.a + e.b) # Prints 30
   return e  # We can return e if we want. This is just a use case scenario

从命名空间 e 转储局部变量

# We start with e, which for example was built with a call to 
# dump_into_namespace(a,b,c) somewhere else in the program,
# in which case e would hold a, b and c 

# We may receive e through a function call or load it from disk, e.g.:

function foo(e):

   # The following call creates the variables a,b and c
   # or updates their values if the already exist in memory
   dump_from_namespace(e) 
   del e

   print(a + b) # Prints 30
   print(c) # Prints hello world

我的第一个问题是:这在 Python 中可能吗?(请注意,方法 dump_into_namespace 不直接接收变量的名称,至少据我所知) .

如果上面的答案是否定的,我怎么能用这样的界面来做呢?

e = dump_into_namespace('a', 'b', 'c')

另外,如何使用 dictionary 而不是 namespace 来做到这一点?

有一些线程似乎与解决动态定义变量的点访问相关,但我认为它们不能解决转储变量的问题:

另见

Python: Extract variables out of namespace Picklable data containers that are dumpable in the current namespace Recursive DotDict How to use a dot "." to access members of dictionary? javascript style dot notation for dictionary keys unpythonic? Accessing dict keys like an attribute? Recursively access dict via attributes as well as index access? Python: Easily access deeply nested dict (get and set) Are there any 'gotchas' with this Python pattern?

是否有任何库可以通过点符号来促进这种类型的访问?

更新:

看起来在 Python 中有一个支持点可访问字典的库,名为 Bunch,但我不确定它是否支持我定义的轻松转储。

【问题讨论】:

我对您的问题的解决方案和含义进行了很多思考。我有一个答案要写,但我的时间目前很忙。 - 第二种情况比第一种更容易。顺便说一句,它不能称为“从命名空间转储局部变量”,而是“将另一个命名空间的项目加载到本地命名空间”。我使用术语“项目”,因为它是唯一适合名称空间中成对元素(标识符、对象)的元素。 顺便说一句,你必须停止使用在 Python 中非常令人困惑的“变量”这个词,因为读者永远不知道在作者的心目中它是指一个标识符,还是一个 Python对象,或者这个词的纯粹意义上的变量('内容可以改变的内存块')。第三种可能是 Python 中的异端,因为 Python 中的一切都是对象,而 Python 对象不充当纯变量。 “将另一个命名空间的项目加载到本地命名空间”很容易,它包括像更新字典一样更新调用命名空间。但是有一个模棱两可的地方。 namespace 这个词可以指定一个真正的命名空间,就像全局和本地命名空间一样。或者 namespace 可以指定一个对象的所谓命名空间(“在某种意义上,一个对象的属性集也形成了一个命名空间。”docs.python.org/2/tutorial/classes.html 不管怎样,不管是哪种情况,因为在这两种情况下,更新都包括更新字典。对于全局命名空间,它是 global()['blah'] = obj_1 。对于本地命名空间,据我所知,是不可能更新的。对于对象的命名空间,它是 objo.__dict__.update(loaded_namespace) 。最后一种更新的细节取决于加载另一个命名空间内容的对象。对于作为对象的模块,另请查看执行特殊操作的函数__import__,但我不知道它的确切作用。 【参考方案1】:

下面的解决方案提供的语法非常接近您的要求,唯一的区别是您必须传递给明确定义变量的函数环境:

x = 10
y = 20

class dump_into_namespace:
    def __init__(self, env, *vars):
        self.vars = dict([(x, env[x]) for v in vars for x in env if v is env[x]])
    def __getattr__(self, name): return self.vars[name]

o = dump_into_namespace(locals(), x, y)
print o.x, o.y

然后您可以将变量“转储”回您的本地人(例如,在不同的函数中):

>>> locals().update(o.vars)
>>> x
10

编辑:

感谢 eyquem 的建议,这可以更短。想法是将变量放入“转储”对象的self.__dict__ 中(注意:此处更新的语法更改):

class dump_into_namespace:
    def __init__(self, env, *vs):
        vars(self).update(dict([(x, env[x]) for v in vs for x in env if v is env[x]]))

def f():
    x = 10
    y = 20
    return dump_into_namespace(locals(), x, y)

o = f() 
print o.x, o.y 
globals().update(vars(o))
print x

【讨论】:

这看起来很棒!它似乎支持我在 OP 中定义的最简单(也是最困难)的接口。我如何将o 的结果转储回本地人? 嗯,你转储到那个东西的变量仍然在locals(),还是你想把它们转储到另一个locals() 假设我移动了o(例如pickle和unpickle它,将它传递给函数等),并且我想将o中的变量转储回我的本地命名空间(如有必要,创建变量)。这可能吗? @user273158 @piokuc 此外,对象 xy 不是对象 o 的真实属性:他们的名字不属于 o 的命名空间,也就是说不属于o.__dict__,因为使用指令print "x" in o.__dict__ 很容易验证。事实上,o 真正的唯一属性是名称为vars 的字典,值是键"x""y" 的字典。 __getattr__ 的覆盖是为了模仿一个命名空间,其中 "x""y" 将是真正的键(如在我的代码中) @user273158 考虑到 eyquem 的想法改进了答案【参考方案2】:

您有多种选择来创建您的“命名空间”。最简单的两个是:

创建一个快速的自定义类:

class Namespace(object):
    def __init__(self, **kw):
        self.__dict__.update(kw)

def dump_into_namespace(**kw):
    return Namespace(**kw)

拨打dump_into_namespace(a='a', b='b', c='c');这需要任意数量的关键字参数。

使用collections.namedtuple class:

from collections import namedtuple

Namespace = namedtuple('Namespace', 'a b c')

def dump_into_namespace(a, b, c):
    return Namespace(a, b, c)

拨打dump_into_namespace('a', 'b', 'c');这只需要固定数量的参数,但您的 dump_into_namespace() 函数可以提供默认值。

你所说的“点表示法”实际上只是属性访问。

【讨论】:

【参考方案3】:

说实话,最简单的方法就是分配它们:

e.a = a
e.b = b
e.c = c

你不能真正做到更动态,因为变量不知道自己的名字。您必须将它们作为关键字参数传递,在这种情况下,您可以直接更新命名空间的 __dict__

def dump_into_namespace(e, **kwargs):
    e.__dict__.update(kwargs)

你必须把它称为:

dump_into_namespace(e, a=a, b=b, c=c)

【讨论】:

【参考方案4】:

编辑:

piokuc 的回答启发我考虑环境

我的解决方案更新了 self._dict_ 这样就不需要定义一个特殊的函数 __getattr__ :传递给函数的对象成为真正的属性,他们的名字属于创建对象的字典。

def dump_into_ns(env,*x):
    class A:
        def __init__(self,*y):
            vars(self).update((n,o) for n,o in env.items()
                              if o in y)
    return A(*x)


a = 19
b = 'Monday'
c = 'Wednesday'


def ftry(x,y):
    palat = 'obastey'
    a = x -1
    b = y +100
    c = x*y -8
    return dump_into_ns(locals(),a,b,c)



h = dump_into_ns(globals(),a,b,c)
print "h.__dict__ ==",h.__dict__
print '"a" in h.__dict__ ==',"a" in h.__dict__,"  h.a ==",h.a
print '"b" in h.__dict__ ==',"b" in h.__dict__,"  h.b ==",h.b
print '"c" in h.__dict__ ==',"c" in h.__dict__,"  h.c ==",h.c
print

e = ftry(20,50)
print "e.__dict__ ==",e.__dict__
print '"a" in e.__dict__ ==',"a" in e.__dict__,"  e.a ==",e.a
print '"b" in e.__dict__ ==',"b" in e.__dict__,"  e.b ==",e.b
print '"c" in e.__dict__ ==',"c" in e.__dict__,"  e.c ==",e.c
print

print 'h.a == e.a  : ',h.a==e.a
print 'h.b == e.b  : ',h.b==e.b
print 'h.c == e.c  : ',h.c==e.c

结果

h.__dict__ == 'a': 19, 'c': 'Wednesday', 'b': 'Monday'
"a" in h.__dict__ == True   h.a == 19
"b" in h.__dict__ == True   h.b == Monday
"c" in h.__dict__ == True   h.c == Wednesday

e.__dict__ == 'a': 19, 'c': 992, 'b': 150
"a" in e.__dict__ == True   e.a == 19
"b" in e.__dict__ == True   e.b == 150
"c" in e.__dict__ == True   e.c == 992

h.a == e.a  :  True
h.b == e.b  :  False
h.c == e.c  :  False

在某种意义上,对象的属性集也形成了一个命名空间。

http://docs.python.org/2/tutorial/classes.html

【讨论】:

谢谢!可以使用相同的机制构建dump_from_ns 吗? 这是一个很好的答案,我绝对对你的它感兴趣。您在 cmets 中为@pyokuc 的回答提出了要点。这个答案中似乎缺少的一件事(以及我个人没有接受它的部分原因)是因为它没有提供等效且兼容的dump_from_ns(e)(即转储变量来自命名空间回到 calling 命名空间)。或许您可以详细说明这一点? @user273158 我在代码中使用了函数ftry() 来表明在这个函数中编写dump_into_ns(locals(),a,b,c) 是用本地对象创建一个转储对象a,b,c 传递给参数a,b,c 并且我把return 指令发送到函数外部的转储。这是一种转储对象工厂,用内部(本地)命名空间的对象创建这样的对象,而不是全局命名空间。 @user273158 但是如果你想在函数ftry()中使用转储对象,只需输入指令local_e = dump_into_ns(globals(),a,b,c)。这能回答你的问题吗?我不完全理解你提出的观点。 我已更新 OP 以阐明“转储”与“转储”的含义。还要注意 OP 中的删除。

以上是关于在 Python 中轻松地将变量从/向命名空间/字典转储的主要内容,如果未能解决你的问题,请参考以下文章

避免使用PHP保留字作为常量类名和方法名,以及命名空间的命名

python之函数命名空间和作用域

python基础之了解函数多一点

命名空间和作用域

在 Python 中,如何从导入的模块访问主模块的命名空间?

函数的命名空间和作用域