命名为可选的关键字参数
我试图将一个长而空的“数据”类转换成命名的元组。 我的课目前看起来像这样:
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)
replace为Node(val, None, None)
但不是我喜欢的。
那么是否存在一个很好的技巧,可以使我的重写成功,而不会增加很多代码复杂性(元编程),或者我应该吞下药片并继续进行“search和replace”? 🙂
将Node.__new__.__defaults__
Node.__new__.func_defaults
(或Node.__new__.func_defaults
之前的Node.__new__.func_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)
您也可以通过缩短__defaults__
列表来填写必填字段。
>>> Node.__new__.__defaults__ = (None, None) >>> Node() Traceback (most recent call last): ... TypeError: __new__() missing 1 required positional argument: 'val' >>> Node(3) Node(val=3, left=None, right=None)
包装纸
这里给你一个很好的包装,甚至可以让你(可选地)将默认值设置为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)
我subclassed __new__
并覆盖__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)
这保留了一个直观的types层次结构,伪装成一个类的工厂函数的创build没有。
把它包装在一个函数中。
NodeT = namedtuple('Node', 'val left right') def Node(val, left=None, right=None): return NodeT(val, left, right)
我不确定是否有一个简单的方法,只是内置的namedtuple。 有一个很好的模块recordtype有这个function:
>>> 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)
这是直接来自文档的示例 :
默认值可以通过使用_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')
所以,OP的例子是:
from collections import namedtuple Node = namedtuple('Node', 'val left right') default_node = Node(None, None, None) example = default_node._replace(val="whut")
不过,我更喜欢这里给出的其他答案。 我只是想补充一点。
这里是一个更简洁的版本,由justinfay的答案启发:
from collections import namedtuple from functools import partial Node = namedtuple('Node', ('val left right')) Node.__new__ = partial(Node.__new__, left=None, right=None)
用None
来初始化所有缺less的参数的一个稍微扩展的例子:
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)
你也可以使用这个:
import inspect def namedtuple_with_defaults(type, default_value=None, **kwargs): args_list = inspect.getargspec(type.__new__).args[1:] params = dict([(x, default_value) for x in args_list]) params.update(kwargs) return type(**params)
这基本上给了你构build任何具有默认值的命名元组的可能性,并覆盖你所需要的参数,例如:
import collections Point = collections.namedtuple("Point", ["x", "y"]) namedtuple_with_defaults(Point) >>> Point(x=None, y=None) namedtuple_with_defaults(Point, x=1) >>> Point(x=1, y=None)
结合@Denis和@Mark的方法:
from collections import namedtuple import inspect class Node(namedtuple('Node', 'left right val')): __slots__ = () def __new__(cls, *args, **kwargs): args_list = inspect.getargspec(super(Node, cls).__new__).args[len(args)+1:] params = {key: kwargs.get(key) for key in args_list + kwargs.keys()} return super(Node, cls).__new__(cls, *args, **params)
这应该支持创build具有位置参数的元组以及混合的情况。 testing用例:
>>> print Node() Node(left=None, right=None, val=None) >>> print Node(1,2,3) Node(left=1, right=2, val=3) >>> print Node(1, right=2) Node(left=1, right=2, val=None) >>> print Node(1, right=2, val=100) Node(left=1, right=2, val=100) >>> print Node(left=1, right=2, val=100) Node(left=1, right=2, val=100) >>> print Node(left=1, right=2) Node(left=1, right=2, val=None)
还支持TypeError:
>>> Node(1, left=2) TypeError: __new__() got multiple values for keyword argument 'left'
简而言之,不会导致人们使用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))
在Python 3.6.1+中使用typing.NamedTuple
,可以为NamedTuple字段提供默认值和types注释。 使用typing.Any
如果你只需要前者:
from typing import Any, NamedTuple class Node(NamedTuple): val: Any left: 'Node' = None right: 'Node' = None
例:
>>> 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)
我觉得这个版本更容易阅读:
from collections import namedtuple def my_tuple(**kwargs): defaults = { 'a': 2.0, 'b': True, 'c': "hello", } default_tuple = namedtuple('MY_TUPLE', ' '.join(defaults.keys()))(*defaults.values()) return default_tuple._replace(**kwargs)
这不像它需要两次创build对象那么高效,但是你可以通过在模块中定义默认的双工来改变它,并且只是让函数做replace行。
这是一个不太灵活,但更简洁的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)
使用Advanced Enum (aenum)
库中的NamedTuple
类并使用class
语法,这非常简单:
from aenum import NamedTuple class Node(NamedTuple): val = 0 left = 1, 'previous Node', None right = 2, 'next Node', None
一个潜在的缺点是对于具有默认值的任何属性的__doc__
string的要求(对于简单属性是可选的)。 在使用中,它看起来像:
>>> Node() Traceback (most recent call last): ... TypeError: values not provided for field(s): val >>> Node(3) Node(val=3, left=None, right=None)
这比justinfay's answer
有以下好处:
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)
是简单的,以及基于metaclass
而不是基于exec
。
如果你永远不能满足这些variables(他们永远都是 None
),那么你就可以做到
class Node(object): def __init__(self, val): self.val = val self.left = None self.right = None
并保持简单。
您可以将NodeVariable = Node(self, val, right, left)
放在一个try
语句中,但是我觉得这不是您想要做的事情。
另一个scheme
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)