Struktur seperti-C dalam Python

446

Apakah ada cara untuk dengan mudah mendefinisikan struktur mirip-C dalam Python? Saya lelah menulis hal-hal seperti:

class MyStruct():
    def __init__(self, field1, field2, field3):
        self.field1 = field1
        self.field2 = field2
        self.field3 = field3
wesc
sumber
5
Secara semi-terkait, tipe data aljabar akan sangat luar biasa, tetapi untuk menggunakannya dengan baik, Anda biasanya perlu pencocokan pola.
Edward Z. Yang
51
Adakah yang salah dengan metode ini selain menulis yang membosankan?
levesque
2
Anda mungkin menemukan duct berguna: github.com/dorkitude/dstruct
Kyle Wild
10
@levesque lebih sulit untuk memasukkan ulang faktor tanpa kesalahan ketik, lebih sulit dibaca sekilas sambil membaca kode, daripadaMyStruct = namedtuple("MyStruct", "field1 field2 field3")
sam boosalis
1
pandas.Series(a=42).aharus melakukannya jika Anda seorang ilmuwan data ...
Mark Horvath

Jawaban:

341

Gunakan tuple bernama , yang ditambahkan ke modul koleksi di perpustakaan standar di Python 2.6. Anda juga dapat menggunakan resep tuple bernama Raymond Hettinger jika Anda perlu mendukung Python 2.4.

Ini bagus untuk contoh dasar Anda, tetapi juga mencakup banyak kasus tepi yang mungkin Anda temui nanti. Fragmen Anda di atas akan ditulis sebagai:

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

Jenis yang baru dibuat dapat digunakan seperti ini:

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

Anda juga dapat menggunakan argumen bernama:

m = MyStruct(field1="foo", field2="bar", field3="baz")
gz.
sumber
164
... tapi namupuple tidak bisa diubah. Contoh dalam OP bisa berubah.
mhowison
28
@ Howhowison - Dalam kasus saya, itu hanya nilai tambah.
ArtOfWarfare
3
Solusi yang bagus. Bagaimana Anda akan memutar melalui array tuple ini? Saya akan berasumsi bahwa bidang 1-3 harus memiliki nama yang sama di seluruh objek tuple.
Michael Smith
2
namedtuple dapat memiliki paling banyak empat argumen sehingga bagaimana kita dapat memetakan struktur dengan lebih banyak anggota data dengan yang bernama namesuple
Kapil
3
@ Kapil - Argumen kedua untuk namedtuple harus berupa daftar nama anggota. Daftar itu bisa panjang.
ArtOfWarfare
225

Pembaruan : Kelas Data

Dengan diperkenalkannya Kelas Data di Python 3.7 kami menjadi sangat dekat.

Contoh berikut mirip dengan contoh NamedTuple di bawah ini, tetapi objek yang dihasilkan bisa berubah dan memungkinkan untuk nilai-nilai default.

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)

Ini berfungsi baik dengan modul pengetikan baru jika Anda ingin menggunakan anotasi jenis yang lebih spesifik.

Saya sudah menunggu dengan putus asa untuk ini! Jika Anda bertanya kepada saya, Kelas Data dan deklarasi NamedTuple baru , dikombinasikan dengan modul pengetikan adalah anugerah!

Deklarasi NamedTuple yang ditingkatkan

Sejak Python 3.6 itu menjadi sangat sederhana dan indah (IMHO), selama Anda dapat hidup dengan kekekalan .

Cara baru untuk mendeklarasikan NamedTuples diperkenalkan, yang memungkinkan anotasi jenis juga:

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'))
Rotareti
sumber
6
Mate, kamu baru saja membuat hariku - dikte abadi - terima kasih: D
Dmitry Arkhipenko
10
The dataclassmodul baru di Python 3.7, tetapi Anda bisa pip install dataclasses. Ini adalah backport di Python 3.6. pypi.org/project/dataclasses/#description
Lavande
+1 untuk deklarasi NamedTuple yang ditingkatkan. Cara lama benar-benar tidak menyenangkan untuk dibaca jika Anda memiliki beberapa variabel ...
gebbissimo
@Lavande Bolehkah saya tahu perubahan apa yang terjadi antara 3,6 dan 3,7 yang harus Anda backport satu versi kecil ke belakang ...?
Purple Ice
1
@PurpleIce Ini adalah implementasi dari PEP 557, Kelas Data Detailnya@dataclass ada di sini: pypi.org/project/dataclasses/#description
Lavande
96

