可选关键字参数namedtuple和默认值可选关键字参数namedtuple和默认值(namedtu

2019-05-12 15:49发布

我想一个稍长的空心“数据”类转换到一个名为元组。 我班目前看起来是这样的:

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)Node(val, None, None)但它不是我的胃口。

所以确实存在着一个很好的技巧,它可以使我重新写成功无需添加大量的代码复杂度(元编程),或者我应该只吞下药丸,并与“查找和替换”继续前进? :)

Answer 1:

Python的3.7

使用默认参数。

>>> 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之前

设置Node.__new__.__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)

Python 2.6中前

设置Node.__new__.func_defaults为默认值。

>>> 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)


Answer 2:

我子类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)

这保留了一个直观的类型层次结构,其中一个工厂函数创建伪装成一个类没有。



Answer 3:

它包装在一个函数。

NodeT = namedtuple('Node', 'val left right')

def Node(val, left=None, right=None):
  return NodeT(val, left, right)


Answer 4:

随着typing.NamedTuple在Python 3.6.1+,你可以同时提供一个默认值和类型标注为NamedTuple场。 使用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)

此外,如果您需要同时默认值和可选的可变性,Python的3.7将有数据类(PEP 557)是可以在某些(多少?)的情况下更换namedtuples。


旁注:当前说明书的一个夸克注释 (表达式后:对参数和变量和后->为函数)在Python是,它们在定义时间*评价。 所以,因为“一旦类的整个身体已经被执行的类名成为定义”,对于标注'Node'类中的字段以上必须是字符串,以避免NameError。

这种类型的提示被称为(“正向参考” [1] , [2] ),并与PEP 563的Python 3.7+都将有一个__future__进口(以被默认启用4.0),将允许使用正不带引号引用,推迟他们的评价。

* AFAICT只有局部变量注释不能在运行时进行评估。 (来源: PEP 526 )



Answer 5:

我不知道是否有只内置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)


Answer 6:

这是直接从文档的例子 :

默认值可以通过使用_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")

不过,我很喜欢这里的一些更好的给其他的答案。 我只是想补充这的完整性。



Answer 7:

下面是justinfay的回答激发了更紧凑的版本:

from collections import namedtuple
from functools import partial

Node = namedtuple('Node', ('val left right'))
Node.__new__ = partial(Node.__new__, left=None, right=None)


Answer 8:

在python3.7 +有一个全新的默认值=关键字参数。

默认值可以是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)


Answer 9:

短,操作简单,并不会导致人们使用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))


Answer 10:

一个扩展的例子来初始化所有丢失参数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)


Answer 11:

您也可以使用此:

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)


Answer 12:

结合@Denis和@马克的方法:

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)

而且还支持类型错误:

>>> Node(1, left=2)
TypeError: __new__() got multiple values for keyword argument 'left'


Answer 13:

Python的3.7:引进的defaults PARAM在namedtuple定义。

如文档中示出的示例:

>>> Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
>>> Account._fields_defaults
{'balance': 0}
>>> Account('premium')
Account(type='premium', balance=0)

阅读更多这里 。



Answer 14:

我觉得这个版本更容易阅读:

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)

因为它需要的对象的创建两次,但你可以通过定义模块内部默认的二倍,只是其功能也替换线改变,这是效率不高。



Answer 15:

由于您使用namedtuple作为数据类,你应该知道,蟒蛇3.7将引入@dataclass装饰为这个目的-当然它有默认值。

从文档的例子 :

@dataclass
class C:
    a: int       # 'a' has no default value
    b: int = 0   # assign a default value for 'b'

更清洁,可读和比黑客可用namedtuple 。 这是不难预料的是使用namedtuple s将被采用的3.7下降。



Answer 16:

灵感来自这个答案到一个不同的问题,在这里是基于我提出的解决方案元类 ,并使用super (正确地处理未来subcalssing)。 这是很相似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)))


Answer 17:

通过jterrace答案使用RECORDTYPE是伟大的,但库的作者建议使用他namedlist项目,它提供两个可变( namedlist )和不可变( 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)


Answer 18:

使用NamedTuple从我的类Advanced Enum (aenum)库,并使用class的语法,这是很简单的:

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'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为主。



Answer 19:

另一种解决方案:

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)


Answer 20:

这里是一个不错的语法与缺省参数命名元组短的,简单通用的答案:

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


Answer 21:

这里是马克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)


文章来源: namedtuple and default values for optional keyword arguments