namedtuple i wartości domyślne dla opcjonalnych argumentów słów kluczowych

300

Próbuję przekonwertować podłużną pustą klasę „data” na krotkę nazwaną. Moja klasa wygląda obecnie tak:

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

Po konwersji namedtuplewygląda następująco:

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

Ale tutaj jest problem. Moja oryginalna klasa pozwoliła mi przekazać tylko wartość i zająłem się wartością domyślną, używając domyślnych wartości dla argumentów nazwanych / słów kluczowych. Coś jak:

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

Ale to nie działa w przypadku mojego refaktoryzowanego krotki o nazwie krotka, ponieważ oczekuje, że przejdę wszystkie pola. Mogę oczywiście zastąpić wystąpienia Node(val)do Node(val, None, None), ale nie jest mi do gustu.

Czy istnieje więc dobra sztuczka, która może sprawić, że moje ponowne pisanie zakończy się powodzeniem bez dodawania dużej złożoności kodu (metaprogramowanie), czy też powinienem po prostu przełknąć pigułkę i przejść do „wyszukiwania i zamiany”? :)

sasuke
źródło
2
Dlaczego chcesz dokonać tej konwersji? Podoba mi się twoja oryginalna Nodeklasa taka, jaka jest. Po co konwertować na nazwaną krotkę?
steveha,
34
Chciałem wykonać tę konwersję, ponieważ bieżąca Nodei inne klasy są prostymi obiektami posiadającymi dane z wieloma różnymi polami ( Nodejest tylko jednym z nich). Te deklaracje klas są niczym więcej niż szumem linii, dlatego IMHO chciał je wyciąć. Po co utrzymywać coś, co nie jest wymagane? :)
sasuke,
W ogóle nie masz żadnych funkcji metod w swoich klasach? Nie masz na przykład .debug_print()metody, która kroczy po drzewie i drukuje je?
steveha
2
Jasne, że tak, ale to dla BinaryTreeklasy. Nodei inni posiadacze danych nie wymagają specjalnych metod esp takie dane, które nazwane krotki mieć przyzwoity __str__i __repr__reprezentacji. :)
sasuke,
OK, wydaje się rozsądny. I myślę, że Ignacio Vazquez-Abrams dał ci odpowiedź: użyj funkcji, która wykonuje wartości domyślne dla twojego węzła.
steveha,

Odpowiedzi:

532

Python 3.7

Użyj parametru defaults .

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

Lub jeszcze lepiej, skorzystaj z nowej biblioteki klas danych , która jest o wiele ładniejsza niż 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)

Przed Pythonem 3.7

Ustaw Node.__new__.__defaults__wartości domyślne.

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

Przed Pythonem 2.6

Ustaw Node.__new__.func_defaultswartości domyślne.

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

Zamówienie

We wszystkich wersjach Pythona, jeśli ustawisz mniej wartości domyślnych niż istnieje w namedtuple, wartości domyślne zostaną zastosowane do skrajnie prawych parametrów. Pozwala to zachować niektóre argumenty jako wymagane argumenty.

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

Opakowanie dla Pythona od 2.6 do 3.6

Oto opakowanie dla Ciebie, które pozwala (opcjonalnie) ustawić wartości domyślne na coś innego niż None. To nie obsługuje wymaganych argumentów.

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

Przykład:

>>> 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
źródło
22
Zobaczmy ... twoja linijka: a) jest najkrótszą / najprostszą odpowiedzią, b) zachowuje efektywność przestrzeni, c) nie psuje się isinstance... wszystkie zalety, bez wad ... szkoda, że ​​spóźniłeś się trochę impreza. To najlepsza odpowiedź.
Gerrat
1
Jeden problem z wersją opakowania: w przeciwieństwie do wbudowanych kolekcji. Nazwa-wersja, ta wersja nie jest możliwa do pobrania / serializacji w wielu procesach, jeśli def () jest zawarty w innym module.
Michael Scott Cuthbert
2
Odpowiedziałem na tę opinię, ponieważ jest ona lepsza niż moja własna. Szkoda jednak, że moja własna odpowiedź wciąż zyskuje na popularności: |
Justin Fay,
3
@ishaaq, problem polega na tym, że (None)nie jest to krotka None. Jeśli użyjesz (None,)zamiast tego, powinno działać dobrze.
Mark Lodato,
2
Świetny! Możesz uogólnić ustawienie domyślne poprzez:Node.__new__.__defaults__= (None,) * len(Node._fields)
ankostis
142

