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?
In Python, when passing a mutable value as a default argument in a function, the default argument is mutated anytime that value is mutated. Here, "mutable value" refers to anything such as a list, a dictionnary or even a class instance.
Named Tuple Its defining feature is being immutable. An immutable object is an object whose state cannot be modified after it is created. In Python, immutable types are int, float, bool, str, tuple and unicode.
[The reason is that namedtuples in Python are "immutable", meaning you can't poke a new field value directly into an existing namedtuple. Strings in Python are also immutable, but lists are mutable, so you can say L[3] = 17 .]
recordclass is a mutable version of collection. namedtuple that inherits it's api, memory footprint, but support assignments.
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)
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
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 named tuple 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With