namedtuple และค่าดีฟอลต์สำหรับอาร์กิวเมนต์คำหลักเพิ่มเติม


300

ฉันพยายามที่จะแปลงคลาส "data" ที่มีลักษณะเป็นโพรงยาวให้เป็น tuple ที่มีชื่อ ชั้นเรียนของฉันดูเหมือนว่านี้:

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

หลังจากแปลงnamedtupleเป็นดูเหมือน:

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

แต่มีปัญหาอยู่ที่นี่ คลาสดั้งเดิมของฉันอนุญาตให้ฉันส่งผ่านค่าและดูแลค่าเริ่มต้นโดยใช้ค่าเริ่มต้นสำหรับอาร์กิวเมนต์ที่ระบุชื่อ / คำหลัก สิ่งที่ต้องการ:

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

แต่สิ่งนี้ไม่ได้ผลในกรณีของ tuple ที่ชื่อว่า refactored เพราะคาดว่าฉันจะผ่านทุกฟิลด์ แน่นอนว่าฉันสามารถแทนที่เหตุการณ์ที่เกิดขึ้นเป็นNode(val)ไปได้Node(val, None, None)แต่ไม่ใช่ความชอบของฉัน

ดังนั้นจึงมีเคล็ดลับที่ดีที่สามารถทำให้การเขียนใหม่ของฉันประสบความสำเร็จโดยไม่ต้องเพิ่มความซับซ้อนของโค้ด (metaprogramming) หรือฉันควรกลืนยาและไปข้างหน้าด้วย "ค้นหาและแทนที่"? :)


2
ทำไมคุณต้องการแปลงนี้ ฉันชอบNodeชั้นเรียนดั้งเดิมของคุณตามที่เป็นอยู่ ทำไมแปลงเป็น tuple ชื่อ?
steveha

34
ฉันต้องการทำการแปลงนี้เนื่องจากNodeคลาสปัจจุบันและคลาสอื่น ๆ เป็นวัตถุค่าของตัวยึดข้อมูลที่เรียบง่ายที่มีฟิลด์ต่าง ๆ มากมาย ( Nodeเป็นเพียงหนึ่งในนั้น) การประกาศคลาสเหล่านี้ไม่มีอะไรมากไปกว่าเสียงรบกวนของสาย IMHO ดังนั้นจึงต้องการตัดออก ทำไมต้องรักษาบางสิ่งที่ไม่จำเป็น? :)
สึเกะ

คุณไม่ได้มีฟังก์ชั่นวิธีการใด ๆ ในชั้นเรียนของคุณเลย? ยกตัวอย่างเช่นคุณไม่มี.debug_print()วิธีการที่เดินบนต้นไม้และพิมพ์มันหรือไม่?
steveha

2
แน่นอนว่าฉันทำ แต่สำหรับBinaryTreeชั้นเรียน Nodeและผู้ถือครองข้อมูลอื่น ๆ ไม่จำเป็นต้องใช้วิธีพิเศษเช่นนี้เนื่องจากว่า tuples ที่ตั้งชื่อนั้นมีความเหมาะสม__str__และ__repr__เป็นตัวแทน :)
สึเกะ

ตกลงดูเหมือนว่าสมเหตุสมผล และฉันคิดว่า Ignacio Vazquez-Abrams ให้คำตอบกับคุณ: ใช้ฟังก์ชั่นที่เป็นค่าเริ่มต้นสำหรับโหนดของคุณ
steveha

คำตอบ:


532

Python 3.7

ใช้พารามิเตอร์ค่าเริ่มต้น

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

หรือดีกว่านั้นให้ใช้ไลบรารีดาต้าคลาสใหม่ซึ่งดีกว่าชื่อทูเพิล

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

ก่อน Python 3.7

ตั้งNode.__new__.__defaults__เป็นค่าเริ่มต้น

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

ก่อน Python 2.6

ตั้งNode.__new__.func_defaultsเป็นค่าเริ่มต้น

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

ใบสั่ง

ในทุกเวอร์ชันของ Python หากคุณตั้งค่าเริ่มต้นน้อยกว่าที่มีอยู่ใน namedtuple ค่าเริ่มต้นจะถูกนำไปใช้กับพารามิเตอร์ด้านขวาสุด สิ่งนี้อนุญาตให้คุณเก็บอาร์กิวเมนต์บางตัวตามอาร์กิวเมนต์ที่ต้องการ

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

แรปเปอร์สำหรับ Python 2.6 ถึง 3.6

