Python struct in Python?

LukS

Rear Admiral Pro
Registriert
Dez. 2009
Beiträge
5.417
Hallo,

ich Programmiere momentan das erste mal etwas mit Python3.
Ich brauche in Phyton etwas das wie ein Array ist, nur das ich in diesem Array zwei Variable gespeichert habe. Bei C# geht das glaube ich mit Strukturen, soweit ich mich erinnern kann.
Ich stelle mir das so vor:
Code:
level[x]
{
    time=121312     #12h 13min 12 sekunden und eventuell noch millisekunden
    value=989.12     #ein float wert
}
Ich möchte also ein Array in dem immer eine Zeit und ein dazugehöriger Wert gespeichert wird. Das Array soll dynamisch größer und kleiner werden können. Momentan soll alle 1 bis 2 Sekunden ein Wert und die zugehörige Uhrzeit gespeichert werden.
Also etwa so:
Code:
level.append(uhrzeit, wert)
Des Weiteren möchte ich mittels einer Funktion alle Werte löschen die länger als X Sekunden im Array sind.
Also mittels Schleife, etwa sowas:
Code:
for x in level
{
    if level[x].time < jetzt-60 Sekunden 
           drop level[x]
}

Wie sieht so eine Struktur in Python aus? Irgendwie stehe ich gerade auf der Leitung und weis auch nicht nach was ich da jetzt bei Google suchen soll. Ich hoffe mal ihr könnt mir ein paar Anregungen dazu geben. Ich brauche keinen fertigen Code, sonder möchte einfach nur wissen wie man soetwas in Python löst. Wenn ihr mir einfach sagt nach was ich suchen soll oder ihr mir ein paar Links geben könnt wäre ich schon glücklich.

mfg LukS
 
Das, was du suchst, nennt sich in Python "named tuple". Ich kann dir da nicht viel helfen, weil ich selbst kein Python programmiere, aber vielleicht steht in der Referenz ja alles, was du brauchst.
 
LukS schrieb:
Hallo,

ich Programmiere momentan das erste mal etwas mit Python3.
Ich brauche in Phyton etwas das wie ein Array ist, nur das ich in diesem Array zwei Variable gespeichert habe. Bei C# geht das glaube ich mit Strukturen, soweit ich mich erinnern kann.
Ich stelle mir das so vor:
Code:
level[x]
{
    time=121312     #12h 13min 12 sekunden und eventuell noch millisekunden
    value=989.12     #ein float wert
}
Ich möchte also ein Array in dem immer eine Zeit und ein dazugehöriger Wert gespeichert wird. Das Array soll dynamisch größer und kleiner werden können. Momentan soll alle 1 bis 2 Sekunden ein Wert und die zugehörige Uhrzeit gespeichert werden.

Von den Standard-Komponenten wäre eine List von Tupeln am nächsten an deinen Wünschen dran.
Code:
my_list = []

a_tuple = (121312, 989.12)
my_list.append(a_tuple)

another_tuple = (2131235, 1921.92)
my_list.append(another_tuple)

Alternative ist das genannte named_tuple. Es ist einfach nur ein Tupel in eine Klassen-artige Konstruktion eingepackt, so dass man statt a_tuple[0] a_tuple.time benutzen kann. Das ist etwas schöner, kostet aber Zeit und wird afaik eher selten benutzt. Hat man nur wenige Werte pro Tupel, nimmt man das einfache Tupel, hat man mehr, nimmt man gleich eine richtige Klasse.


LukS schrieb:
Also etwa so:
Code:
level.append(uhrzeit, wert)
Des Weiteren möchte ich mittels einer Funktion alle Werte löschen die länger als X Sekunden im Array sind.
Also mittels Schleife, etwa sowas:
Code:
for x in level
{
    if level[x].time < jetzt-60 Sekunden 
           drop level[x]
}

Code:
new_list = [tup for tup in my_list if tup[0] < (datetime.now() - timedelta(seconds=60))]
Damit das funktioniert muss deine Zeit natürlich als datetime Objekt vorliegen und nicht als einfacher String.
 
Zuletzt bearbeitet:
Vielleicht irre ich mich, aber ich versuchs mal:

