C-ähnliche Strukturen in Python


446

Gibt es eine Möglichkeit, eine C-ähnliche Struktur in Python bequem zu definieren? Ich bin es leid Dinge zu schreiben wie:

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

5
Halbbezogen wären algebraische Datentypen absolut wunderbar, aber um sie gut zu nutzen, benötigen Sie normalerweise einen Mustervergleich.
Edward Z. Yang

50
Stimmt etwas mit dieser Methode nicht, außer dass das Schreiben mühsam ist?
Levesque

2
Sie können dstruct nützlich finden: github.com/dorkitude/dstruct
Kyle Wild

10
@levesque schwerer ohne Tippfehler neu zu faktorisieren, schwerer auf einen Blick zu lesen, während Code MyStruct = namedtuple("MyStruct", "field1 field2 field3")
überflogen

1
pandas.Series(a=42).asollte es tun, wenn Sie ein Datenwissenschaftler sind ...
Mark Horvath

Antworten:


341

Verwenden Sie ein benanntes Tupel , das dem Sammlungsmodul in der Standardbibliothek in Python 2.6 hinzugefügt wurde . Es ist auch möglich, das benannte Tupelrezept von Raymond Hettinger zu verwenden, wenn Sie Python 2.4 unterstützen müssen.

Es ist gut für Ihr grundlegendes Beispiel, deckt aber auch eine Reihe von Randfällen ab, auf die Sie später möglicherweise stoßen. Ihr Fragment oben würde geschrieben werden als:

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

Der neu erstellte Typ kann folgendermaßen verwendet werden:

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

Sie können auch benannte Argumente verwenden:

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

164
... aber namedtuple ist unveränderlich. Das Beispiel im OP ist veränderlich.
mhowison

28
@mhowison - In meinem Fall ist das nur ein Plus.
ArtOfWarfare

3
Schöne Lösung. Wie würden Sie eine Reihe dieser Tupel durchlaufen? Ich würde annehmen, dass die Felder 1-3 über Tupelobjekte hinweg dieselben Namen haben müssen.
Michael Smith

2
namedtuple kann höchstens vier Argumente haben, wie wir also die Struktur mit mehr Datenelementen mit dem entsprechenden namedtuple abbilden können
Kapil

3
@Kapil - Das zweite Argument für namedtuple sollte eine Liste der Namen von Mitgliedern sein. Diese Liste kann beliebig lang sein.
ArtOfWarfare

224

Update : Datenklassen

Mit der Einführung von Datenklassen in Python 3.7 kommen wir uns sehr nahe.

Das folgende Beispiel ähnelt dem folgenden NamedTuple- Beispiel, das resultierende Objekt ist jedoch veränderbar und lässt Standardwerte zu.

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)

Dies funktioniert gut mit dem neuen Typisierungsmodul, falls Sie spezifischere Typanmerkungen verwenden möchten.

Ich habe verzweifelt darauf gewartet! Wenn Sie mich fragen, Datenklassen und die neue NamedTuple Erklärung, mit der kombinierten Eingabe - Modul sind ein Geschenk des Himmels!

Verbesserte NamedTuple-Deklaration

Seit Python 3.6 ist es ziemlich einfach und schön geworden (IMHO), solange man mit Unveränderlichkeit leben kann .

Eine neue Art und Weise NamedTuples deklarieren wurde eingeführt, die für ermöglicht Typenannotationen auch:

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
Kumpel, du hast gerade meinen Tag gemacht - unveränderliche Diktate - danke: D
Dmitry Arkhipenko

10
Das dataclassModul ist neu in Python 3.7, aber Sie können pip install dataclasses. Es ist der Backport auf Python 3.6. pypi.org/project/dataclasses/#description
Lavande

+1 für eine verbesserte NamedTuple-Deklaration. Der alte Weg war wirklich unangenehm zu lesen, wenn Sie mehrere Variablen hatten ...
gebbissimo

@Lavande Darf ich wissen, welche bahnbrechenden Änderungen zwischen 3.6 und 3.7 passiert sind, dass Sie eine kleinere Version zurückportieren müssen ...?
Lila Eis

1
@PurpleIce Es ist eine Implementierung des PEP - 557 war, Datenklassen @dataclassDie Details sind hier: pypi.org/project/dataclasses/#description
Lavande

96

Sie können ein Tupel für viele Dinge verwenden, bei denen Sie eine Struktur in C verwenden würden (z. B. x-, y-Koordinaten oder RGB-Farben).

