命名为可选的关键字参数

我试图将一个长而空的“数据”类转换成命名的元组。 我的课目前看起来像这样:

class Node(object): def __init__(self, val, left=None, right=None): self.val = val self.left = left self.right = right 

转换为namedtuple后,它看起来像:

 from collections import namedtuple Node = namedtuple('Node', 'val left right') 

但是这里有一个问题。 我原来的类允许我只传入一个值,并通过使用默认值的命名/关键字参数来照顾默认值。 就像是:

 class BinaryTree(object): def __init__(self, val): self.root = Node(val) 

但是这不适用于我的重构命名的元组,因为它期望我通过所有的字段。 我当然可以将Node(val)replace为Node(val, None, None)但不是我喜欢的。

那么是否存在一个很好的技巧,可以使我的重写成功,而不会增加很多代码复杂性(元编程),或者我应该吞下药片并继续进行“search和replace”? 🙂

Node.__new__.__defaults__ Node.__new__.func_defaults (或Node.__new__.func_defaults之前的Node.__new__.func_defaults )设置为默认值。

 >>> from collections import namedtuple >>> Node = namedtuple('Node', 'val left right') >>> Node.__new__.__defaults__ = (None,) * len(Node._fields) >>> Node() Node(val=None, left=None, right=None) 

您也可以通过缩短__defaults__列表来填写必填字段。

 >>> Node.__new__.__defaults__ = (None, None) >>> Node() Traceback (most recent call last): ... TypeError: __new__() missing 1 required positional argument: 'val' >>> Node(3) Node(val=3, left=None, right=None) 

包装纸

这里给你一个很好的包装,甚至可以让你(可选地)将默认值设置为None以外的值。 (这不支持必需的参数。):

 import collections def namedtuple_with_defaults(typename, field_names, default_values=()): T = collections.namedtuple(typename, field_names) T.__new__.__defaults__ = (None,) * len(T._fields) if isinstance(default_values, collections.Mapping): prototype = T(**default_values) else: prototype = T(*default_values) T.__new__.__defaults__ = tuple(prototype) return T 

例:

 >>> Node = namedtuple_with_defaults('Node', 'val left right') >>> Node() Node(val=None, left=None, right=None) >>> Node = namedtuple_with_defaults('Node', 'val left right', [1, 2, 3]) >>> Node() Node(val=1, left=2, right=3) >>> Node = namedtuple_with_defaults('Node', 'val left right', {'right':7}) >>> Node() Node(val=None, left=None, right=7) >>> Node(4) Node(val=4, left=None, right=7) 

我subclassed __new__并覆盖__new__方法:

 from collections import namedtuple class Node(namedtuple('Node', ['value', 'left', 'right'])): __slots__ = () def __new__(cls, value, left=None, right=None): return super(Node, cls).__new__(cls, value, left, right) 

这保留了一个直观的types层次结构,伪装成一个类的工厂函数的创build没有。

把它包装在一个函数中。

 NodeT = namedtuple('Node', 'val left right') def Node(val, left=None, right=None): return NodeT(val, left, right) 

我不确定是否有一个简单的方法,只是内置的namedtuple。 有一个很好的模块recordtype有这个function:

 >>> from recordtype import recordtype >>> Node = recordtype('Node', [('val', None), ('left', None), ('right', None)]) >>> Node(3) Node(val=3, left=None, right=None) >>> Node(3, 'L') Node(val=3, left=L, right=None) 

这是直接来自文档的示例 :

默认值可以通过使用_replace()来实现一个原型实例来实现:

 >>> Account = namedtuple('Account', 'owner balance transaction_count') >>> default_account = Account('<owner name>', 0.0, 0) >>> johns_account = default_account._replace(owner='John') >>> janes_account = default_account._replace(owner='Jane') 

所以,OP的例子是:

 from collections import namedtuple Node = namedtuple('Node', 'val left right') default_node = Node(None, None, None) example = default_node._replace(val="whut") 

不过,我更喜欢这里给出的其他答案。 我只是想补充一点。

这里是一个更简洁的版本,由justinfay的答案启发:

 from collections import namedtuple from functools import partial Node = namedtuple('Node', ('val left right')) Node.__new__ = partial(Node.__new__, left=None, right=None) 

None来初始化所有缺less的参数的一个稍微扩展的例子:

 from collections import namedtuple class Node(namedtuple('Node', ['value', 'left', 'right'])): __slots__ = () def __new__(cls, *args, **kwargs): # initialize missing kwargs with None all_kwargs = {key: kwargs.get(key) for key in cls._fields} return super(Node, cls).__new__(cls, *args, **all_kwargs) 

你也可以使用这个:

 import inspect def namedtuple_with_defaults(type, default_value=None, **kwargs): args_list = inspect.getargspec(type.__new__).args[1:] params = dict([(x, default_value) for x in args_list]) params.update(kwargs) return type(**params) 

这基本上给了你构build任何具有默认值的命名元组的可能性,并覆盖你所需要的参数,例如:

 import collections Point = collections.namedtuple("Point", ["x", "y"]) namedtuple_with_defaults(Point) >>> Point(x=None, y=None) namedtuple_with_defaults(Point, x=1) >>> Point(x=1, y=None) 

