Jump to content

python3 wieder einmal ...


Recommended Posts

Geschrieben

sorry liebe gemeinde, aber es ist eol v. python2.x und wer macht sich nicht da gedanken über python3 ;-)

 

beim drüberfliegen über die ipconn-library habe ich mehr fragen als antworten :

 

da werden prober-threads, callback-threads usw. wie ein feuerwerk geballert. schön.

 

da seh ich queues, kontexte, wasauchimmer - grossartig

 

diesen code von einem async* aus zu bändigen : mahlzeit

 

wie habt ihr euch das so vorgestellt ?

 

wie soll dieses feuerwerk unter einer python-3 eventloop ansteuerbar sein ???

 

aber bitte keine kryptischen no-na-du-depp-meldungen.

 

ein klares design bitte.

 

ODER MUSS ICH JETZT ALLE MEINE TF-HARDWARE VERSCHROTTEN WEIL ICH MIR DIE HÄNDE BRECHE MIT EINER STATE-OF-THE-ART-IMPLEMENTIERUNG von standard-vorgängen mit asyncio ????????????????

 

lg wp

Geschrieben

... und damits nicht sagen könnts, da tut einer bloss blöd daherreden :

https://www.usenix.org/system/files/login/articles/login_spring16_11_beazley.pdf

 

damit kann ja vielleicht eure resistenz (bzw. unwillen, bzw. widerstand da tausende zeilen python incl. brickd, logger, mqtt-bridge usw. usw. usw. neu zu kodieren) etwas "weicher" machen.

 

ja, ich hab verständnis. nein. mit wegschauen geht : GAR NIX

Geschrieben

... und damit es nicht heisst, da ist ein obergscheiter am nölen :

 

https://realpython.com/python-concurrency/

 

ich hab ein problem - asyncio integration von tinker*

 

aber auch ihr habt ein problem mit der momentanen architektur unter solchen voraussetzungen.

 

ich wünsche mir konstruktive und passende lösungen ....

 

lg wp

Geschrieben

Hallo piwo,

 

wenn ich mir die Liste der Programmiersprachen ansehe, die TF so unterstützt empfinde ich dein "ich will aber" sehr komisch.

 

Wenn dir Teile der Implementierung nicht gefallen könntest du ja es besser machen, zumal du dich wohl gut damit auskennst.

 

So wie ich die TF Jungs kenne wird es sicher bald ne Lösung geben. Also wo liegt das Problem??

Geschrieben

@piwo: Dass die Python Bindings Threads nutzen ist ja eigentlich erstmal ein internes Implementierungsdetail. Hast du mal ein konkretes Beispiel wo du asyncio extern verwendest aber die aktuellen Python Bindings nicht nutzen kannst?

Geschrieben

** implementierungsdetail **

 

frage : wie verhält sich das implementierungsdetail wenn ich es aus coroutinen heraus verwenden muss

 

SÄMTLICHE callbacks kann ich m.m.n. schmeissen - oder wie könnte man die in die eventloop einbinden ???

 

oder gebt mal eine implementierungsempfehlung unter verwendung von co-routinen an bitte ...

 

w

Geschrieben

@jgmischke

 

wir sind hier bei einem kommerziellen anbieter von hardware und nicht bei einem wald-und-wiesen-opensource

 

ein minimum an zeitgemässer api ist keine frechheit, sondern ein grundrecht

 

hast schon einmal in die python-implementierung von ipconnection reingesehen ? da gehen mir die kommentare aus ...

 

lg

Geschrieben

Eine gängige Möglichkeit Callbacks in eine Eventloop einzubinden ist im Callback die Daten in eine Queue zu packen und im Eventloop die Queue auszulesen.

 

Im Brick Viewer nutzen wir zum Beispiel eine Queue um die Daten des Tinkerforge Systems in die Qt-Eventloop zu bekommen ohne das die GUI blockiert wird: https://github.com/Tinkerforge/brickv/blob/master/src/brickv/async_call.py

Geschrieben

lieber borg

 

ich habe jetzt jahrelang prozedural und mit threading mich herumgeschlagen und das nur um eine ipconn am leben zu erhalten und einen stack auszulesen bzw. mich mit events versorgen zu lassen.

 

das design das ihr vorgebt erzeugt ganz schlimme und unlesbare/unwartbare monolithen.

 

