โครงสร้างแบบ C ใน Python


447

มีวิธีกำหนดโครงสร้าง C-like ใน Python หรือไม่? ฉันเบื่อที่จะเขียนสิ่งต่าง ๆ เช่น:

class MyStruct():
    def __init__(self, field1, field2, field3):
        self.field1 = field1
        self.field2 = field2
        self.field3 = field3

5
ประเภทข้อมูลเชิงพีชคณิตมีส่วนเกี่ยวข้องอย่างน่าอัศจรรย์ แต่หากใช้อย่างดีคุณจะต้องมีการจับคู่รูปแบบ
Edward Z. Yang

51
มีวิธีการอื่นนอกเหนือจากที่น่าเบื่อในการเขียนหรือไม่?
levesque

2
คุณอาจพบว่า dstruct มีประโยชน์: github.com/dorkitude/dstruct
Kyle Wild

10
@levesque ยากขึ้นอีกครั้งโดยไม่ต้องพิมพ์ตัวหนังสือยากที่จะอ่านได้อย่างรวดเร็วในขณะที่ skimming รหัสกว่าMyStruct = namedtuple("MyStruct", "field1 field2 field3")
sam boosalis

1
pandas.Series(a=42).aควรจะทำมันถ้าข้อมูลของคุณนักวิทยาศาสตร์ ...
มาร์ค Horvath

คำตอบ:


341

ใช้tuple ที่มีชื่อซึ่งถูกเพิ่มไปยังโมดูลชุดรวมในไลบรารีมาตรฐานใน Python 2.6 อาจเป็นไปได้ที่จะใช้สูตรการตั้งชื่อของ Raymond Hettinger หากคุณต้องการสนับสนุน Python 2.4

เป็นเรื่องที่ดีสำหรับตัวอย่างพื้นฐานของคุณ แต่ยังครอบคลุมถึงกรณีขอบจำนวนมากที่คุณอาจพบเจอในภายหลังเช่นกัน ส่วนของคุณด้านบนจะเขียนเป็น:

from collections import namedtuple
MyStruct = namedtuple("MyStruct", "field1 field2 field3")

ประเภทที่สร้างขึ้นใหม่สามารถใช้ดังนี้:

m = MyStruct("foo", "bar", "baz")

คุณยังสามารถใช้อาร์กิวเมนต์ที่มีชื่อ:

m = MyStruct(field1="foo", field2="bar", field3="baz")

164
... แต่ชื่อ tuple นั้นไม่เปลี่ยนรูป ตัวอย่างใน OP ไม่แน่นอน
mhowison

28
@mhowison - ในกรณีของฉันนั่นเป็นเพียงข้อดี
ArtOfWarfare

3
ทางออกที่ดี คุณจะวนลูปผ่านอาร์เรย์ของสิ่งอันดับเหล่านี้อย่างไร ฉันคิดว่าเขต 1-3 จะต้องมีชื่อเดียวกันกับวัตถุทูเปิล
Michael Smith

2
namedtuple สามารถมีอาร์กิวเมนต์ได้สูงสุดสี่อาร์กิวเมนต์ดังนั้นวิธีที่เราสามารถแมปโครงสร้างกับสมาชิกข้อมูลที่มี namedtuple ที่สอดคล้องกันได้มากขึ้น
Kapil

3
@Kapil - อาร์กิวเมนต์ที่สองสำหรับ namedtuple ควรเป็นรายชื่อสมาชิก รายการนั้นอาจมีความยาวเท่าใดก็ได้
ArtOfWarfare

226

อัปเดต : คลาสข้อมูล

ด้วยการเปิดตัวData ClassesในPython 3.7เราเข้าใกล้มาก

ตัวอย่างต่อไปนี้คล้ายกับตัวอย่างNamedTupleด้านล่าง แต่วัตถุที่เป็นผลลัพธ์ไม่แน่นอนและอนุญาตให้ใช้ค่าเริ่มต้น

from dataclasses import dataclass


@dataclass
class Point:
    x: float
    y: float
    z: float = 0.0


p = Point(1.5, 2.5)

print(p)  # Point(x=1.5, y=2.5, z=0.0)

สิ่งนี้จะเล่นได้ดีกับโมดูลการพิมพ์ใหม่ในกรณีที่คุณต้องการใช้คำอธิบายประกอบที่เฉพาะเจาะจงมากขึ้น

ฉันรออย่างนี้หมดแล้ว! ถ้าคุณถามฉันคลาสข้อมูลและการประกาศNamedTupleใหม่รวมกับโมดูลการพิมพ์เป็นสวรรค์!

ปรับปรุงการประกาศ NamedTuple

ตั้งแต่Python 3.6มันค่อนข้างเรียบง่ายและสวยงาม (IMHO) ตราบใดที่คุณสามารถมีชีวิตอยู่กับความไม่เปลี่ยนแปลง

วิธีใหม่ของการประกาศ NamedTuplesถูกนำมาใช้ซึ่งจะช่วยให้ประเภทคำอธิบายประกอบเช่นกัน:

from typing import NamedTuple


class User(NamedTuple):
    name: str


