python可选参数和可变量参数_关于python:namedtuple和可选关键字参数的默认值

我正在尝试将一个longish中空的"data"类转换为一个命名的元组。我的班级目前如下:

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),但我不喜欢。

那么,有没有一个好的技巧可以使我的重写成功而不增加很多代码复杂性(元编程),或者我应该吞下药丸继续"搜索和替换"?:)

为什么要进行此转换?我喜欢你原来的Node课程。为什么要转换为命名元组?

我之所以要进行这种转换,是因为当前的Node和其他类都是简单的数据持有者值对象,具有一系列不同的字段(Node只是其中之一)。这些类声明只不过是行噪声imho,因此希望将它们删掉。为什么要维护一些不需要的东西?:)

您的类中根本没有任何方法函数?例如,您没有一个.debug_print()方法,可以在树上行走并打印它?

当然可以,但那是为江户一〔4〕班准备的。Node和其他数据持有者不需要这种特殊的方法,特别是当命名的元组具有一个合适的__str__和__repr__表示时。:)

好吧,看起来很合理。我认为IgnacioVazquezAbrams已经给出了答案:使用一个为节点提供默认值的函数。

类的定义是清晰的,并且完全符合读者的期望。这里的许多答案都很复杂,并且有令人惊讶的副作用。是的,node是一个数据持有者类,但是在幕后,name-duple也是这样!

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)

让我们看看……你的一句话:a)是最短/最简单的答案,b)保持空间效率,c)不打破isinstance……所有的优点,没有缺点……太糟糕了,你来派对有点晚了。这是最好的答案。

包装版本有一个问题:与builtin collections.namedtuple不同,如果def()包含在其他模块中,则此版本不可pickle/multiprocess序列化。

我对这个答案投了赞成票,因为它比我自己的更可取。但遗憾的是,我自己的回答总是被否决:|

你怎么能只让left和right可选?val不应是可选的!

好主意!我更新了答案来解释。

你应该提到我的名字@robert…-P

是否有一种方法可以在类子类中设置名称为duple的默认值,或者@justinfay的答案是唯一的方法?

@用户3467349,如果您是namedtuple的子类,请使用@justinfay的方法。你也许可以和__defaults__混在一起,但你为什么要这样做?

有时,你的元组中会有很长的参数列表——你的方法要简洁得多。不过,这只是一种风格偏好。

很好的回答!小诡辩:你不需要在None, None, None周围加括号。

@Marklodato我想插句话,我认为这是解决OP问题的最佳方案。但是,它似乎不适用于最后一个可选参数,即,如果设置Node.__new__.__defaults__ = (None),那么您似乎必须始终指定所有三个参数,似乎只有在您有两个或更多可选参数时才有效。

@ishaaq,问题是(None)不是元组,而是None。如果你用(None,)代替,它会很好地工作。

杰出的!您可以使用:Node.__new__.__defaults__= (None,) * len(Node._fields)概括默认设置。

我们可以在不使用可变默认gotcha的情况下,将单行版本与[]一起使用吗?

@僵尸-好主意!我相应地更新了答案。

@chaimg:这个代码不受可变默认gotcha的影响,但是我把default_values的默认值改成了tuple,使它更清楚地表明它不能被更改。

请不要编写分配给.__defaults__的代码。这是晦涩难懂的。

您可以在tuple(prototype)之后通过[:len(default_values)]支持所需的参数。

我把名字分为两个子类,然后超越了__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)

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

为了保持命名元组的空间效率,这可能需要插槽和字段属性。

由于某种原因,当使用_replace时,不会调用__new__。

请看一下@marc lodato的答案,下面的imho比这个更好。

但是@marc lodato的答案并不能使子类具有不同的默认值。

@Pepijn,你能解释一下这里的空间效率有什么问题吗?

@Jasons,我怀疑对于具有不同默认值的子类,可能会违反LSP。然而,一个子类可能非常希望有更多的默认值。在任何情况下,子类都可以使用justinfay的方法,而基类可以使用marc的方法。

@alexey——默认的构造函数值与lsp无关,它们用于为构造函数提供特定的实例信息。让我们在现实生活中使用"IS-A"测试:如果我说,给我买一辆车,如果我没有另外指定,安全默认假设是它将有4个轮子,4个门,一个后备箱,和一个汽油发动机。但是有更多的特定的汽车或类型的汽车,有3个轮子,或2个门,或一个掀背车,或一个带电池的电动发动机。

用函数包装它。

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

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

return NodeT(val, left, right)

这很聪明,是一个很好的选择,但也可能通过破坏isinstance(Node('val'), Node)而引起问题:它现在会引发异常,而不是返回真值。虽然有点冗长,@justinfay的答案(下面)正确地保留了类型层次结构信息,如果其他人要与节点实例交互,那么这可能是更好的方法。

我喜欢这个答案的简洁。也许可以通过命名函数def make_node(...):而不是假装它是类定义来解决上述注释中的问题。这样,用户就不想检查函数的类型多态性,而是使用元组定义本身。

请参阅我的答案,了解这方面的变化,它不会误导人们错误地使用isinstance。

使用python 3.6.1+中的typing.NamedTuple,您可以为NamedDuple字段提供默认值和类型注释。如果您只需要前者,请使用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)。cases替换namedtuples。旁注:在python中,注释(参数和变量的:后面的表达式和函数的->后面的表达式)的当前规范的一个奇怪之处是,它们是在定义时*进行计算的。因此,由于"一旦类的整个主体被执行,类名称就被定义了",因此上面的类字段中的EDOCX1[4]的注释必须是字符串,以避免名称错误。

这种类型提示称为"转发引用"([1],[2]),使用PEP 563,python 3.7+将有一个__future__导入(默认情况下在4.0中启用),允许使用不带引号的转发引用,从而推迟其评估。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值