man sehe sich nur euren code für den mqtt-proxy an....

wenn das die spitze der fahnenstange bzgl "generische programmierung/strukturen" ist, dann mahlzeit.

 

ich brauche ein flexibles producer/consumer-system auf basis von python3 und coroutinen/tasks.

 

wie ich DAS mache, das ist meine sache. aber was mir da entgegensteht ist weniger die API sondern deren implementierung.

 

also bitte macht euch mal gedanken über alles was mit ASYNC/AWAIT anfängt ...

 

- wie sind client-calls zu wrappen

- wie sind callbackfunktionen zu wrappen

 

lg

Geschrieben

<<< wir sind hier bei einem kommerziellen anbieter von hardware und nicht bei einem wald-und-wiesen-opensource >>>

 

Genau, dieser Anbieter unterstützt fast 20(!) APIs aller gängigen Sprachen und das mit ein paar Programmierern. TF ist nicht MS und muss mit den Resourcen sicherlich vorsichtig umgehen.

 

Wenn ich mir etwa den C-Code von TF ansehe, kann ich nichts ungewöhnliches feststellen, wohl deswegen weil C natürlich hardwarenahe Programmierung sehr gut unterstützt. Vielleicht wäre ein Umstieg auf C das richtige für dich.

 

 

 

Geschrieben

man sehe sich nur euren code für den mqtt-proxy an....

wenn das die spitze der fahnenstange bzgl "generische programmierung/strukturen" ist, dann mahlzeit.

Du meinst die neuen MQTT Bindings die noch in der Beta sind? Der Code ist zu 100% generiert und in der Tat nicht zum durchlesen gedacht. Allerdings ist dort ja nun wirklich MQTT die Schnittstelle und der Python-Code im Hintergrund für den Nutzer nicht relevant.

 

- wie sind client-calls zu wrappen

- wie sind callbackfunktionen zu wrappen

 

Du meinst sowas?

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

HOST = "localhost"
PORT = 4223
UID = "GKp" # Change XYZ to the UID of your Air Quality Bricklet

import asyncio
from concurrent.futures import ThreadPoolExecutor

from tinkerforge.ip_connection import IPConnection
from tinkerforge.bricklet_air_quality import BrickletAirQuality

import time

_executor = ThreadPoolExecutor(10)

async def tf_async_call(name, func, delay):
    def _non_async_delayed_call(func, delay):
        time.sleep(delay)
        return func()

    value = await asyncio.get_event_loop().run_in_executor(_executor, lambda: _non_async_delayed_call(func, delay))
    print('{0}: {1}'.format(name, value))

async def async_main(aq):
    call_temperature = tf_async_call('Temperature', aq.get_temperature, 2)
    call_humidity = tf_async_call('Humidity', aq.get_humidity, 1)

    task1 = asyncio.ensure_future(call_temperature)
    task2 = asyncio.get_event_loop().create_task(call_humidity)


    print('Do something useful here while humidity and temperature are gathered...')
    start = time.time()
    while start + 2 > time.time():
        await asyncio.sleep(0.1)
        print(time.time())

    await asyncio.wait([task1, task2])
    print('Done!')

if __name__ == "__main__":
    ipcon = IPConnection() # Create IP connection
    aq = BrickletAirQuality(UID, ipcon) # Create device object

    ipcon.connect(HOST, PORT) # Connect to brickd
    # Don't use device before ipcon is connected

    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.ensure_future(async_main(aq)))

    ipcon.disconnect()

 

Edit: Hab das gerade nochmal ein bisschen aufgebohrt, damit man sieht das ich wirklich async calls machen kann während der getter-Aufruf läuft

Geschrieben
async def tf_async_call(name, func, delay):

    await asyncio.sleep(delay) # Add some artifical wait time

async def tf_async_call(name, func, delay):

    def _non_async_delayed_call(func, delay):

        time.sleep(delay)

def test(func, delay):

    time.sleep(delay)

 

wozu all die delays ? wir wollen ja nicht umsonst warten in einem async/await-umfeld ...

 

ich pflege bei allen bricklet-initialisierungen ein br_obj.set_response_expected_all(True) aufzurufen, in der hoffentlich nicht irrigen meinung, dass damit ein optimiertes antwortzeitverhalten erzielt wird ...