Podklasowałem nazwanepleple i zastąpiłem __new__metodę:

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)

Zachowuje to intuicyjną hierarchię typów, czego nie robi tworzenie funkcji fabryki ukrytej pod klasą.

Justin Fay
źródło
7
Może to wymagać właściwości gniazd i pól w celu utrzymania wydajności przestrzeni nazwanej krotki.
Pepijn,
Z jakiegoś powodu __new__nie jest wywoływany, gdy _replacejest używany.
1
Proszę spojrzeć na odpowiedź @ marc-lodato, poniżej której IMHO jest lepszym rozwiązaniem niż to.
Justin Fay
1
ale odpowiedź @ marc-lodato nie zapewnia możliwości, aby podklasa miała inne wartości domyślne
Jason S
1
@JasonS, podejrzewam, że podklasa mająca różne wartości domyślne może naruszać LSP . Jednak podklasa może bardzo chcieć mieć więcej wartości domyślnych. W każdym razie podklasa używałaby metody justinfay , a klasa bazowa byłaby w porządku z metodą Marca .
Alexey,
94

Zawiń to w funkcję.

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

def Node(val, left=None, right=None):
  return NodeT(val, left, right)
Ignacio Vazquez-Abrams
źródło
15
Jest to sprytne i może być dobrą opcją, ale może również powodować problemy poprzez zerwanie isinstance(Node('val'), Node): spowoduje teraz wyjątek, zamiast zwracać wartość True. Chociaż nieco bardziej szczegółowe, odpowiedź @ justinfay (poniżej) prawidłowo zachowuje informacje o hierarchii typów, więc prawdopodobnie jest to lepsze podejście, jeśli inni będą wchodzić w interakcje z instancjami Węzła.
Gabriel Grant,
4
Lubię zwięzłość tej odpowiedzi. Być może problem w powyższym komentarzu można rozwiązać, nazywając funkcję, def make_node(...):a nie udając, że jest to definicja klasy. W ten sposób użytkownicy nie mają ochoty sprawdzać polimorfizmu typu w funkcji, ale używają samej definicji krotki.
user1556435
Zobacz moją odpowiedź na odmianę tego, która nie cierpi z powodu wprowadzających w błąd osób do isinstanceniewłaściwego używania .
Elliot Cameron,
70

W typing.NamedTuplePythonie 3.6.1+ możesz podać zarówno wartość domyślną, jak i adnotację typu w polu NamedTuple. Użyj, typing.Anyjeśli potrzebujesz tylko tego pierwszego:

from typing import Any, NamedTuple


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

Stosowanie:

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

Ponadto, jeśli potrzebujesz zarówno wartości domyślnych, jak i opcjonalnej zmienności, Python 3.7 będzie miał klasy danych (PEP 557), które mogą w niektórych (wielu?) Przypadkach zastąpić nazwane pary.


Sidenote: jednym dziwactwem obecnej specyfikacji adnotacji (wyrażeń po :parametrach i zmiennych oraz po ->funkcjach) w Pythonie jest to, że są one oceniane w czasie definicji * . Tak więc, ponieważ „nazwy klas są definiowane po wykonaniu całego ciała klasy”, adnotacje 'Node'w polach klasy powyżej muszą być ciągami znaków, aby uniknąć błędu nazwy.

Tego rodzaju wskazówki typu nazywa się „referencjami do przodu” ( [1] , [2] ), a w PEP 563 Python 3.7+ będzie miał __future__import (domyślnie włączony w 4.0), który pozwoli na korzystanie z referencji do przodu bez cytatów, odroczenie ich oceny.

* Adnotacje tylko lokalnych zmiennych AFAICT nie są analizowane w czasie wykonywania. (źródło: PEP 526 )