Du brauchst eine Liste von Tupeln, da ein Tupel nicht veränderbar ist in Python (genauso wie Strings). Tupel werden verwendet um Zugehörigkeiten zu speichern und können beliebiger Größe sein und unterschiedliche Typen enthalten (Strings, Float, Int, Tupel, Etc)):

Code:
tupel1 = ('A', 1)
tupel2 =  ('A', 1, 'Peter')
tupel3 = ((1,2),(2,3))

tupel3 ist also Tupel von Tupeln.

Wie zu sehen ist wird eine runde Klammer () für Tupel verwendet.

Da du nun in einem Tupel die Zugehörigkeit von Temperatur und Zeit speichern möchtest, braucht du nur ein Tupel der Größe 2:

Code:
tupel1 = (121312, 989.12)

Der Zugriff erfolgt mit [] wie in C bei Arrays:

tupel1[0] ergibt 121312, tupel1[1] ergibt 989.12

Nun möchtest du ja mehrere Wertepaare speichern. Da ein Tupel nicht veränderbar ist, brauchst du eine Liste. Listen können beliebig viele Elemente enthalten. Listen werden mit eckigen Klammern [] kenntlich gemacht:

Code:
list1 = ['A', 'B', 'C']

Du kannst nun mit append() und remove() bzw pop() Elemente zur Liste hinzufügen und entfernen. https://docs.python.org/2/tutorial/datastructures.html

Code:
list1.append((111212, 777.78))

list1 wird also ein Tupel angefügt.

Der Zugriff erfolgt mit [] wie in C bei Arrays:

list1[0] ergibt das Tupel (111212, 777.78)

Was du nun im zweiten Schritt haben möchtest ist ja alle Elemente (hier Tupel) zu löschen, die länger als X Sekunden existeren.
Eines vorweg: Bei Python werden keine Klammern bei If, While etc gemacht, sowie auch nicht { Klammern bei einer For-Loop. Durch Einrücken vermittelst du, welcher Programmcode in der For-Loop ausgeführt wird und welcher nicht:

Angenommen deine list1 sieht wie folgt aus:

Code:
list1 = [(081212, 700.78), (091312, 800.78), (101215, 777.78), (111212, 900.78), (121212, 1000.78), (131212, 1100.78)]

Dann kannst du folgendes schreiben:

Code:
for tupel in list1
  if tupel[0] < jetzt-60
    list1.remove(tupel)

Du gehst also alle Tupel der Liste list1 durch und betrachtest jeweils den Zeitwert mittels [0] und ermittelst damit ob das Tupel zu löschen ist. Wenn ja, entfernst du das Tupel tupel aus der Liste list1

was jetzt-60 genau meint, wusste ich jetzt nicht :)

Ich hoffe ich konnte dir helfen.

Wenn nicht kannst du dir mal bei Youtube die Videoserie von Google anschauen: Python Google Class. Dort wird alles über Python super erklärt.
 
Zuletzt bearbeitet:
Erst mal danke für die Antworten. Vor allem Danke an Limit und hell-student. :daumen:
Ich werde es mithilfe einer Kombination aus Listen und Tupel machen. Ich habe dazu mal folgendes Beispiel geschrieben:
Python:
def average(level):
    average=0.0
    for tupel in level:
        print (tupel[1]) #die Temperatur ausgeben
        average+=tupel[1]
    print("Sum:", average, "\nCount:", len(level))
    average/=len(level)
    return average

def cleartupelout(level, time):
    print("Level bevor clear out:", level)
    for tupel in level:
        if tupel[0] < 12347 - time:
            level.remove(tupel)
    print("Level after clear out:", level)
    return level

def tupeltest():
    level = []
    tupel = (12345, 21) #Zeit und Temp #kann man Zeit durch ein Date-Object ersetzten? muss ich als naechstes testens
    level.append(tupel)
    tupel = (12346,20)
    level.append(tupel)
    #print((level[0][1] + level[1][1])/2)
    tupel = (12012, 23)
    level.append(tupel)
    print(level,'\n')
    print("Average:", average(level))
    level = cleartupelout(level, 10)
    print("Average:", average(level))
    
