namedtuple dan nilai default untuk argumen kata kunci opsional

300

Saya mencoba untuk mengkonversi "data" kelas gondrong gondrong menjadi tuple bernama. Kelas saya saat ini terlihat seperti ini:

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

Setelah konversi menjadi namedtupleseperti:

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

Tapi ada masalah di sini. Kelas asli saya memungkinkan saya untuk memberikan nilai dan menjaga standar dengan menggunakan nilai default untuk argumen nama / kata kunci. Sesuatu seperti:

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

Tapi ini tidak berfungsi dalam kasus tuple bernama refactored saya karena mengharapkan saya untuk lulus semua bidang. Tentu saja saya bisa menggantikan kejadian Node(val)untuk Node(val, None, None)tetapi tidak sesuai dengan keinginan saya.

Jadi apakah ada trik bagus yang dapat membuat penulisan ulang saya berhasil tanpa menambahkan banyak kerumitan kode (metaprogramming) atau haruskah saya menelan pil dan melanjutkan dengan "pencarian dan ganti"? :)

sasuke
sumber
2
Mengapa Anda ingin melakukan konversi ini? Saya suka Nodekelas orisinal Anda apa adanya. Mengapa mengonversi ke nama tuple?
steveha
34
Saya ingin melakukan konversi ini karena kelas saat ini Nodedan lainnya adalah objek nilai pemegang data sederhana dengan sekelompok bidang yang berbeda ( Nodehanya salah satunya). Deklarasi kelas ini tidak lebih dari garis noise IMHO karena itu ingin memangkasnya. Mengapa mempertahankan sesuatu yang tidak diperlukan? :)
sasuke
Anda sama sekali tidak memiliki fungsi metode di kelas Anda? Misalnya, Anda tidak memiliki .debug_print()metode berjalan di pohon dan mencetaknya?
steveha
2
Tentu saya lakukan, tapi itu untuk BinaryTreekelas. Nodedan pemegang data lainnya tidak memerlukan metode khusus seperti itu mengingat tupel yang diberi nama memiliki representasi yang layak __str__dan __repr__memadai. :)
sasuke
Oke, sepertinya masuk akal. Dan saya pikir Ignacio Vazquez-Abrams telah memberi Anda jawabannya: gunakan fungsi yang melakukan nilai default untuk node Anda.
steveha

Jawaban:

532

Python 3.7

Gunakan parameter default .

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

Atau lebih baik lagi, gunakan perpustakaan kacamata baru , yang jauh lebih bagus daripada 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)

Sebelum Python 3.7

Setel Node.__new__.__defaults__ke nilai default.

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

Sebelum Python 2.6

Setel Node.__new__.func_defaultske nilai default.

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

Memesan

Di semua versi Python, jika Anda menetapkan lebih sedikit nilai default daripada yang ada di namesuple, default diterapkan ke parameter paling kanan. Ini memungkinkan Anda menyimpan beberapa argumen seperti argumen yang diperlukan.

>>> 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 untuk Python 2.6 hingga 3.6

Inilah pembungkus untuk Anda, yang bahkan memungkinkan Anda (secara opsional) mengatur nilai default ke sesuatu selain None. Ini tidak mendukung argumen yang diperlukan.

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

Contoh:

>>> 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
sumber
22
Mari kita lihat ... one-liner Anda: a) adalah jawaban terpendek / paling sederhana, b) menjaga efisiensi ruang, c) tidak merusak isinstance... semua pro, tidak ada kontra ... sayang sekali Anda sedikit terlambat untuk pesta. Ini jawaban terbaik.
Gerrat
1
Satu masalah dengan versi wrapper: tidak seperti builtin collections.namedtuple, versi ini tidak dapat dipilih / multiproses diproses secara serial jika def () termasuk dalam modul yang berbeda.
Michael Scott Cuthbert
2
Saya telah memberikan jawaban ini suara positif karena lebih baik daripada jawaban saya sendiri. Sayang sekali bahwa jawaban saya sendiri terus terangkat: |
Justin Fay
3
@ishaaq, masalahnya (None)bukan tuple, ini None. Jika Anda menggunakannya (None,), itu akan berfungsi dengan baik.
Mark Lodato
2
Luar biasa! Anda dapat menggeneralisasi pengaturan default dengan:Node.__new__.__defaults__= (None,) * len(Node._fields)
ankostis
142

