namedtuple和可选关键字参数的默认值

sas*_*uke 265 python default-value optional-arguments namedtuple

我正在尝试将一个冗长的空洞"数据"类转换为一个命名元组.我的班级目前看起来像这样:

class Node(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
Run Code Online (Sandbox Code Playgroud)

转换为namedtuple它后看起来像:

from collections import namedtuple
Node = namedtuple('Node', 'val left right')
Run Code Online (Sandbox Code Playgroud)

但这里有一个问题.我的原始类允许我传入一个值,并使用命名/关键字参数的默认值来处理默认值.就像是:

class BinaryTree(object):
    def __init__(self, val):
        self.root = Node(val)
Run Code Online (Sandbox Code Playgroud)

但是这对我重构的名为元组的情况不起作用,因为它希望我传递所有字段.当然,我可以代替的出现Node(val)Node(val, None, None),但它不是我的胃口.

所以确实存在着一个很好的技巧,它可以使我重新写成功无需添加大量的代码复杂度(元编程),或者我应该只吞下药丸,并与"查找和替换"继续前进?:)

Mar*_*ato 472

Python 3.7

使用defaults参数.

>>> from collections import namedtuple
>>> fields = ('val', 'left', 'right')
>>> Node = namedtuple('Node', fields, defaults=(None,) * len(fields))
>>> Node()
Node(val=None, left=None, right=None)
Run Code Online (Sandbox Code Playgroud)

在Python 3.7之前

设置Node.__new__.__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)
Run Code Online (Sandbox Code Playgroud)

在Python 2.6之前

设置Node.__new__.func_defaults为默认值.

>>> from collections import namedtuple
>>> Node = namedtuple('Node', 'val left right')
>>> Node.__new__.func_defaults = (None,) * len(Node._fields)
>>> Node()
Node(val=None, left=None, right=None)
Run Code Online (Sandbox Code Playgroud)

订购

在所有版本的Python中,如果您设置的默认值少于namedtuple中存在的默认值,则默认值将应用于最右侧的参数.这允许您将一些参数保留为必需参数.

>>> Node.__new__.__defaults__ = (1,2)
>>> Node()
Traceback (most recent call last):
  ...
TypeError: __new__() missing 1 required positional argument: 'val'
>>> Node(3)
Node(val=3, left=1, right=2)
Run Code Online (Sandbox Code Playgroud)

Python 2.6到3.6的包装器

这里有一个包装器,甚至可以让你(可选)将默认值设置为其他值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
Run Code Online (Sandbox Code Playgroud)

例:

>>> 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)
Run Code Online (Sandbox Code Playgroud)

  • 让我们看看......你的单线:a)是最短/最简单的答案,b)保持空间效率,c)不打破"isinstance"......所有专业人士,没有缺点......太糟糕了你是一个派对迟到了.这是最好的答案. (21认同)
  • @ishaaq,问题是`(无)`不是元组,它是'无'.如果你改用`(None,)`,它应该可以正常工作. (3认同)
  • 我给了这个答案一个upvote,因为它比我自己更好.遗憾的是,我自己的答案不断上升:| (2认同)
  • 优秀!您可以使用以下命令来概括默认设置:``Node .__ new __.__ defaults __ =(None,)*len(Node._fields)`` (2认同)

Jus*_*Fay 139

我继承了namedtuple并覆盖了该__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)
Run Code Online (Sandbox Code Playgroud)

这保留了一种直观的类型层次结构,即伪造成类的工厂函数的创建不会.

  • 这可能需要插槽和字段属性,以便维持命名元组的空间效率. (7认同)
  • 但 @marc-lodato 的答案没有提供子类具有不同默认值的能力 (2认同)

Ign*_*ams 90

将它包装在一个函数中.

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

def Node(val, left=None, right=None):
  return NodeT(val, left, right)
Run Code Online (Sandbox Code Playgroud)

  • 这很聪明,可以是一个很好的选择,但也可以通过打破`isinstance(Node('val'),Node)来引起问题`:它现在会引发一个异常,而不是返回True.虽然有点冗长,[@ justinfay的回答(如下)](http://stackoverflow.com/a/16721002/260491)正确保留了类型层次结构信息,因此如果其他人要与Node实例进行交互,这可能是更好的方法. (12认同)
  • 我喜欢这个答案的简洁.也许上面注释中的关注可以通过命名函数`def make_node(...):`而不是假装它是一个类定义来解决.这样,用户不会试图检查函数的类型多态性,而是使用元组定义本身. (4认同)

mon*_*ime 63

随着typing.NamedTuple在Python 3.6.1+,你可以同时提供一个默认值和类型标注为NamedTuple场.typing.Any如果您只需要前者,请使用:

from typing import Any, NamedTuple


class Node(NamedTuple):
    val: Any
    left: 'Node' = None
    right: 'Node' = None
Run Code Online (Sandbox Code Playgroud)

用法:

>>> 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)
Run Code Online (Sandbox Code Playgroud)