นี่คือเสื้อคลุมสำหรับคุณซึ่งแม้จะช่วยให้คุณ (เลือก) Noneตั้งค่าเริ่มต้นอย่างอื่นที่ไม่ใช่ สิ่งนี้ไม่สนับสนุนอาร์กิวเมนต์ที่ต้องการ

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

ตัวอย่าง:

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

22
ลองดู ... ซับในของคุณ: a) เป็นคำตอบที่สั้นที่สุด / ง่ายที่สุด b) รักษาประสิทธิภาพของพื้นที่ c) ไม่หยุดisinstance... ข้อดีทั้งหมดไม่มีข้อเสีย ... เลวร้ายเกินไปคุณมาช้าไปหน่อย งานเลี้ยง. นี่คือคำตอบที่ดีที่สุด
Gerrat

1
ปัญหาหนึ่งที่เกิดขึ้นกับรุ่น wrapper: ซึ่งแตกต่างจาก builtin collection.namedtuple เวอร์ชันนี้จะไม่สามารถเลือกได้ / มัลติโพรเซสที่สามารถต่ออนุกรมได้หาก def () รวมอยู่ในโมดูลอื่น
Michael Scott Cuthbert

2
ฉันได้ให้คำตอบนี้ upvote ตามความต้องการของฉันเอง น่าเสียดาย แต่คำตอบของฉันเองก็ยังได้รับการโหวต: |
Justin Fay

3
@ishaaq เป็นปัญหาที่(None)ไม่ได้เป็น tuple Noneก็ ถ้าคุณใช้(None,)มันควรจะทำงานได้ดี
Mark Lodato

2
ยอดเยี่ยม คุณสามารถพูดคุยเกี่ยวกับการตั้งค่าเริ่มต้นด้วย:Node.__new__.__defaults__= (None,) * len(Node._fields)
ankostis

142

ฉัน subclassed namedtuple และ overrode __new__วิธีการ:

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)

สิ่งนี้รักษาลำดับชั้นของประเภทที่ใช้งานง่ายซึ่งการสร้างฟังก์ชั่นจากโรงงานซึ่งปลอมตัวเป็นคลาสไม่ได้


7
สิ่งนี้อาจต้องการคุณสมบัติสล็อตและฟิลด์เพื่อรักษาประสิทธิภาพของพื้นที่ของ tuple ที่มีชื่อ
Pepijn

ด้วยเหตุผลบางอย่าง__new__จะไม่ถูกเรียกเมื่อ_replaceถูกใช้

1
โปรดดูที่ @ marc-lodato คำตอบด้านล่างซึ่ง IMHO เป็นทางออกที่ดีกว่านี้
Justin Fay

1
แต่คำตอบของ @ marc-lodato ไม่ได้ให้ความสามารถสำหรับคลาสย่อยที่จะมีค่าเริ่มต้นที่แตกต่างกัน
Jason S

1
@JasonS ฉันสงสัยว่าสำหรับประเภทรองที่จะมีค่าเริ่มต้นที่แตกต่างกันอาจละเมิดLSP อย่างไรก็ตามคลาสย่อยอาจต้องการค่าเริ่มต้นมากขึ้น ในกรณีใด ๆ มันจะเป็นสำหรับประเภทรองกับการใช้ justinfay ของวิธีการและชั้นฐานจะดีกับมาร์คของวิธีการ
Alexey

94

ห่อไว้ในฟังก์ชั่น

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

def Node(val, left=None, right=None):
  return NodeT(val, left, right)

15
นี่เป็นวิธีที่ฉลาดและสามารถเป็นตัวเลือกที่ดี แต่ยังสามารถทำให้เกิดปัญหาโดยการแตกisinstance(Node('val'), Node): ตอนนี้มันจะยกข้อยกเว้นมากกว่ากลับ True ในขณะที่ verbose อีกเล็กน้อย@ justinfay คำตอบ (ด้านล่าง)รักษาข้อมูลลำดับชั้นของประเภทอย่างถูกต้องดังนั้นอาจเป็นวิธีที่ดีกว่าถ้าคนอื่นจะโต้ตอบกับอินสแตนซ์โหนด
Gabriel Grant

4
ฉันชอบความกระชับของคำตอบนี้ บางทีความกังวลในความคิดเห็นด้านบนสามารถแก้ไขได้ด้วยการตั้งชื่อฟังก์ชั่นdef make_node(...):แทนที่จะทำเป็นว่ามันเป็นคำจำกัดความของคลาส ด้วยวิธีการดังกล่าวผู้ใช้จะไม่ถูกล่อลวงให้ตรวจสอบความหลากหลายชนิดในฟังก์ชั่น แต่ใช้นิยาม tuple เอง
user1556435