Anda dapat menggunakan tuple untuk banyak hal di mana Anda akan menggunakan struct di C (misalnya x, y koordinat atau warna RGB misalnya).

Untuk yang lainnya, Anda dapat menggunakan kamus, atau kelas utilitas seperti ini :

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

Saya pikir diskusi "pasti" ada di sini , dalam versi yang diterbitkan dari Python Cookbook.

dF.
sumber
5
Apakah kelas kosong hanya akan melakukan hal yang sama?
Kurt Liu
44
Catatan jika Anda baru menggunakan python: tuple hanya-baca sekali dibuat, tidak seperti C struct
LeBleu
2
@ KurtLiu Tidak, itu mungkin akan mengatakanTypeError: this constructor takes no arguments
Evgeni Sergeev
84

Mungkin Anda mencari Structs tanpa konstruktor:

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
Jose M Balaguer
sumber
5
Apa yang Anda lakukan di sini berfungsi, secara teknis, tetapi mungkin tidak segera jelas bagi banyak pengguna mengapa itu berfungsi. Pernyataan Anda di bawah class Sample:tidak segera melakukan apa pun; mereka mengatur atribut kelas. Itu selalu dapat diakses seperti misalnya Sample.name.
Channing Moore
22
Apa yang sebenarnya Anda lakukan adalah menambahkan properti instance ke objek s1dan s2saat runtime. Kecuali jika tidak dilarang, Anda dapat menambah atau mengubah nameatribut pada instance kelas apa pun kapan saja, baik kelas tersebut memiliki nameatribut atau tidak . Mungkin masalah fungsional terbesar dengan melakukan ini adalah bahwa instance berbeda dari kelas yang sama akan berperilaku berbeda tergantung pada apakah Anda telah menetapkan name. Jika Anda memperbarui Sample.name, objek apa pun tanpa nameproperti yang ditetapkan secara eksplisit akan mengembalikan yang baru name.
Channing Moore
2
Ini sedekat dengan 'kelas' singkat - tanpa 'metode', 'bidang' (atribut kelas, saya tahu) dengan nilai standar. Selama itu bukan tipe yang bisa berubah-ubah (dict, list), Anda baik-baik saja. Tentu saja, Anda dapat menekan PEP-8 atau "IDE" ramah memeriksa seperti "kelas tidak memiliki metode init " PyCharm .
Tomasz Gandor
4
Saya bereksperimen dengan efek samping yang dijelaskan oleh Channing Moore. Tidak sebanding dengan ekonomi beberapa selfkata kunci dan garis konstruktor jika Anda bertanya kepada saya. Saya akan menghargai jika Jose dapat mengedit jawabannya untuk menambahkan pesan peringatan tentang risiko berbagi nilai secara tidak sengaja di seluruh instance.
Stéphane C.
@ChanningMoore: Saya mencoba untuk membuat ulang masalah yang Anda gambarkan, tetapi gagal. Bisakah Anda menyajikan contoh kerja minimal di mana masalah muncul?
gebbissimo
67

Bagaimana dengan kamus?

Sesuatu seperti ini:

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

Kemudian Anda bisa menggunakan ini untuk memanipulasi nilai:

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

Dan nilainya tidak harus berupa string. Mereka bisa menjadi hampir semua objek lain.

Mark Biek
sumber
34
Ini telah menjadi pendekatan saya juga, tetapi saya merasa itu berbahaya justru karena kamus dapat menerima apa pun untuk kunci. Tidak akan ada kesalahan jika saya mengatur myStruct ["ffield"] ketika saya bermaksud mengatur myStruct ["field"]. Masalahnya mungkin (atau mungkin tidak) menjadi jelas ketika saya menggunakan atau menggunakan kembali myStruct ["bidang"] nanti. Saya suka pendekatan PabloG.
mobabo
Masalah yang sama ada dengan PabloG's. Coba tambahkan kode berikut ini ke dalam bukunya: pt3.w = 1 print pt3.w Dalam bahasa dengan dikt, lebih baik menggunakannya, terutama untuk objek yang serial, karena Anda dapat secara otomatis menggunakan impor json untuk menyimpannya dan perpustakaan serialisasi lainnya selama Anda tidak memiliki yang aneh barang-barang di dalam dikt Anda. Diktik adalah solusi untuk menjaga data dan logika terpisah dan lebih baik daripada struct untuk orang-orang yang tidak ingin menulis serialisasi kustom dan fungsi unserialize dan tidak ingin menggunakan serializers non-portabel seperti acar.
Poikilos
27