Saya subclassed namedtuple dan mengalahkan __new__metode:

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)

Ini mempertahankan hierarki tipe intuitif, yang penciptaan fungsi pabrik tidak menyamar sebagai kelas.

Justin Fay
sumber
7
Ini mungkin membutuhkan properti slot dan bidang untuk menjaga efisiensi ruang dari tuple bernama.
Pepijn
Untuk beberapa alasan, __new__tidak dipanggil saat _replacedigunakan.
1
Silakan lihat jawaban @ marc-lodato di bawah ini yang merupakan solusi IMHO yang lebih baik dari ini.
Justin Fay
1
tetapi jawaban @ marc-lodato tidak memberikan kemampuan bagi subclass untuk memiliki standar yang berbeda
Jason S
1
@JasonS, saya menduga bahwa untuk subclass memiliki standar yang berbeda dapat melanggar LSP . Namun, sebuah subclass mungkin ingin memiliki lebih banyak default. Bagaimanapun, subclass akan menggunakan metode justinfay , dan kelas dasar akan baik-baik saja dengan metode Marc .
Alexey
94

Bungkus dalam suatu fungsi.

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

def Node(val, left=None, right=None):
  return NodeT(val, left, right)
Ignacio Vazquez-Abrams
sumber
15
Ini cerdas, dan bisa menjadi pilihan yang baik, tetapi juga dapat menyebabkan masalah dengan melanggar isinstance(Node('val'), Node): sekarang akan meningkatkan pengecualian, daripada mengembalikan True. Meskipun sedikit lebih bertele-tele, jawaban @ justinfay (di bawah) mempertahankan informasi hierarki jenis dengan benar, jadi mungkin pendekatan yang lebih baik jika orang lain akan berinteraksi dengan instance Node.
Gabriel Grant
4
Saya suka singkatnya jawaban ini. Mungkin kekhawatiran dalam komentar di atas dapat diatasi dengan memberi nama fungsi def make_node(...):daripada berpura-pura sebagai definisi kelas. Dengan cara itu pengguna tidak tergoda untuk memeriksa tipe polimorfisme pada fungsi tetapi menggunakan definisi tuple itu sendiri.
user1556435
Lihat jawaban saya untuk variasi ini yang tidak membuat orang yang menyesatkan isinstancesalah menggunakan .
Elliot Cameron
70

Dengan typing.NamedTupledi Python 3.6.1+ Anda bisa memberikan nilai default dan anotasi tipe ke bidang NamedTuple. Gunakan typing.Anyjika Anda hanya membutuhkan yang pertama:

from typing import Any, NamedTuple


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

Pemakaian:

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

Juga, jika Anda memerlukan nilai default dan mutabilitas opsional, Python 3.7 akan memiliki kelas data (PEP 557) yang dapat dalam beberapa kasus (banyak?) Menggantikan namedtuple.


Sidenote: satu kekhasan dari spesifikasi anotasi saat ini (ekspresi setelah :untuk parameter dan variabel dan setelah ->untuk fungsi) dengan Python adalah bahwa mereka dievaluasi pada waktu definisi * . Jadi, karena "nama-nama kelas menjadi didefinisikan setelah seluruh badan kelas telah dieksekusi", penjelasan untuk'Node' dalam bidang kelas di atas harus berupa string untuk menghindari NameError.

Jenis petunjuk ini disebut "referensi maju" ( [1] , [2] ), dan dengan PEP 563 Python 3.7+ akan memiliki __future__impor (akan diaktifkan secara default di 4.0) yang akan memungkinkan untuk menggunakan referensi maju tanpa kutipan, menunda evaluasi mereka.

* AFAICT hanya anotasi variabel lokal yang tidak dievaluasi saat runtime. (sumber: PEP 526 )