noch dazu ein (sync) time.sleep() statt eines (async) asyncio.sleep() ... es erfolgt KEIN scheduling "anderer" coroutinen - aber genau das ist ja das ziel - für sowas brauch ich keine coroutinen ...

 

rationale ??? bitte ein tragfähiges design und keine ad-hoc-lösungen

 

lg wp

 

 

 

 

Geschrieben

bezüglich mqtt-proxy :

 

ich bin schon dazu übergegangen, den "normalen" d.h. nicht den BETA mqtt-proxy für queuing in einem async-umfeld zu "durchleuchten".

 

bin noch auf keinen verifizierbaren grünen zweig gekommen.

absicht dabei ist : statt topics in paho zu injizieren diese in eine async-queue zu füttern und vice-versa

 

ich wäre ja schon zufrieden, wenn ich diesen (universellen) moloch als seperaten thread weit weg von den "normalen" coroutinen bedienen könnte. einen thread pro stack. der soll machen was er will, hauptsache, er passt in ein "echtes" asyncio-umfeld.

 

got the idea ?

Geschrieben

Die ganzen delays sind da zu Demonstrationszwecken, damit die non-async Funktionen viel Zeit benötigen.

 

Mit dieser Schleife hier

 

    
while start + 2 > time.time():
        await asyncio.sleep(0.1)
        print(time.time())

Zeige ich dann das ich während die Tinkerforge Getter aufgerufen werden weitere async Funktionen aufrufen kann, was ja Sinn der Sache ist.

 

 

Hier eine vereinfachte Version:

 

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

HOST = "localhost"
PORT = 4223
UID = "GKp" # Change XYZ to the UID of your Air Quality Bricklet

import asyncio
from concurrent.futures import ThreadPoolExecutor

from tinkerforge.ip_connection import IPConnection
from tinkerforge.bricklet_air_quality import BrickletAirQuality

import time

_executor = ThreadPoolExecutor(10)

async def tf_async_call(name, func):
    value = await asyncio.get_event_loop().run_in_executor(_executor, func)
    print('{0}: {1}'.format(name, value))

async def async_main(aq):
    task1 = asyncio.ensure_future(tf_async_call('Temperature', aq.get_temperature))
    task2 = asyncio.ensure_future(tf_async_call('Humidity', aq.get_humidity))

    # Here your async code
    await asyncio.wait([task1, task2])
    print('Done!')

if __name__ == "__main__":
    ipcon = IPConnection() # Create IP connection
    aq = BrickletAirQuality(UID, ipcon) # Create device object

    ipcon.connect(HOST, PORT) # Connect to brickd
    # Don't use device before ipcon is connected

    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.ensure_future(async_main(aq)))

    ipcon.disconnect()

 

Edit: Deine Idee mit den MQTT Bindings zu benutzen um damit Python asyncio zu machen hab ich leider nicht verstanden, aber was spricht dagegen den Code oben in einem asyncio-Umfeld einzusetzen?

Geschrieben
Edit: Hab das gerade nochmal ein bisschen aufgebohrt, damit man sieht das ich wirklich async calls machen kann während der getter-Aufruf läuft

 

bitte sieh all das nicht als quälerei ...

vielleicht bin ich ja der erste (trottel), aber diese dinge kommen sowieso bald auf euch zu ...

 

ich kann dir ja mal eine "old-fashioned" implementierung mailen, wo ich rein durch (rekursives, irrsinniges) __init__ eine stackumgebung definiert habe und dann eine alarmanlagenüberwachung "soft-pluggable" gemacht habe : wenn die objekte "richtig" definiert wurden, ist das ding losgerattert für BELIEBIGE STACKS ;-)

 

aber ich hab im moment von solchen zaubereien die nase voll und will in ein NEUES "soft-configurable" design wechseln auf basis von asyncio und python3 - die "alten dinger" zahlen sich heutzutage in den "neuen zeiten" nicht mehr aus. python2 und prozedurale zaubereien sind für io-bound tasks VORBEI ....  echt .... ;-)

Geschrieben

corr: "optimiertes antwortzeitverhalten" :

 

wenn ich eine setter-function absetze, will ich als paranoider mensch auch, dass ich nicht nochmals einen getter nachwerfen, ob das auch so passt ... d.h. will nicht nachher womögich über exceptions stolpern, die "so nicht ausgemacht sind" ;-)

 

