Eingaben innerhalb eines Zeitfensters erlauben + Listencheck

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
Tuedelue
User
Beiträge: 8
Registriert: Samstag 29. März 2014, 13:02

Tach,

als Python-Neuling habe ich mir die letzte Nacht um die Ohren gehauen, um eine (höchstwahrscheinlich) simple Sache zu programmieren - hat aber leider nicht geklappt. Von daher wäre ich dankbar, wenn ihr helfen könntet.
Folgende Situation: Es kommen permanent Werte in einer Anwendung an - sei es durch inputs oder durch das Auslesen von Sensoren. Dies sind aber derart viele, dass nicht immer alle Werte verarbeitet werden sollen, sondern nur "global" operiert werden soll :oops: - also: pro Sekunde kommt mindestens ein Wert an. Nach 5 Sekunden werden alle eingegangenen Werte verglichen. Sind 4 von 5 Werte (bzw. mind. 80%) größer als die Zahl x, soll die Operation 1 ausgeführt werden. Sind 4 von 5 Werten kleiner als die Zahl x, soll die Operation 2 ausgeführt werden. Sind höchstens 3 von 5 Werten größer/kleinert als die Zahl x, soll nix passieren.

Ich scheitere schon daran, die inputs eines Zeitfensters in einer Liste oder einer Datei zu erfassen (habe z.B. mit pickle experimentiert) und anschließend die Anzahl (mit len ??) der Werte ausgeben zu lassen.

Bisher sieht das bei mir so aus - wie ihr seht, seht ihr nicht viel....

Code: Alles auswählen

# -*- coding: utf-8 -*-
import pickle

## Nur Werte filtern, die größer als 0 sind.
def positiv(x): 
    return (x > 0)

## Generierung einer Liste mit 5 Werten - geht das nicht auch hübscher?
liste1=[input()]
liste2=[liste1, input()]
liste3=[liste2, input()]
liste4=[liste3, input()]
liste5=[liste4, input()]

## Speichern der Liste in data.pkl
output = open('data.pkl', 'w')
pickle.dump(liste5, output)
output.close()

## Öffnen der Liste
f = open("data.pkl")
ergebnisse = pickle.load(f)
print ergebnisse

## Ausgabe der positiven Zahlen
positive = filter(positiv, liste5)
print positive

## Anzahl der gezählten Werte
anzahlpositive = len(positive)
print anzahlpositive
Um das Ganze jetzt noch mit dem Zeitfenster 5 Sekunden zu verbinden, wollte ich für 5 Sekunden inputs zulassen - aber auch da ist der Wurm drin:

Code: Alles auswählen

import time

## Hier der 5-Sekunden Countdown. Hier ist die Verknüpfung von time und input jedoch noch nicht richtig. Die Ausgabe "Zeit abgelaufen" erfolgt nur, wenn 5 Eingaben getätigt wurden.
Messintervall = 5
while Messintervall > 0:
    time.sleep(1)
    x = input()
    Messintervall -=1
print("Zeit abgelaufen", x)
Danke im voraus
BlackJack

@Tuedelue: Das sieht nach einem Fall für nebenläufige Programmierung aus. Schau Dir mal das `threading`-Modul an. Das Auslesen der Sensoren, oder Benutzereingaben würde dann parallel zur Verarbeitung laufen.

In der Funktion die die Datenerfasst steckt man einfach jedes mal wenn ein Wert vorliegt, diesen in eine `Queue.Queue`. Und die Verarbeitung ist dann eine Schleife die alle x Sekunden alles nimmt, was bis dahin in der Queue steckt, und es verarbeitet.

Anmerkungen zum Quelltext:

Die Klammern um den Rückgabewert von `positiv()` sind unnötig.

Du hast da keine Liste mit 5 Werten, sondern 5 Listen bei denen eine nur einen Wert enthält und die anderen jeweils zwei Werte. Hast Du Dir das Ergebnis denn mal angesehen? Wenn man die Zahlen 1 bis 5 eingibt:

Code: Alles auswählen

[[[[[1], 2], 3], 4], 5]
Dieses durchnummerieren von Namen ist ja gerade etwas was man mittels Listen loswerden möchte. Erstelle eine leere Liste und hänge dann dort die Werte an. Zum Beispiel in einer Schleife die fünf mal durchlaufen wird.