waktu bhikkhu
sumber
4
Ini sepertinya solusi terbersih untuk pengguna 3.6.1+. Perhatikan bahwa contoh ini (sedikit) membingungkan karena petunjuk tipe untuk bidang leftdan right(yaitu Node) adalah tipe yang sama dengan kelas yang didefinisikan dan karenanya harus ditulis sebagai string.
101
1
@ 101, terima kasih, saya telah menambahkan catatan tentang ini pada jawabannya.
waktu biarawan
2
Apa analog untuk idiom itu my_list: List[T] = None self.my_list = my_list if my_list is not None else []? Tidak bisakah kita menggunakan parameter default seperti ini?
weberc2
@ weberc2 Pertanyaan bagus! Saya tidak yakin apakah solusi ini untuk def bisa berubah. nilai dimungkinkan dengan typing.NamedTuple. Tetapi dengan kelas data Anda dapat menggunakan Field objek dengan default_factoryattr. untuk ini, ganti idiom Anda dengan my_list: List[T] = field(default_factory=list).
waktu biarawan
20

Ini adalah contoh langsung dari dokumen :

Nilai default dapat diimplementasikan dengan menggunakan _replace () untuk menyesuaikan contoh prototipe:

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

Jadi, contoh OP adalah:

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

Namun, saya suka beberapa jawaban lain yang diberikan di sini lebih baik. Saya hanya ingin menambahkan ini untuk kelengkapan.

Tim Tisdall
sumber
2
+1. Sangat aneh bahwa mereka memutuskan untuk menggunakan _metode (yang pada dasarnya berarti metode pribadi) untuk sesuatu replaceyang sepertinya cukup berguna ..
sasuke
@sasuke - Saya juga bertanya-tanya. Sudah agak aneh bahwa Anda mendefinisikan elemen-elemen dengan string yang dipisahkan ruang alih-alih *args. Mungkin saja itu ditambahkan ke bahasa sebelum banyak dari hal-hal itu distandarisasi.
Tim Tisdall
12
The _prefix adalah untuk menghindari bertabrakan dengan nama-nama bidang tuple ditetapkan pengguna (kutipan doc relevan: "Setiap berlaku Python identifier dapat digunakan untuk fieldname kecuali untuk nama dimulai dengan garis bawah."). Adapun string yang dipisahkan ruang, saya pikir itu hanya untuk menyimpan beberapa penekanan tombol (dan Anda dapat melewati urutan string jika Anda mau).
Søren Løvborg
1
Ah, ya, saya lupa Anda mengakses elemen-elemen dari tuple bernama sebagai atribut, jadi itu sangat _masuk akal.
Tim Tisdall
2
Solusi Anda sederhana dan terbaik. Sisanya IMHO agak jelek. Saya hanya akan melakukan satu perubahan kecil. Alih-alih default_node saya lebih suka node_default karena membuat pengalaman yang lebih baik dengan IntelliSense. Jika Anda mulai mengetik simpul, Anda menerima semua yang Anda butuhkan :)
Pavel Hanpari
19

Saya tidak yakin apakah ada cara mudah hanya dengan namesuple bawaan. Ada modul bagus bernama recordtype yang memiliki fungsi ini:

