Explain Codes LogoExplain Codes Logo

Named tuple and default values for optional keyword arguments

python
namedtuple
default-values
type-annotations
Nikita BarsukovbyNikita Barsukov·Oct 9, 2024
TLDR

Equip your NamedTuple with default values utilizing the collections module and dataclasses available from Python 3.7+. Here's an example:

from typing import NamedTuple from dataclasses import dataclass, field @dataclass class Point(NamedTuple): x: int = field(default=0) # Initialize in the 'flat land' (x=0)! y: int = field(default=0) # Initialize at 'ground level' (y=0)! # Defaults: Point(x=0, y=0) # Specific values: Point(x=5, y=10)

This approach elegantly bundles default values with named tuple fields, boosting code clarity and expressiveness.

Dealing with defaults in lower Python versions

Setting internal defaults in ancient Python versions

If Python 3.7 still feels too futuristic and you're using earlier versions, setting defaults can feel a bit like a treasure hunt:

from collections import namedtuple Node = namedtuple('Node', 'value left right') # Time to set those default values, treasure hunters! Node.__new__.__defaults__ = (None, None, None) # Applicable in Python 3.6

But worry not! For the Indiana Jones of coding using Python pre-3.6, replace the __defaults__ method with func_defaults:

Node.__new__.func_defaults = (None,) * len(Node._fields) # Our archaeological artifact!

Overriding the old __new__ method

Distaste for traditional methods? Just subclass a NamedTuple and override the __new__ to set your own default parameters:

class Node(namedtuple('NodeBase', 'value, left, right')): def __new__(cls, value, left=None, right=None): # Override with defaults return super(Node, cls).__new__(cls, value, left, right)

Prototypes: a blueprint to success

Embrace your inner architect! Create a prototype instance and use the _replace method for setting defaults:

prototype_node = Node(None, None, None) # A Node bluepring! # Now, let's construct a new building (or just a Node instance)! new_node = prototype_node._replace(value='value')

Typing away with type annotations and defaults

Python 3.6.1+: a lifesaver for typing

Python 3.6.1+ improves our typing situation with typing.NamedTuple, supporting type annotations and defaults in a cleaner and more elegant way:

from typing import NamedTuple class Point(NamedTuple): # We're investing in proper typing! x: int = 0 y: int = 0

Data classes: the new kids on the block

Data classes might be newcomers in Python 3.7, but they already rock as a solid alternative to namedtuples for complex structures:

from dataclasses import dataclass @dataclass class Point: # Fresh, clean, and extremely useful! x: int = 0 y: int = 0

Advanced maneuvers and avoiding pitfalls

Implementing slots: 'Don't touch my variables!'

If you're worried about dynamic creation of instance dictionaries (it wrecks havoc on memory!), implement __slots__ in a namedtuple subclass:

class SlotNode(NamedTuple): __slots__ = () # 'No touchy' policy value: int left: 'SlotNode' = None right: 'SlotNode' = None

The future is now: Forward references

In Python 3.7+, forward references in type annotations work magic without quotation marks. All you need is a little help from a __future__ import:

from __future__ import annotations class Tree(NamedTuple): left: Tree | None = None # No fortune teller needed right: Tree | None = None

Saving the day with wrapper functions

A wrapper function for setting defaults can handle both positional and mapping default values. Just like a superhero saving the day!

def namedtuple_with_defaults(typename, field_names, default_values=()): T = namedtuple(typename, field_names) T.__new__.__defaults__ = (None,) * len(T._fields) # Whew! Saved again if isinstance(default_values, dict): prototype = T(**default_values) else: prototype = T(*default_values) T.__new__.__defaults__ = prototype return T