tupeltest()
Ausgabe:
Code:
[(12345, 21), (12346, 20), (12012, 23)] 

21
20
23
Sum: 64.0 
Count: 3
Average: 21.333333333333332
Level bevor clear out: [(12345, 21), (12346, 20), (12012, 23)]
Level after clear out: [(12345, 21), (12346, 20)]
21
20
Sum: 41.0 
Count: 2
Average: 20.5
Sieht ja mal gut aus.

Das Ganze mit einem datetime-Objekt:
Python:
import datetime
def cleartimetupelout(level, time):
    print("Level bevor clear out:", level)
    for tupel in level:
        if tupel[0] < (datetime.datetime.now() - datetime.timedelta(seconds=time)):
            level.remove(tupel)
    print("Level after clear out:", level)
    return level

def timetest():
    level = []
    tupel = (datetime.datetime.now(),22)
    level.append(tupel)
    tupel = ((datetime.datetime.now()-datetime.timedelta(seconds=60)),20)
    level.append(tupel)
    print(level)
    print("Clear tuple older then 30 seconds:")
    cleartimetupelout(level, 30)
    
timetest()
Ausgabe:
Code:
[(datetime.datetime(2014, 11, 21, 13, 26, 46, 536123), 22), (datetime.datetime(2014, 11, 21, 13, 25, 46, 536123), 20)]
Clear tuple older then 30 seconds:
Level bevor clear out: [(datetime.datetime(2014, 11, 21, 13, 26, 46, 536123), 22), (datetime.datetime(2014, 11, 21, 13, 25, 46, 536123), 20)]
Level after clear out: [(datetime.datetime(2014, 11, 21, 13, 26, 46, 536123), 22)]
Funktioniert auch. :hammer_alt:

Da ich ja den Temperaturwert (später ist es auch noch der Druck sowohl über als auch unter einer Wasseroberfläche) als ASCII-String über die Serielle Schnittstelle bekomme, habe ich noch getestet wie es mit dem Umwandeln von String in Float hinhaut und ob man dann damit Rechnen kann. Nur falls es noch jemanden interessiert, hier ist mein Testcode:
Python:
def tupelteststring():
    level = []
    wert_als_string = "20.5"
    print(wert_als_string)
    wert_als_float = float(wert_als_string)
    print(wert_als_float)
    tupel = (datetime.datetime.now(),wert_als_float)
    level.append(tupel)
    print(level)
    print(level[0][1]-40)  

tupelteststring()
Ausgabe:
Code:
20.5
20.5
[(datetime.datetime(2014, 11, 21, 13, 21, 28, 818950), 20.5)]
-19.5

Hab ich das richtig verstanden das man Tupel nicht verändern kann?
Wenn ich also bei einem Tupel der schon in der Liste ist, einen Wert ändern möchte, dann muss ich aus dem vorhanden Tupel einen neuen mit veränderten Werten erstellen? Dann lösche ich den alten aus der Liste und hänge den neuen wieder an. Habe ich das richtig verstanden? Gibt es da keine andere Möglichkeit?
 
Zuletzt bearbeitet:
LukS schrieb:
Hab ich das richtig verstanden das man Tupel nicht verändern kann?
Wenn ich also bei einem Tupel der schon in der Liste ist, einen Wert ändern möchte, dann muss ich aus dem vorhanden Tupel einen neuen mit veränderten Werten erstellen? Dann lösche ich den alten aus der Liste und hänge den neuen wieder an. Habe ich das richtig verstanden? Gibt es da keine andere Möglichkeit?

Hast du richtig verstanden. Wenn du einen Wert in einem Tupel verändern möchtest, mußt du das alte Tupel wegschmeißen und einfach ein neues Tupel mit den richtigen Werten erstellen. Tuples sind was Python als immutable bezeichnet (genau wie, zum Beispiel, Strings).

Wenn du individuelle Elemente einer Sequenz verändern möchtest, dann nimm halt herkömmliche Listen statt Tuples.
 