Code: Alles auswählen

values = list()
for _ in xrange(5):
    values.append(input())

# Oder als "list comprehension":

values = [input() for _ in xrange(5)]
Pickle-Dateien sind Binärdateien, die muss man im Binärmodus öffnen, sonst kann es sein dass man die Daten nicht auf anderen Systemen laden kann, oder wenn es blöd läuft nicht einmal mehr auf dem System wo

Was das Speichern und gleich wieder Laden bringen soll, habe ich nicht so ganz verstanden‽

Wenn man Dateien mit der ``with``-Anweisung zusammen öffnet, dann werden sie auch ganz sicher wieder geschlossen, egal aus welchen Gründen der ``with``-Block verlassen wird. Die Datei zum Laden schliesst Du gar nicht.

Die `input()`-Funktion blockiert den Programmfluss solange bis der Benutzer etwas eingegeben hat. Erst danach geht es mit der nächsten Programmzeile weiter.
Tuedelue
User
Beiträge: 8
Registriert: Samstag 29. März 2014, 13:02

Hi BlackJack,

Danke, ich glaube, das hat geholfen! Code sieht jetzt so aus und tut, was er soll:

Code: Alles auswählen

# -*- coding: utf-8 -*-

# Nötige Module
import time, thread
Ergebnisse = list()

# Zwischenspeicherung der Daten
def sensor():
    for _ in xrange(5):
        Ergebnisse.append(input())

# Definition der übrigen Funktionen
def positiv(x):
    return x > 0
    print positiv

def negativ(x):
    return x <= 0
    print negativ

def filterung():
    len(filter(positiv, Ergebnisse))
    len(filter(negativ, Ergebnisse))
    if len(filter(positiv, Ergebnisse)) > len(filter(negativ, Ergebnisse)):
        print("Mehr positive Werte")
    elif len(filter(positiv, Ergebnisse)) == len(filter(negativ, Ergebnisse)):
        print("Gleichstand")
    else: print("Mehr negative Werte")

# Programmstart: 2 Sekunden zum Sammeln von Ergebnissen; danach Ausführung des Filters; bei 5 Ergebnissen wird die Liste zurückgesetzt
while 0 == 0:
    thread.start_new_thread(sensor,())
    time.sleep(2)
    thread.start_new_thread(filterung,())
    time.sleep(1)
    if len(Ergebnisse) == 5:
        del Ergebnisse[:]
Bedankt!
BlackJack

@Tuedelue: Das `thread`-Modul ist veraltet und sollte nicht mehr verwendet werden, deshalb hatte ich das `threading`-Modul genannt.

Veränderbare Datenstrukturen auf Modulebene sind keine gute Idee. Das wird sehr unübersichtlich und die Funktionen die so etwas verwenden sind keine in sich geschlossenen Einheiten mehr sondern tauschen auf undurchsichtige Weise Daten aus. Alles was in Funktionen oder Methoden verwendet wird und keine Konstante ist, sollte als Argument übergeben werden. Und auf Modulebene sollten nur Konstanten, Funktionen, und Klassen definiert werden.

Der Name `Ergebnisse` deutet von seiner Schreibweise auf einen Datentyp hin, es ist aber kein Typ sondern ein Exemplar. Siehe Style Guide for Python Code.

Die `input()`-Funktion ist irgendwas zwischen schwer beherrschbar bis gefährlich und sollte nicht benutzt werden. Der Benutzer kann dort beliebige Python-Ausdrücke eingeben, die dann ausgewertet werden. Dabei können alle Möglichen Ausnahmen auftreten. Und der Benutzer kann natürlich auch absichtlich Code eingeben der Schaden anrichtet, oder Einfluss auf den Programmablauf nimmt, der so nicht vorgesehen war.

Nach einem ``return`` wird kein Code mehr ausgeführt, denn die Ausführung der Funktion wird durch die ``return``-Anweisung ja beendet. Es ist auch nicht wirklich sinnvoll das Funktionsobjekt auszugeben. Und die Definition von `negativ()` entspricht nicht den üblichen Erwartungen — die meisten Leute werden die 0 als positiv sehen.