此外,如果您需要默认值和可选的可变性,Python 3.7将具有数据类(PEP 557),可以在某些(许多?)情况下替换namedtuples.


旁注:Python中当前注释规范(:参数和变量之后的表达式以及->函数之后的表达式)的一个怪癖是它们在定义时*进行评估.因此,由于"一旦类的整个主体被执行就会定义类名",'Node'上面类字段中的注释必须是字符串以避免NameError.

这种类型的提示被称为"前向引用"([1],[2]),而对于PEP 563, Python 3.7+将具有__future__导入(默认情况下在4.0中启用),允许使用前向引用没有引号,推迟评估.

* AFAICT仅在运行时不评估局部变量注释.(来源:PEP 526)

  • 对于3.6.1+用户来说,这似乎是最干净的解决方案.请注意,此示例(略微)令人困惑,因为字段"left"和"right"(即"Node")的类型提示与要定义的类的类型相同,因此必须写为字符串. (3认同)
  • 如果my_list不是其他[]`,那么成语“ my_list”的类比是什么:List [T] = None``self.my_list = my_list?我们不能使用这样的默认参数吗? (2认同)

Tim*_*all 20

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

可以使用_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')
Run Code Online (Sandbox Code Playgroud)

因此,OP的例子是:

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

但是,我更喜欢这里给出的其他一些答案.我只是想补充一下这个完整性.

  • `_`前缀是为了避免与用户定义的元组字段的名称冲突(相关的文档引用:"任何有效的Python标识符都可以用于字段名,但以下划线开头的名称除外.").至于空格分隔的字符串,我认为这只是为了节省一些键击(如果你愿意,你可以传递一系列字符串). (12认同)
  • +1。很奇怪,他们决定使用 `_` 方法(这基本上意味着一个私有的方法)来代替类似 `replace` 的东西,这看起来非常有用.. (2认同)
  • 您的解决方案简单而且最好.其余的是恕我直言,相当丑陋.我只做一个小改动.我宁愿使用node_default而不是default_node,因为它可以更好地体验IntelliSense.如果你开始输入节点,你收到了你需要的一切:) (2认同)

jte*_*ace 19

我不确定是否有一个简单的方法只有内置的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)
Run Code Online (Sandbox Code Playgroud)

  • 只是要注意`recordtype`是可变的,而`namedtuple`则不是.如果你想让对象可以清洗(我猜你没有,因为它最初是一个类),这可能很重要. (3认同)
  • 啊,尽管`recordtype`确实看起来对未来的工作看起来很有趣,但是不可能使用第三方包.+1 (2认同)

Gus*_*son 13

这是一个更紧凑的版本,灵感来自justinfay的答案:

from collections import namedtuple
from functools import partial

Node = namedtuple('Node', ('val left right'))
Node.__new__ = partial(Node.__new__, left=None, right=None)
Run Code Online (Sandbox Code Playgroud)

  • 请注意`Node(1,2)`不适用于此配方,但适用于@ justinfay的答案.否则,它非常漂亮(+1). (6认同)

Ant*_*ile 11

在python3.7 +中有一个全新的defaults = keyword参数.

默认值可以是None或可以迭代的默认值.由于具有默认值的字段必须位于没有默认值的任何字段之后,因此默认值将应用于最右侧的参数.例如,如果字段名是['x', 'y', 'z']和默认值(1, 2),x则将是必需参数,y默认为1,z默认为2.

用法示例:

$ ./python
Python 3.7.0b1+ (heads/3.7:4d65430, Feb  1 2018, 09:28:35) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from collections import namedtuple
>>> nt = namedtuple('nt', ('a', 'b', 'c'), defaults=(1, 2))
>>> nt(0)
nt(a=0, b=1, c=2)
>>> nt(0, 3)  
nt(a=0, b=3, c=2)
>>> nt(0, c=3)
nt(a=0, b=1, c=3)
Run Code Online (Sandbox Code Playgroud)


Ell*_*ron 6

简短,简单,不会导致人们使用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))
Run Code Online (Sandbox Code Playgroud)


Den*_*zov 5

一个稍微扩展的示例,用于初始化所有缺少的参数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)
Run Code Online (Sandbox Code Playgroud)


Jul*_*eri 5

Python 3.7:defaults在namedtuple定义中引入了param。

文档中显示的示例:

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

在这里阅读更多。