使用带位置参数

时间:2017-10-06 15:47:38

标签: python python-3.x

摘要

我最近发现types.SimpleNamespace classnifty as heck

使用此类创建类型会很有用,但允许它们使用定义位置参数的签名而不是仅关键字参数。允许位置参数可以为实例创建提供一些额外的灵活性。

我这样做的尝试没有用,所以我正在寻找建议。

详细

如果使用位置参数子类types.SimpleNamespace,则会收到错误:

>>> class Test1(SimpleNamespace):
...  def __init__(self, a, b):
...   super().__init__(a, b)
...
>>> Test1(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: no positional arguments expected

但是,您可以通过在调用super时为参数指定名称来解决此问题:

>>> class Test2(SimpleNamespace):
...  def __init__(self, a, b):
...   super().__init__(a=a, b=b)
...
>>> Test2(1,2)
Test2(a=1, b=2)

大。但现在让我们说我想要一个方法来创建类似上面的类型,而不必在调用super().__init__时重复命名参数,使用与上面所示相同的完全相同的代码。我该怎么办?

编辑:事实证明我的问题是由一个简单的拼写错误引起的。一切都适用于下面答案中显示的代码。

由于将SimpleNamespace与位置参数一起使用是其他人可能对理解如何操作感兴趣的事情,我认为保留这个问题而不是关闭它是个好主意。

1 个答案:

答案 0 :(得分:0)

当父类使用命名参数签名时,为了更容易地为子类创建位置类签名,我们将创建一个基类,它接受位置参数并将它们作为命名参数提供给构造函数方法。

为此,我们需要使用inspect.getargspec。这样我们就可以将__init__的签名名称与已传入其中的位置值进行匹配。

from types import SimpleNamespace
import inspect

class PositionalSimpleNamespaceBase(SimpleNamespace):
    def __init__(self, *args, **kwargs):
        args_name = inspect.getargspec(type(self).__init__)[0][1:]
        print('__init__ args: ', args_name)
        super().__init__(**dict(zip(args_name, args), **kwargs))

class ChildClass(PositionalSimpleNamespaceBase):
    def __init__(self, a, b):
        super().__init__(a, b)

现在:

>>> obj=ChildClass(1,2)
__init__ args:  ['a', 'b']
>>> obj
ChildClass(a=1, b=2)

有效!

如果希望动态地创建这样的类型,那么一种方法是使用类似于内置collections模块中namedtuple模块的方法。可以创建一个从PositionalSimpleNamespaceBase继承的模板,然后使用exec函数执行模板。

下面是一个简化版本(此处提供的字段名称未经过清理,因此如图所示使用不安全!):

def positional_simple_namespace(name, fields):
    field_names = fields.replace(',',' ').split() if isinstance(fields, str) else tuple(map(str, fields))
    field_text = ', '.join(field_names)
    super_sigs = (f'{n!s} = {n!s}' for n in field_names)
    super_text = ', '.join(super_sigs)
    code = f'''class {name!s}(PositionalSimpleNamespaceBase):
        def __init__(self, {field_text}):
            super().__init__({super_text})
    '''
    namespace = {}
    exec(code, dict(PositionalSimpleNamespaceBase = PositionalSimpleNamespaceBase), namespace)
    cls = namespace[name]
    return cls

像这样使用:

>>> X=positional_simple_namespace('Test', 'a b c')
>>> X(1,2,3)
__init__ args:  ['a', 'b', 'c']
Test(a=1, b=2, c=3)