Für alles andere können Sie ein Wörterbuch oder eine Dienstprogrammklasse wie diese verwenden :

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

Ich denke, die "endgültige" Diskussion ist hier in der veröffentlichten Version des Python-Kochbuchs.


5
Würde eine leere Klasse dasselbe tun?
Kurt Liu

44
Hinweis, wenn Sie Python noch nicht kennen: Tupel sind nach ihrer Erstellung
schreibgeschützt

2
@ KurtLiu Nein, es würde wahrscheinlich sagenTypeError: this constructor takes no arguments
Evgeni Sergeev

84

Vielleicht suchen Sie nach Strukturen ohne Konstruktoren:

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
Was Sie hier tun, funktioniert technisch gesehen, aber vielen Benutzern ist wahrscheinlich nicht sofort klar, warum es funktioniert. Ihre Erklärungen unter class Sample:tun nichts sofort; Sie setzen Klassenattribute. Auf diese kann immer zugegriffen werden als z Sample.name.
Channing Moore

22
Was du eigentlich bist tun, ist das Hinzufügen von Instanzeigenschaften zu den Objekten s1unds2 zur Laufzeit. Sofern nicht anders verboten, können Sie das nameAttribut jederzeit für jede Instanz einer Klasse hinzufügen oder ändern , unabhängig davon, ob die Klasse über ein nameAttribut verfügt oder nicht . Das wahrscheinlich größte funktionale Problem dabei ist, dass sich verschiedene Instanzen derselben Klasse unterschiedlich verhalten, je nachdem, ob Sie festgelegt haben name. Wenn Sie aktualisieren Sample.name, geben alle Objekte ohne explizit festgelegte nameEigenschaft die neue zurück name.
Channing Moore

2
Dies ist so nah wie möglich an einer Struktur - kurze 'Klasse' ohne Methoden, 'Felder' (Klassenattribute, ich weiß) mit Standardwerten. Solange es sich nicht um einen veränderlichen Typ handelt (Diktat, Liste), geht es Ihnen gut. Natürlich können Sie gegen PEP-8 oder "freundliche" IDE-Checks schlagen, wie PyCharms "Klasse keine hat Init- Methode" antreten.
Tomasz Gandor

4
Ich habe die von Channing Moore beschriebene Nebenwirkung experimentiert. Es lohnt sich nicht, ein paar selfSchlüsselwörter und eine Konstruktorzeile zu sparen, wenn Sie mich fragen. Ich würde mich freuen, wenn Jose seine Antwort bearbeiten könnte, um eine Warnmeldung über das Risiko des versehentlichen Teilens von Werten über Instanzen hinweg hinzuzufügen.
Stéphane C.

@ChanningMoore: Ich habe versucht, das von Ihnen beschriebene Problem neu zu erstellen, bin jedoch fehlgeschlagen. Könnten Sie ein minimales Arbeitsbeispiel vorstellen, bei dem das Problem auftritt?
Gebbissimo

67

Wie wäre es mit einem Wörterbuch?

Etwas wie das:

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

Dann können Sie dies verwenden, um Werte zu manipulieren:

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

Und die Werte müssen keine Zeichenfolgen sein. Sie können so ziemlich jedes andere Objekt sein.


34
Dies war auch mein Ansatz, aber ich halte es gerade deshalb für gefährlich, weil ein Wörterbuch alles für einen Schlüssel akzeptieren kann. Es tritt kein Fehler auf, wenn ich myStruct ["ffield"] einstelle, als ich myStruct ["field"] festlegen wollte. Das Problem kann auftreten (oder auch nicht), wenn ich myStruct ["Feld"] später verwende oder wieder verwende. Ich mag den Ansatz von PabloG.
Mobabo

Das gleiche Problem besteht bei PabloGs. Versuchen Sie, seinen folgenden Code hinzuzufügen: pt3.w = 1 print pt3.w In einer Sprache mit Diktaten ist es besser, sie zu verwenden, insbesondere für Objekte, die serialisiert werden, da Sie import json automatisch verwenden können, um sie und andere Serialisierungsbibliotheken zu speichern, solange Sie keine Seltsamkeiten haben Zeug in deinem Diktat. Dicts sind die Lösung, um Daten und Logik getrennt zu halten. Sie sind besser als Strukturen für Personen, die keine benutzerdefinierten Serialisierungs- und Unserialisierungsfunktionen schreiben und keine nicht tragbaren Serialisierer wie pickle verwenden möchten.
Poikilos

27

