Reputation: 7514
I came across a neat way of having namedtuples use default arguments from here.
from collections import namedtuple
Node = namedtuple('Node', 'val left right')
Node.__new__.__defaults__ = (None, None, None)
Node()
Node(val=None, left=None, right=None)
What would you do if you would want the default value for 'right' to be a empty list? As you may know, using a mutable default argument such as a list is a no no.
Is there a simple way to implement this?
Upvotes: 7
Views: 5636
Reputation: 1525
Since this question has been asked, the dataclasses
module has been proposed and accepted into Python. This module has a lot of overlapping use cases with namedtuples
but with some more flexibility and power. In particular, you can specify a factory function for when you want to specify a default for a mutable field.
from typing import List
from dataclasses import dataclass, field
@dataclass
class Node:
val: str
left: List["Node"] = field(default_factory=list)
right: List["Node"] = field(default_factory=list)
In a data class you specify the types of the various fields, so in this case I had to fill in a few blanks and assume that val
would be a string and that left
and right
would both be lists of other Node
objects.
Since right
and left
are the left hand side of an assignment in the class definition, they are optional arguments when we initialize a Node
object. Further, we could supply a default value, but instead we supplied a default factory, which is a function that is called with 0 arguments whenever we initialize a Node
object without specifying those fields.
For example:
node_1 = Node('foo')
# Node(val='foo', left=[], right=[])
node_2 = Node('bar', left=[node_1])
# Node(val='bar', left=[Node(val='foo', left=[], right=[])], right=[])
node_3 = Node('baz')
# Node(val='baz', left=[], right=[])
node_4 = Node('quux', left=[node_2], right=[node_3])
# Node(val='quux', left=[Node(val='bar', left=[Node(val='foo', left=[], right=[])], right=[])], right=[Node(val='baz', left=[], right=[])])
Personally I find myself reaching for dataclasses
over namedtuples
for any application where I need more than just the thinnest container for data.
Upvotes: 3
Reputation: 331
Just small change in implementation from Rick Teachey, the default value can be set outside class:
NodeBase = namedtuple('NodeBase', 'val left right')
class Node(NodeBase):
__slots__ = ()
def __new__(cls, *, right=[], **kwargs):
obj = super().__new__(cls, right=right, **kwargs)
return obj
#IMPLEMENTATION
kw = {'val': 1, 'left':12}
m = Node(**kw)
# outputs Node(val=1, left=12, right=[])
Upvotes: -1
Reputation: 45231
The way given in the accepted answer works great. The only downside I see is that one has to both know (in the case of some other user) and remember to use the factory function instead of the named tuple class- both when creating the object, and when doing things like this:
isinstance(node, Node) # success
isinstance(node, makeNode) # misery
A way around this problem might be to do something like what is shown below.
NodeBase = nt('NodeBase', 'val left right')
NodeBase.__new__.__defaults__ = (None, None, None)
class Node(NodeBase):
'''A namedtuple defined as:
Node(val, left, right)
with default values of (None, None, [])'''
__slots__ = ()
def __new__(cls, *args, **kwargs):
obj = super().__new__(cls, *args, **kwargs)
if obj.right is None:
obj = obj._replace(right = [])
return obj
Upvotes: 3
Reputation: 251373
You can't do that that way, because the values in __defaults__
are the actual default values. That is, if you wrote a function that did had someargument=None
, and then checked inside the function body with someargument = [] if someargument is None else someargument
or the like, the corresponding __defaults__
entry would still be None. In other words, you can do that with a function because in a function you can write code to do whatever you want, but you can't write custom code inside a namedtuple.
But if you want default values, just make a function that has that logic and then creates the right namedtuple:
def makeNode(val=None, left=None, right=None):
if right is None:
val = []
return Node(val, left, right)
Upvotes: 6