>>> 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
sumber
2
Ah, tidak mungkin menggunakan paket pihak ketiga meskipun recordtypetentu terlihat menarik untuk pekerjaan di masa depan. +1
sasuke
Modul ini cukup kecil dan hanya satu file sehingga Anda selalu bisa menambahkannya ke proyek Anda.
jterrace
Cukup adil, meskipun saya akan menunggu beberapa saat lagi untuk solusi tuple murni bernama ada satu di luar sana sebelum menandai ini diterima! :)
sasuke
Python murni yang disetujui akan menyenangkan, tapi saya tidak berpikir ada satu :(
jterrace
3
Hanya untuk dicatat bahwa recordtypebisa berubah sedangkan namedtupletidak. Ini mungkin penting jika Anda ingin objek menjadi hashable (yang saya kira Anda tidak, karena dimulai sebagai kelas).
bavaza
14

Ini adalah versi yang lebih ringkas yang terinspirasi oleh jawaban 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
sumber
7
Waspadalah yang Node(1, 2)tidak bekerja dengan resep ini, tetapi bekerja di jawaban @ justinfay. Kalau tidak, ini cukup bagus (+1).
jorgeca
12

Di python3.7 + ada default baru = argumen kata kunci.

nilai default dapat berupa Nonenilai default yang dapat diubah. Karena bidang dengan nilai default harus datang setelah bidang apa pun tanpa standar, standar diterapkan ke parameter paling kanan. Misalnya, jika nama fieldnya ['x', 'y', 'z']dan defaultnya adalah (1, 2), maka xakan menjadi argumen yang diperlukan, yakan default ke 1, dan zakan default ke 2.

Contoh penggunaan:

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

Singkat, sederhana, dan tidak membuat orang menggunakan dengan isinstancetidak tepat:

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

Contoh yang sedikit diperluas untuk menginisialisasi semua argumen yang hilang dengan 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
sumber
5

Python 3.7: pengenalan defaultsparam dalam namedtuple definition.

Contoh seperti yang ditunjukkan dalam dokumentasi:

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

Baca lebih lanjut di sini .

Julian Camilleri
sumber
4

Anda juga dapat menggunakan ini:

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)

Ini pada dasarnya memberi Anda kemungkinan untuk membuat tuple bernama apa pun dengan nilai default dan mengabaikan hanya parameter yang Anda butuhkan, misalnya:

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

Menggabungkan pendekatan @Denis dan @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) 

Itu harus mendukung pembuatan tuple dengan argumen posisi dan juga dengan kasus campuran. Kasus uji:

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

tetapi juga mendukung TypeError:

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

Saya menemukan versi ini lebih mudah dibaca:

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)

Ini tidak seefisien karena membutuhkan penciptaan objek dua kali tetapi Anda bisa mengubahnya dengan mendefinisikan duple default di dalam modul dan hanya memiliki fungsi melakukan ganti baris.

Dave31415
sumber
3

Karena Anda menggunakan namedtuplesebagai kelas data, Anda harus menyadari bahwa python 3.7 akan memperkenalkan a@dataclass dekorator untuk tujuan ini - dan tentu saja ia memiliki nilai default.

Contoh dari dokumen :

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

Jauh lebih bersih, mudah dibaca dan dapat digunakan daripada meretas namedtuple. Tidak sulit untuk memprediksi bahwa penggunaan namedtuples akan turun dengan adopsi 3,7.

P-Gn
sumber
2

Terinspirasi oleh jawaban ini untuk pertanyaan yang berbeda, berikut ini adalah solusi yang saya usulkan berdasarkan metaclass dan penggunaannya super(untuk menangani pengaliran di masa depan dengan benar). Ini sangat mirip dengan jawaban 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__ = ()

Kemudian:

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

Jawaban oleh jterrace untuk menggunakan recordtype sangat bagus, tetapi penulis perpustakaan merekomendasikan untuk menggunakan proyek daftar namanya , yang menyediakan implementasi yang bisa berubah ( namedlist) dan tidak berubah ( 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
sumber
1

Inilah jawaban umum singkat dan sederhana dengan sintaks yang bagus untuk tuple bernama dengan argumen default:

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

Pemakaian:

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

Diperkecil:

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

Menggunakan NamedTuplekelas dari Advanced Enum (aenum)perpustakaan saya , dan menggunakan classsintaks, ini cukup sederhana:

from aenum import NamedTuple

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

Satu kelemahan potensial adalah persyaratan untuk __doc__string untuk atribut apa pun dengan nilai default (itu opsional untuk atribut sederhana). Dalam penggunaannya terlihat seperti:

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

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

Kelebihan ini lebih dari 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)

adalah kesederhanaan, serta metaclassberbasis bukan execberbasis.

Ethan Furman
sumber
0

Solusi lain:

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

Pemakaian:

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

Berikut ini versi pembungkus Mark Lodato yang kurang fleksibel, tetapi lebih ringkas: Dibutuhkan bidang dan default sebagai kamus.

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

Contoh:

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
sumber
4
dicttidak memiliki jaminan pemesanan.
Ethan Furman