我试图将一个较长的中空“数据”类转换为命名元组。我的类目前看起来是这样的:
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)的出现,但这不是我喜欢的。
那么,是否存在一个好技巧,可以让我的重写成功,而不增加大量的代码复杂性(元编程),或者我应该吞下药丸,继续“搜索和替换”?:)
下面是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)
使用我的高级Enum (aenum)库中的NamedTuple类,并使用类语法,这是相当简单的:
from aenum import NamedTuple
class Node(NamedTuple):
val = 0
left = 1, 'previous Node', None
right = 2, 'next Node', None
一个潜在的缺点是,任何具有默认值的属性都需要__doc__字符串(对于简单属性是可选的)。在实际使用中是这样的:
>>> Node()
Traceback (most recent call last):
...
TypeError: values not provided for field(s): val
>>> Node(3)
Node(val=3, left=None, right=None)
这比justinfay的答案更有优势:
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)
是简单,以及是基于元类而不是基于exec。
下面是一个简短、简单的通用答案,对于带默认参数的命名元组,它有一个很好的语法:
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)