wenn ein "return" returned, dann sollte er auch indizieren, dass alles passt ... auch bei den settern

ob das wirklich SO der fall ist, weiss ich nach jahren noch immer nicht, bzw. GLAUBE, dass das dann auch "passt"

 

please enlighten me ;-)

 

lg wp

Geschrieben

Wenn du set_response_expected_all(True) aufrufst wird für alle Aufrufe (auch für Setter) eine Nachricht vom Brick/Bricklet zurück zum PC gesendet und wenn diese nicht ankommt bekommst du eine TimeoutException.

Geschrieben

ok.

 

asyncio getters werd ich implementieren ...

 

callbacks werd ich :

 

a) vergessen (ist kein drama ; ich bevorzuge sowieso totale kontrolle nach "meinen vorstellungen" ;-)

 

b) entgegen euren empfehlungen ".... callbacks sind IMMER zu bevorzugen ..." NICHT IMPLEMENTIEREN

 

c) in eine awaitable Queue zwingen : ok. und wie ?

da muss man ja die callbacks irgendwie für coroutinen tauglich machen ...

 

ad c)

 

ich fürchte, das geht nicht "ohne weiteres". dein beispiel mit dem injizieren in die QT-loop ist zwar schlüssig, setzt aber die verwendung von den brickv-(client)-imports voraus.

 

ich hoffe, diese unterscheiden sich nicht allzusehr von den "normalen" (client)-python-bindings ... ( -> am besten durch NULL DIFFERENCE ;-)

 

GENERELL :

 

ich fürchte mich nicht vor separaten threads, die es in einem co-routinen-tauglichen umfelt zu initialisiern gibt ...

 

threading.local-kontexte are your friend ;-)

 

ziel sollte halt sein, dass irgendein x-beliebiger brickd (vor allem auf red-brick) oder ein x-beliebiger tinkerforge-stack mit wlan-extension-v1,v2 via tcp-bindings angesteuert werden kann in einem co-routinen-tauglichen umfeld ...

 

welche helper oder wrapper oder leck-mich-am-... ich da brauche ist mir EGAL. am ende sollen asyncio-tasks konstruierbar sein, die auch entsprechend yielden und nicht in der nase bohren (= pfui teufel ;-) ) ...

Geschrieben

... na da sieht man, wie gut das ist : hatte noch niemals eine TimeoutException über jahre ;-)

... vielleicht hat auch keiner die gehandelt ;-) ;-)

Geschrieben

ich hoffe, diese unterscheiden sich nicht allzusehr von den "normalen" (client)-python-bindings ... ( -> am besten durch NULL DIFFERENCE ;-)

Der aktuelle Brick Viewer nutzt immer die aktuell veröffentlichten Python-Bindings.

 

 

Hier das Beispiel nochmal mit zusätzlichem Callback:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

HOST = "localhost"
PORT = 4223
UID = "GKp" # Change XYZ to the UID of your Air Quality Bricklet

import asyncio
from concurrent.futures import ThreadPoolExecutor

from tinkerforge.ip_connection import IPConnection
from tinkerforge.bricklet_air_quality import BrickletAirQuality

import time

_executor = ThreadPoolExecutor(10)
_air_pressure_queue = asyncio.Queue()

def cb_air_pressure(air_pressure, loop):
    asyncio.run_coroutine_threadsafe(_air_pressure_queue.put(air_pressure), loop)

async def tf_async_call(name, func):
    value = await asyncio.get_event_loop().run_in_executor(_executor, func)
    print('{0}: {1}'.format(name, value))

async def async_main(aq):
    task1 = asyncio.ensure_future(tf_async_call('Temperature', aq.get_temperature))
    task2 = asyncio.ensure_future(tf_async_call('Humidity', aq.get_humidity))

    air_pressure = await _air_pressure_queue.get()
    print('Air Pressure: {0}'.format(air_pressure))

    # Here your async code
    await asyncio.wait([task1, task2])
    print('Done!')