class MyStruct(NamedTuple):
    foo: str
    bar: int
    baz: list
    qux: User


my_item = MyStruct('foo', 0, ['baz'], User('peter'))

print(my_item) # MyStruct(foo='foo', bar=0, baz=['baz'], qux=User(name='peter'))

6
Mate คุณเพิ่งทำวันของฉัน - เปลี่ยนไม่ได้ - ขอบคุณ: D
Dmitry Arkhipenko

10
dataclassโมดูลใหม่ในหลาม 3.7 pip install dataclassesแต่คุณสามารถ มันเป็น backport ของ Python 3.6 pypi.org/project/dataclasses/#description
Lavande

+1 สำหรับการปรับปรุงการประกาศ NamedTuple วิธีการแบบเก่านั้นไม่น่าอ่านเลยถ้าคุณมีตัวแปรหลายตัว ...
38490

@Lavande ฉันขอทราบว่าการเปลี่ยนแปลงที่เกิดขึ้นระหว่าง 3.6 และ 3.7 นั้นคุณต้องย้อนกลับไปหนึ่งเวอร์ชันรอง ...
น้ำแข็งสีม่วง

1
@PurpleIce เป็นการใช้งาน PEP 557, Data Classes @dataclassรายละเอียดอยู่ที่นี่: pypi.org/project/dataclasses/#description
Lavande

96

คุณสามารถใช้ tuple สำหรับสิ่งต่างๆมากมายที่คุณจะใช้ struct ใน C (เช่น x, y พิกัดหรือสี RGB เป็นต้น)

สำหรับทุกสิ่งอื่นคุณสามารถใช้พจนานุกรมหรือคลาสยูทิลิตี้เช่นนี้ :

>>> class Bunch:
...     def __init__(self, **kwds):
...         self.__dict__.update(kwds)
...
>>> mystruct = Bunch(field1=value1, field2=value2)

ฉันคิดว่าการอภิปราย "ที่ชัดเจน" อยู่ที่นี่แล้วใน Python Cookbook เวอร์ชันที่เผยแพร่


5
ชั้นว่างจะทำเช่นเดียวกันหรือไม่
Kurt Liu

44
หมายเหตุถ้าคุณยังใหม่กับงูหลาม: tuples ถูกสร้างขึ้นเพียงอ่านครั้งเดียวซึ่งแตกต่างจาก C structs
LeBleu

2
@ KurtLiu ไม่มันอาจจะพูดได้TypeError: this constructor takes no arguments
Evgeni Sergeev

84

บางทีคุณกำลังมองหา Structs ที่ไม่มี constructors:

class Sample:
  name = ''
  average = 0.0
  values = None # list cannot be initialized here!


s1 = Sample()
s1.name = "sample 1"
s1.values = []
s1.values.append(1)
s1.values.append(2)
s1.values.append(3)

s2 = Sample()
s2.name = "sample 2"
s2.values = []
s2.values.append(4)

for v in s1.values:   # prints 1,2,3 --> OK.
  print v
print "***"
for v in s2.values:   # prints 4 --> OK.
  print v

5
สิ่งที่คุณทำอยู่ที่นี่ทำงานได้ในทางเทคนิค แต่อาจไม่ปรากฏแก่ผู้ใช้หลายรายทันทีว่าทำไมมันถึงใช้งานได้ ประกาศของคุณภายใต้class Sample:ไม่ได้ทำอะไรทันที; พวกเขาตั้งค่าคุณสมบัติระดับ Sample.nameผู้ที่จะสามารถเข้าถึงได้เป็นเช่น
Channing Moore

22
สิ่งที่คุณกำลังทำจริง ๆคือการเพิ่มคุณสมบัติอินสแตนซ์ให้กับวัตถุs1และs2ในขณะทำงาน คุณสามารถเพิ่มหรือแก้ไขแอnameททริบิวต์ในอินสแตนซ์ของคลาสใดก็ได้ไม่ว่าคลาสนั้นจะมีแอnameททริบิวต์หรือไม่ก็ตาม ปัญหาการใช้งานที่ใหญ่ที่สุดในการทำเช่นนี้ก็คืออินสแตนซ์ที่ต่างกันของคลาสเดียวกันจะทำงานแตกต่างกันไปขึ้นอยู่กับว่าคุณตั้งค่าไว้nameหรือไม่ ถ้าคุณปรับปรุงSample.nameวัตถุใด ๆ โดยไม่ต้องกำหนดอย่างชัดเจนสถานที่ให้บริการจะกลับมาใหม่name name
Channing Moore

2
สิ่งนี้ใกล้เคียงกับโครงสร้าง - สั้น 'คลาส' โดยไม่มีเมธอด 'ฟิลด์' (แอตทริบิวต์คลาสฉันรู้) โดยมีค่าเริ่มต้น ตราบใดที่มันไม่ใช่ประเภทที่ไม่แน่นอน (dict, list) คุณก็สบายดี แน่นอนว่าคุณสามารถปะทะกับการตรวจสอบ PEP-8 หรือการตรวจสอบ IDE แบบ "เป็นมิตร" เช่นเดียวกับคลาส "PyCharm" ที่ไม่มีวิธีการเริ่มต้น "
Tomasz Gandor

