namedtuple und Standardwerte für optionale Schlüsselwortargumente

300

Ich versuche, eine längliche hohle "Daten" -Klasse in ein benanntes Tupel umzuwandeln. Meine Klasse sieht derzeit so aus:

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

Nach der Konvertierung namedtuplesieht es so aus:

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

Aber hier gibt es ein Problem. In meiner ursprünglichen Klasse konnte ich nur einen Wert übergeben und mich um den Standard kümmern, indem ich Standardwerte für die Argumente named / keyword verwendete. Etwas wie:

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

Dies funktioniert jedoch nicht bei meinem überarbeiteten benannten Tupel, da erwartet wird, dass ich alle Felder übergebe. Ich kann natürlich die Vorkommen von Node(val)to ersetzen , Node(val, None, None)aber es gefällt mir nicht.

Gibt es also einen guten Trick, der mein Umschreiben erfolgreich macht, ohne viel Codekomplexität hinzuzufügen (Metaprogrammierung), oder sollte ich einfach die Pille schlucken und mit dem "Suchen und Ersetzen" fortfahren? :) :)

Sasuke
quelle
2
Warum möchten Sie diese Konvertierung vornehmen? Ich mag deine ursprüngliche NodeKlasse so wie sie ist. Warum in benanntes Tupel konvertieren?
Steveha
34
Ich wollte diese Konvertierung durchführen, da die aktuelle Nodeund andere Klassen einfache Datenhalter-Wertobjekte mit einer Reihe verschiedener Felder sind ( Nodeist nur eines davon). Diese Klassendeklarationen sind meiner Meinung nach nicht viel mehr als Leitungsrauschen und wollten sie daher reduzieren. Warum etwas pflegen, was nicht benötigt wird? :)
Sasuke
Sie haben überhaupt keine Methodenfunktionen für Ihre Klassen? Sie haben zum Beispiel keine .debug_print()Methode, die über den Baum läuft und ihn druckt?
Steveha
2
Klar, aber das ist für die BinaryTreeKlasse. Nodeund andere Dateninhaber benötigen keine solchen speziellen Methoden, insbesondere da benannte Tupel eine anständige __str__und __repr__repräsentative Form haben . :)
Sasuke
Okay, scheint vernünftig. Und ich denke, Ignacio Vazquez-Abrams hat Ihnen die Antwort gegeben: Verwenden Sie eine Funktion, die die Standardwerte für Ihren Knoten ausführt.
Steveha

Antworten:

532

Python 3.7

Verwenden Sie den Standardparameter .

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

Oder noch besser, verwenden Sie die neue Datenklassenbibliothek , die viel besser ist als 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)

Vor Python 3.7

Auf Node.__new__.__defaults__die Standardwerte setzen.

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

Vor Python 2.6

Auf Node.__new__.func_defaultsdie Standardwerte setzen.

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

Auftrag

Wenn Sie in allen Python-Versionen weniger Standardwerte als im Namedtuple festlegen, werden die Standardeinstellungen auf die Parameter ganz rechts angewendet. Auf diese Weise können Sie einige Argumente als erforderliche Argumente beibehalten.

>>> 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 für Python 2.6 bis 3.6

Hier ist ein Wrapper für Sie, mit dem Sie sogar (optional) die Standardwerte auf etwas anderes als festlegen können None. Dies unterstützt keine erforderlichen Argumente.

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

Beispiel:

>>> 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
quelle
22
Mal sehen ... Ihr Einzeiler: a) ist die kürzeste / einfachste Antwort, b) bewahrt die Raumeffizienz, c) bricht nicht isinstance... alle Vor- und Nachteile ... schade, dass Sie etwas spät dran waren die Party. Dies ist die beste Antwort.
Gerrat
1
Ein Problem mit der Wrapper-Version: Im Gegensatz zu den integrierten Sammlungen. Namedtuple ist diese Version nicht auswählbar / multiprozess-serialisierbar, wenn def () in einem anderen Modul enthalten ist.
Michael Scott Cuthbert
2
Ich habe dieser Antwort eine positive Bewertung gegeben, da sie meiner eigenen vorzuziehen ist. Es ist jedoch schade, dass meine eigene Antwort immer wieder positiv bewertet wird: |
Justin Fay
3
@ishaaq, das Problem ist, dass (None)es kein Tupel ist, es ist None. Wenn Sie (None,)stattdessen verwenden, sollte es gut funktionieren.
Mark Lodato
2
Ausgezeichnet! Sie können die Node.__new__.__defaults__= (None,) * len(Node._fields)
Standardeinstellung
142