dF: das ist ziemlich cool ... ich wusste nicht, dass ich mit dict auf die Felder in einer Klasse zugreifen kann.

Mark: Die Situationen, die ich mir wünschte, wären genau dann, wenn ich ein Tupel möchte, aber nichts so "schwer" wie ein Wörterbuch.

Sie können mit einem Wörterbuch auf die Felder einer Klasse zugreifen, da die Felder einer Klasse, ihre Methoden und alle ihre Eigenschaften intern mithilfe von Dicts gespeichert werden (zumindest in CPython).

... was uns zu Ihrem zweiten Kommentar führt. Zu glauben, dass Python-Diktate "schwer" sind, ist ein äußerst nicht-pythonistisches Konzept. Und das Lesen solcher Kommentare tötet meinen Python Zen. Das ist nicht gut.

Sie sehen, wenn Sie eine Klasse deklarieren, erstellen Sie tatsächlich einen ziemlich komplexen Wrapper um ein Wörterbuch. Wenn überhaupt, fügen Sie also mehr Overhead hinzu als mit einem einfachen Wörterbuch. Ein Overhead, der übrigens ohnehin bedeutungslos ist. Wenn Sie an leistungskritischen Anwendungen arbeiten, verwenden Sie C oder etwas anderes.


5
# 1, Cython! = CPython. Ich denke, Sie haben über CPython gesprochen, die Implementierung von Python in C, nicht über Cython, ein Projekt, um Python-Code in C-Code zu kompilieren. Ich habe Ihre Antwort bearbeitet, um das zu beheben. # 2, ich denke, als er sagte, dass Diktate schwer sind, bezog er sich auf die Syntax. self['member']ist 3 Zeichen länger als self.memberund diese Zeichen sind alle relativ unhandgelenkfreundlich.
ArtOfWarfare

19

Sie können die C-Struktur, die in der Standardbibliothek verfügbar ist, in Unterklassen unterteilen. Das Modul ctypes stellt eine Strukturklasse bereit . Das Beispiel aus den Dokumenten:

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

Ich möchte auch eine Lösung hinzufügen, die Slots verwendet :

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

Überprüfen Sie auf jeden Fall die Dokumentation auf Slots, aber eine kurze Erklärung der Slots ist, dass Python sagt: "Wenn Sie diese Attribute und nur diese Attribute in der Klasse sperren können, verpflichten Sie sich, dass Sie nach der Klasse keine neuen Attribute hinzufügen." wird instanziiert (ja, Sie können einer Klasseninstanz neue Attribute hinzufügen, siehe Beispiel unten), dann werde ich die große Speicherzuordnung beseitigen, die das Hinzufügen neuer Attribute zu einer Klasseninstanz ermöglicht, und genau das verwenden, was ich für diese Slots benötige Attribute . "

Beispiel für das Hinzufügen von Attributen zur Klasseninstanz (daher werden keine Slots verwendet):

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

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

Ausgabe: 8

Beispiel für den Versuch, Attribute zu Klasseninstanzen hinzuzufügen, in denen Slots verwendet wurden:

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

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

Ausgabe: AttributeError: Das Objekt 'Point' hat kein Attribut 'z'.

Dies kann effektiv als Struktur funktionieren und benötigt weniger Speicher als eine Klasse (wie eine Struktur, obwohl ich nicht genau untersucht habe, wie viel). Es wird empfohlen, Slots zu verwenden, wenn Sie eine große Anzahl von Instanzen des Objekts erstellen und keine Attribute hinzufügen müssen. Ein Punktobjekt ist ein gutes Beispiel dafür, da es wahrscheinlich ist, dass man viele Punkte instanziieren kann, um einen Datensatz zu beschreiben.


17

Sie können die Init-Parameter auch nach Position an die Instanzvariablen übergeben

# 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
Bei der Aktualisierung nach Position wird die Deklarationsreihenfolge der Attribute ignoriert und stattdessen deren alphabetische Sortierung verwendet. Wenn Sie also die Zeilenreihenfolge in der Point3dStructDeklaration ändern , Point3dStruct(5, 6)funktioniert dies nicht wie erwartet. Es ist seltsam, dass dies in allen 6 Jahren niemand geschrieben hat.
Lapis

Könnten Sie Ihrem fantastischen Code eine Python 3-Version hinzufügen? Gute Arbeit! Ich mag es, dass Sie etwas Abstraktes nehmen und es mit der zweiten spezifischen Klasse explizit machen. Das sollte gut für die Fehlerbehandlung / das Abfangen sein. Für Python 3 ändern Sie einfach print> print()und attrs[n]> next(attrs)(Filter ist jetzt ein eigenes iterierbares Objekt und erfordert next).
Jonathan Komar

