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

2024-04-25 19:42:44 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试将一个长的空心“data”类转换为一个命名元组。我的班级目前看起来是这样的:

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)

但这在我的重构命名元组中不起作用,因为它希望我传递所有字段。我当然可以把Node(val)的出现替换成Node(val, None, None),但这不符合我的喜好。

那么,有没有一个好的技巧可以使我的重新编写成功,而不增加很多代码复杂性(元编程),或者我应该吞下药丸,继续“搜索和替换”?:)


Tags: selfrightnonenodedataobjectinitdef
3条回答

Python3.7

使用默认值参数。

>>> from collections import namedtuple
>>> fields = ('val', 'left', 'right')
>>> Node = namedtuple('Node', fields, defaults=(None,) * len(fields))
>>> Node()
Node(val=None, left=None, right=None)

或者更好的方法是使用新的dataclasses库,它比namedtuple好得多。

>>> from dataclasses import dataclass
>>> from typing import Any
>>> @dataclass
... class Node:
...     val: Any = None
...     left: 'Node' = None
...     right: 'Node' = None
>>> Node()
Node(val=None, left=None, right=None)

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

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

命令

在所有版本的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)

Python2.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

示例:

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

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

这保留了一个直观的类型层次结构,而工厂函数伪装成类的创建并没有这样做。

把它包装成一个函数。

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

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

相关问题 更多 >