Ich habe namedtuple in eine Unterklasse unterteilt und die __new__Methode überschrieben :

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)

Dadurch bleibt eine intuitive Typhierarchie erhalten, die bei der Erstellung einer als Klasse getarnten Factory-Funktion nicht möglich ist.

Justin Fay
quelle
7
Dies erfordert möglicherweise Slots- und Feldeigenschaften, um die Raumeffizienz eines benannten Tupels aufrechtzuerhalten.
Pepijn
Aus irgendeinem Grund __new__wird nicht aufgerufen, wenn _replaceverwendet wird.
1
Bitte werfen Sie einen Blick auf die Antwort von @ marc-lodato, unter der IMHO eine bessere Lösung ist.
Justin Fay
1
Aber @ marc-lodatos Antwort bietet nicht die Möglichkeit für eine Unterklasse, unterschiedliche Standardeinstellungen zu haben
Jason S
1
@ JasonS, ich vermute, dass eine Unterklasse mit unterschiedlichen Standardeinstellungen den LSP verletzen könnte . Eine Unterklasse möchte jedoch möglicherweise mehr Standardeinstellungen haben. In jedem Fall wäre es Sache der Unterklasse , die Methode von justinfay zu verwenden , und die Basisklasse wäre mit der Methode von Marc in Ordnung .
Alexey
94

Wickeln Sie es in eine Funktion.

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

def Node(val, left=None, right=None):
  return NodeT(val, left, right)
Ignacio Vazquez-Abrams
quelle
15
Dies ist clever und kann eine gute Option sein, kann aber auch Probleme verursachen, indem es bricht isinstance(Node('val'), Node): Es wird jetzt eine Ausnahme ausgelöst, anstatt True zurückzugeben. Die Antwort von @ justinfay (unten) ist zwar etwas ausführlicher, behält jedoch die Informationen zur Typhierarchie ordnungsgemäß bei. Dies ist wahrscheinlich ein besserer Ansatz, wenn andere mit Node-Instanzen interagieren.
Gabriel Grant
4
Ich mag die Kürze dieser Antwort. Möglicherweise kann das Problem im obigen Kommentar behoben werden, indem die Funktion benannt wird, def make_node(...):anstatt vorzutäuschen, dass es sich um eine Klassendefinition handelt. Auf diese Weise sind Benutzer nicht versucht, die Funktion auf Typpolymorphismus zu überprüfen, sondern verwenden die Tupeldefinition selbst.
user1556435
Siehe meine Antwort für eine Variation davon, die nicht unter irreführenden Personen leidet, die isinstancefalsch verwendet werden.
Elliot Cameron
70

Mit typing.NamedTuplein Python 3.6.1+ können Sie einem NamedTuple-Feld sowohl einen Standardwert als auch eine Typanmerkung bereitstellen. Verwenden typing.AnySie, wenn Sie nur die erstere benötigen:

from typing import Any, NamedTuple


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

Verwendungszweck:

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

Falls Sie sowohl Standardwerte als auch optionale Veränderbarkeit benötigen, wird Python 3.7 Datenklassen (PEP 557) haben , die in einigen (vielen?) Fällen Namedtuples ersetzen können.


Nebenbemerkung: Eine Besonderheit der aktuellen Spezifikation von Annotationen (Ausdrücke nach :für Parameter und Variablen und nach ->für Funktionen) in Python ist, dass sie zum Definitionszeitpunkt * ausgewertet werden . Da also "Klassennamen definiert werden, sobald der gesamte Hauptteil der Klasse ausgeführt wurde", müssen die Anmerkungen 'Node'in den obigen Klassenfeldern Zeichenfolgen sein, um NameError zu vermeiden.

Diese Art von Typhinweisen wird als "Vorwärtsreferenz" ( [1] , [2] ) bezeichnet, und mit PEP 563 wird Python 3.7+ einen __future__Import haben (der in 4.0 standardmäßig aktiviert ist), der die Verwendung von Vorwärtsreferenzen ermöglicht ohne Anführungszeichen, Verschiebung ihrer Bewertung.

* AFAICT Nur lokale Variablenanmerkungen werden zur Laufzeit nicht ausgewertet. (Quelle: PEP 526 )