ดูคำตอบของฉันสำหรับความหลากหลายของสิ่งนี้ที่ไม่ได้รับจากการทำให้ผู้อื่นเข้าใจผิดในการใช้isinstanceอย่างไม่ถูกต้อง
Elliot Cameron

70

ด้วยtyping.NamedTuplePython 3.6.1+ คุณสามารถให้ทั้งค่าเริ่มต้นและหมายเหตุประกอบชนิดลงในฟิลด์ NamedTuple ใช้typing.Anyหากคุณต้องการอดีต:

from typing import Any, NamedTuple


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

การใช้งาน:

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

นอกจากนี้ในกรณีที่คุณต้องการทั้งค่าเริ่มต้นและความไม่แน่นอนทางเลือก Python 3.7 ก็จะมีเช่นกัน คลาสข้อมูล (PEP 557)ที่สามารถในบางกรณี (มาก?) แทนที่ชื่อที่มีชื่อว่า


Sidenote: หนึ่งมุมแหลมของข้อกำหนดปัจจุบันของคำอธิบายประกอบ (การแสดงออกหลังจาก:สำหรับพารามิเตอร์และตัวแปรและหลัง->สำหรับฟังก์ชั่น) ในหลามคือว่าพวกเขาได้รับการประเมินในเวลานิยาม* ดังนั้นเนื่องจาก "ชื่อคลาสกลายเป็นคำจำกัดความเมื่อเนื้อความทั้งหมดของคลาสได้รับการดำเนินการ" แล้วคำอธิบายประกอบสำหรับ'Node'ในฟิลด์คลาสด้านบนจะต้องเป็นสตริงเพื่อหลีกเลี่ยง NameError

คำใบ้ประเภทนี้เรียกว่า "การอ้างอิงไปข้างหน้า" ( [1] , [2] ) และด้วยPEP 563 Python 3.7+ จะมี__future__การนำเข้า (จะเปิดใช้งานโดยค่าเริ่มต้นใน 4.0) ที่จะอนุญาตให้ใช้การอ้างอิงไปข้างหน้า โดยไม่มีเครื่องหมายคำพูดเลื่อนการประเมินผล

* AFAICT เฉพาะคำอธิบายประกอบแบบโลคัลตัวแปรจะไม่ถูกประเมินที่รันไทม์ (ที่มา: PEP 526 )


4
ดูเหมือนว่าจะเป็นคำตอบที่สะอาดที่สุดสำหรับผู้ใช้ที่ 3.6.1 ขึ้นไป โปรดทราบว่าตัวอย่างนี้ (เล็กน้อย) ทำให้เกิดความสับสนเป็นคำใบ้ประเภทสำหรับเขตข้อมูลleftและright(เช่นNode) เป็นประเภทเดียวกันกับชั้นเรียนที่ถูกกำหนดและดังนั้นจึงต้องเขียนเป็นสตริง
101

1
@ 101 ขอบคุณฉันได้เพิ่มบันทึกเกี่ยวกับคำตอบนี้แล้ว
เวลาของพระภิกษุ

2
อะนาล็อกสำหรับสำนวนmy_list: List[T] = None self.my_list = my_list if my_list is not None else []อะไร? เราไม่สามารถใช้พารามิเตอร์เริ่มต้นเช่นนี้ได้หรือไม่?
weberc2

@ weberc2 เป็นคำถามที่ดีมาก! ฉันไม่แน่ใจว่าวิธีแก้ปัญหานี้สำหรับ def ที่ไม่แน่นอน typing.NamedTupleค่าเป็นไปได้ด้วย แต่ด้วยคลาสข้อมูลคุณสามารถใช้ Fieldวัตถุที่มีdefault_factoryattr my_list: List[T] = field(default_factory=list)สำหรับเรื่องนี้เปลี่ยนสำนวนของคุณด้วย
เวลาของพระภิกษุ

20

นี่คือตัวอย่างตรงจากเอกสาร :

ค่าเริ่มต้นสามารถนำมาใช้โดยใช้ _replace () เพื่อปรับแต่งอินสแตนซ์ต้นแบบ:

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

ดังนั้นตัวอย่างของ OP จะเป็น:

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

อย่างไรก็ตามฉันชอบคำตอบอื่น ๆ ที่ได้รับที่นี่ดีกว่า ฉันแค่อยากจะเพิ่มสิ่งนี้เพื่อความสมบูรณ์


2
+1 มันแปลกมากที่พวกเขาตัดสินใจที่จะใช้_วิธีการ (ซึ่งโดยทั่วไปหมายถึงแบบส่วนตัว) สำหรับสิ่งreplaceที่ดูเหมือนว่ามีประโยชน์ทีเดียว ..
สึเกะ