antred hat Recht. Wenn du eine Tupel verändern willst sind Tupel der falsche Datentyp. Dann nehm eine List aus Listen statt der Liste aus Tupeln. Wenn die Zahl der Elemente pro Tupel/Liste größer wird, kannst du auch überlegen direkt eine Klasse zu machen statt der Tupel/Liste.

Zu deinen Funktionen würde ich sagen, dass sie noch nicht sehr "pythonisch" aussehen, sondern eher bnach Java oder C/C++/C# "riechen". Als Umsteiger braucht man meist ein wenig bis man sich umgewöhnt hat, aber es lohnt sich auf jeden Fall. Man bekommt kürzeren Code, der zusätzlich noch lesbarer ist.

Hier mal ein paar Beispiele, wie es "pythonischer" aussehen könnte:
Code:
def average(level):
    return sum((x[0] for x in level)) / len(level)

def cleartupelout(level, time):
    return [x for x in level if x[0] < 12347 - time]

Hier wurde einfach die Schleifen du sog. list comprehension ersetzt. Man beschreibt welche Elemente man in der Liste haben will anstatt die Liste Schritt für Schritt zu modifizieren.
 
LukS schrieb:
Hab ich das richtig verstanden das man Tupel nicht verändern kann?
Wenn ich also bei einem Tupel der schon in der Liste ist, einen Wert ändern möchte, dann muss ich aus dem vorhanden Tupel einen neuen mit veränderten Werten erstellen? Dann lösche ich den alten aus der Liste und hänge den neuen wieder an. Habe ich das richtig verstanden? Gibt es da keine andere Möglichkeit?

Wie schon von anderen erwähnt, kannst du in einem Tupel keine Einträge verändern:
Code:
>>> foo = (1, 2)
>>> foo[0] = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Wenn du jetzt eine Liste von Tupeln hast, musst du aber nicht umständlich den Eintrag suchen, löschen und einen neuen hinten dran hängen, sondern kannst das gleich in-place machen:
Code:
>>> bar = [(1, 2), (3, 4), (5, 7)]
>>> bar[1] = (8, 9)
>>> print bar
[(1, 2), (8, 9), (5, 7)]
 
stwe schrieb:
Wenn du jetzt eine Liste von Tupeln hast, musst du aber nicht umständlich den Eintrag suchen, löschen und einen neuen hinten dran hängen, sondern kannst das gleich in-place machen:
OK, danke für die Info.

@Limit
Ja, da hast du richtig "gerochen". ;) Ich habe damals in der HTL C, C++, C# und Java gelernt. Mein letztes Programm habe ich in C# geschrieben. Ist schon wieder ein halbes Jahr her. Davor habe ich das letzte mal etwas vor 7 Jahren bei meiner HTL-Matura programmiert. Ich glaube das war irgendeine Client-/Serveranwendung die ich in C++ geschrieben habe. Hab dafür nur eine drei bekommen, denn Programmieren hat mich damals wie heute eher weniger interessiert. Aber da mein Chef das Programm nicht selbst schreiben möchte und ich der Einzige andere bin der Programmieren kann, muss ich es machen.

Danke, für den Tipp mit der list comprehension. Das habe ich bisher noch nicht gesehen.

Zum testen habe ich einmal verschachtelte Listen erstellt. Das geht also im Prinzip genau so wie mit Tupel nur das ich sie auch ändern kann.
Wenn ich die Werte also irgendwann ändern möchte nehme ich Listen, sonst Tupel.
Ich habe mal zwei Funktionen von clearlist gemacht. Im Prinzip machen beide das selbe. Die erste ist so wie ich es schreiben würde. Die zweite soll die "pythonshere" sein, wie Limit es ausgedrückt hat. Gibt es einen Laufzeit unterschied zwischen den beiden? Oder ist das nur für das Aussehen und die Leserlichkeit? Denn wenn es keinen unterschied in der Laufzeit macht, dann werde ich es weiterhin wie in Variante 1 machen, da es für mich persönlich einfacher zu lesen und interpretieren ist.
Code:
import datetime
def clearlist(level, time):
    for entry in level:
        if entry[0] < (datetime.datetime.now() - datetime.timedelta(seconds=time)):
            level.remove(entry)
    return level