Die ersten beiden Zeilen in der `filterung()`-Funktion haben keinen Effekt ausser Rechenzeit zu verbrauchen.

Da die `filterung()` parallel zur Eingabe von neuen Werten und der ``while``-Schleife weiter unten läuft, haben wir hier ausserdem eine „race condition”: Jederzeit können neue Werte hinzukommen oder der Inhalt der Liste gelöscht werden. Du filterst die Liste vier mal und jedes mal kann der Listeninhalt anders aussehen, trotzdem vergleichst Du die Ergebnisse, die sich potentiell auf andere Werte beziehen.

Wenn die beiden Tests exakt das Gegenteil prüfen, also zusammen den gesamten Wertebereich der Elemente in `Ergebnisse` abdecken, braucht man auch gar nicht zweimal filtern. Der jeweils andere Wert lässt sich ja aus der Anzahl der Elemente und der Länge der gefilterten Werte berechnen. Wenn man 10 Elemente hat, und 8 davon positiv sind, dann weiss man das zwei negativ sein müssen, auch ohne die tatsächlich zu filtern und zu zählen.

`filterung()` ist auch ein schlechter Name für eine Funktion im allgemeinen und diese Funktion im besonderen. Funktionen tun etwas, darum eignen sich Tätigkeiten besser als Beschreibung. Und diese Funktion filtert nicht. Nicht als eigentliche Hauptaufgabe. Sie wertet die Ergebnisse aus.

Der Ausdruck ``0 == 0`` ergibt `True`:

Code: Alles auswählen

In [24]: 0 == 0
Out[24]: True
Das Ergebnis kann man also auch gleich direkt als Bedingung der ``while``-Schleife verwenden.

Die ``while``-Schleife halte ich aber insgesamt für einen Fehler. Es sollen ja eigentlich zwei Sachen parallel gemacht werden: Die Werte erfassen, und sie auswerten. Die ``while``-Schleife läuft *zusätzlich*. Die startet alle drei Sekunden einen neuen Thread der 5 Eingaben entgegennimmt, auch wenn ein oder mehrerer solcher Threads schon laufen! Und die konkurrieren dann um die Standardeingabe.

Und sie startet die `filterung()`-Funktion in einem Thread und wartet dann eine Sekunde. Warum rufst Du da die Funktion nicht einfach normal auf? Der asynchrone Start macht absolut keinen Sinn.

Ausserdem kann es passieren dass die `Ergebnisse` nicht geleert werden und die Liste immer weiter anwachsen kann. Man braucht nur warten bis mindestens zwei Threads auf Benutzereingaben warten und kann dann innerhalb der drei Sekunden bis der Test auf Länge gleich 5 gemacht wird *mehr als 5 Werte eingeben*!
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@Tuedelue: zusätzlich zu dem was Blackjack schon geschrieben hat: Deine Kommentare sind nichtssagend bis verwirrend. »print« ist keine Funktion, braucht also deshalb auch keine Klammern.
Du hast Dein Problem sequenziell gelöst nur mit dem Unterschied, dass Du, statt die Funktionen direkt aufzurufen, (fehlerhaft) Threads erzeugst, die die Funktionen aufruft.
Sensordaten kommen unabhänig vom restlichen Programmablauf an, das heißt, Du brauchst einen Thread, der ständig bereit ist, neue Daten zu lesen. Diese Daten mußt Du über eine threadsichere Datenstruktur dem Hauptprogramm, das die Daten verarbeitet, übergeben. Dabei mußt Du darauf achten, dass an jeder beliebigen Stelle im Hauptprogramm Daten hinzukommen könnten. Daher muß man an Stellen, an denen eine threadübergreifende Datenstruktur benutzt wird, besonderes aufpassen.

Code: Alles auswählen

from threading import Thread
from collections import deque
import logging
import time

def read_sensor(queue):
    while True:
        try:
            queue.append(float(raw_input()))
        except ValueError:
            logging.exception('Lesefehler')

def collect_values(queue):
    return [queue.popleft() for _ in xrange(len(queue))]

def process_values(values):
    negative = sum(value<0 for value in values)
    positive = len(values) - negative
    if negative < positive:
        print "Mehr positive Werte"
    elif negative == positive:
        print "Gleichstand"
    else:
        print "Mehr negative Werte"

