命名元组和可选关键字参数的默认值

394

我正在尝试将一个比较冗长的“数据”类转换为一个命名元组。我的类目前看起来像这样:

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) 的所有出现替换为 Node(val, None, None),但我并不喜欢这种方式。

那么,是否存在一种好的技巧,可以让我成功地重写代码,而不会增加太多的代码复杂性(元编程),或者我只能咽下这颗苦果,继续使用“搜索和替换”方法?:)


3
你为什么想要进行这个转换?我喜欢你的原始“Node”类,就像它现在的样子。为什么要转换成命名元组? - steveha
44
我希望进行这种转换,因为当前的“Node”和其他类都是简单的数据持有者值对象,具有许多不同的字段(“Node”只是其中之一)。在我看来,这些类声明不过是无用的代码,所以我希望将它们删减掉。为什么要维护不必要的东西呢? :) - sasuke
你的类中完全没有任何方法函数吗?例如,你没有一个.debug_print()方法来遍历树并打印它吗? - steveha
4
当然可以,但是这是针对BinaryTree类而言的。而Node和其他的数据容器并不需要这样特殊的方法,尤其是因为命名元组已经有了相当不错的__str____repr__表示方式。 :) - sasuke
好的,看起来很合理。我认为Ignacio Vazquez-Abrams已经给出了答案:使用一个函数为您的节点设置默认值。 - steveha
类定义清晰,完全符合读者的预期。这里的许多答案都很复杂,并且具有令人惊讶的副作用。是的,Node是一个数据持有者类,但在幕后,namedtuple也是如此! - Lorenz Forvang
23个回答

0

另一种解决方案:

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)

0

如果您想保留使用类型注释的可能性,那么很遗憾,@mark-lodato提供的非常好的解决方案无法使用(在设置__defaults__时失败)。 另一种选择是使用attrs

import attr

 
@attr.s
class Node(object):
    val: str = attr.ib()
    left: 'Node' = attr.ib(None)
    right: 'Node' = attr.ib(None)

这个有:

  • 类型注释
  • 漂亮的__str____repr__
  • 可定制,因为它是一个真正的类
  • 所有Python版本都有相同的实现

-1
这是 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)

5
dict 没有顺序保证。 - Ethan Furman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接