python - namedtuple和可选关键字参数的默认值
我正在尝试将一个冗长的空洞“数据”类转换为一个命名元组。 我的班级目前看起来像这样:
class Node(object):
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
转换为Node(val)之后,它看起来像:
from collections import namedtuple
Node = namedtuple('Node', 'val left right')
但这里有一个问题。 我的原始类允许我传入一个值,并使用命名/关键字参数的默认值来处理默认值。 就像是:
class BinaryTree(object):
def __init__(self, val):
self.root = Node(val)
但是这对我重构的名为元组的情况不起作用,因为它希望我传递所有字段。 我当然可以将Node(val)的出现更换为Node(val, None, None),但这不是我喜欢的。
那么有没有一个好的技巧可以让我的重写成功而不会增加很多代码复杂性(元编程)或者我应该吞下药丸并继续“搜索和替换”?:)
21个解决方案
354 votes
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)
在Python 3.7之前
将None设置为默认值。
>>> 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)
在Python 2.6之前
将None设置为默认值。
>>> 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)
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
例:
>>> 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)
Mark Lodato answered 2019-03-13T03:27:08Z
133 votes
我子类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)
这保留了一种直观的类型层次结构,即伪造成类的工厂函数的创建不会。
justinfay answered 2019-03-13T03:27:41Z
87 votes
将它包装在一个函数中。
NodeT = namedtuple('Node', 'val left right')
def Node(val, left=None, right=None):
return NodeT(val, left, right)
Ignacio Vazquez-Abrams answered 2019-03-13T03:28:08Z
44 votes
使用Python 3.6.1+中的__future__,您可以为NamedTuple字段提供默认值和类型注释。 如果您只需要前者,请使用__future__:
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)
此外,如果您需要默认值和可选的可变性,Python 3.7将具有数据类(PEP 557),可以在某些(许多?)情况下替换namedtuples。旁注:Python中注释的当前规范的一个怪癖(参数和变量之后的表达式__future__之后以及函数之后的->之后的表达式)是它们在定义时*进行评估。 因此,由于“类名称在类的整个主体被执行后定义”,因此上面类字段中的'Node'的注释必须是字符串以避免NameError。
这种类型的提示被称为“前向引用”([1],[2]),而PEP 563 Python 3.7+将具有__future__导入(默认情况下在4.0中启用),允许使用前向 没有引号的引用,推迟了他们的评价。
* AFAICT仅在运行时不评估局部变量注释。
monk-time answered 2019-03-13T03:29:07Z
19 votes
我不确定是否有一个简单的方法只有内置的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)
jterrace answered 2019-03-13T03:29:34Z
19 votes
这是一个直接来自文档的示例:
可以使用_replace()来自定义a来实现默认值 原型实例:
>>> Account = namedtuple('Account', 'owner balance transaction_count')
>>> default_account = Account('', 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")
但是,我更喜欢这里给出的其他一些答案。 我只是想补充一下这个完整性。
Tim Tisdall answered 2019-03-13T03:30:30Z
12 votes
这是一个更紧凑的版本,灵感来自justinfay的答案:
from collections import namedtuple
from functools import partial
Node = namedtuple('Node', ('val left right'))
Node.__new__ = partial(Node.__new__, left=None, right=None)
Gustav Larsson answered 2019-03-13T03:30:55Z
8 votes
在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)
Anthony Sottile answered 2019-03-13T03:31:34Z
5 votes
使用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)
Dennis Golomazov answered 2019-03-13T03:32:03Z
4 votes
你也可以用这个:
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)
这基本上使您可以构造具有默认值的任何命名元组,并仅覆盖您需要的参数,例如:
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)
acerisara answered 2019-03-13T03:32:35Z
4 votes
结合@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)
这应该支持使用位置参数和混合大小写创建元组。测试用例:
>>> 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'
teodor answered 2019-03-13T03:33:17Z
4 votes
简短,简单,并且不会导致人们不正确地使用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))
Elliot Cameron answered 2019-03-13T03:33:47Z
4 votes
Python 3.7:在namedtuple定义中引入defaults param。
示例如文档中所示:
>>> Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
>>> Account._fields_defaults
{'balance': 0}
>>> Account('premium')
Account(type='premium', balance=0)
在这里阅读更多。
Julian Camilleri answered 2019-03-13T03:34:27Z
3 votes
我发现这个版本更容易阅读:
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)
这不是需要两次创建对象的效率,但是你可以通过在模块中定义默认的duple并让函数执行替换行来改变它。
Dave31415 answered 2019-03-13T03:35:01Z
2 votes
由于您使用namedtuple作为数据类,您应该知道python 3.7将为此目的引入namedtuple装饰器 - 当然它具有默认值。
来自文档的一个例子:
@dataclass
class C:
a: int # 'a' has no default value
b: int = 0 # assign a default value for 'b'
比黑客namedtuple更清洁,可读和可用。不难预测namedtuples的使用将随着3.7的采用而下降。
P-Gn answered 2019-03-13T03:35:38Z
1 votes
受这个对不同问题的回答的启发,这是我提出的基于元类的解决方案,并使用super(正确处理未来的子计算)。 这与justinfay的回答非常相似。
from collections import namedtuple
NodeTuple = namedtuple("NodeTuple", ("val", "left", "right"))
class NodeMeta(type):
def __call__(cls, val, left=None, right=None):
return super(NodeMeta, cls).__call__(val, left, right)
class Node(NodeTuple, metaclass=NodeMeta):
__slots__ = ()
然后:
>>> Node(1, Node(2, Node(4)),(Node(3, None, Node(5))))
Node(val=1, left=Node(val=2, left=Node(val=4, left=None, right=None), right=None), right=Node(val=3, left=None, right=Node(val=5, left=None, right=None)))
Alexey answered 2019-03-13T03:36:07Z
0 votes
使用我的metaclass库中的metaclass类,并使用exec语法,这非常简单:
from aenum import NamedTuple
class Node(NamedTuple):
val = 0
left = 1, 'previous Node', None
right = 2, 'next Node', None
一个潜在的缺点是对于具有默认值的任何属性需要metaclass字符串(对于简单属性,它是可选的)。 在使用中它看起来像:
>>> Node()
Traceback (most recent call last):
...
TypeError: values not provided for field(s): val
>>> Node(3)
Node(val=3, left=None, right=None)
这个优势超过了metaclass:
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。
Ethan Furman answered 2019-03-13T03:36:56Z
0 votes
另一种方案:
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)
sirex answered 2019-03-13T03:37:21Z
0 votes
这是一个简短的通用答案,带有一个带有默认参数的命名元组的良好语法:
import collections
def dnamedtuple(typename, field_names, **defaults):
fields = sorted(field_names.split(), key=lambda x: x in defaults)
T = collections.namedtuple(typename, ' '.join(fields))
T.__new__.__defaults__ = tuple(defaults[field] for field in fields[-len(defaults):])
return T
用法:
Test = dnamedtuple('Test', 'one two three', two=2)
Test(1, 3) # Test(one=1, three=3, two=2)
精缩:
def dnamedtuple(tp, fs, **df):
fs = sorted(fs.split(), key=df.__contains__)
T = collections.namedtuple(tp, ' '.join(fs))
T.__new__.__defaults__ = tuple(df[i] for i in fs[-len(df):])
return T
Matthew D. Scholefield answered 2019-03-13T03:37:57Z
0 votes
jterrace使用recordtype的答案很棒,但是该库的作者建议使用他的namedlist项目,它提供了mutable(namedlist)和immutable(namedtuple)实现。
from namedlist import namedtuple
>>> Node = namedtuple('Node', ['val', ('left', None), ('right', None)])
>>> Node(3)
Node(val=3, left=None, right=None)
>>> Node(3, 'L')
Node(val=3, left=L, right=None)
nbarraille answered 2019-03-13T03:38:27Z
-1 votes
这是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)
Li-Wen Yip answered 2019-03-13T03:38:57Z