Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mutable default argument for a Python namedtuple

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?

like image 477
ChaimG Avatar asked Dec 08 '15 04:12

ChaimG


People also ask

What is a mutable default argument in Python?

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.

Is Namedtuple mutable in Python?

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.

Can Namedtuples have mutable fields?

[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 .]

Are custom types using Recordclass mutable?

recordclass is a mutable version of collection. namedtuple that inherits it's api, memory footprint, but support assignments.


3 Answers

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)
like image 165
BrenBarn Avatar answered Oct 17 '22 05:10

BrenBarn


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
like image 42
Rick supports Monica Avatar answered Oct 17 '22 07:10

Rick supports Monica


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.

like image 39
Kyle Parsons Avatar answered Oct 17 '22 07:10

Kyle Parsons