我试图将一个较长的中空“数据”类转换为命名元组。我的类目前看起来是这样的:
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)
但这在重构的命名tuple中不起作用,因为它期望我传递所有字段。我当然可以替换Node(val)到Node(val, None, None)的出现,但这不是我喜欢的。
那么,是否存在一个好技巧,可以让我的重写成功,而不增加大量的代码复杂性(元编程),或者我应该吞下药丸,继续“搜索和替换”?:)
下面是一个简短、简单的通用答案,对于带默认参数的命名元组,它有一个很好的语法:
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
另一个解决方案:
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)
在python3.7+中,有一个全新的defaults= keyword参数。
defaults可以是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)
下面是一个简短、简单的通用答案,对于带默认参数的命名元组,它有一个很好的语法:
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
在打字。在Python 3.6.1+中,您可以为NamedTuple字段提供默认值和类型注释。使用打字。如果你只需要前者:
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)
Also, in case you need both default values and optional mutability, Python 3.7 is going to have data classes (PEP 557) that can in some (many?) cases replace namedtuples.
Sidenote: one quirk of the current specification of annotations (expressions after : for parameters and variables and after -> for functions) in Python is that they are evaluated at definition time*. So, since "class names become defined once the entire body of the class has been executed", the annotations for 'Node' in the class fields above must be strings to avoid NameError.
这种类型提示被称为“前向引用”([1],[2]),在PEP 563中,Python 3.7+将有__future__导入(在4.0中默认启用),允许使用不带引号的前向引用,推迟它们的计算。
* AFAICT只有局部变量注释不计算在运行时。(来源:PEP 526)