4
ฉันทดลองผลข้างเคียงที่อธิบายโดย Channing Moore ไม่คุ้มค่ากับการประหยัดของselfคำค้นหาและคอนสตรัคเตอร์หากคุณถามฉัน ฉันขอขอบคุณถ้าโฮเซ่สามารถแก้ไขคำตอบของเขาเพื่อเพิ่มข้อความเตือนเกี่ยวกับความเสี่ยงของการแบ่งปันค่าโดยบังเอิญในกรณีต่างๆ
Stéphane C.

@ChanningMoore: ฉันพยายามที่จะสร้างปัญหาที่คุณอธิบาย แต่ล้มเหลว คุณช่วยนำเสนอตัวอย่างการทำงานขั้นต่ำที่ปัญหาปรากฏขึ้น
gebbissimo

67

แล้วพจนานุกรมล่ะ

บางสิ่งเช่นนี้

myStruct = {'field1': 'some val', 'field2': 'some val'}

จากนั้นคุณสามารถใช้สิ่งนี้เพื่อจัดการค่า:

print myStruct['field1']
myStruct['field2'] = 'some other values'

และค่าไม่จำเป็นต้องเป็นสตริง พวกมันอาจเป็นวัตถุอื่นใดก็ได้


34
นี่เป็นวิธีการของฉันเช่นกัน แต่ฉันรู้สึกว่ามันอันตรายอย่างแน่นอนเพราะพจนานุกรมสามารถยอมรับสิ่งใดก็ได้สำหรับคีย์ จะไม่มีข้อผิดพลาดหากฉันตั้งค่า myStruct ["ffield"] เมื่อฉันต้องการตั้งค่า myStruct ["field"] ปัญหาอาจ (หรืออาจไม่) ปรากฏเมื่อฉันใช้หรือใช้ myStruct ["field"] ใหม่ในภายหลัง ฉันชอบแนวทางของ PabloG
mobabo

มีปัญหาเดียวกันกับ PabloG's ลองเพิ่มรหัสต่อไปนี้pt3.w = 1 print pt3.w ในภาษาที่มี dicts จะดีกว่าที่จะใช้พวกเขาโดยเฉพาะอย่างยิ่งสำหรับวัตถุที่เป็นอนุกรมเนื่องจากคุณสามารถใช้ json นำเข้าโดยอัตโนมัติเพื่อบันทึกพวกเขาและห้องสมุดอนุกรมอื่น ๆ ตราบใดที่คุณไม่แปลก สิ่งที่อยู่ภายใน Dict ของคุณ Dicts เป็นวิธีแก้ปัญหาในการแยกข้อมูลและตรรกะออกจากกันและดีกว่า structs สำหรับผู้ที่ไม่ต้องการเขียนฟังก์ชัน serialize และ unserialize ที่กำหนดเองและไม่ต้องการใช้ serializers ที่ไม่ใช่แบบพกพาเช่น pickle
Poikilos

27

dF: มันเจ๋งมาก ... ฉันไม่รู้ว่าฉันสามารถเข้าถึงฟิลด์ในชั้นเรียนโดยใช้ dict

Mark: สถานการณ์ที่ฉันต้องการฉันจะได้สิ่งนี้อย่างแม่นยำเมื่อฉันต้องการ tuple แต่ไม่มีอะไรที่ "หนัก" เหมือนพจนานุกรม

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

... ซึ่งนำเราไปสู่ความคิดเห็นที่สองของคุณ การเชื่อว่า Python dicts นั้นเป็น "หนัก" เป็นแนวคิดที่ไม่มากนัก และการอ่านความคิดเห็นดังกล่าวทำให้ Python Zen ของฉันตาย นั่นไม่ดีเลย

คุณเห็นเมื่อคุณประกาศชั้นเรียนที่คุณกำลังสร้าง wrapper ที่ซับซ้อนรอบ ๆ พจนานุกรม - ดังนั้นถ้ามีอะไรคุณกำลังเพิ่มโอเวอร์เฮดมากกว่าการใช้พจนานุกรมง่าย ๆ ค่าโสหุ้ยซึ่งโดยวิธีไม่มีความหมายในกรณีใด ๆ หากคุณกำลังทำงานกับแอพพลิเคชั่นที่สำคัญด้านประสิทธิภาพให้ใช้ C หรือบางอย่าง


5
# 1, Cython! = CPython ฉันคิดว่าคุณกำลังพูดถึง CPython การใช้งาน Python ที่เขียนใน C ไม่ใช่ Cython โครงการที่จะคอมไพล์รหัส Python เป็น C code ฉันแก้ไขคำตอบของคุณเพื่อแก้ไข # 2 ฉันคิดว่าเมื่อเขาพูดว่า dicts หนักเขาพูดถึงไวยากรณ์ self['member']มีความยาวมากกว่า 3 อักขระself.memberและอักขระเหล่านั้นค่อนข้างเป็นมิตรกับข้อมือ
ArtOfWarfare