def clearlist2(level, time): #"pythonsher"
    return [entry for entry in level if entry[0] > (datetime.datetime.now() - datetime.timedelta(seconds=time))]

def listtest():
    level = []
    entry = [(datetime.datetime.now()-datetime.timedelta(seconds=60)),21]
    level.append(entry)
    entry = [(datetime.datetime.now()-datetime.timedelta(seconds=30)),22]
    level.append(entry)
    print(level)
    print (level[0][1])
    level[0][1]+=0.5
    print (level[0][1])
    level.append([datetime.datetime.now(),24])
    print("Average:", sum((x[1] for x in level)) / len(level))
    print("Bevore clearout:", level)
    print ("After clearout:", clearlist2(level, 40))
    
listtest()
Ausgabe:
Code:
[[datetime.datetime(2014, 11, 24, 16, 26, 49, 562571), 21], [datetime.datetime(2014, 11, 24, 16, 27, 19, 562571), 22]]
21
21.5
Average: 22.5
Bevore clearout: [[datetime.datetime(2014, 11, 24, 16, 26, 49, 562571), 21.5], [datetime.datetime(2014, 11, 24, 16, 27, 19, 562571), 22], [datetime.datetime(2014, 11, 24, 16, 27, 49, 562571), 24]]
After clearout: [[datetime.datetime(2014, 11, 24, 16, 27, 19, 562571), 22], [datetime.datetime(2014, 11, 24, 16, 27, 49, 562571), 24]]
 
Zuletzt bearbeitet:
LukS schrieb:
Ich habe mal zwei Funktionen von clearlist gemacht. Im Prinzip machen beide das selbe. Die erste ist so wie ich es schreiben würde. Die zweite soll die "pythonshere" sein, wie Limit es ausgedrückt hat. Gibt es einen Laufzeit unterschied zwischen den beiden?

Die List-Implementierung von Python kannst du mit Array-Lists in Java vergleichen, d.h. jedes mal, wenn du ein Element löschst, müssen alle nachfolgenden Elemente ein Feld vorgerückt werden, ergo O(n * m), wobei n die Länge der Liste und m die Zahl der zu entfernenden Elemente ist. List comprehension erzeugt hingegen erzeugt eine neue leere Liste und übernimmt die Elemente, die die Bedingungen erfüllen (O(n)). Das gleiche Verhalten kann man natürlich auch mit einer for-Schleifen realisieren.

Falls du öfters mit Python arbeitest (arbeiten musst), würde ich dir dazu raten dich an den "pythonischen Stil" zu gewöhnen. Langfristig ist er deutlich kürzer und wenn man sich ein wenig dran gewöhnt hat auch sehr gut lesbar. Aber das wirst du vermutlich selbst schnell merken, wenn du mehr mit Python arbeitest.

Wenn du eine Klasse wie z.B. datetime oder timedelta häufiger benutzt, kannst du sie auch direkt in den eigenen Namespace laden und/oder ein kürzeres Alias verwenden.

Die klassische Variante:
Code:
import datetime
timestamp = datetime.datetime.now() - datetime.timedelta(seconds=60)

Laden der Klassenobjekte in den eigenen Namespace. Solange du nicht verschiedene datetime oder timedelta Implementierungen benutzt ist das sicher (Das ist vermutlich die häufigst benutzte Variante)
Code:
from datetime import datetime, timedelta 
timestamp = datetime.now() - timedelta(seconds=60)

Falls du die gleichen Klassennamen aus verschiedenen Modulen importieren willst, kannst du den Modulen kürzere Alias-Namen verpassen.
Code:
import datetime as dt
timestamp = dt.datetime.now() - dt.timedelta(seconds=60)

Du kannst auch das Laden in den Namespace und Aliase kombinieren. (Kann Verwirrung stiften, wenn man fremden Code liest)
Code:
from datetime import datetime as dtime, timedelta as tdelta
timestamp = dtime.now() - tdelta(seconds=60)
 
Ich habe mein Programm soweit fertig, jetzt muss ich es nur mehr auf dem Raspberry Pi testen. Also erst mal Danke für eure Hilfe. Vor allem dir, Limit, Danke ich für deine Hilfe.