def main():
    queue = deque()
    reader = Thread(target=read_sensor, args=(queue,))
    reader.daemon = True
    reader.start()
    while True:
        time.sleep(5)
        process_values(collect_values(queue))
        
if __name__ == '__main__':
    main()
BlackJack

@Sirius3: Das halte ich so nicht für threadsicher, auch wenn die jetzige Implementierung von `deque` das vielleicht in CPython sein mag. `Queue.Queue` verwendet ja auch intern eine `deque`, sichert die Operationen aber mit einem `threading.Lock` ab.
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@BlackJack: Die Dokumentation ist da eindeutig: "Deques support thread-safe, memory efficient appends and pops from either side of the deque with approximately the same O(1) performance in either direction."
Tuedelue
User
Beiträge: 8
Registriert: Samstag 29. März 2014, 13:02

Hi,

... :?: ... :K ...

Ich würde lügen, wenn ich behaupten würde, euch bei eurer Diskussion folgen zu können.

@Sirius3:
Danke für das Code-Update. Unter Windows mit Python2.7 klappt das soweit super, unter Linux bzw. RaspberryPi jedoch nicht - weder als 2.7 noch als 3...u.a. musste ich wieder Klammern um die print-Befehle machen und "xrange" soll ich gegen "range" ersetzen (Python3). Außerdem meckert die Konsole, dass "len" für module-Typen nicht verfügbar wäre.

Wie müsste ich den Code denn anpassen, damit letztere Fehlermeldung verschwindet?
BlackJack

@Tuedelue: Der Code von Sirius3 sollte auch auf dem Raspi mit Python 2.7 laufen. Bei beiden Stellen wo `len()` verwendet wird, wird es nicht auf ein Modul angewendet, also kann diese Fehlermeldung nicht sein.
Tuedelue
User
Beiträge: 8
Registriert: Samstag 29. März 2014, 13:02

Also beim Raspberry kriege ich unter Python 2.7. folgende Fehlermeldung:
Line9: descriptor 'append' requires a 'collections.deque' object but received a float
Unter Windows ist alles knorke.

Irgendwelche Tipps? :K
BlackJack

@Tuedelue: Auch das kann nicht sein. Du solltest vielleicht mal den gesamten Traceback zeigen und vielleicht auch den Quelltext, denn der von Sirius3 ist das sicher nicht. Wenn ich mal raten müsste hast Du da Anstelle von dem `queue`-Exemplar jeweils den Datentyp `deque` stehen.
Tuedelue
User
Beiträge: 8
Registriert: Samstag 29. März 2014, 13:02

:oops: :oops: :oops:

...ich möchte dich bitte heiraten....Problem erledigt!
Tuedelue
User
Beiträge: 8
Registriert: Samstag 29. März 2014, 13:02

Dürfte ich euch bzgl. des obigen Codes nochmal kurz auf die Nerven gehen?

Im nächsten Schritt möchte ich die Werte nicht per Hand eingeben, sondern per Sensor auslesen. Den Code für den Sensor gibt es hier:http://www.rn-wissen.de/index.php/Begre ... nsschleife.
Von Basic auf pythonisch übersetzt könnte da bei 1 Sensor ungefähr sowas rauskommen:

Code: Alles auswählen

def Begrenzungsschleife:
#ret tuple: (Svr, i, impulsbreite)
A0 = read.bus_byte(0x48)
Max0 = A0 + 30
Min0 = A0 - 30

i = 1
while i <= 2000:
Svr = read.bus_byte(0x48)
if Svr > Max0 or Svr < Min0:
Svr = read.bus_byte(0x48)
break
i = i + 1

# Falls Schleife kaputt
if i > 2000:
return

impulsbreite = 1
while impulsbreite <=15:
if read.bus_byte(0x48) > Max0 and Svr < A0:
break
if read.bus_byte(0x48) < Min0 and Svr > A0:
break
impulsbreite = impulsbreite + 1