19

คุณสามารถคลาสย่อยโครงสร้าง C ที่มีอยู่ในไลบรารีมาตรฐาน ctypesโมดูลมีระดับโครงสร้าง ตัวอย่างจากเอกสาร:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = [("x", c_int),
...                 ("y", c_int)]
...
>>> point = POINT(10, 20)
>>> print point.x, point.y
10 20
>>> point = POINT(y=5)
>>> print point.x, point.y
0 5
>>> POINT(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: too many initializers
>>>
>>> class RECT(Structure):
...     _fields_ = [("upperleft", POINT),
...                 ("lowerright", POINT)]
...
>>> rc = RECT(point)
>>> print rc.upperleft.x, rc.upperleft.y
0 5
>>> print rc.lowerright.x, rc.lowerright.y
0 0
>>>

18

ฉันต้องการเพิ่มโซลูชันที่ใช้ช่อง :

class Point:
    __slots__ = ["x", "y"]
    def __init__(self, x, y):
        self.x = x
        self.y = y

ตรวจสอบเอกสารประกอบสำหรับสล็อตอย่างชัดเจน แต่คำอธิบายอย่างรวดเร็วเกี่ยวกับสล็อตคือมันเป็นวิธีการของไพ ธ อน: "ถ้าคุณสามารถล็อคคุณสมบัติเหล่านี้และมีเพียงแอตทริบิวต์เหล่านี้ในชั้นเรียนที่คุณยอมรับว่าคุณจะไม่เพิ่มคุณสมบัติใหม่ใด ๆ เป็นอินสแตนซ์ (ใช่คุณสามารถเพิ่มคุณสมบัติใหม่ให้กับอินสแตนซ์ของคลาสดูตัวอย่างด้านล่าง) จากนั้นฉันจะจัดการกับการจัดสรรหน่วยความจำขนาดใหญ่ที่อนุญาตให้เพิ่มคุณสมบัติใหม่ให้กับอินสแตนซ์ของคลาสและใช้สิ่งที่ต้องการสำหรับแอตทริบิวต์slottedเหล่านี้"

ตัวอย่างการเพิ่มแอททริบิวไปยังอินสแตนซ์ของชั้นเรียน (ดังนั้นไม่ใช้ช่อง):

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p1 = Point(3,5)
p1.z = 8
print(p1.z)

ผลลัพธ์: 8

ตัวอย่างของการพยายามเพิ่มคุณสมบัติให้กับอินสแตนซ์ของคลาสที่มีการใช้ช่องโฆษณา:

class Point:
    __slots__ = ["x", "y"]
    def __init__(self, x, y):
        self.x = x
        self.y = y

p1 = Point(3,5)
p1.z = 8

เอาต์พุต: AttributeError: วัตถุ 'Point' ไม่มีแอตทริบิวต์ 'z'

สิ่งนี้สามารถทำงานเป็น struct ได้อย่างมีประสิทธิภาพและใช้หน่วยความจำน้อยกว่าคลาส (เช่นเดียวกับ struct แม้ว่าฉันจะไม่ได้วิจัยเท่าไหร่) ขอแนะนำให้ใช้ช่องถ้าคุณจะสร้างอินสแตนซ์ของวัตถุจำนวนมากและไม่จำเป็นต้องเพิ่มคุณลักษณะ วัตถุจุดเป็นตัวอย่างที่ดีของเรื่องนี้เพราะมันอาจเป็นไปได้ที่จะยกตัวอย่างหลายจุดเพื่ออธิบายชุดข้อมูล


17

นอกจากนี้คุณยังสามารถส่งพารามิเตอร์ init ไปยังตัวแปรอินสแตนซ์ตามตำแหน่ง

# Abstract struct class       
class Struct:
    def __init__ (self, *argv, **argd):
        if len(argd):
            # Update by dictionary
            self.__dict__.update (argd)
        else:
            # Update by position
            attrs = filter (lambda x: x[0:2] != "__", dir(self))
            for n in range(len(argv)):
                setattr(self, attrs[n], argv[n])

# Specific class
class Point3dStruct (Struct):
    x = 0
    y = 0
    z = 0

pt1 = Point3dStruct()
pt1.x = 10

print pt1.x
print "-"*10

pt2 = Point3dStruct(5, 6)

print pt2.x, pt2.y
print "-"*10

pt3 = Point3dStruct (x=1, y=2, z=3)
print pt3.x, pt3.y, pt3.z
print "-"*10

7
การอัปเดตตามตำแหน่งจะไม่สนใจลำดับการประกาศของแอตทริบิวต์และใช้การเรียงลำดับตามตัวอักษรแทน ดังนั้นหากคุณเปลี่ยนลำดับของบรรทัดในPoint3dStructการประกาศPoint3dStruct(5, 6)จะไม่ทำงานตามที่คาดไว้ มันแปลกที่ไม่มีใครเขียนเรื่องนี้ตลอด 6 ปี
ไพฑูรย์

สามารถเพิ่มเวอร์ชัน Python 3 ลงในโค้ดที่น่ากลัวของคุณได้หรือไม่ การทำงานที่ดี! ฉันชอบที่คุณใช้สิ่งที่เป็นนามธรรมและทำให้ชัดเจนกับชั้นเรียนที่สองที่เฉพาะเจาะจง นั่นควรจะดีสำหรับการจัดการ / จับข้อผิดพลาด สำหรับ Python 3 เพียงแค่เปลี่ยนprint> print()และattrs[n]> next(attrs)(ตอนนี้ตัวกรองเป็นวัตถุที่สามารถทำซ้ำได้ของตัวเองและต้องการnext)
โจนาธานโคมาร์

10

เมื่อใดก็ตามที่ฉันต้องการ "วัตถุข้อมูลทันใจที่ทำหน้าที่เหมือนพจนานุกรม" (ฉันไม่คิดว่า C structs!) ฉันคิดว่าแฮ็กน่ารักนี้:

class Map(dict):
    def __init__(self, **kwargs):
        super(Map, self).__init__(**kwargs)
        self.__dict__ = self

ตอนนี้คุณสามารถพูดได้ว่า:

struct = Map(field1='foo', field2='bar', field3=42)

self.assertEquals('bar', struct.field2)
self.assertEquals(42, struct['field3'])

มีประโยชน์อย่างสมบูรณ์แบบสำหรับช่วงเวลาที่คุณต้องการ "ถุงข้อมูลที่ไม่ใช่คลาส" และเมื่อตั้งชื่อสิ่งที่ไม่เข้าใจ ...


ฉันใช้ pandas.Series (a = 42) ;-)
มาร์ค Horvath

