Daten-Struktur selbst definieren und Verschachteln

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Idefix_1024
User
Beiträge: 19
Registriert: Montag 11. Januar 2010, 14:36

Hallo liebe Python-Gemeinde,

ich verwende sehr gerne solche Strukturen um Signale zu bündeln zB

Code: Alles auswählen

# - - - 
class Signal():
    _fields_ = [("name", str),
                ("value", float),
                ("gain", float),
                ("offset", float),
                ("time", float)]
# - - - 
signal1 = Signal()
signal1.name = "channel1"
signal1.value = [2.5, 2.3, 2.4]
signal1.gain = 1.8
signal1.offset = 0.2
signal1.time = [0, 1, 2]

print(signal1.name, signal1.value, signal1.gain, signal1.offset, signal1.time)
danach kann man recht übersichtlich mit solchen Signalen arbeiten. Nun stellt sich mir die Frage ob ich da recht großen Unsinn tue und wie ich das erweitern kann.
Wie kann ich eines der Felder zB "gain" noch aufteilen in weitere drei Felder zB gain.a gain.b und gain.c ?
Muss ich da wirklich drei separate Felder in der Klasse Signal() anlegen (also statt gain eben gainA, gainB und gainC anlegen) oder kann ich das auch irgendwie besser anstellen?
Ich würde mir denken, dass ich diese Konstruktion irgendwie verschachteln können muss oder?
Kann ich ("gain", float) vielleicht irgendwie so definieren (gain = ClassGain()) und in ClassGain werden dann die Felder für a, b und c definiert?

Grüße!
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist grosser Unsinn, ja. Du missbrauchst eine Funktionalitaet die zum expliziten arbeiten mit C-Aufrufen gedacht ist, um komplett am Kern von Python vorbei mit festen Typen zu arbeiten. Das macht man so nicht.

Stattdessen benutzt man fuer so etwas zB collections.namedtuple:

Code: Alles auswählen


from collections import namedtuple
Gain = namedtuple("Gain", ["a", "b", "c"])
Signal = namedtuple("Signal", ["name", "value", "gain"...])

s = Signal("foo", 100.0, Gain(1, 2, 3, ...)
Falls die Werte veraenderlich sein muessen, dann sind ggf. python-attrs https://github.com/python-attrs/attrs oder das eingebaute dataclasses einen Blick wert: https://docs.python.org/3/library/datac ... ataclasses
Idefix_1024
User
Beiträge: 19
Registriert: Montag 11. Januar 2010, 14:36

danke für diese schnelle Antwort und die beiden Quellen!
Irgendwie hatte ich schon befürchtet, dass ich auf dem Holzweg bin...
Benutzeravatar
__blackjack__
User
Beiträge: 13942
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Idefix_1024: Wobei Du ja nicht einmal das machst was der Code vermuten lässt – das sieht ja so ein bisschen so aus wie man Klassen von `ctypes.Struct` ableiten würde, nur das Du das ja gar nicht machst und damit das `_fields_`-Klassenattribut gar keinen Sinn macht, weil das überhaupt gar keinen Effekt hat. Deinen Code könnte man auch einfach so schreiben:

Code: Alles auswählen

class Signal:
    pass


def main():
    signal = Signal()
    signal.name = 'channel1'
    signal.value = [2.5, 2.3, 2.4]
    signal.gain = 1.8
    signal.offset = 0.2
    signal.time = [0, 1, 2]

    print(signal.name, signal.value, signal.gain, signal.offset, signal.time)
    # -> channel1 [2.5, 2.3, 2.4] 1.8 0.2 [0, 1, 2]
`namedtuple` hat den Vorteil das man bei der Definition des Typs schon sieht welche Attribute es gibt, während bei dem gezeigten Beispiel – und ja auch bei Deinem Code – die Attribute erst nach dem erzeugen des Objekts dynamisch eingeführt werden.

Mit `namedtuple` wird dann auch der Code für die Ausgabe einfacher wenn man einfach nur wissen will was man da vor sich hat, weil man einfach das `signal`-Objekt ausgeben kann und dann nicht nur die Werte sieht, sondern auch die Attributnamen und den Typ des Objekts selbst:

Code: Alles auswählen

from collections import namedtuple


Signal = namedtuple('Signal', 'name value gain offset time')


def main():
    signal = Signal('channel1', [2.5, 2.3, 2.4], 1.8, 0.2, [0, 1, 2])
    
    print(signal)
    # -> Signal(name='channel1', value=[2.5, 2.3, 2.4], gain=1.8, offset=0.2, time=[0, 1, 2])
Oder das ganze mit dem `attr`-Modul:

Code: Alles auswählen

from attr import attrib, attrs


@attrs(frozen=True)
class Signal:
    name = attrib()
    value = attrib()
    gain = attrib()
    offset = attrib()
    time = attrib()


def main():
    signal = Signal('channel1', [2.5, 2.3, 2.4], 1.8, 0.2, [0, 1, 2])
    
    print(signal)
    # -> Signal(name='channel1', value=[2.5, 2.3, 2.4], gain=1.8, offset=0.2, time=[0, 1, 2])
Wobei man sowohl bei `namedtuple` als auch bei `attr` Objekte von diesen Typen verschachteln kann. Auch gegenseitig – also man könnte sowohl bei `Gain` als auch bei `Signal` unabhängig entscheiden ob man das als `namedtuple` oder `attrs`-dekorierte Klasse macht.

Wobei Attributnamen `a`, `b`, und `c` ja irgendwie eher für eine Liste oder ein Tupel sprechen. Das ist ja letztlich nicht viel besser als einen Namen durch zu nummerieren.

Mein persönliches Entscheidungskriterium `namedtuple` vs. `attr`-Modul ist: Wenn es unveränderbar sein soll und keine zusätzlichen Methoden braucht → `namedtuple`, sonst `attr`-Modul.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Antworten