print (Svr - A0)
Wenn ich jetzt wahlweise in Sirius' Code anstelle des "queue.append(float(raw_input()))" ein "queue.append(float(Svr - A0))" setze oder am Ende des "def Begrenzungsschleife():" ein "main()", ist das irgendwie nicht zielführend. Wahlweise wird nur 1x ein Sensorwert gelesen oder der Sensor liest mehrmals, das Ergebnis ("mehr positive Werte"/"mehr negative Werte") bleibt immer gleich - ganz egal, welche Werte reinkommen.

:oops: :oops: :K
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Einrückung ist bei Python Teil der Syntax, also benutze diese bitte auch. Ohne ist dein Code vollkommen unverständlich.
Das Leben ist wie ein Tennisball.
BlackJack

@Tuedelue: Selbst mit Einrückung wäre das syntaktisch falsch. Auch bei Funktionen die keine Argumente entgegen nehmen, muss man trotzdem eine leere Argumentenliste bei der Definition angeben.

Schreib am besten erst einmal eine Funktion die man tatsächlich aufrufen kann, und die einen Wert zurück liefert. Und dann teste die. Einzeln, unabhängig von dem Rest von dem Programm. Solange Du nämlich keine Funktion hast die je nach Positionierung des Sensors zur Begrenzungsschleife einen passenden Wert liefert, macht es keinen Sinn die in den Rest einzubauen und festzustellen dass das Gesamtprogramm nicht funktioniert.

Bei dem BASIC-Programm handelt es sich um eines für einen Mikroprozessor. Die Zahlenwerte für die Schleifen fallen nicht vom Himmel und sind auch nicht universal gültig, sondern hängen natürlich von der Ausführungsgeschwindigkeit des Programms im Verhältnis zur Frequenz ab mit der der Sender die Begrenzungsschleife mit Impulsen versorgt. Also würde es eventuell Sinn machen sich die empfangenen Werte mal anzuschauen, also zum Beispiel einfach mal eine paar Impulse lang aufzeichnen und grafisch auswerten, um zu sehen ob Python auf einem Linux auf einem Raspi für die gewählten Werte zu schnell oder zu langsam oder vielleicht sogar grundsätzlich zu unregelmässig ausgeführt wird um die Impulse zuverlässig erkennen zu können.
Tuedelue
User
Beiträge: 8
Registriert: Samstag 29. März 2014, 13:02

Mit folgendem Code schmeißt der Sensor fleißig Daten (angeschlossen per 8-Bit Wandler):

Code: Alles auswählen

# -*- coding: utf-8 -*-
from smbus import SMBus
import time

bus = SMBus(1)
A0=bus.read_byte(0x48)
Max0 = A0 + 50
Min0 = A0 - 50


while (0 == 0):
# Ret tuple: (Svr, i, impulsbreite)
    A0=bus.read_byte(0x48)
    Max0 = A0 + 50
    Min0 = A0 - 50

    i = 1
    while i <=2000:
        Svr = bus.read_byte(0x48)
        if Svr > Max0 or Svr < Min0:
            Svr = bus.read_byte(0x48)
            print(Svr-A0)
Nur die Kombination mit Sirius' Geschreibsel lässt noch zu wünschen übrig. :?:
BlackJack

@Tuedelue: Das ist ja auch keine Funktion die *einen* Wert liefert, je nach dem wo sich der Sensor befindet. Die ausgegebenen Werte sind auch nicht alle die Werte die Du haben möchtest. So wie die Logik momentan ist, ist die äussere ``while``-Schleife sinnfrei weil die innere ebenfalls eine Endlosschleife ist und nie verlassen wird. Und die gibt nicht nur den ersten Wert aus, der ausserhalb der Nulllinie liegt, sondern alles was ausserhalb liegt. Also viel zu viele Werte.
Tuedelue
User
Beiträge: 8
Registriert: Samstag 29. März 2014, 13:02

Meine Basic-Kenntnisse sind recht überschaubar. Wie wäre denn das hier als Alternative?

Code: Alles auswählen

from smbus import SMBus
import time

Startwert = bus.read_byte(0x48)
Obergrenze = Startwert + 30
Untergrenze = Startwert - 30

# Start des Auslesens:
Startwert = bus.read_byte(0x48)
while 0 == 0:
    Zwischenwert = bus.read_byte(0x48)
    if Zwischenwert > Obergrenze or Zwischenwert < Untergrenze:
        print("Sensor ist außerhalb der Schleife")
        break
Antworten