czas mnicha
źródło
4
To wydaje się być najczystszym rozwiązaniem dla użytkowników wersji 3.6.1+. Zauważ, że ten przykład jest (nieco) mylący, ponieważ wskazówka dotycząca typu dla pól lefti right(tj. Node) Jest tego samego typu co definiowana klasa, a zatem musi być napisany jako ciąg.
101
1
@ 101, dziękuję, do odpowiedzi dodałem notatkę.
czasu mnicha
2
Jaki jest analog dla tego idiomu my_list: List[T] = None self.my_list = my_list if my_list is not None else []? Czy nie możemy użyć takich domyślnych parametrów?
weberc2
@ weberc2 Świetne pytanie! Nie jestem pewien, czy to obejście dla zmiennego def. wartości są możliwe przy pomocy typing.NamedTuple. Ale dzięki klasom danych możesz używać Field obiektów o default_factoryattr. w tym celu zastępując swój idiom my_list: List[T] = field(default_factory=list).
czasu mnicha,
20

Oto przykład prosto z dokumentacji :

Wartości domyślne można zaimplementować za pomocą _replace () w celu dostosowania instancji prototypu:

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

Tak więc przykładem PO byłoby:

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

Jednak bardziej podoba mi się niektóre inne odpowiedzi podane tutaj. Chciałem tylko dodać to dla kompletności.

Tim Tisdall
źródło
2
+1. To bardzo dziwne, że zdecydowali się na _metodę (która w zasadzie oznacza metodę prywatną) dla czegoś, replaceco wydaje się dość przydatne ..
sasuke
@sasuke - Też się nad tym zastanawiałem. To już trochę dziwne, że zamiast elementów definiujesz elementy za pomocą łańcucha oddzielanego spacjami *args. Być może zostało to dodane do języka, zanim wiele z tych rzeczy zostało znormalizowanych.
Tim Tisdall
12
_Prefiks jest, aby uniknąć kolizji z nazwami pól krotka zdefiniowane przez użytkownika (dotyczy doc cytat: „Każda ważna Python identyfikator może być używany do nazwy pola z wyjątkiem nazw zaczynających się od znaku podkreślenia.”). Jeśli chodzi o ciąg oddzielony spacjami, myślę, że to po prostu zaoszczędzić kilka naciśnięć klawiszy (i możesz przekazać sekwencję ciągów, jeśli wolisz).
Søren Løvborg,
1
Ach, tak, zapomniałem dostępu do elementów nazwanej krotki jako atrybutów, więc _ma to sens.
Tim Tisdall
2
Twoje rozwiązanie jest proste i najlepsze. Reszta to IMHO raczej brzydka. Zrobiłbym tylko jedną małą zmianę. Zamiast default_node wolałbym node_default, ponieważ zapewnia lepszą obsługę IntelliSense. Jeśli zaczniesz pisać, otrzymałeś wszystko, czego potrzebujesz :)
Pavel Hanpari
19

Nie jestem pewien, czy istnieje prosty sposób dzięki wbudowanej nazwie o nazwie. Jest ładny moduł o nazwie recordtype, który ma tę funkcjonalność:

>>> 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
źródło
2
Ach, nie jest możliwe użycie pakietu innej firmy, choć z recordtypepewnością wygląda interesująco dla przyszłych prac. +1
sasuke
Moduł jest dość mały i zawiera tylko jeden plik, więc zawsze możesz po prostu dodać go do swojego projektu.
jterrace
W porządku, ale poczekam jeszcze trochę czasu na rozwiązanie o nazwie krotki o czystej nazwie, jest jedno, zanim oznaczę to jako zaakceptowane! :)
sasuke,
Zgadzam się, że czysty python byłby miły, ale nie sądzę, że jest jeden :(
jterrace
3
Wystarczy zauważyć, że recordtypemożna go modyfikować, podczas gdy namedtuplenie. Może to mieć znaczenie, jeśli chcesz, aby obiekt był haszowalny (co, jak sądzę, nie masz, ponieważ zaczął jako klasa).
bavaza,
14

Oto bardziej kompaktowa wersja inspirowana odpowiedzią justinfay:

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
źródło
7
Uwaga, Node(1, 2)który nie działa z tym przepisem, ale działa w odpowiedzi @ justinfay. W przeciwnym razie jest całkiem fajny (+1).
jorgeca
12

W python3.7 + jest nowy argument defaults = słowo kluczowe.

wartościami domyślnymi mogą być Nonelub iterowalne wartości domyślne. Ponieważ pola o wartości domyślnej muszą znajdować się po polach bez wartości domyślnej, wartości domyślne są stosowane do parametrów skrajnie prawych. Na przykład, jeśli fieldnames są ['x', 'y', 'z']i są domyślne (1, 2), a następnie xbędzie wymagany argument ybędzie domyślnie 1i zbędzie domyślnie 2.

Przykładowe użycie:

$ ./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
źródło
7

Krótko, prosto i nie prowadzi do isinstanceniewłaściwego użycia :

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
źródło
5

Nieco rozszerzony przykład inicjalizacji wszystkich brakujących argumentów za pomocą 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)
Dennis Golomazov
źródło
5