@ sasuke - ฉันก็สงสัยเช่นกัน มันมีอยู่แล้วแปลก ๆ *argsที่คุณกำหนดองค์ประกอบที่มีพื้นที่แยกสตริงแทน อาจเป็นเพราะมันถูกเพิ่มเข้าไปในภาษาก่อนที่สิ่งเหล่านั้นจะได้มาตรฐาน
Tim Tisdall

12
_คำนำหน้าคือการหลีกเลี่ยงการชนกับชื่อของผู้ใช้กำหนดเขต tuple (การอ้างเอกสารที่เกี่ยวข้อง: "ใด ๆ ที่ถูกต้องหลามตัวระบุอาจจะใช้สำหรับ fieldname ยกเว้นสำหรับชื่อเริ่มต้นด้วยการขีดเส้นใต้ได้.") สำหรับสตริงที่คั่นด้วยช่องว่างฉันคิดว่าเป็นเพียงการบันทึกการกดแป้นบางครั้ง (และคุณสามารถผ่านลำดับของสตริงได้หากคุณต้องการ)
SørenLøvborg

1
อาใช่ฉันลืมคุณเข้าถึงองค์ประกอบของชื่อ tuple เป็นคุณลักษณะดังนั้นจึง_ทำให้รู้สึกมาก
Tim Tisdall

2
ทางออกของคุณง่ายและดีที่สุด ส่วนที่เหลือเป็น IMHO ค่อนข้างน่าเกลียด ฉันจะเปลี่ยนแปลงเพียงเล็กน้อยเท่านั้น แทนที่จะเป็น default_node ฉันต้องการ node_default เพราะมันจะทำให้ประสบการณ์ดีขึ้นกับ IntelliSense ในกรณีที่คุณเริ่มต้นโหนดการพิมพ์ที่คุณได้รับทุกสิ่งที่คุณต้องการ :)
พาเวล Hanpari

19

ฉันไม่แน่ใจว่ามีวิธีง่าย ๆ เพียงแค่ตั้งชื่อในตัว มีโมดูลที่ดีที่เรียกว่าประเภทบันทึกที่มีฟังก์ชั่นนี้:

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

2
อาไม่สามารถใช้แพ็คเกจของบุคคลที่สามได้ แต่recordtypeแน่นอนว่ามันน่าสนใจสำหรับงานในอนาคต +1
สึเกะ

โมดูลมีขนาดค่อนข้างเล็กและมีเพียงไฟล์เดียวดังนั้นคุณสามารถเพิ่มลงในโครงการของคุณได้ตลอดเวลา
jterrace

พอใช้ได้แม้ว่าฉันจะรออีกนานเพื่อแก้ปัญหา tuple บริสุทธิ์ที่มีชื่อมีอยู่ก่อนที่จะทำเครื่องหมายที่ยอมรับ! :)
สึเกะ

เห็นด้วยหลามบริสุทธิ์จะดี แต่ฉันไม่คิดว่าจะมีหนึ่ง :(
jterrace

3
เพียงแค่ต้องทราบว่าrecordtypeไม่แน่นอนในขณะที่namedtupleไม่ได้ สิ่งนี้อาจสำคัญหากคุณต้องการให้วัตถุนั้นแฮช (ซึ่งฉันเดาว่าคุณทำไม่ได้เนื่องจากมันเริ่มเป็นคลาส)
bavaza

14

นี่คือรุ่นกะทัดรัดที่ได้รับแรงบันดาลใจจากคำตอบของ justinfay:

from collections import namedtuple
from functools import partial

Node = namedtuple('Node', ('val left right'))
Node.__new__ = partial(Node.__new__, left=None, right=None)

7
ระวังว่าใช้Node(1, 2)ไม่ได้กับสูตรนี้ แต่ใช้ได้ในคำตอบของ @ justinfay มิฉะนั้นจะค่อนข้างดี (+1)
jorgeca

12

ใน python3.7 + มีการเริ่มต้นใหม่=อาร์กิวเมนต์คำหลัก

ค่าเริ่มต้นสามารถเป็นNoneหรือซ้ำค่าเริ่มต้น เนื่องจากเขตข้อมูลที่มีค่าเริ่มต้นจะต้องอยู่หลังเขตข้อมูลใด ๆ ที่ไม่มีค่าเริ่มต้นจึงใช้ค่าเริ่มต้นกับพารามิเตอร์ด้านขวาสุด ตัวอย่างเช่นถ้ามี fieldnames ['x', 'y', 'z']และค่าเริ่มต้นที่มี(1, 2)แล้วxจะมีการโต้แย้งที่จำเป็นyจะเริ่มต้น1และจะเริ่มต้นz2

ตัวอย่างการใช้งาน:

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

7

สั้นง่ายและไม่นำผู้คนไปใช้isinstanceอย่างไม่เหมาะสม:

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

5

ตัวอย่างเพิ่มเติมเล็กน้อยเพื่อเริ่มต้นอาร์กิวเมนต์ที่หายไปทั้งหมดด้วย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)