10

Immer wenn ich ein " Sofortdatenobjekt brauche, das sich auch wie ein Wörterbuch verhält" (ich denke nicht an C-Strukturen!), Denke ich an diesen niedlichen Hack:

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

Jetzt können Sie einfach sagen:

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

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

Perfekt für Zeiten, in denen Sie eine "Datentasche benötigen, die KEINE Klasse ist" und in der Namedtuples unverständlich sind ...


Ich benutze pandas.Series (a = 42) ;-)
Mark Horvath

8

Sie greifen auf folgende Weise auf die C-Style-Struktur in Python zu.

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

Wenn Sie nur das Objekt cstruct verwenden möchten

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)

Wenn Sie ein Array von Objekten von cstruct erstellen möchten

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)

Hinweis: Verwenden Sie anstelle des Namens 'cstruct' Ihren Strukturnamen anstelle von var_i, var_f, var_str. Definieren Sie die Mitgliedsvariable Ihrer Struktur.


3
Unterscheidet sich das von dem, was in stackoverflow.com/a/3761729/1877426 steht ?
Lagweezle

8

Einige der Antworten hier sind sehr ausführlich. Die einfachste Option, die ich gefunden habe, ist (von: http://norvig.com/python-iaq.html ):

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

Initialisierung:

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

Hinzufügen von mehr:

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

edit: Dieses Beispiel wurde leider noch nicht weiter unten angezeigt.


5

Dies mag etwas spät sein, aber ich habe eine Lösung mit Python Meta-Classes (Dekorator-Version unten auch) erstellt.

Wann __init__ es zur Laufzeit aufgerufen wird, erfasst es jedes der Argumente und ihren Wert und weist sie Ihrer Klasse als Instanzvariablen zu. Auf diese Weise können Sie eine strukturähnliche Klasse erstellen, ohne jeden Wert manuell zuweisen zu müssen.

In meinem Beispiel gibt es keine Fehlerprüfung, daher ist es einfacher zu folgen.

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 

Hier ist es in Aktion.

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


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

Ich habe es auf reddit gepostet und / u / matchu hat eine Dekorationsversion gepostet, die sauberer ist. Ich würde Sie ermutigen, es zu verwenden, es sei denn, Sie möchten die Metaklassenversion erweitern.

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

Verdammt - ich habe heute zwei Stunden damit verbracht, meinen eigenen Dekorateur dafür aufzuschreiben, und dann habe ich das gefunden. Wie auch immer, poste meine, weil sie Standardwerte behandelt, während deine dies nicht tun. stackoverflow.com/a/32448434/901641
ArtOfWarfare

+1 für die Erwähnung von func_code. Begann in diese Richtung zu graben und fand dort viele interessante Sachen.
Wombatonfire

5

Ich habe einen Dekorator geschrieben, den Sie für jede Methode verwenden können, damit alle übergebenen Argumente oder Standardeinstellungen der Instanz zugewiesen werden.

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

Eine kurze Demonstration. Beachten Sie, dass ich ein Positionsargument a, den Standardwert für bund ein benanntes Argument verwende c. Ich drucke dann alle 3 Verweise aus self, um zu zeigen, dass sie ordnungsgemäß zugewiesen wurden, bevor die Methode eingegeben wird.

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

Beachten Sie, dass mein Dekorateur mit jeder Methode arbeiten sollte, nicht nur __init__.


5

Ich sehe diese Antwort hier nicht, also werde ich sie wohl hinzufügen, da ich gerade Python lehne und sie gerade entdeckt habe. Das Python-Tutorial (in diesem Fall Python 2) enthält das folgende einfache und effektive Beispiel:

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

Das heißt, ein leeres Klassenobjekt wird erstellt, dann instanziiert und die Felder werden dynamisch hinzugefügt.

Das Beste daran ist, dass es wirklich einfach ist. Der Nachteil ist, dass es nicht besonders selbstdokumentierend ist (die beabsichtigten Mitglieder sind an keiner Stelle in der Klassendefinition aufgeführt), und nicht gesetzte Felder können beim Zugriff Probleme verursachen. Diese beiden Probleme können gelöst werden durch:

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

Jetzt können Sie auf einen Blick sehen, welche Felder das Programm erwartet.

Beide sind anfällig für Tippfehler, john.slarly = 1000werden erfolgreich sein. Trotzdem funktioniert es.


4

Hier ist eine Lösung, die eine Klasse (nie instanziiert) verwendet, um Daten zu speichern. Ich mag es, dass dieser Weg sehr wenig Eingabe erfordert und keine zusätzlichen Pakete usw. erfordert .

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

Sie können später nach Bedarf weitere Felder hinzufügen:

myStruct.field3 = 3

Um die Werte zu erhalten, wird wie gewohnt auf die Felder zugegriffen:

>>> myStruct.field1
'one'

2

Ich persönlich mag diese Variante auch. Es erweitert die Antwort von @ 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__)

