我试图将一个较长的中空“数据”类转换为命名元组。我的类目前看起来是这样的:
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)的出现,但这不是我喜欢的。
那么,是否存在一个好技巧,可以让我的重写成功,而不增加大量的代码复杂性(元编程),或者我应该吞下药丸,继续“搜索和替换”?:)
在打字。在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)
另一个解决方案:
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)
1. 使用NamedTuple >= Python 3.6
从Python 3.7+开始,您可以从支持默认值的typing模块中使用NamedTuple。
https://docs.python.org/3/library/typing.html#typing.NamedTuple
from typing import NamedTuple
class Employee(NamedTuple):
name: str
id: int = 3
employee = Employee('Guido')
assert employee.id == 3
注意:虽然NamedTuple在类语句中作为超类出现,但它实际上不是。打字。NamedTuple使用元类的高级功能来自定义用户类的创建。
issubclass(Employee, typing.NamedTuple)
# return False
issubclass(Employee, tuple)
# return True
2. 使用数据类>= Python 3.7
from dataclasses import dataclass
@dataclass(frozen=True)
class Employee:
name: str
id: int = 3
employee = Employee('Guido')
assert employee.id == 3
frozen=True使数据类不可变。
在打字。在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)
结合@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'
在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)