393

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"? :)

Roshin Raphel
  • 2,612
  • 4
  • 22
  • 40
sasuke
  • 6,589
  • 5
  • 36
  • 35
  • 3
    Why do you want to make this conversion? I like your original `Node` class just the way it is. Why convert to named tuple? – steveha Jul 05 '12 at 19:29
  • 43
    I wanted to make this conversion because the current `Node` and other classes are simple data-holder value objects with a bunch of different fields (`Node` is just one of them). These class declarations are nothing much more than line noise IMHO hence wanted to trim them out. Why maintain something which isn't required? :) – sasuke Jul 05 '12 at 19:33
  • You don't have any method functions on your classes at all? You don't, for example, have a `.debug_print()` method that walks the tree and prints it? – steveha Jul 05 '12 at 20:09
  • 3
    Sure I do, but that's for the `BinaryTree` class. `Node` and other data holders don't require such special methods esp given that named tuples have a decent `__str__` and `__repr__` representation. :) – sasuke Jul 05 '12 at 21:11
  • Okay, seems reasonable. And I think Ignacio Vazquez-Abrams has given you the answer: use a function that does the default values for your node. – steveha Jul 06 '12 at 00:27
  • The class definition is clear and does exactly what readers would expect. Many of the answers here are complex and have surprising side effects. Yes, Node is a data-holder class, but behind the scenes, so is namedtuple! – Lorenz Forvang Sep 21 '17 at 23:36

23 Answers23