Mönchszeit
quelle
4
Dies scheint die sauberste Lösung für Benutzer ab 3.6.1 zu sein. Beachten Sie, dass dieses Beispiel (leicht) verwirrend ist, da der Typhinweis für die Felder leftund right(dh Node) vom selben Typ wie die zu definierende Klasse ist und daher als Zeichenfolgen geschrieben werden muss.
101
1
@ 101, danke, ich habe der Antwort einen Hinweis dazu hinzugefügt.
Mönch-Zeit
2
Was ist das Analogon für die Redewendung my_list: List[T] = None self.my_list = my_list if my_list is not None else []? Können wir solche Standardparameter nicht verwenden?
weberc2
@ weberc2 Gute Frage! Ich bin nicht sicher, ob diese Problemumgehung für veränderliche def. Werte ist möglich mit typing.NamedTuple. Bei Datenklassen können Sie jedoch Field Objekte mit einem default_factoryattr verwenden. Ersetzen Sie dazu Ihre Redewendung durch my_list: List[T] = field(default_factory=list).
Mönch-Zeit
20

Dies ist ein Beispiel direkt aus den Dokumenten :

Standardwerte können mithilfe von _replace () implementiert werden, um eine Prototypinstanz anzupassen:

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

Das Beispiel des OP wäre also:

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

Ich mag jedoch einige der anderen hier gegebenen Antworten besser. Ich wollte dies nur der Vollständigkeit halber hinzufügen.

Tim Tisdall
quelle
2
+1. Es ist sehr seltsam, dass sie sich für eine _Methode entschieden haben (was im Grunde genommen eine private bedeutet), für etwas, replacedas ziemlich nützlich erscheint.
Sasuke
@ Sasuke - das habe ich mich auch gefragt. Es ist schon etwas seltsam, dass Sie die Elemente stattdessen mit einer durch Leerzeichen getrennten Zeichenfolge definieren *args. Es kann sein, dass es der Sprache hinzugefügt wurde, bevor viele dieser Dinge standardisiert wurden.
Tim Tisdall
12
Das _Präfix soll verhindern, dass Kollisionen mit den Namen der benutzerdefinierten Tupelfelder auftreten (relevantes Dokumentzitat: "Für einen Feldnamen kann jeder gültige Python-Bezeichner verwendet werden, außer für Namen, die mit einem Unterstrich beginnen."). Was die durch Leerzeichen getrennte Zeichenfolge betrifft, denke ich, dass dies nur dazu dient, ein paar Tastenanschläge zu speichern (und Sie können eine Folge von Zeichenfolgen übergeben, wenn Sie dies bevorzugen).
Søren Løvborg
1
Ah, ja, ich habe vergessen, dass Sie als Attribute auf die Elemente des genannten Tupels zugreifen, daher _macht das dann sehr viel Sinn.
Tim Tisdall
2
Ihre Lösung ist einfach und die beste. Der Rest ist meiner Meinung nach ziemlich hässlich. Ich würde nur eine kleine Änderung vornehmen. Anstelle von default_node würde ich node_default bevorzugen, da dies eine bessere Erfahrung mit IntelliSense ermöglicht. Falls Sie anfangen, Knoten
einzugeben, haben
19

Ich bin mir nicht sicher, ob es nur mit dem eingebauten Namedupuple einen einfachen Weg gibt. Es gibt ein nettes Modul namens recordtype , das diese Funktionalität hat:

