Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Named tuple and default values for optional keyword arguments

I'm trying to convert a longish hollow "data" class into a named tuple. My class currently looks like this:

class Node(object):     def __init__(self, val, left=None, right=None):         self.val = val         self.left = left         self.right = right 

After conversion to namedtuple it looks like:

from collections import namedtuple Node = namedtuple('Node', 'val left right') 

But there is a problem here. My original class allowed me to pass in just a value and took care of the default by using default values for the named/keyword arguments. Something like:

class BinaryTree(object):     def __init__(self, val):         self.root = Node(val) 

But this doesn't work in the case of my refactored named tuple since it expects me to pass all the fields. I can of course replace the occurrences of Node(val) to Node(val, None, None) but it isn't to my liking.

So does there exist a good trick which can make my re-write successful without adding a lot of code complexity (metaprogramming) or should I just swallow the pill and go ahead with the "search and replace"? :)

like image 422
sasuke Avatar asked Jul 05 '12 19:07

sasuke


1 Answers

Python 3.7

Use the defaults parameter.

>>> from collections import namedtuple >>> fields = ('val', 'left', 'right') >>> Node = namedtuple('Node', fields, defaults=(None,) * len(fields)) >>> Node() Node(val=None, left=None, right=None) 

Or better yet, use the new dataclasses library, which is much nicer than namedtuple.

>>> from dataclasses import dataclass >>> from typing import Any >>> @dataclass ... class Node: ...     val: Any = None ...     left: 'Node' = None ...     right: 'Node' = None >>> Node() Node(val=None, left=None, right=None) 

Before Python 3.7

Set Node.__new__.__defaults__ to the default values.

>>> 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) 

Before Python 2.6

Set Node.__new__.func_defaults to the default values.

>>> 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) 

Order

In all versions of Python, if you set fewer default values than exist in the namedtuple, the defaults are applied to the rightmost parameters. This allows you to keep some arguments as required arguments.

>>> 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) 

Wrapper for Python 2.6 to 3.6

Here's a wrapper for you, which even lets you (optionally) set the default values to something other than None. This does not support required arguments.

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 

Example:

>>> 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) 
like image 172
Mark Lodato Avatar answered Sep 16 '22 11:09

Mark Lodato