Cześć,
jak pisałem we wcześniejszym wpisie, różne zdarzenia życiowe spowodowały że nagle mam znacznie więcej czasu, więc dziś poopowiadam Wam o ciekawym libie Python’owym – manhole. Z angielskiego manhole znaczy.. Właz. Kanalizacyjny. Taki jak mijacie na ulicach. A co on robi w Pythonie? Ano zgodnie z tytułem wpisu – pozwala wejść do programu w sposób interaktywny trochę tak „z boku”.
Ale powoli, napiszmy kawałek kodu który będzie agregował jakieś dane w pamięci. Niech ten program w odpowiedzi na wysyłanie do niego komunikatów wykonuje operacje które znajdują się w tym komunikacie. Uprośćmy to maksymalnie – komunikaty będą kazały wykonywać jedną z podstawowych działań matematycznych z aktualną wartością w pamięci a wartością wysłaną w owym komunikacie. Brzmi zagmatwanie? Nieee, jest turbo proste.
Stwórzmy 3 pliki w takiej hierarchii:
data_store
__init__.py
subscriber
__init__.py
main.py
I plik w folderze data_store niech ma taką zawartość:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | import inspect myself = lambda: inspect.stack()[1][3] class DataStore(): def __init__(self): self.current_value = 0 self.history = [] def __add__(self, other): self.current_value = self.current_value + other self.history.append([ other, str(myself()) ]) def __mul__(self, other): self.current_value = self.current_value + other self.history.append([ other, str(myself()) ]) def __sub__(self, other): self.current_value = self.current_value - other self.history.append([ other, str(myself()) ]) def __div__(self, other): self.current_value = self.current_value / other self.history.append([ other, str(myself()) ]) def __str__(self): return str(self.current_value) data_store = DataStore() |
Nic skomplikowanego – klasa definiuje zachowania operatorów dodawania, odejmowania, mnożenia i dzielenia jako operacje na jednej z zmiennych tej klasy i zapisuje te zmiany jako historię.
Teraz plik w folderze subscriber:
1 2 3 4 5 6 7 8 9 | import redis def main(worker): redis_conn = redis.Redis() subscriber = redis_conn.pubsub() subscriber.subscribe('test') for item in subscriber.listen(): worker(item['data']) |
Co tu robimy? Prawie nic. Na każdą wiadomość która przyjdzie na kanał redisowy o nazwe „test” reagujemy wykonując przekazaną w parametrze funkcję. Banalne, nie?
I na koniec plik main.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from subscriber import main from datastore import data_store import operator from json import loads def worker(data): try: data = loads(data) print data op_func = getattr(operator, data['operation']) op_func(data_store, data['value']) except: pass if name == 'main': main(worker) |
Jako funkcję wejściową wykonujemy funkcję ze subscriber’a i przekazując mu jako argument naszą funkcję. Jaki będzie efekt? Bardzo prosty:
Jeśli na kanał wrzucimy 3 razy coś takiego:
redis-cli publish test '{"operation": "add", "value":6}'
To funkcja „worker” pobierze metodę dodającą i wykona na aktualnej wartości przechowywanej w data storze tę metodę. Co przy pierwszym wysłaniu spowoduje dodanie do 0 liczby 6. Drugie – do liczby 6 kolejną 6. I trzecie – do liczby 12(sumy) – kolejną 6. Co powoduje przechowanie w pamięci liczby 18 i 3 elementowej listy z historią tych operacji.
I teraz, na białym koniu, cały na biało – wjeżdża manhole.
Bo co jeśli w kodzie jest np. błąd i wartość przechowywana w pamięci jest zepsuta? Oczywiście można logować każdą zmianę i w myśl event source’ingu – odtworzyć stan i tak go skorygować dodatkowymi komunikatami by był poprawny, ale raz że to dość pracochłonne zajęcie a dwa wymusza wyczyszczenie pamięci programu co może wiązać się różnymi konsekwencjami. Np. straceniem tych danych które są poprawne lub przerwę w działaniu usługi.
Zmodyfikujmy więc nasz kod troszkę:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | from subscriber import main from datastore import data_store import operator from json import loads import manhole def worker(data): if data == 'enable_manhole': manhole.install(locals={'data_store':data_store}) else: try: data = loads(data) print data op_func = getattr(operator, data['operation']) op_func(data_store, data['value']) except: pass if __name__ == '__main__': main(worker) |
Dodaliśmy tu obsługę „zainstalowania” naszego włazu programistycznego. Wiec sprawdźmy to! Wyślijmy:
redis-cli publish test 'enable_manhole'
co spowoduje wypisanie na standardowe wyjście naszego programu:
Manhole[17744:1524079049.5979]: Patched <built-in function fork> and <built-in function fork>.
Manhole[17744:1524079049.5988]: Manhole UDS path: /tmp/manhole-17744
Manhole[17744:1524079049.5988]: Waiting for new connection (in pid:17744) ...
Co znaczy że manhole się zainstalował w osobnym wątku i udostępnia dostęp do programu (a dokładniej do zmiennej data_store co jest zdefiniowane parametrem locals) jako socket unixowy pod ścieżką /tmp/manhole-17744.
Połączmy się!
sudo nc -U /tmp/manhole-17744
Co spowoduje info o przyjęciu połączenia:
Manhole[17744:1524079333.6725]: Started ManholeConnectionThread thread. Checking credentials …
Manhole[17744:1524079333.6726]: Accepted connection on fd:5 from PID:18093 UID:0 GID:0
a my mamy dostęp do programu w formie interaktywnej konsoli Pythona:
możemy też edytować zmienne:
Wydaje mi się że łatwo zauważyć potencjał tej biblioteki.
Cześć. Dziś luźny artykuł, bo dziś pobawimy się jedną z pierwszy wersji Pythona. Skompilujemy go i zobaczymy co tam w… Read More
Nowy rok czas zacząć! Więc lećmy z podsumowaniem. Nowy artykuł Nie uwierzycie, ale pojawił się na blogu nowy artykuł! Piszę… Read More
Cześć! W Pythonie 3.13 dodano JITa! JIT, czyli just-in-time compiler to optymalizacja na która Python naprawdę długo czekał. Na nasze… Read More
Cześć! Zapraszam na podsumowanie roku 2024. Książki W sumie rok 2024 był pod względem ilości książek nieco podobny do roku… Read More
Podtrzymując tradycję, prawie regularnych podsumowań, zapraszam na wpis! Nie mogło obyć się bez Karkonoszy We wrześniu odwiedziłem z kolegą Karkonosze,… Read More
Oj daaawnoo mnie tu nie było. Ale wakacje to był czas dużej liczby intensywnych wyjazdów i tak naprawdę, dopiero jakoś… Read More
Pokaż komentarze
Ciekawy artykuł. Zdaje się, że koniecznie muszę nadrobić swoje zaległości jeśli chodzi o redisa, bo kiedy w przeszłości miałem okazję używać tej bazy, to nigdy nie bawiłem się w niej kanałami, a wyglądają one na zdecydowanie przydatną funkcjonalność. ;)