dF: itu sangat keren ... Saya tidak tahu bahwa saya bisa mengakses bidang dalam kelas menggunakan dict.

Mark: situasi yang kuharap aku punya ini tepatnya ketika aku menginginkan sebuah tuple tetapi tidak ada yang seberat kamus.

Anda dapat mengakses bidang kelas menggunakan kamus karena bidang kelas, metode, dan semua propertinya disimpan secara internal menggunakan dicts (setidaknya dalam CPython).

... Yang mengarahkan kami ke komentar kedua Anda. Percaya bahwa perintah Python "berat" adalah konsep yang sangat non-pythonistik. Dan membaca komentar seperti itu membunuh Python Zen saya. Itu tidak baik.

Anda tahu, ketika Anda mendeklarasikan kelas Anda sebenarnya membuat pembungkus yang cukup rumit di sekitar kamus - jadi, jika ada, Anda menambahkan lebih banyak overhead daripada menggunakan kamus sederhana. Bagaimanapun juga, overhead yang tidak ada artinya. Jika Anda bekerja pada aplikasi kritis kinerja, gunakan C atau sesuatu.

Vicent Marti
sumber
5
# 1, Cython! = CPython. Saya pikir Anda berbicara tentang CPython, implementasi Python yang ditulis dalam C, bukan Cython, sebuah proyek untuk lintas kompilasi kode Python menjadi kode C. Saya mengedit jawaban Anda untuk memperbaikinya. # 2, saya pikir ketika dia mengatakan diktta berat, dia mengacu pada sintaks. self['member']lebih dari 3 karakter self.member, dan semua karakter tersebut relatif tidak ramah pergelangan tangan.
ArtOfWarfare
19

Anda bisa subkelas struktur C yang tersedia di perpustakaan standar. The ctypes modul menyediakan kelas Struktur . Contoh dari dokumen:

>>> 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
>>>
Ella Rose
sumber
18

Saya juga ingin menambahkan solusi yang menggunakan slot :

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

Jelas memeriksa dokumentasi untuk slot tetapi penjelasan cepat slot adalah bahwa itu adalah cara python mengatakan: "Jika Anda dapat mengunci atribut ini dan hanya atribut ini ke dalam kelas sehingga Anda berkomitmen bahwa Anda tidak akan menambahkan atribut baru setelah kelas instantiated (ya Anda dapat menambahkan atribut baru ke instance kelas, lihat contoh di bawah) maka saya akan menghapus alokasi memori besar yang memungkinkan untuk menambahkan atribut baru ke instance kelas dan menggunakan apa yang saya butuhkan untuk atribut slotted ini ".

Contoh menambahkan atribut ke instance kelas (sehingga tidak menggunakan slot):

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

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

Output: 8

Contoh mencoba menambahkan atribut ke instance kelas di mana slot digunakan:

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

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

Output: AttributeError: objek 'Point' tidak memiliki atribut 'z'

Ini secara efektif dapat berfungsi sebagai struct dan menggunakan memori lebih sedikit daripada kelas (seperti struct akan, meskipun saya belum meneliti persis berapa banyak). Disarankan untuk menggunakan slot jika Anda akan membuat banyak instance objek dan tidak perlu menambahkan atribut. Objek titik adalah contoh yang baik dari ini karena ada kemungkinan bahwa orang dapat instantiate banyak poin untuk menggambarkan dataset.

Oamar Kanji
sumber
17

Anda juga dapat meneruskan parameter init ke variabel instan dengan posisi

# 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
PabloG
sumber
7
Memperbarui berdasarkan posisi mengabaikan urutan deklarasi atribut dan menggunakan penyortiran alfabet mereka sebagai gantinya. Jadi, jika Anda mengubah urutan baris dalam Point3dStructdeklarasi, Point3dStruct(5, 6)tidak akan berfungsi seperti yang diharapkan. Sungguh aneh bahwa tidak ada yang menulis ini dalam semua 6 tahun.
lapis
Bisakah menambahkan versi Python 3 ke kode hebat Anda? Kerja bagus! Saya suka bahwa Anda mengambil sesuatu yang abstrak dan membuatnya eksplisit dengan kelas spesifik kedua. Itu harus baik untuk penanganan / penangkapan kesalahan. Untuk Python 3, cukup ganti print> print(), dan attrs[n]> next(attrs)(filter sekarang adalah objek yang dapat diulang sendiri dan membutuhkan next).
Jonathan Komar
10