结合@Denis和@Mark的方法:

 from collections import namedtuple import inspect class Node(namedtuple('Node', 'left right val')): __slots__ = () def __new__(cls, *args, **kwargs): args_list = inspect.getargspec(super(Node, cls).__new__).args[len(args)+1:] params = {key: kwargs.get(key) for key in args_list + kwargs.keys()} return super(Node, cls).__new__(cls, *args, **params) 

这应该支持创build具有位置参数的元组以及混合的情况。 testing用例:

 >>> print Node() Node(left=None, right=None, val=None) >>> print Node(1,2,3) Node(left=1, right=2, val=3) >>> print Node(1, right=2) Node(left=1, right=2, val=None) >>> print Node(1, right=2, val=100) Node(left=1, right=2, val=100) >>> print Node(left=1, right=2, val=100) Node(left=1, right=2, val=100) >>> print Node(left=1, right=2) Node(left=1, right=2, val=None) 

还支持TypeError:

 >>> Node(1, left=2) TypeError: __new__() got multiple values for keyword argument 'left' 

简而言之,不会导致人们使用isinstance

 class Node(namedtuple('Node', ('val', 'left', 'right'))): @classmethod def make(cls, val, left=None, right=None): return cls(val, left, right) # Example x = Node.make(3) x._replace(right=Node.make(4)) 

在Python 3.6.1+中使用typing.NamedTuple ,可以为NamedTuple字段提供默认值和types注释。 使用typing.Any如果你只需要前者:

 from typing import Any, NamedTuple class Node(NamedTuple): val: Any left: 'Node' = None right: 'Node' = None 

例:

 >>> Node(1) Node(val=1, left=None, right=None) >>> n = Node(1) >>> Node(2, left=n) Node(val=2, left=Node(val=1, left=None, right=None), right=None) 

我觉得这个版本更容易阅读:

 from collections import namedtuple def my_tuple(**kwargs): defaults = { 'a': 2.0, 'b': True, 'c': "hello", } default_tuple = namedtuple('MY_TUPLE', ' '.join(defaults.keys()))(*defaults.values()) return default_tuple._replace(**kwargs) 

这不像它需要两​​次创build对象那么高效,但是你可以通过在模块中定义默认的双工来改变它,并且只是让函数做replace行。

这是一个不太灵活,但更简洁的Mark Lodato的包装版本:它将字段和默认值作为字典。

 import collections def namedtuple_with_defaults(typename, fields_dict): T = collections.namedtuple(typename, ' '.join(fields_dict.keys())) T.__new__.__defaults__ = tuple(fields_dict.values()) return T 

例:

 In[1]: fields = {'val': 1, 'left': 2, 'right':3} In[2]: Node = namedtuple_with_defaults('Node', fields) In[3]: Node() Out[3]: Node(val=1, left=2, right=3) In[4]: Node(4,5,6) Out[4]: Node(val=4, left=5, right=6) In[5]: Node(val=10) Out[5]: Node(val=10, left=2, right=3) 

使用Advanced Enum (aenum)库中的NamedTuple类并使用class语法,这非常简单:

 from aenum import NamedTuple class Node(NamedTuple): val = 0 left = 1, 'previous Node', None right = 2, 'next Node', None 

一个潜在的缺点是对于具有默认值的任何属性的__doc__string的要求(对于简单属性是可选的)。 在使用中,它看起来像:

 >>> Node() Traceback (most recent call last): ... TypeError: values not provided for field(s): val >>> Node(3) Node(val=3, left=None, right=None) 

这比justinfay's answer有以下好处:

 from collections import namedtuple class Node(namedtuple('Node', ['value', 'left', 'right'])): __slots__ = () def __new__(cls, value, left=None, right=None): return super(Node, cls).__new__(cls, value, left, right) 

是简单的,以及基于metaclass而不是基于exec

如果你永远不能满足这些variables(他们永远都是 None ),那么你就可以做到

 class Node(object): def __init__(self, val): self.val = val self.left = None self.right = None 

并保持简单。

您可以将NodeVariable = Node(self, val, right, left)放在一个try语句中,但是我觉得这不是您想要做的事情。

另一个scheme

 import collections def defaultargs(func, defaults): def wrapper(*args, **kwargs): for key, value in (x for x in defaults[len(args):] if len(x) == 2): kwargs.setdefault(key, value) return func(*args, **kwargs) return wrapper def namedtuple(name, fields): NamedTuple = collections.namedtuple(name, [x[0] for x in fields]) NamedTuple.__new__ = defaultargs(NamedTuple.__new__, [(NamedTuple,)] + fields) return NamedTuple 

用法:

 >>> Node = namedtuple('Node', [ ... ('val',), ... ('left', None), ... ('right', None), ... ]) __main__.Node >>> Node(1) Node(val=1, left=None, right=None) >>> Node(1, 2, right=3) Node(val=1, left=2, right=3)