if __name__ == "__main__":
    ipcon = IPConnection() # Create IP connection
    aq = BrickletAirQuality(UID, ipcon) # Create device object

    ipcon.connect(HOST, PORT) # Connect to brickd
    # Don't use device before ipcon is connected

    loop = asyncio.get_event_loop()

    aq.register_callback(aq.CALLBACK_AIR_PRESSURE, lambda x: cb_air_pressure(x, loop))
    aq.set_air_pressure_callback_configuration(100, False, 'x', 0, 0)

    loop.run_until_complete(asyncio.ensure_future(async_main(aq)))

    ipcon.disconnect()

Consolen-Ausgabe:

olaf@pc2:~/build20/python$ ./async_example2.py

Air Pressure: 99750

Temperature: 2478

Humidity: 2707

Done!

Geschrieben

d.h.

pro bricklet eine Queue und 10 workers ?

oder gibt es da noch optimierungen ?

 

sorry, aber ich habe meistens 12-16 bricklets pro stack ;-)

Geschrieben

Das hängt davon ab wie du dein Programm gestaltest und was mit den Werten gemacht werden muss und wie oft du Daten abfragst etc. Der ThreadPoolExecutor kann natürlich von allen Bricklets genutzt werden. Wahrscheinlich reichen da auch 2-4 Worker für beliebig viele Bricklets.

 

Statt einer Queue pro Bricklet kannst du natürlich auch eine Queue für alle verwenden und neben den Daten den Messwert-Typ mit in die Queue packen.

Geschrieben

... gibts das obige beispiel auch mit argumenten bei den calls des

 

*** tf_async_call ***

 

???

 

dass callbacks direkt aufgerufen werden und in die queue einspeisen ist irgendwo klar.

 

aber wie geht man mit argumenten bzw. dem handling von argumenten von settern in deiner implementierung um ?

 

async def tf_async_call(name, func, *args):

... run_in_executor(_executor, func,*args)

 

oder mit partials ?

 

lgw

Geschrieben

Die Argumente kannst du mit functools.partial umsetzen, ist auch so in der Python-Doku so dokumentiert: https://docs.python.org/3/library/asyncio-eventloop.html

 

Rückgabewerte in Python asyncio kann man ganz allgemein mit Futures machen: https://docs.python.org/3/library/asyncio-future.html#asyncio.Future

 

In dem Beispiel würdest du das Future (future = asyncio.Future()) mit bei dem tf_async_call übergeben und dann da wo aktuell das print ist future.set_result(value) aufrufen und an der Stelle wo du es benutzen willst kannst du dann result = await future machen.

 

Hier gibt es ein Beispiel dafür: https://pymotw.com/3/asyncio/futures.html

 

In dem Beispiel oben kannst du in tf_async_call aber auch einfach den Wert zurückgeben und nach dem await asyncio.wait([task1, task2]) kannst du task1.result() und task2.result() aufrufen.

Geschrieben

... ich möchte die low-level routinen vermeiden ...

 

d.h. keine futures, sondern ** tasks ** die ge-gathered werden

 

möglicherweise mit einem loop-argument falls ich mehrere loops instanziere, und mit return_exceptions=True/False

 

die auswirkungen von Exceptions auf das canceln von concurrent tasks bzw. das handling in der routine, die die tasks scheduled bin noch heftig am studieren ;-)

 

jedenfalls soll das design sowas ähnliches wie invariante ergebnisse (d.h. alles oder nichts) über alle rekursiven task-ketten liefern

 

ich habe keine lust, mich mit problemen "unten" einzeln durchzukämpfen...

 

entweder alle operationen laufen, oder der ganze stack (und auch alle von seinen operationen abhägigen komponenten werden gecancelt oder auch restarted ... d.h.

 

der stack ist in meinem design producer/consumer für sämtliche darübergelagerten tasks, die im wesentlichen aber nur ein "wiring" bzw. "mapping" von daten vom stack machen.

alles was vom stack kommt ("sensor") oder in diesen injiziert wird ("actor") ist als gesamtheit zu sehen. als "kontrakt" zwischen "stack" und "rest". die state-machine dieser "beziehung" kennt nur 2 zustände des stacks : RUNNING oder FAILED.

nach N mal FAILED in einem floating fenster gibts dann einen sys.exit(1) mit entsprechendem alarm per sms oder so ...

 

eigentlich nichts wirklich unübliches, denke ich.

und ich denke so ein design mit "minimalem coding" ist durchaus im interesse der allgemeinheit ....

 

lgw

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gast
Reply to this topic...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

×
×
  • Neu erstellen...