Setiap kali saya membutuhkan "objek data instan yang juga berperilaku seperti kamus" (Saya tidak memikirkan C struct!), Saya memikirkan hack lucu ini:

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

Sekarang Anda bisa mengatakan:

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

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

Sangat berguna untuk saat-saat ketika Anda membutuhkan "tas data yang BUKAN kelas", dan untuk saat namatuple tidak dapat dipahami ...

Phlip
sumber
Saya menggunakan panda. Seri (a = 42) ;-)
Mark Horvath
8

Anda mengakses struct C-Style dalam python dengan cara berikut.

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

jika Anda hanya ingin menggunakan objek cuct

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)

jika Anda ingin membuat array objek 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)

Catatan: alih-alih nama 'cstruct', harap gunakan nama struct Anda alih-alih var_i, var_f, var_str, harap tentukan variabel anggota struktur Anda.

Sujal Sheth
sumber
3
Apakah ini berbeda dari apa yang ada di stackoverflow.com/a/3761729/1877426 ?
lagweezle
8

Beberapa jawaban di sini sangat rumit. Opsi paling sederhana yang saya temukan adalah (dari: http://norvig.com/python-iaq.html ):

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

Inisialisasi:

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

menambahkan lebih banyak:

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

sunting: Maaf tidak melihat contoh ini lebih jauh ke bawah.

w_jay
sumber
5

Ini mungkin agak terlambat tapi saya membuat solusi menggunakan Python Meta-Classes (versi dekorator di bawah ini juga).

Ketika __init__dipanggil pada saat run time, ia mengambil setiap argumen dan nilainya dan menetapkannya sebagai variabel instan ke kelas Anda. Dengan cara ini Anda bisa membuat kelas struct-like tanpa harus menetapkan setiap nilai secara manual.

Contoh saya tidak memiliki pengecekan kesalahan sehingga lebih mudah diikuti.

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 

Ini dia beraksi.

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


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

Saya diposting di reddit dan / u / matchu memposting versi dekorator yang lebih bersih. Saya mendorong Anda untuk menggunakannya kecuali jika Anda ingin memperluas versi 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
>>> 
pengguna124757
sumber
Sialan - saya menghabiskan dua jam hari ini menulis dekorator saya sendiri untuk melakukan ini dan kemudian saya menemukan ini. Ngomong-ngomong, memposting milikku karena menangani nilai default sedangkan milikmu tidak. stackoverflow.com/a/32448434/901641
ArtOfWarfare
+1 untuk menyebutkan func_code. Mulai menggali ke arah itu dan menemukan banyak hal menarik di sana.
wombatonfire
5

Saya menulis dekorator yang dapat Anda gunakan pada metode apa saja untuk membuatnya sehingga semua argumen yang diteruskan, atau default apa pun, ditetapkan ke instance.

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

Peragaan cepat. Perhatikan bahwa saya menggunakan argumen posisi a, gunakan nilai default untuk b, dan argumen bernama c. Saya kemudian mencetak semua 3 referensi self, untuk menunjukkan bahwa mereka telah ditugaskan dengan benar sebelum metode dimasukkan.

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

Perhatikan bahwa dekorator saya harus bekerja dengan metode apa pun, bukan hanya __init__.

ArtOfWarfare
sumber
5

Saya tidak melihat jawaban ini di sini, jadi saya pikir saya akan menambahkannya karena saya condong ke Python sekarang dan baru saja menemukannya. The Python tutorial (Python 2 dalam hal ini) memberikan sederhana berikut dan contoh yang efektif:

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

Yaitu, objek kelas kosong dibuat, kemudian dipakai, dan bidang ditambahkan secara dinamis.

Sisi baiknya adalah ini sangat sederhana. Kelemahannya adalah tidak mendokumentasikan diri sendiri (anggota yang dituju tidak terdaftar di manapun di "definisi" kelas), dan bidang yang tidak disetel dapat menyebabkan masalah saat diakses. Kedua masalah tersebut dapat diselesaikan dengan:

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

Sekarang sekilas Anda setidaknya bisa melihat bidang apa yang akan Anda harapkan dari program ini.

Keduanya rentan terhadap kesalahan ketik, john.slarly = 1000akan berhasil. Tetap saja berhasil.

Jason C
sumber
4

Berikut ini adalah solusi yang menggunakan kelas (tidak pernah dipakai) untuk menyimpan data. Saya suka bahwa cara ini melibatkan sangat sedikit pengetikan dan tidak memerlukan paket tambahan dll.

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

Anda dapat menambahkan lebih banyak bidang nanti, sesuai kebutuhan:

myStruct.field3 = 3

Untuk mendapatkan nilai, bidang diakses seperti biasa:

>>> myStruct.field1
'one'
Jochen
sumber
2

Secara pribadi, saya suka varian ini juga. Itu memperluas jawaban @ dF .

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

Ini mendukung dua mode inisialisasi (yang dapat dicampur):

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

Juga, ini mencetak lebih baik:

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

Solusi untuk struct berikut ini diilhami oleh implementasi namesuple dan beberapa jawaban sebelumnya. Namun, tidak seperti nametuple itu bisa berubah, dalam nilai-nilai itu, tetapi seperti struct c-style yang tidak dapat diubah dalam nama / atribut, yang bukan kelas atau dikt normal.

_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

Pemakaian:

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

Ada paket python persis untuk tujuan ini. lihat cstruct2py

cstruct2pyadalah pustaka python murni untuk menghasilkan kelas python dari kode C dan menggunakannya untuk mengemas dan membongkar data. Perpustakaan dapat mengurai C headres (struct, unions, enums, dan deklarasi array) dan meniru mereka dengan python. Kelas pythonic yang dihasilkan dapat menguraikan dan mengemas data.

Sebagai contoh:

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"

Cara Penggunaan

Pertama kita perlu membuat struct pythonic:

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

Sekarang kita dapat mengimpor semua nama dari kode C:

parser.update_globals(globals())

Kami juga dapat melakukannya secara langsung:

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

Menggunakan tipe dan definisi dari kode 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)

