【问题标题】:Mutable default argument for a Python namedtuplePython namedtuple 的可变默认参数
【发布时间】:2016-03-12 21:44:20
【问题描述】:

我发现了一种让 namedtuples 使用来自 here 的默认参数的巧妙方法。

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:我们可以修改 __new__() 以便它将“无”更改为新的 [],就像我们对用户定义的类所做的那样?

标签: python namedtuple


【解决方案1】:

自从提出这个问题以来,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 对象时使用 0 个参数调用的函数,而无需指定这些字段。

例如:

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

【讨论】:

    【解决方案2】:

    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=[])
    【解决方案3】:

    the accepted answer 中给出的方法效果很好。我看到的唯一缺点是必须同时知道(在其他用户的情况下)和记住才能使用工厂函数而不是命名元组类-两者创建对象时,以及执行以下操作时:

    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
    

    【讨论】:

      【解决方案4】:

      您不能那样做,因为__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)
      

      【讨论】:

      • 此解决方案的缺点是必须知道或记住实际使用工厂函数而不是 Node 类。这种设计选择并不总是显而易见的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-27
      • 2021-10-11
      • 2021-08-18
      • 1970-01-01
      • 2011-05-07
      • 2019-07-30
      • 2014-05-31
      相关资源
      最近更新 更多