我可以将命名空间对象从可变的转换为不可变的吗?

Can I convert a namespace object from mutable to immutable?

我从命令行参数中收到一个命名空间对象。 我不想修改它。 我可以这样做吗?或者你有什么想法吗?

# -*- coding: utf-8 -*-

import argparse

def parse_args():
    parser = argparse.ArgumentParser(description='This script is ...')
    parser.add_argument('--confdir', type=str, required=True)
    parser.add_argument('--outdir', type=str, required=True)
    return parser.parse_args()

if __name__ == '__main__':
    mutable_namespace = parse_args()

    # I want to prevent overwrite like that.
    mutable_namespace.confdir = "xxx"

您可以在 mutable_namespace 中重新定义 __setattr__:

class NotMutableException(Exception):pass

class SomeObject(object):
    def init(self):
        self.x = 10
        self.y = 20

some_obj = SomeObject()
some_obj.z = 30

def not_setattr(self, name, value):
    raise NotMutableException

type(some_obj).__setattr__ = not_setattr

some_obj.a = 1000

我最初提出自定义命名空间 class,但我更喜欢将 args 复制到 NamedTuple 的想法。

命名元组

另一种选择是将值从 args 复制到不可变的 object/class。命名元组可能会很好地完成这项工作。

创建命名空间

In [1157]: dest=['x','y']
In [1158]: args=argparse.Namespace()
In [1159]: for name in dest:
   ......:     setattr(args, name, 23)
   ......:     
In [1160]: args
Out[1160]: Namespace(x=23, y=23)

现在定义一个命名元组

In [1161]: from collections import namedtuple
In [1163]: Foo = namedtuple('Foo',dest)

您还可以从命名空间本身获取元组名称(解析后)

Foo = namedtuple('Foo',vars(args).keys())

使用 args:

中的值创建这样一个元组
In [1165]: foo=Foo(**vars(args))
In [1166]: foo
Out[1166]: Foo(x=23, y=23)
In [1167]: foo.x
Out[1167]: 23

并且它是不可变的:

In [1168]: foo.x=34
... 
AttributeError: can't set attribute

这样的 namedtuple 不能用作命名空间,因为 setattr(foo,'x',34) 会产生相同的错误。

完成所有这一切的一种简洁方法是将其全部包装在一个函数中:

def do_parse():
   parser = ....
   Foo = namedtuple(...)
   args = parser.parse_args()
   foo = Foo(**vars(args))
   return foo

调用代码永远不会看到可变的 args,只会看到不可变的 foo

自定义命名空间class

要基于 Ingaz 答案,argparse 可以使用您自己的 Namespace class.

https://docs.python.org/3/library/argparse.html#the-namespace-object

class MyNamespace(argparse.Namespace):
    pass
    <customize one or more methods>

anamespace = MyNamespace()
args = parser.parse_args(namespace=anamespace)

现在 argsanamespace 引用同一个 MyNamespace 对象。只要 getattr(anamespace, adest)setattr(anamespace, adest, avalue) 工作,argparse 就可以使用这个命名空间对象。

现在,您可以允许 setattr(anamespace, 'string', 'value'),但不允许 anamespace.string = value 吗?我认为你可以,但需要很好地理解后一种表达方式的工作原理。可能只是需要定制.__setattr__,不过我好久没研究Python这方面了

根据设计,'monkey patch' argparse 命名空间是可能的,甚至是可接受的 - 使用像这样的自定义 class。