8

คุณเข้าถึงโครงสร้าง C-Style ในหลามด้วยวิธีการดังต่อไปนี้

class cstruct:
    var_i = 0
    var_f = 0.0
    var_str = ""

ถ้าคุณเพียงต้องการใช้วัตถุของ cstruct

obj = cstruct()
obj.var_i = 50
obj.var_f = 50.00
obj.var_str = "fifty"
print "cstruct: obj i=%d f=%f s=%s" %(obj.var_i, obj.var_f, obj.var_str)

ถ้าคุณต้องการที่จะสร้างอาร์เรย์ของวัตถุของ cstruct

obj_array = [cstruct() for i in range(10)]
obj_array[0].var_i = 10
obj_array[0].var_f = 10.00
obj_array[0].var_str = "ten"

#go ahead and fill rest of array instaces of struct

#print all the value
for i in range(10):
    print "cstruct: obj_array i=%d f=%f s=%s" %(obj_array[i].var_i, obj_array[i].var_f, obj_array[i].var_str)

หมายเหตุ: แทนชื่อ 'cstruct' โปรดใช้ชื่อ struct ของคุณแทน var_i, var_f, var_str โปรดกำหนดตัวแปรสมาชิกโครงสร้างของคุณ


3
นี่แตกต่างจากที่อยู่ในstackoverflow.com/a/3761729/1877426หรือไม่?
lagweezle

8

