我试图将一个较长的中空“数据”类转换为命名元组。我的类目前看起来是这样的:

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')

但这里有一个问题。我最初的类允许我只传入一个值,并通过为named/keyword参数使用默认值来处理默认值。喜欢的东西:

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

但这在重构的命名tuple中不起作用,因为它期望我传递所有字段。我当然可以替换Node(val)到Node(val, None, None)的出现,但这不是我喜欢的。

那么,是否存在一个好技巧,可以让我的重写成功,而不增加大量的代码复杂性(元编程),或者我应该吞下药丸,继续“搜索和替换”?:)


当前回答

将其包装在函数中。

NodeT = namedtuple('Node', 'val left right')

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

其他回答

Python 3.7:在namedtuple定义中引入默认参数。

示例如文档所示:

>>> Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
>>> Account._fields_defaults
{'balance': 0}
>>> Account('premium')
Account(type='premium', balance=0)

点击这里阅读更多。

由于您正在使用namedtuple作为数据类,您应该注意到python 3.7将为此目的引入@dataclass装饰器——当然它有默认值。

文档中的一个例子:

@dataclass
class C:
    a: int       # 'a' has no default value
    b: int = 0   # assign a default value for 'b'

比破解namedtuple更干净,可读和可用。不难预测,随着3.7的采用,namedtuples的使用将会减少。

一个稍微扩展的例子,用None初始化所有缺失的参数:

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)

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

>>> 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)

下面是一个简短、简单的通用答案,对于带默认参数的命名元组,它有一个很好的语法:

import collections

def dnamedtuple(typename, field_names, **defaults):
    fields = sorted(field_names.split(), key=lambda x: x in defaults)
    T = collections.namedtuple(typename, ' '.join(fields))
    T.__new__.__defaults__ = tuple(defaults[field] for field in fields[-len(defaults):])
    return T

用法:

Test = dnamedtuple('Test', 'one two three', two=2)
Test(1, 3)  # Test(one=1, three=3, two=2)

缩小:

def dnamedtuple(tp, fs, **df):
    fs = sorted(fs.split(), key=df.__contains__)
    T = collections.namedtuple(tp, ' '.join(fs))
    T.__new__.__defaults__ = tuple(df[i] for i in fs[-len(df):])
    return T