5

Python 3.7: การแนะนำของdefaultsพารามิเตอร์ในนิยาม namedtuple

ตัวอย่างที่แสดงในเอกสารประกอบ:

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

อ่านเพิ่มเติมที่นี่


4

คุณยังสามารถใช้สิ่งนี้:

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)

สิ่งนี้ทำให้คุณมีความเป็นไปได้ในการสร้าง tuple ที่มีชื่อด้วยค่าเริ่มต้นและแทนที่พารามิเตอร์ที่คุณต้องการตัวอย่างเช่น

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)

4

การรวมแนวทางของ @Denis และ @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) 

ที่ควรสนับสนุนการสร้าง tuple ด้วยอาร์กิวเมนต์ตำแหน่งและในกรณีต่างๆ กรณีทดสอบ:

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

แต่ยังรองรับ TypeError:

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

3

ฉันพบว่ารุ่นนี้อ่านง่ายกว่า:

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)

สิ่งนี้ไม่ได้มีประสิทธิภาพเท่าที่จำเป็นต้องมีการสร้างวัตถุสองครั้ง แต่คุณสามารถเปลี่ยนมันได้โดยกำหนด duple เริ่มต้นภายในโมดูลและเพียงแค่ให้ฟังก์ชั่นทำการเปลี่ยนบรรทัด


3

เนื่องจากคุณใช้namedtupleเป็นคลาสข้อมูลคุณควรระวังว่า python 3.7 จะแนะนำ@dataclassdecorator สำหรับจุดประสงค์นี้ - และแน่นอนว่ามันมีค่าเริ่มต้น

ตัวอย่างจากเอกสาร :

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

namedtupleทำความสะอาดมากสามารถอ่านได้และใช้งานได้กว่าแฮ็ค ไม่ยากที่จะคาดการณ์ว่าการใช้namedtuples จะลดลงเมื่อใช้ 3.7


2

ได้รับแรงบันดาลใจจากคำตอบนี้สำหรับคำถามที่แตกต่างกันนี่คือโซลูชันของฉันที่เสนอจากmetaclassและการใช้super(เพื่อจัดการ subcalssing ในอนาคตอย่างถูกต้อง) มันค่อนข้างคล้ายกับคำตอบของ 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__ = ()

แล้ว:

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

2

คำตอบโดย jterrace ที่จะใช้ recordtype นั้นยอดเยี่ยม แต่ผู้เขียนของไลบรารีแนะนำให้ใช้projectlistlistโครงการของเขาซึ่งมีการใช้งานทั้งที่ไม่แน่นอน ( namedlist) และไม่เปลี่ยนรูป ( 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)

1

ต่อไปนี้เป็นคำตอบทั่วไปแบบสั้น ๆ ที่เรียบง่ายพร้อมไวยากรณ์ที่ดีสำหรับ tuple ที่มีชื่อพร้อมอาร์กิวเมนต์เริ่มต้น:

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

การใช้งาน:

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

0

ใช้NamedTupleคลาสจากAdvanced Enum (aenum)ห้องสมุดของฉันและใช้classไวยากรณ์นี้ค่อนข้างง่าย:

from aenum import NamedTuple

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

ข้อเสียเปรียบหนึ่งที่อาจเกิดขึ้นคือข้อกำหนดสำหรับ__doc__สตริงสำหรับแอตทริบิวต์ใด ๆ ที่มีค่าเริ่มต้น (เป็นตัวเลือกสำหรับแอตทริบิวต์แบบง่าย) ในการใช้งานดูเหมือนว่า:

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

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

ข้อดีนี้มีมากกว่า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)

เป็นความเรียบง่ายเช่นเดียวกับการเป็นmetaclassพื้นฐานแทนที่จะเป็นexecพื้นฐาน


0

ทางออกอื่น:

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

การใช้งาน:

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

-1

นี่คือเวอร์ชันที่ยืดหยุ่นน้อยกว่า แต่รัดกุมกว่าของ wrapper ของ Mark Lodato: ใช้ฟิลด์และค่าเริ่มต้นเป็นพจนานุกรม

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

ตัวอย่าง:

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)

4
dictไม่มีการรับประกันการสั่งซื้อ
Ethan Furman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.