Python namedtuple的可变默认参数

7

我在这里找到了一种聪明的方法,可以让命名元组使用默认参数。这里有更多信息。

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

节点(val=None,left=None,right=None)

如果您希望'right'的默认值是一个空列表,那么该怎么办呢?正如您所知,使用可变的默认参数(例如列表)是不可取的。

有没有一种简单的方法来实现这个要求呢?


你为什么要请求一些你自己说不行的东西? - John Zwinck
@John Zwinck:我们能否修改__new__()方法,使其将'None'转换为类似用户定义类中的新[]? - ChaimG
4个回答

6
以这种方式无法完成任务,因为`__defaults__`中的值是实际的默认值。也就是说,如果你编写了一个函数,其中包含`someargument=None`,然后在函数体内使用`someargument = [] if someargument is None else someargument`或类似的代码进行检查,相应的`__defaults__`条目仍将为`None`。换句话说,你可以在函数中这样做,因为在函数中可以编写任何你想要的代码,但是你不能在命名元组中编写自定义代码。
但是如果你需要默认值,只需要创建一个具有该逻辑并创建正确的命名元组的函数即可:
def makeNode(val=None, left=None, right=None):
    if right is None:
        val = []
    return Node(val, left, right)

2
这种解决方案的缺点是,人们必须知道或记住实际使用工厂函数而不是Node类。这种设计选择并不总是显而易见的。 - Rick

3

自从这个问题被提出以来,dataclasses 模块已经被提议并被接受到 Python 中。该模块具有许多与 namedtuples 重叠的用例,但具有更多的灵活性和功能。特别是,当您想为可变字段指定默认值时,可以指定一个工厂函数。

from typing import List
from dataclasses import dataclass, field

@dataclass
class Node:
    val: str
    left: List["Node"] = field(default_factory=list)
    right: List["Node"] = field(default_factory=list)

在数据类中,您可以指定各个字段的类型。因此,在这种情况下,我必须填写一些空白,并假设val是一个字符串,而leftright都是其他Node对象的列表。
由于rightleft是类定义中赋值语句的左侧,所以当我们初始化Node对象时,它们是可选参数。此外,我们可以提供默认值,但我们提供了一个默认工厂,这是一个函数,每当我们初始化没有指定这些字段的Node对象时,就会调用该函数。
例如:
node_1 = Node('foo')
# Node(val='foo', left=[], right=[])

node_2 = Node('bar', left=[node_1])
# Node(val='bar', left=[Node(val='foo', left=[], right=[])], right=[])

node_3 = Node('baz')
# Node(val='baz', left=[], right=[])

node_4 = Node('quux', left=[node_2], right=[node_3])
# Node(val='quux', left=[Node(val='bar', left=[Node(val='foo', left=[], right=[])], right=[])], right=[Node(val='baz', left=[], right=[])])

就个人而言,我发现对于任何需要不仅仅是数据最薄的容器的应用程序,我更喜欢使用 dataclasses 而不是 namedtuples


3

被接受的答案 中提供的方式非常好。 我唯一看到的缺点是,不仅要在创建对象时,还要在像这样的操作时记住使用工厂函数而不是命名元组类-(对于某些其他用户的情况)。

isinstance(node, Node) #  success
isinstance(node, makeNode) #  misery

解决这个问题的方法可能是像下面所示的那样做。
NodeBase = nt('NodeBase', 'val left right')
NodeBase.__new__.__defaults__ = (None, None, None)

class Node(NodeBase):
    '''A namedtuple defined as:

    Node(val, left, right)

    with default values of (None, None, [])'''
    __slots__ = ()
    def __new__(cls, *args, **kwargs):
        obj = super().__new__(cls, *args, **kwargs)
            if obj.right is None:
                obj = obj._replace(right = [])
            return obj

-1

从Rick Teachey的实现中只需进行小改动,就可以在类外设置默认值:

NodeBase = namedtuple('NodeBase', 'val left right')

class Node(NodeBase):
    __slots__ = ()
    def __new__(cls, *, right=[], **kwargs):
        obj = super().__new__(cls, right=right, **kwargs)
        return obj

#IMPLEMENTATION
kw = {'val': 1, 'left':12}

m  = Node(**kw) 
# outputs Node(val=1, left=12, right=[])

使用这段代码 Node(val=1, left=12, right=1) 会产生 Node(val=1, left=12, right=[]) 的结果。 - Clément

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