>>> 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
quelle
2
Ah, es ist nicht möglich, ein Paket von Drittanbietern zu verwenden, obwohl es recordtypefür zukünftige Arbeiten sicherlich interessant aussieht. +1
Sasuke
Das Modul ist recht klein und nur eine einzige Datei, sodass Sie es jederzeit einfach zu Ihrem Projekt hinzufügen können.
Jterrace
Fair genug, obwohl ich noch einige Zeit auf eine reine Tupellösung warten werde, gibt es eine, bevor ich diese als akzeptiert markiere! :)
Sasuke
Einverstanden reine Python wäre schön, aber ich glaube nicht, dass es eine gibt :(
jterrace
3
Nur um zu bemerken, dass dies recordtypeveränderlich ist, während dies namedtuplenicht der Fall ist. Dies kann von Bedeutung sein, wenn Sie möchten, dass das Objekt hashbar ist (was Sie wahrscheinlich nicht tun, da es als Klasse begann).
Bayaza
14

Hier ist eine kompaktere Version, die von der Antwort von justinfay inspiriert wurde:

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
quelle
7
Node(1, 2)Beachten Sie, dass dies mit diesem Rezept nicht funktioniert, aber in der Antwort von @ justinfay funktioniert. Ansonsten ist es ziemlich geschickt (+1).
Jorgeca
12

In Python3.7 + gibt es ein brandneues Argument für Standardwerte = Schlüsselwörter.

Standardwerte kann Noneder Standardwerte oder eine iterable. Da Felder mit einem Standardwert muss ohne Standard nach beliebigen Feldern kommen, die Standardwerte werden an die am weitesten rechts stehenden Parameter angewendet. Wenn beispielsweise die Feldnamen ['x', 'y', 'z']und die Standardwerte lauten (1, 2), ist xdies ein erforderliches Argument, ywird standardmäßig verwendet 1und zwird standardmäßig verwendet 2.

Anwendungsbeispiel:

$ ./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
quelle
7

Kurz, einfach und führt nicht dazu, dass Menschen isinstanceunsachgemäß verwenden:

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
quelle
5

Ein leicht erweitertes Beispiel zum Initialisieren aller fehlenden Argumente mit 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
quelle
5

Python 3.7: Einführung von defaultsparam in die Namedtuple-Definition.

Beispiel wie in der Dokumentation gezeigt:

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

Lesen Sie hier mehr .

Julian Camilleri
quelle
4

Sie können auch Folgendes verwenden:

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)

Dies gibt Ihnen grundsätzlich die Möglichkeit, ein beliebiges benanntes Tupel mit einem Standardwert zu erstellen und nur die Parameter zu überschreiben, die Sie benötigen, zum Beispiel:

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
quelle
4

Kombinieren von Ansätzen von @Denis und @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) 

Dies sollte die Erstellung des Tupels mit Positionsargumenten und auch mit gemischten Fällen unterstützen. Testfälle:

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

unterstützen aber auch TypeError:

>>> Node(1, left=2)
TypeError: __new__() got multiple values for keyword argument 'left'
teodor
quelle
3

Ich finde diese Version leichter zu lesen:

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)

Dies ist nicht so effizient, da das Objekt zweimal erstellt werden muss. Sie können dies jedoch ändern, indem Sie das Standard-Duple im Modul definieren und nur die Funktion die Ersetzungszeile ausführen lassen.

Dave31415
quelle
3

Da Sie namedtupleals Datenklasse verwenden, sollten Sie sich darüber im Klaren sein, dass Python 3.7 genau @dataclasszu diesem Zweck einen Dekorator einführt - und natürlich Standardwerte hat.

Ein Beispiel aus den Dokumenten :

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

Viel sauberer, lesbarer und benutzerfreundlicher als Hacken namedtuple. Es ist nicht schwer vorherzusagen, dass die Verwendung von namedtuples mit der Einführung von 3.7 sinken wird.

P-Gn
quelle
2

Inspiriert von dieser Antwort auf eine andere Frage, ist hier meine vorgeschlagene Lösung, die auf einer Metaklasse basiert und verwendet super(um zukünftige Subkalibrierungen korrekt zu handhaben). Es ist der Antwort von justinfay ziemlich ähnlich .

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

Dann:

>>> 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
quelle
2

Die Antwort von jterrace auf die Verwendung von recordtype ist großartig, aber der Autor der Bibliothek empfiehlt, sein Namedlist- Projekt zu verwenden, das sowohl veränderbare ( namedlist) als auch unveränderliche ( namedtuple) Implementierungen bietet .

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
quelle
1

Hier ist eine kurze, einfache generische Antwort mit einer schönen Syntax für ein benanntes Tupel mit Standardargumenten:

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

Verwendungszweck:

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

Minimiert:

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
quelle
0

Mit der NamedTupleKlasse aus meiner Advanced Enum (aenum)Bibliothek und der classSyntax ist dies ganz einfach:

from aenum import NamedTuple

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

Der einzige mögliche Nachteil ist die Anforderung einer __doc__Zeichenfolge für jedes Attribut mit einem Standardwert (optional für einfache Attribute). Im Gebrauch sieht es so aus:

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

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

Die Vorteile gegenüber 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)

ist Einfachheit, sowie metaclassbasiert statt execbasiert.

Ethan Furman
quelle
0

Eine andere Lösung:

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

Verwendungszweck:

>>> 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
quelle
-1

Hier ist eine weniger flexible, aber präzisere Version von Mark Lodatos Wrapper: Er verwendet die Felder und Standardeinstellungen als Wörterbuch.

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

Beispiel:

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
quelle
4
dicthat keine Garantie für die Bestellung.
Ethan Furman