Python 3.7: wprowadzenie defaultsparam w definicji namedtuple.

Przykład pokazany w dokumentacji:

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

Przeczytaj więcej tutaj .

Julian Camilleri
źródło
4

Możesz także użyć tego:

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)

Zasadniczo daje to możliwość skonstruowania dowolnej nazwanej krotki z wartością domyślną i zastąpienia tylko potrzebnych parametrów, na przykład:

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
źródło
4

Łącząc podejścia @Denis i @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) 

To powinno wspierać tworzenie krotki z argumentami pozycyjnymi, a także z mieszanymi przypadkami. Przypadki testowe:

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

ale także obsługuje TypeError:

>>> Node(1, left=2)
TypeError: __new__() got multiple values for keyword argument 'left'
teodor
źródło
3

Ta wersja jest dla mnie łatwiejsza do odczytania:

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)

Nie jest to tak wydajne, jak wymaga dwukrotnego utworzenia obiektu, ale można to zmienić, definiując domyślny dupleks w module i po prostu uruchamiając funkcję zastępowania wiersza.

Dave31415
źródło
3

Ponieważ używasz namedtuplejako klasy danych, powinieneś być świadom, że Python 3.7 wprowadzi@dataclass dekorator do tego właśnie celu - i oczywiście ma wartości domyślne.

Przykład z dokumentów :

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

Znacznie czystszy, czytelny i użyteczny niż hakowanie namedtuple. Nie jest trudno przewidzieć, że użycie namedtuples spadnie wraz z przyjęciem 3,7.

P-Gn
źródło
2

Zainspirowany tą odpowiedzią na inne pytanie, oto moje proponowane rozwiązanie oparte na metaklasie i użyciu super(do prawidłowej obsługi przyszłych subkalowań). Jest dość podobny do odpowiedzi justinfay .

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__ = ()

Następnie:

>>> 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
źródło
2

Odpowiedź jterrace'a na użycie typu record jest świetna, ale autor biblioteki zaleca skorzystanie ze swojego projektu nazwanego , który zapewnia implementacje zarówno mutable ( namedlist), jak i immutable ( namedtuple).

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
źródło
1

Oto krótka, prosta ogólna odpowiedź z ładną składnią dla nazwanej krotki z domyślnymi argumentami:

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

Stosowanie:

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

Zminimalizowane:

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
źródło
0

Używając NamedTupleklasy z mojej Advanced Enum (aenum)biblioteki i używając classskładni, jest to dość proste:

from aenum import NamedTuple

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

Jedyną potencjalną wadą jest wymóg dla __doc__dowolnego atrybutu o wartości domyślnej (jest opcjonalny dla prostych atrybutów). W użyciu wygląda to tak:

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

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

Zalety ma to w porównaniu z 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)

to prostota, a także bycie metaclassopartym zamiast execopartym.

Ethan Furman
źródło
0

Inne rozwiązanie:

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

Stosowanie:

>>> 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
źródło
-1

Oto mniej elastyczna, ale bardziej zwięzła wersja opakowania Marka Lodato: Pobiera pola i domyślnie jako słownik.

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

Przykład:

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
źródło
4
dictnie ma gwarancji zamówienia.
Ethan Furman,