บางคำตอบที่นี่มีความละเอียดมาก ตัวเลือกที่ง่ายที่สุดที่ฉันพบคือ (จาก: http://norvig.com/python-iaq.html ):

class Struct:
    "A structure that can have any fields defined."
    def __init__(self, **entries): self.__dict__.update(entries)

initialising:

>>> options = Struct(answer=42, linelen=80, font='courier')
>>> options.answer
42

เพิ่มมากขึ้น:

>>> options.cat = "dog"
>>> options.cat
dog

แก้ไข:ขออภัยไม่เห็นตัวอย่างนี้ลดลงอีก


5

อาจจะช้าไปหน่อย แต่ฉันก็แก้ปัญหาโดยใช้ Python Meta-Classes (เวอร์ชันมัณฑนากรด้านล่างด้วย)

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

ตัวอย่างของฉันไม่มีการตรวจสอบข้อผิดพลาดดังนั้นจึงง่ายต่อการติดตาม

class MyStruct(type):
    def __call__(cls, *args, **kwargs):
        names = cls.__init__.func_code.co_varnames[1:]

        self = type.__call__(cls, *args, **kwargs)

        for name, value in zip(names, args):
            setattr(self , name, value)

        for name, value in kwargs.iteritems():
            setattr(self , name, value)
        return self 

ที่นี่มันอยู่ในการดำเนินการ

>>> class MyClass(object):
    __metaclass__ = MyStruct
    def __init__(self, a, b, c):
        pass


>>> my_instance = MyClass(1, 2, 3)
>>> my_instance.a
1
>>> 

ฉันโพสต์ไว้บน redditและ/ u / matchuโพสต์เวอร์ชันมัณฑนากรซึ่งสะอาดกว่า ฉันขอแนะนำให้คุณใช้มันหากคุณไม่ต้องการขยายรุ่น metaclass

>>> def init_all_args(fn):
    @wraps(fn)
    def wrapped_init(self, *args, **kwargs):
        names = fn.func_code.co_varnames[1:]

        for name, value in zip(names, args):
            setattr(self, name, value)

        for name, value in kwargs.iteritems():
            setattr(self, name, value)

    return wrapped_init

>>> class Test(object):
    @init_all_args
    def __init__(self, a, b):
        pass


>>> a = Test(1, 2)
>>> a.a
1
>>> 

Damnit - ฉันใช้เวลาสองชั่วโมงในวันนี้เขียนมัณฑนากรของตัวเองเพื่อทำสิ่งนี้แล้วฉันก็พบสิ่งนี้ อย่างไรก็ตามโพสต์ของฉันเพราะมันจัดการค่าเริ่มต้นในขณะที่คุณไม่ได้ stackoverflow.com/a/32448434/901641
ArtOfWarfare

+1 สำหรับการกล่าวถึง func_code เริ่มขุดในทิศทางนั้นและพบสิ่งที่น่าสนใจมากมายที่นั่น
wombatonfire

5

ฉันเขียนมัณฑนากรซึ่งคุณสามารถใช้กับวิธีการใด ๆ เพื่อให้อาร์กิวเมนต์ทั้งหมดที่ส่งผ่านหรือค่าเริ่มต้นถูกกำหนดให้กับอินสแตนซ์

def argumentsToAttributes(method):
    argumentNames = method.func_code.co_varnames[1:]

    # Generate a dictionary of default values:
    defaultsDict = {}
    defaults = method.func_defaults if method.func_defaults else ()
    for i, default in enumerate(defaults, start = len(argumentNames) - len(defaults)):
        defaultsDict[argumentNames[i]] = default

    def newMethod(self, *args, **kwargs):
        # Use the positional arguments.
        for name, value in zip(argumentNames, args):
            setattr(self, name, value)

        # Add the key word arguments. If anything is missing, use the default.
        for name in argumentNames[len(args):]:
            setattr(self, name, kwargs.get(name, defaultsDict[name]))

        # Run whatever else the method needs to do.
        method(self, *args, **kwargs)

    return newMethod

การสาธิตอย่างรวดเร็ว หมายเหตุที่ผมใช้อาร์กิวเมนต์ตำแหน่งaใช้ค่าเริ่มต้นสำหรับการและอาร์กิวเมนต์ชื่อb cจากนั้นฉันพิมพ์การอ้างอิงทั้ง 3 selfรายการเพื่อแสดงว่าพวกเขาได้รับมอบหมายอย่างถูกต้องก่อนที่จะป้อนวิธีการ

class A(object):
    @argumentsToAttributes
    def __init__(self, a, b = 'Invisible', c = 'Hello'):
        print(self.a)
        print(self.b)
        print(self.c)

A('Why', c = 'Nothing')

โปรดทราบว่ามัณฑนากรของฉันควรจะทำงานด้วยวิธีการใด ๆ __init__ที่ไม่เพียง


5

ฉันไม่เห็นคำตอบนี้ที่นี่ดังนั้นฉันคิดว่าฉันจะเพิ่มเพราะฉันกำลังเรียน Python และเพิ่งค้นพบมัน หลามกวดวิชา (งูใหญ่ 2 ในกรณีนี้) จะช่วยให้ง่ายต่อไปและเป็นตัวอย่างที่มีประสิทธิภาพ:

class Employee:
    pass

john = Employee()  # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

นั่นคืออ็อบเจ็กต์คลาสว่างถูกสร้างขึ้นจากนั้นสร้างอินสแตนซ์และฟิลด์จะถูกเพิ่มแบบไดนามิก

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

class Employee:
    def __init__ (self):
        self.name = None # or whatever
        self.dept = None
        self.salary = None

ตอนนี้คุณสามารถดูว่าโปรแกรมจะคาดหวังว่าฟิลด์ใด

ทั้งสองมีแนวโน้มที่จะพิมพ์ผิดjohn.slarly = 1000จะประสบความสำเร็จ ยังใช้งานได้


4

นี่คือโซลูชันที่ใช้คลาส (ไม่มีอินสแตนซ์) เพื่อเก็บข้อมูล ฉันชอบที่วิธีนี้เกี่ยวข้องกับการพิมพ์น้อยมากและไม่จำเป็นต้องมีแพคเกจเพิ่มเติมอื่น ๆ

class myStruct:
    field1 = "one"
    field2 = "2"

คุณสามารถเพิ่มฟิลด์เพิ่มเติมได้ในภายหลังตามต้องการ:

myStruct.field3 = 3

หากต้องการรับค่าฟิลด์จะเข้าถึงได้ตามปกติ:

>>> myStruct.field1
'one'

2

โดยส่วนตัวแล้วฉันชอบตัวแปรนี้เช่นกัน มันยาวยืด@ คำตอบของ

class struct:
    def __init__(self, *sequential, **named):
        fields = dict(zip(sequential, [None]*len(sequential)), **named)
        self.__dict__.update(fields)
    def __repr__(self):
        return str(self.__dict__)

สนับสนุนสองโหมดของการเริ่มต้น (ที่สามารถผสม):

# Struct with field1, field2, field3 that are initialized to None.
mystruct1 = struct("field1", "field2", "field3") 
# Struct with field1, field2, field3 that are initialized according to arguments.
mystruct2 = struct(field1=1, field2=2, field3=3)

นอกจากนี้มันยังพิมพ์ได้ดีกว่า:

print(mystruct2)
# Prints: {'field3': 3, 'field1': 1, 'field2': 2}

2

โซลูชันต่อโครงสร้างต่อไปนี้ได้รับแรงบันดาลใจจากการติดตั้ง namedtuple และคำตอบก่อนหน้านี้บางส่วน อย่างไรก็ตามแตกต่างจากชื่อ tuple มันไม่แน่นอนในค่าของมัน แต่เช่นเดียวกับโครงสร้างสไตล์ c ไม่เปลี่ยนรูปในชื่อ / คุณลักษณะซึ่งระดับปกติหรือทictไม่ได้

_class_template = """\
class {typename}:
def __init__(self, *args, **kwargs):
    fields = {field_names!r}

    for x in fields:
        setattr(self, x, None)            

    for name, value in zip(fields, args):
        setattr(self, name, value)

    for name, value in kwargs.items():
        setattr(self, name, value)            

def __repr__(self):
    return str(vars(self))

def __setattr__(self, name, value):
    if name not in {field_names!r}:
        raise KeyError("invalid name: %s" % name)
    object.__setattr__(self, name, value)            
"""

def struct(typename, field_names):

    class_definition = _class_template.format(
        typename = typename,
        field_names = field_names)

    namespace = dict(__name__='struct_%s' % typename)
    exec(class_definition, namespace)
    result = namespace[typename]
    result._source = class_definition

    return result

การใช้งาน:

Person = struct('Person', ['firstname','lastname'])
generic = Person()
michael = Person('Michael')
jones = Person(lastname = 'Jones')


In [168]: michael.middlename = 'ben'
Traceback (most recent call last):

  File "<ipython-input-168-b31c393c0d67>", line 1, in <module>
michael.middlename = 'ben'

  File "<string>", line 19, in __setattr__

KeyError: 'invalid name: middlename'

2

มีแพ็คเกจไพ ธ อนสำหรับจุดประสงค์นี้อย่างแน่นอน เห็นcstruct2py

cstruct2pyเป็นคลังหลามบริสุทธิ์สำหรับการสร้างคลาสหลามจากรหัส C และใช้พวกเขาในการแพ็คและแกะข้อมูล ไลบรารีสามารถแยกวิเคราะห์ headres C (struct, unions, enums และ arrays declarations) และจำลองในไพ ธ อน คลาส pythonic ที่สร้างขึ้นสามารถแยกวิเคราะห์และจัดเก็บข้อมูลได้

ตัวอย่างเช่น:

typedef struct {
  int x;
  int y;
} Point;

after generating pythonic class...
p = Point(x=0x1234, y=0x5678)
p.packed == "\x34\x12\x00\x00\x78\x56\x00\x00"

วิธีใช้

ก่อนอื่นเราต้องสร้างโครงสร้างไพ ธ อน:

import cstruct2py
parser = cstruct2py.c2py.Parser()
parser.parse_file('examples/example.h')

ตอนนี้เราสามารถนำเข้าชื่อทั้งหมดจากรหัส C:

parser.update_globals(globals())

เราสามารถทำสิ่งนั้นโดยตรง:

A = parser.parse_string('struct A { int x; int y;};')

ใช้ประเภทและกำหนดจากรหัส C

a = A()
a.x = 45
print a
buf = a.packed
b = A(buf)
print b
c = A('aaaa11112222', 2)
print c
print repr(c)

ผลลัพธ์จะเป็น:

{'x':0x2d, 'y':0x0}
{'x':0x2d, 'y':0x0}
{'x':0x31316161, 'y':0x32323131}
A('aa111122', x=0x31316161, y=0x32323131)

โคลน

สำหรับการcstruct2pyเรียกใช้โคลน:

git clone https://github.com/st0ky/cstruct2py.git --recursive

0

ฉันคิดว่าพจนานุกรมโครงสร้าง Python เหมาะสำหรับความต้องการนี้

d = dict{}
d[field1] = field1
d[field2] = field2
d[field2] = field3

0

https://stackoverflow.com/a/32448434/159695ไม่ทำงานใน Python3

https://stackoverflow.com/a/35993/159695ทำงานได้ใน Python3

และฉันขยายมันเพื่อเพิ่มค่าเริ่มต้น

class myStruct:
    def __init__(self, **kwds):
        self.x=0
        self.__dict__.update(kwds) # Must be last to accept assigned member variable.
    def __repr__(self):
        args = ['%s=%s' % (k, repr(v)) for (k,v) in vars(self).items()]
        return '%s(%s)' % ( self.__class__.__qualname__, ', '.join(args) )

a=myStruct()
b=myStruct(x=3,y='test')
c=myStruct(x='str')

>>> a
myStruct(x=0)
>>> b
myStruct(x=3, y='test')
>>> c
myStruct(x='str')

0

หากคุณไม่มี 3.7 สำหรับ @dataclass และต้องการความไม่แน่นอนรหัสต่อไปนี้อาจใช้ได้สำหรับคุณ มันค่อนข้างจัดทำเอกสารด้วยตนเองและเป็นมิตรกับ IDE (เติมข้อความอัตโนมัติ) ป้องกันการเขียนสิ่งต่าง ๆ ได้สองครั้งสามารถขยายได้อย่างง่ายดายและง่ายมากที่จะทดสอบว่าตัวแปรอินสแตนซ์ทั้งหมดได้รับการเริ่มต้นอย่างสมบูรณ์:

class Params():
    def __init__(self):
        self.var1 : int = None
        self.var2 : str = None

    def are_all_defined(self):
        for key, value in self.__dict__.items():
            assert (value is not None), "instance variable {} is still None".format(key)
        return True


params = Params()
params.var1 = 2
params.var2 = 'hello'
assert(params.are_all_defined)

0

นี่คือเคล็ดลับที่รวดเร็วและสกปรก:

>>> ms = Warning()
>>> ms.foo = 123
>>> ms.bar = 'akafrit'

มันทำงานอย่างไร มันแค่ใช้คลาส builtin Warning(มาจากException) อีกครั้งและใช้มันเหมือนเดิมคุณเป็นเจ้าของคลาสที่กำหนดไว้

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

โดยวิธีการที่ฉันพยายามที่จะหาสิ่งที่เรียบง่ายเหมือนms = object()แต่ไม่สามารถทำได้ (ตัวอย่างสุดท้ายนี้ไม่ทำงาน) หากคุณมีหนึ่งฉันสนใจ


0

วิธีที่ดีที่สุดที่ฉันพบคือการใช้คลาสพจนานุกรมที่กำหนดเองตามที่อธิบายในโพสต์นี้: https://stackoverflow.com/a/14620633/8484485

หากต้องการการสนับสนุนการเติมข้อความอัตโนมัติของ iPython เพียงแค่กำหนดฟังก์ชั่นdir () ดังนี้:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self
    def __dir__(self):
        return self.keys()

จากนั้นคุณกำหนดโครงสร้างหลอกของคุณดังนี้: (อันนี้ซ้อนอยู่)

my_struct=AttrDict ({
    'com1':AttrDict ({
        'inst':[0x05],
        'numbytes':2,
        'canpayload':False,
        'payload':None
    })
})

จากนั้นคุณสามารถเข้าถึงค่าภายใน my_struct ดังนี้:

print(my_struct.com1.inst)

=>[5]


0

NamedTupleสะดวกสบาย แต่ไม่มีใครแบ่งปันประสิทธิภาพและการจัดเก็บ

from typing import NamedTuple
import guppy  # pip install guppy
import timeit


class User:
    def __init__(self, name: str, uid: int):
        self.name = name
        self.uid = uid


class UserSlot:
    __slots__ = ('name', 'uid')

    def __init__(self, name: str, uid: int):
        self.name = name
        self.uid = uid


class UserTuple(NamedTuple):
    # __slots__ = ()  # AttributeError: Cannot overwrite NamedTuple attribute __slots__
    name: str
    uid: int


def get_fn(obj, attr_name: str):
    def get():
        getattr(obj, attr_name)
    return get
if 'memory test':
    obj = [User('Carson', 1) for _ in range(1000000)]      # Cumulative: 189138883
    obj_slot = [UserSlot('Carson', 1) for _ in range(1000000)]          # 77718299  <-- winner
    obj_namedtuple = [UserTuple('Carson', 1) for _ in range(1000000)]   # 85718297
    print(guppy.hpy().heap())  # Run this function individually. 
    """
    Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 1000000    24 112000000 34 112000000  34 dict of __main__.User
     1 1000000    24 64000000  19 176000000  53 __main__.UserTuple
     2 1000000    24 56000000  17 232000000  70 __main__.User
     3 1000000    24 56000000  17 288000000  87 __main__.UserSlot
     ...
    """

if 'performance test':
    obj = User('Carson', 1)
    obj_slot = UserSlot('Carson', 1)
    obj_tuple = UserTuple('Carson', 1)

    time_normal = min(timeit.repeat(get_fn(obj, 'name'), repeat=20))
    print(time_normal)  # 0.12550550000000005

    time_slot = min(timeit.repeat(get_fn(obj_slot, 'name'), repeat=20))
    print(time_slot)  # 0.1368690000000008

    time_tuple = min(timeit.repeat(get_fn(obj_tuple, 'name'), repeat=20))
    print(time_tuple)  # 0.16006120000000124

    print(time_tuple/time_slot)  # 1.1694481584580898  # The slot is almost 17% faster than NamedTuple on Windows. (Python 3.7.7)

หากคุณ__dict__ไม่ได้ใช้งานโปรดเลือกระหว่าง__slots__(ประสิทธิภาพที่สูงขึ้นและพื้นที่เก็บข้อมูล) และNamedTuple(ชัดเจนสำหรับการอ่านและการใช้)

คุณสามารถตรวจสอบลิงค์นี้ ( การใช้ช่อง ) เพื่อรับ__slots__ข้อมูลเพิ่มเติม

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