710

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)
Mark Lodato
  • 50,015
  • 5
  • 41
  • 32
  • 28
    Let's see...your one-liner: a) is the shortest/simplest answer, b) preserves space efficiency, c) doesn't break `isinstance` ...all pros, no cons...too bad you were a little late to the party. This is the best answer. – Gerrat May 02 '14 at 13:39
  • 1
    One problem with the wrapper version: unlike the builtin collections.namedtuple, this version is not pickleable/multiprocess serializable if the def() is included in a different module. – Michael Scott Asato Cuthbert Jun 22 '14 at 11:03
  • 3
    I have given this answer an upvote as it is preferable to my own. Its a pity however that my own answer keeps getting upvoted :| – Justin Fay Mar 16 '15 at 21:51
  • 1
    How can you make only `left` and `right` optional? `val` should not be optional! – Robert Siemer Apr 23 '15 at 14:44
  • You should have mentioned my name @Robert... :-P – Robert Siemer Apr 27 '15 at 04:50
  • Is there a way to set defaults in a Class subclassing namedtuple or is @justinfay 's answer the only way ? – user3467349 May 01 '15 at 21:10
  • @user3467349, if you're subclassing from `namedtuple`, use @justinfay's method. You might be able to muck around with `__defaults__` but why would you want to? – Mark Lodato May 02 '15 at 04:16
  • Sometimes you have tuples with long argument lists -- your approach is a lot more succinct. It's only a stylistic preference though. – user3467349 May 02 '15 at 04:51
  • Nice answer! Minor quibble: you don't need the parentheses around `None, None, None`. – 1'' Jul 31 '15 at 19:33
  • 1
    @MarkLodato I would like to chime in and say I think this is the best solution to the OP's question.However it doesn't seem to work with just the last parameter as optional, i.e. if you set `Node.__new__.__defaults__ = (None)` then you seem to have to always specify all three args, it seems to only work if you have two or more optional args. – ishaaq Aug 07 '15 at 05:00
  • 4
    @ishaaq, the problem is that `(None)` is not a tuple, it's `None`. If you use `(None,)` instead, it should work fine. – Mark Lodato Aug 07 '15 at 18:34
  • 4
    Excellent! You can generalize the setting-of-defaults with: ``Node.__new__.__defaults__= (None,) * len(Node._fields)`` – ankostis Aug 31 '15 at 09:37
  • Can we use the one liner version with `[]` without running into the [mutable default gotcha](https://www.google.com/search?q=python+mutable+default+argument)? – ChaimG Dec 08 '15 at 04:02
  • @ankostis - Good idea! I updated the answer accordingly. – Mark Lodato Dec 09 '15 at 20:28
  • @ChaimG: This code is not affected by the mutable default gotcha, but I changed the default value of `default_values` to be a tuple to make it more clear that it cannot be changed. – Mark Lodato Dec 09 '15 at 20:30
  • 1
    Please **never write code that assigns to `.__defaults__`**. That is obscure and unintelligible. – gps Jun 23 '17 at 20:07
  • You can support required arguments via `[:len(default_values)]` after `tuple(prototype)`. – Matthew D. Scholefield Jul 17 '18 at 00:46
  • using this with ([],) or (list(),) instead of (None,) has the problem that all attributes receive the same list, amazingly. :o Doing Node.left.append('item') adds the item to all three lists. – K.-Michael Aye May 20 '19 at 19:42
  • 1
    Using `dataclass` this way is not strictly better than using `namedtuple` because the later produces immutable instances. On the other hand you could use `@dataclass(frozen=True)` together with `dataclasses.replace` to modify your instances. That also seems a bit nicer than using the `._replace` method on a `namedtuple`. – cglacet Oct 14 '20 at 09:26
  • Thanks for the suggestion of using dataclasses, I find this very useful – nixon Dec 14 '21 at 00:52
156

I subclassed namedtuple and overrode the __new__ method:

from collections import namedtuple

class Node(namedtuple('Node', ['value', 'left', 'right'])):
    __slots__ = ()
    def __new__(cls, value, left=None, right=None):
        return super(Node, cls).__new__(cls, value, left, right)

This preserves an intuitive type hierarchy, which the creation of a factory function disguised as a class does not.

pppery
  • 3,731
  • 22
  • 33
  • 46
Justin Fay
  • 2,576
  • 1
  • 20
  • 27
  • 9
    This might need slots and fields properties in order to maintain the space efficiency of a named tuple. – Pepijn Apr 07 '14 at 09:16
  • For some reason, `__new__` is not being called when `_replace` is used. –  Sep 09 '14 at 09:49
  • 1
    Please take a look at @marc-lodato answer below which IMHO is a better solution than this. – Justin Fay Feb 26 '15 at 12:53
  • 2
    but @marc-lodato's answer doesn't provide the ability for a subclass to have different defaults – Jason S May 04 '17 at 23:23
  • @Pepijn, could you please explain what is the problem here with space efficiency? – Alexey Feb 24 '18 at 17:33
  • 1
    @JasonS, i suspect that for a subclass to have different defaults could violate the [LSP](https://en.wikipedia.org/wiki/Liskov_substitution_principle). However, a subclass may very well want to have *more* defaults. In any case, it would be for the *subclass* to use justinfay's [method](https://stackoverflow.com/a/16721002), and the base class would be fine with Marc's [method](https://stackoverflow.com/a/18348004). – Alexey Feb 25 '18 at 09:01
  • @Alexey -- default constructor values have nothing to do with LSP, they are used to provide specific instance information for a constructor. Let's use the "is-a" test in real life: If I say, buy me a car, the safe default assumption if I don't specify otherwise is that it will have 4 wheels, 4 doors, a trunk, and a gasoline engine. But there are more specific cars or types of cars that have 3 wheels, or 2 doors, or a hatchback, or an electric engine with battery. – Jason S Feb 27 '18 at 17:38
109

With typing.NamedTuple in Python 3.6.1+ you can provide both a default value and a type annotation to a NamedTuple field. Use typing.Any if you only need the former:

from typing import Any, NamedTuple


class Node(NamedTuple):
    val: Any
    left: 'Node' = None
    right: 'Node' = None

Usage:

>>> Node(1)
Node(val=1, left=None, right=None)
>>> n = Node(1)
>>> Node(2, left=n)
Node(val=2, left=Node(val=1, left=None, right=None), right=None)

Also, in case you need both default values and optional mutability, Python 3.7 is going to have data classes (PEP 557) that can in some (many?) cases replace namedtuples.


Sidenote: one quirk of the current specification of annotations (expressions after : for parameters and variables and after -> for functions) in Python is that they are evaluated at definition time*. So, since "class names become defined once the entire body of the class has been executed", the annotations for 'Node' in the class fields above must be strings to avoid NameError.

This kind of type hints is called "forward reference" ([1], [2]), and with PEP 563 Python 3.7+ is going to have a __future__ import (to be enabled by default in 4.0) that will allow to use forward references without quotes, postponing their evaluation.

* AFAICT only local variable annotations are not evaluated at runtime. (source: PEP 526)

monk-time
  • 2,123
  • 1
  • 14
  • 17
  • 4
    This seems like the cleanest solution for 3.6.1+ users. Note that this example is (slightly) confusing as the type hint for the fields `left` and `right` (i.e. `Node`) is the same type as the class being defined and therefore must be written as strings. – 101 Feb 08 '18 at 23:54
  • 1
    @101, thank you, I've added a note about this to the answer. – monk-time Feb 10 '18 at 13:53
  • 2
    What's the analog for the idiom `my_list: List[T] = None` `self.my_list = my_list if my_list is not None else []`? Can we not use default parameters like this? – weberc2 Apr 19 '18 at 21:29
  • @weberc2 Great question! I'm not sure if this workaround for mutable def. values is possible with `typing.NamedTuple`. But with data classes [you can use](https://www.python.org/dev/peps/pep-0557/#mutable-default-values) `Field` objects with a `default_factory` attr. for this, replacing your idiom with `my_list: List[T] = field(default_factory=list)`. – monk-time Apr 20 '18 at 07:28
104

Wrap it in a function.

NodeT = namedtuple('Node', 'val left right')

def Node(val, left=None, right=None):
  return NodeT(val, left, right)
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 20
    This is clever, and can be a good option, but can also cause problems by breaking `isinstance(Node('val'), Node)`: it will now raise an exception, rather than returning True. While a bit more verbose, [@justinfay's answer (below)](http://stackoverflow.com/a/16721002/260491) preserves type hierarchy information properly, so is probably a better approach if others are going to interact with Node instances. – Gabriel Grant Jul 07 '13 at 22:04
  • 6
    I like the brevity of this answer. Perhaps the concern in the comment above can be addressed by naming the function `def make_node(...):` rather than pretending it is a class definition. In that way users are not tempted to check for type polymorphism on the function but use the tuple definition itself. – user1556435 Mar 23 '16 at 12:14
  • See my answer for a variation of this that doesn't suffer from misleading people to use `isinstance` incorrectly. – Elliot Cameron Jul 12 '16 at 15:09
22

This is an example straight from the docs:

Default values can be implemented by using _replace() to customize a prototype instance:

>>> Account = namedtuple('Account', 'owner balance transaction_count')
>>> default_account = Account('<owner name>', 0.0, 0)
>>> johns_account = default_account._replace(owner='John')
>>> janes_account = default_account._replace(owner='Jane')

So, the OP's example would be:

from collections import namedtuple
Node = namedtuple('Node', 'val left right')
default_node = Node(None, None, None)
example = default_node._replace(val="whut")

However, I like some of the other answers given here better. I just wanted to add this for completeness.

Tim Tisdall
  • 9,914
  • 3
  • 52
  • 82
  • 2
    +1. It's very strange that they decided to go with an `_` method (which basically means a private one) for something like `replace` which seems pretty useful.. – sasuke Aug 22 '15 at 18:50
  • @sasuke - I was wondering that too. It's already a little odd that you define the elements with a space separated string instead of `*args`. It may just be that it was added to the language before a lot of those things were standardized. – Tim Tisdall Aug 24 '15 at 12:43
  • 12
    The `_` prefix is to avoid colliding with the names of the user defined tuple fields (relevant doc quote: "Any valid Python identifier may be used for a fieldname except for names starting with an underscore."). As for the space separated string, I think that's just to save a few keystrokes (and you can pass a sequence of strings if you prefer). – Søren Løvborg Sep 09 '15 at 18:22
  • 1
    Ah, yeah, I forgot you access the elements of the named tuple as attributes, so the `_` makes a lot of sense then. – Tim Tisdall Sep 09 '15 at 18:25
  • 2
    Your solution is simple and the best. The rest is IMHO rather ugly. I would do only one small change. Instead of default_node I would prefer node_default because it makes better experience with IntelliSense. In case you start typing node you received everything you need :) – Pavel Hanpari Jan 01 '16 at 18:40
20

I'm not sure if there's an easy way with just the built-in namedtuple. There's a nice module called recordtype that has this functionality:

>>> from recordtype import recordtype
>>> Node = recordtype('Node', [('val', None), ('left', None), ('right', None)])
>>> Node(3)
Node(val=3, left=None, right=None)
>>> Node(3, 'L')
Node(val=3, left=L, right=None)
jterrace
  • 64,866
  • 22
  • 157
  • 202
  • 2
    Ah, not possible to use a third party package though `recordtype` certainly looks interesting for future work. +1 – sasuke Jul 05 '12 at 19:31
  • The module is quite small and only a [single file](https://bitbucket.org/ericvsmith/recordtype/src/c970cb94dc82/recordtype.py) so you could always just add it to your project. – jterrace Jul 05 '12 at 19:42
  • Fair enough, though I'll wait for some more time for a pure named tuple solution is there is one out there before marking this accepted! :) – sasuke Jul 05 '12 at 19:43
  • Agreed pure python would be nice, but I don't think there is one :( – jterrace Jul 05 '12 at 19:44
  • 3
    Just to note that `recordtype` is mutable whereas `namedtuple` is not. This might matter if you want the object to be hashable (which I guess you don't, since it started out as a class). – bavaza Sep 03 '13 at 07:27
17

In python3.7+ there's a brand new defaults= keyword argument.

defaults can be None or an iterable of default values. Since fields with a default value must come after any fields without a default, the defaults are applied to the rightmost parameters. For example, if the fieldnames are ['x', 'y', 'z'] and the defaults are (1, 2), then x will be a required argument, y will default to 1, and z will default to 2.

Example usage:

$ ./python
Python 3.7.0b1+ (heads/3.7:4d65430, Feb  1 2018, 09:28:35) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from collections import namedtuple
>>> nt = namedtuple('nt', ('a', 'b', 'c'), defaults=(1, 2))
>>> nt(0)
nt(a=0, b=1, c=2)
>>> nt(0, 3)  
nt(a=0, b=3, c=2)
>>> nt(0, c=3)
nt(a=0, b=1, c=3)
anthony sottile
  • 61,815
  • 15
  • 148
  • 207
15

Here is a more compact version inspired by justinfay's answer:

from collections import namedtuple
from functools import partial

Node = namedtuple('Node', ('val left right'))
Node.__new__ = partial(Node.__new__, left=None, right=None)
Gustav Larsson
  • 8,199
  • 3
  • 31
  • 51
  • 7
    Beware that `Node(1, 2)` doesn't work with this recipe, but works in @justinfay's answer. Otherwise, it's quite nifty (+1). – jorgeca Feb 20 '14 at 01:46
6

Short, simple, and doesn't lead people to use isinstance improperly:

class Node(namedtuple('Node', ('val', 'left', 'right'))):
    @classmethod
    def make(cls, val, left=None, right=None):
        return cls(val, left, right)

# Example
x = Node.make(3)
x._replace(right=Node.make(4))
Elliot Cameron
  • 5,235
  • 2
  • 27
  • 34
6

Python 3.7: introduction of defaults param in namedtuple definition.

Example as shown in the documentation:

>>> Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
>>> Account._fields_defaults
{'balance': 0}
>>> Account('premium')
Account(type='premium', balance=0)

Read more here.

Julian Camilleri
  • 2,975
  • 1
  • 25
  • 34
5

A slightly extended example to initialize all missing arguments with None:

from collections import namedtuple

class Node(namedtuple('Node', ['value', 'left', 'right'])):
    __slots__ = ()
    def __new__(cls, *args, **kwargs):
        # initialize missing kwargs with None
        all_kwargs = {key: kwargs.get(key) for key in cls._fields}
        return super(Node, cls).__new__(cls, *args, **all_kwargs)
pppery
  • 3,731
  • 22
  • 33
  • 46
Dennis Golomazov
  • 16,269
  • 5
  • 73
  • 81
4

You can also use this:

import inspect

def namedtuple_with_defaults(type, default_value=None, **kwargs):
    args_list = inspect.getargspec(type.__new__).args[1:]
    params = dict([(x, default_value) for x in args_list])
    params.update(kwargs)

    return type(**params)

This basically gives you the possibility to construct any named tuple with a default value and override just the parameters you need, for example:

import collections

Point = collections.namedtuple("Point", ["x", "y"])
namedtuple_with_defaults(Point)
>>> Point(x=None, y=None)

namedtuple_with_defaults(Point, x=1)
>>> Point(x=1, y=None)
acerisara
  • 121
  • 5
4

Combining approaches of @Denis and @Mark:

from collections import namedtuple
import inspect

class Node(namedtuple('Node', 'left right val')):
    __slots__ = ()
    def __new__(cls, *args, **kwargs):
        args_list = inspect.getargspec(super(Node, cls).__new__).args[len(args)+1:]
        params = {key: kwargs.get(key) for key in args_list + kwargs.keys()}
        return super(Node, cls).__new__(cls, *args, **params) 

That should support creating the tuple with positional arguments and also with mixed cases. Test cases:

>>> print Node()
Node(left=None, right=None, val=None)

>>> print Node(1,2,3)
Node(left=1, right=2, val=3)

>>> print Node(1, right=2)
Node(left=1, right=2, val=None)

>>> print Node(1, right=2, val=100)
Node(left=1, right=2, val=100)

>>> print Node(left=1, right=2, val=100)
Node(left=1, right=2, val=100)

>>> print Node(left=1, right=2)
Node(left=1, right=2, val=None)

but also support TypeError:

>>> Node(1, left=2)
TypeError: __new__() got multiple values for keyword argument 'left'
pppery
  • 3,731
  • 22
  • 33
  • 46
teodor
  • 85
  • 1
  • 7
4

I find this version easier to read:

from collections import namedtuple

def my_tuple(**kwargs):
    defaults = {
        'a': 2.0,
        'b': True,
        'c': "hello",
    }
    default_tuple = namedtuple('MY_TUPLE', ' '.join(defaults.keys()))(*defaults.values())
    return default_tuple._replace(**kwargs)

This is not as efficient as it requires creation of the object twice but you could change that by defining the default duple inside the module and just having the function do the replace line.

Dave31415
  • 2,846
  • 4
  • 26
  • 34
4

Since you are using namedtuple as a data class, you should be aware that python 3.7 will introduce a @dataclass decorator for this very purpose -- and of course it has default values.

An example from the docs:

@dataclass
class C:
    a: int       # 'a' has no default value
    b: int = 0   # assign a default value for 'b'

Much cleaner, readable and usable than hacking namedtuple. It is not hard to predict that usage of namedtuples will drop with the adoption of 3.7.

P-Gn
  • 23,115
  • 9
  • 87
  • 104
2

Inspired by this answer to a different question, here is my proposed solution based on a metaclass and using super (to handle future subcalssing correctly). It is quite similar to justinfay's answer.

from collections import namedtuple

NodeTuple = namedtuple("NodeTuple", ("val", "left", "right"))

class NodeMeta(type):
    def __call__(cls, val, left=None, right=None):
        return super(NodeMeta, cls).__call__(val, left, right)

class Node(NodeTuple, metaclass=NodeMeta):
    __slots__ = ()

Then:

>>> Node(1, Node(2, Node(4)),(Node(3, None, Node(5))))
Node(val=1, left=Node(val=2, left=Node(val=4, left=None, right=None), right=None), right=Node(val=3, left=None, right=Node(val=5, left=None, right=None)))
Alexey
  • 3,843
  • 6
  • 30
  • 44
2

The answer by jterrace to use recordtype is great, but the author of the library recommends to use his namedlist project, which provides both mutable (namedlist) and immutable (namedtuple) implementations.

from namedlist import namedtuple
>>> Node = namedtuple('Node', ['val', ('left', None), ('right', None)])
>>> Node(3)
Node(val=3, left=None, right=None)
>>> Node(3, 'L')
Node(val=3, left=L, right=None)
nbarraille
  • 9,926
  • 14
  • 65
  • 92
1

Here's a short, simple generic answer with a nice syntax for a named tuple with default arguments:

import collections

def dnamedtuple(typename, field_names, **defaults):
    fields = sorted(field_names.split(), key=lambda x: x in defaults)
    T = collections.namedtuple(typename, ' '.join(fields))
    T.__new__.__defaults__ = tuple(defaults[field] for field in fields[-len(defaults):])
    return T

Usage:

Test = dnamedtuple('Test', 'one two three', two=2)
Test(1, 3)  # Test(one=1, three=3, two=2)

Minified:

def dnamedtuple(tp, fs, **df):
    fs = sorted(fs.split(), key=df.__contains__)
    T = collections.namedtuple(tp, ' '.join(fs))
    T.__new__.__defaults__ = tuple(df[i] for i in fs[-len(df):])
    return T
Matthew D. Scholefield
  • 2,977
  • 3
  • 31
  • 42
0

Using the NamedTuple class from my Advanced Enum (aenum) library, and using the class syntax, this is quite simple:

from aenum import NamedTuple

class Node(NamedTuple):
    val = 0
    left = 1, 'previous Node', None
    right = 2, 'next Node', None

The one potential drawback is the requirement for a __doc__ string for any attribute with a default value (it's optional for simple attributes). In use it looks like:

>>> Node()
Traceback (most recent call last):
  ...
TypeError: values not provided for field(s): val

>>> Node(3)
Node(val=3, left=None, right=None)

The advantages this has over justinfay's answer:

from collections import namedtuple

class Node(namedtuple('Node', ['value', 'left', 'right'])):
    __slots__ = ()
    def __new__(cls, value, left=None, right=None):
        return super(Node, cls).__new__(cls, value, left, right)

is simplicity, as well as being metaclass based instead of exec based.

Community
  • 1
  • 1
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
0

Another solution:

import collections


def defaultargs(func, defaults):
    def wrapper(*args, **kwargs):
        for key, value in (x for x in defaults[len(args):] if len(x) == 2):
            kwargs.setdefault(key, value)
        return func(*args, **kwargs)
    return wrapper


def namedtuple(name, fields):
    NamedTuple = collections.namedtuple(name, [x[0] for x in fields])
    NamedTuple.__new__ = defaultargs(NamedTuple.__new__, [(NamedTuple,)] + fields)
    return NamedTuple

Usage:

>>> Node = namedtuple('Node', [
...     ('val',),
...     ('left', None),
...     ('right', None),
... ])
__main__.Node

>>> Node(1)
Node(val=1, left=None, right=None)

>>> Node(1, 2, right=3)
Node(val=1, left=2, right=3)
sirex
  • 4,593
  • 2
  • 32
  • 21
0

If you want to retain the possibility of using type annotation, unfortunately the very nice solution by @mark-lodato isn't usable (it fails for me on setting __defaults__). An alternative is using attrs:

import attr

 
@attr.s
class Node(object):
    val: str = attr.ib()
    left: 'Node' = attr.ib(None)
    right: 'Node' = attr.ib(None)

This has:

  • type annotations
  • nice __str__ and __repr__
  • customizable, since it's a real class
  • same implementation wit all Python versions
Nicola
  • 81
  • 3
0

1. Using NamedTuple >= Python 3.6

Since Python 3.7+ you can use NamedTuple from typing module that support defaults.

https://docs.python.org/3/library/typing.html#typing.NamedTuple

from typing import NamedTuple

class Employee(NamedTuple):
    name: str
    id: int = 3

employee = Employee('Guido')
assert employee.id == 3

Note: Although NamedTuple appears in the class statement as a superclass, it’s actually not. typing.NamedTuple uses the advanced functionality of a metaclass to customize the creation of the user’s class.

issubclass(Employee, typing.NamedTuple)
# return False
issubclass(Employee, tuple)
# return True

2. Using dataclass >= Python 3.7

from dataclasses import dataclass

@dataclass(frozen=True)
class Employee:
    name: str
    id: int = 3

employee = Employee('Guido')
assert employee.id == 3

frozen=True makes dataclass immutable.

Gaurov Soni
  • 111
  • 3
-1

Here's a less flexible, but more concise version of Mark Lodato's wrapper: It takes the fields and defaults as a dictionary.

import collections
def namedtuple_with_defaults(typename, fields_dict):
    T = collections.namedtuple(typename, ' '.join(fields_dict.keys()))
    T.__new__.__defaults__ = tuple(fields_dict.values())
    return T

Example:

In[1]: fields = {'val': 1, 'left': 2, 'right':3}

In[2]: Node = namedtuple_with_defaults('Node', fields)

In[3]: Node()
Out[3]: Node(val=1, left=2, right=3)

In[4]: Node(4,5,6)
Out[4]: Node(val=4, left=5, right=6)

In[5]: Node(val=10)
Out[5]: Node(val=10, left=2, right=3)
Li-Wen Yip
  • 325
  • 3
  • 9