Outputnya adalah:

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

Klon

Untuk cstruct2pymenjalankan klon :

git clone https://github.com/st0ky/cstruct2py.git --recursive
שמואל ביאליסטוקי
sumber
0

Saya pikir kamus struktur Python cocok untuk persyaratan ini.

d = dict{}
d[field1] = field1
d[field2] = field2
d[field2] = field3
Yujun Li
sumber
0

https://stackoverflow.com/a/32448434/159695 tidak berfungsi di Python3.

https://stackoverflow.com/a/35993/159695 berfungsi di Python3.

Dan saya memperluasnya untuk menambahkan nilai default.

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')
Galaksi
sumber
0

Jika Anda tidak memiliki 3,7 untuk @dataclass dan perlu mutabilitas, kode berikut ini mungkin cocok untuk Anda. Ini cukup mendokumentasikan diri sendiri dan ramah-IDE (auto-complete), mencegah penulisan dua kali, mudah diperpanjang dan sangat mudah untuk menguji bahwa semua variabel instan sepenuhnya diinisialisasi:

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

Ini trik cepat dan kotor:

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

Bagaimana cara kerjanya? Itu hanya menggunakan kembali kelas builtin Warning(berasal dari Exception) dan menggunakannya karena Anda sendiri kelas yang ditentukan.

Poin baiknya adalah Anda tidak perlu mengimpor atau mendefinisikan apa pun terlebih dahulu, bahwa "Peringatan" adalah nama pendek, dan itu juga menjelaskan bahwa Anda melakukan sesuatu yang kotor yang tidak boleh digunakan di tempat lain selain naskah kecil Anda.

Ngomong-ngomong, saya mencoba menemukan sesuatu yang lebih sederhana seperti ms = object()tetapi tidak bisa (contoh terakhir ini tidak berfungsi). Jika Anda memilikinya, saya tertarik.

calandoa
sumber
0

Cara terbaik yang saya temukan untuk melakukan ini adalah dengan menggunakan kelas kamus khusus seperti yang dijelaskan dalam posting ini: https://stackoverflow.com/a/14620633/8484485

Jika dukungan pelengkapan otomatis iPython diperlukan, cukup tentukan fungsi dir () seperti ini:

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

Anda kemudian mendefinisikan struct semu Anda seperti: (ini bersarang)

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

Anda kemudian dapat mengakses nilai-nilai di dalam my_struct seperti ini:

print(my_struct.com1.inst)

=>[5]

Tioneb
sumber
0

NamedTuple nyaman. tetapi tidak ada yang berbagi kinerja dan penyimpanan.

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)

Jika Anda __dict__tidak menggunakan, silakan pilih antara __slots__(kinerja dan penyimpanan lebih tinggi) dan NamedTuple(jelas untuk membaca dan menggunakan)

Anda dapat meninjau tautan ini ( Penggunaan slot ) untuk mendapatkan __slots__informasi lebih lanjut.

Carson
sumber