Ich habe jetzt doch mit list comprehensions gearbeitet. Das ist gut zu wissen das bei list comprehensions mit einer Kopie der ursprünglichen Liste gearbeitet wird.
Code:
#remove every entry older then now()-timedelta
#liste = nasted list; timedelta = time in seconds
#returns the cleared list
def clearlistout(liste, timedelta):
    #print("Clear the List")
    return [entry for entry in liste if entry[0] > (datetime.datetime.now() - datetime.timedelta(seconds=timedelta))]


#Calculates the average temperature of all entrys in the list
#returns the average temperature
def averagetemp(liste):
    return sum((x[1] for x in liste)) / len(liste)

Limit schrieb:
Wenn du eine Klasse wie z.B. datetime oder timedelta häufiger benutzt, kannst du sie auch direkt in den eigenen Namespace laden und/oder ein kürzeres Alias verwenden.
Ja, das habe ich schon gewusst, jedoch bin ich damit nicht ganz zurecht gekommen. Irgendwie bin ich da immer durcheinander gekommen. Jetzt, mit deiner Beschreibung ist mir alles klar. Muss jetzt erst mal etwas anderes erledigen, wenn ich Zeit habe werde ich das dann ausprobieren und einbauen. Vielen Dank nochmal.

Irgendjemand hat oben geschrieben, das man das Konstrukt aus verschachtelten Listen auch durch eine Klasse ersetzten könnte. Ich habe mich jetzt in Python noch nicht mit Klassen beschäftigt, aber kann mir kurz einer erklären wie so etwas auszusehen hätte? Ich habe gestern kurz eine Beispielklasse erstellt, jedoch bin ich nicht ganz zurecht gekommen. Kann mir eventuell jemand erklären wie das anhand meines Problems mit verschachtelden Listen auszusehen hätte? Muss man, dann sein Hauptprogramm auch in eine Klasse packen?
 
LukS schrieb:
Irgendjemand hat oben geschrieben, das man das Konstrukt aus verschachtelten Listen auch durch eine Klasse ersetzten könnte. Ich habe mich jetzt in Python noch nicht mit Klassen beschäftigt, aber kann mir kurz einer erklären wie so etwas auszusehen hätte? Ich habe gestern kurz eine Beispielklasse erstellt, jedoch bin ich nicht ganz zurecht gekommen. Kann mir eventuell jemand erklären wie das anhand meines Problems mit verschachtelden Listen auszusehen hätte?

Du könntest die inneren Liste bzw. Tupel durch eine Klasse abbilden. Die äußere List ließe sich auch durch eine Klasse abbilden. Was von beidem sinnvoll ist, hängt natürlich stark davon ab, ob und falls, wie du das Programm noch änderst.

Code:
from datetime import datetime, timedelta

class Entry(object):
    def __init__(self, timestamp, temperature):
        self.timestamp = timestamp
        self.temperature = temperature

class EntryStore(object):
    def __init__(self):
        store = []

    def add_entry(self, entry):
        self.store.append(entry)

    def calc_average(self):
        return sum((e.temperature for e in self.store)) / len(self.store)

    def clear_old_entries(self):
        self.store = [e for e in self.store if e.timestamp > datetime.now() - timedelta(seconds=60)]

store = EntryStore()
e = Entry(datetime.now(), 123.4) 
store.add(e)
e = Entry(datetime.now(), 123.6)
store.add(e)

print(store.calc_average())
store.clear_old_entries()

Die __init__ Funktion ist technisch gesehen kein Konstruktor, wird aber genau so benutzt. Klassenmethoden haben immer eine Selbst-Referenz als ersten Parameter. Der Name ist im Prinzip frei wählbar und man könnte z.B. auch this verwenden wie bei C++/Java, aber die gängige Konvention besteht auf self.

LukS schrieb:
Muss man, dann sein Hauptprogramm auch in eine Klasse packen?
Das ist nicht notwendig. In der Hinsicht ist Python sehr pragmatisch. Benutze eine Klasse, wenn du sie für sinnvoll hälst, ansonsten lass sie weg.
 
Zurück
Oben