Es werden zwei Initialisierungsmodi unterstützt (die gemischt werden können):

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

Außerdem druckt es schöner:

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

2

Die folgende Lösung für eine Struktur ist von der Implementierung von namedtuple und einigen der vorherigen Antworten inspiriert. Im Gegensatz zum namedtuple ist es jedoch in seinen Werten veränderbar, aber wie die Struktur im c-Stil, die in den Namen / Attributen unveränderlich ist, was eine normale Klasse oder ein Diktat nicht ist.

_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

Verwendungszweck:

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

Genau für diesen Zweck gibt es ein Python-Paket. siehe cstruct2py

cstruct2pyist eine reine Python-Bibliothek zum Generieren von Python-Klassen aus C-Code und zum Packen und Entpacken von Daten. Die Bibliothek kann C-Headres (Strukturen, Vereinigungen, Aufzählungen und Arrays-Deklarationen) analysieren und in Python emulieren. Die generierten Python-Klassen können die Daten analysieren und packen.

Zum Beispiel:

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"

Wie benutzt man

Zuerst müssen wir die pythonischen Strukturen erzeugen:

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

Jetzt können wir alle Namen aus dem C-Code importieren:

parser.update_globals(globals())

Das können wir auch direkt machen:

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

Verwenden von Typen und Definitionen aus dem C-Code

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

Die Ausgabe wird sein:

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

Klon

Für den Klonlauf cstruct2py:

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

0

Ich denke, Python-Strukturwörterbuch ist für diese Anforderung geeignet.

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

0

https://stackoverflow.com/a/32448434/159695 funktioniert in Python3 nicht.

https://stackoverflow.com/a/35993/159695 funktioniert in Python3.

Und ich erweitere es, um Standardwerte hinzuzufügen.

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

Wenn Sie keine 3.7 für @dataclass haben und Veränderbarkeit benötigen, funktioniert der folgende Code möglicherweise für Sie. Es ist ziemlich selbstdokumentierend und IDE-freundlich (automatisch vervollständigen), verhindert das zweimalige Schreiben von Dingen, ist leicht erweiterbar und es ist sehr einfach zu testen, ob alle Instanzvariablen vollständig initialisiert sind:

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

Hier ist ein schneller und schmutziger Trick:

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

Wie funktioniert es? Es verwendet einfach die eingebaute Klasse Warning(abgeleitet von Exception) und verwendet sie so, wie Sie es selbst definiert haben.

Die guten Punkte sind, dass Sie nichts importieren oder definieren müssen, dass "Warnung" ein Kurzname ist und dass es auch klar macht, dass Sie etwas schmutziges tun, das nicht anderswo als in einem kleinen Skript von Ihnen verwendet werden sollte.

Übrigens habe ich versucht, etwas noch einfacheres zu finden ms = object(), konnte es aber nicht (dieses letzte Beispiel funktioniert nicht). Wenn Sie eine haben, bin ich interessiert.


0

Der beste Weg, dies zu tun, war die Verwendung einer benutzerdefinierten Wörterbuchklasse, wie in diesem Beitrag erläutert: https://stackoverflow.com/a/14620633/8484485

Wenn Unterstützung für die automatische Vervollständigung von iPython benötigt wird, definieren Sie einfach die Funktion dir () wie folgt :

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

Sie definieren dann Ihre Pseudostruktur wie folgt: (Diese ist verschachtelt)

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

Sie können dann wie folgt auf die Werte in my_struct zugreifen:

print(my_struct.com1.inst)

=>[5]


0

NamedTuple ist komfortabel. aber dort teilt niemand die Leistung und Speicherung.

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)

Wenn Sie __dict__nicht verwenden, wählen Sie bitte zwischen __slots__(höhere Leistung und Speicherplatz) und NamedTuple(zum Lesen und Verwenden freigegeben).

Sie können diesen Link ( Verwendung von Slots ) überprüfen , um weitere __slots__Informationen zu erhalten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.