Better python for network engineers

Wszystko o rozwiązaniach Software-defined networking (SDN), OpenFlow, APIC-EM, ACI, REST API i pokrewnych
Wiadomość
Autor
horac
wannabe
wannabe
Posty: 72
Rejestracja: 18 kwie 2016, 21:04
Lokalizacja: CCIE

Better python for network engineers

#1

#1 Post autor: horac »

Czesc tak sobie pomyslalem ze podziele sie jak functional programming moze ulatwic prace ze strukturami danych

Na poczatku jedno zdanie czym jest functional programming. To podejscie do programowania i pisania kodu ktory w zasadzie
nie powinien modyfikowac danych wejsciowych (pure functions), byc odporny na bugi, zapobiegac DRY(Don't repeat yourself)
i byc jak najkrotszy.

Kilka przykladow:

zakladamy ze mamy prosta strukture danych jak ponizej, gdzie interfaces to tablica/lista zawierajaca slowniki
gdzie kazdy slownik odpowiada wartoscia(danym) z danego interfejsu

Kod: Zaznacz cały

 interfaces = [{"type":"GitabitEthernet", "status":"up", "rx":2000, "tx":3000, "id":1},
		    {"type":"GigabitEthernet", "status":"up", "rx":2100, "tx":3309, "id":2},
		    {"type":"GigabitEthernet", "status":"down", "rx":200, "tx":5000, "id":3},
		    {"type":"GigabitEthernet", "status":"up", "rx":1000, "tx":2000, "id":4}]
Zadanie nr 1.

Zwrocic interfejsy ktore sa w stanie up.

Mozna to zrobic na dwa trzy sposoby.

a) Tradycyjny: petla for sprawdzenie warunku i wrzuceniu do nowej listy
interfejsy spelniajace warunek.

Kod: Zaznacz cały

>>> def traditionalFunc(status,interfaces):
...     statusInterfaces = []
...     for x in interfaces:
...             if x.get('status') == status:
...                     statusInterfaces.append(x)
...     return statusInterfaces
... 
>>> traditionalFunc('up')
[{'type': 'GitabitEthernet', 'id': 1, 'status': 'up', 'tx': 3000, 'rx': 2000}, {'type': 'GigabitEthernet', 'id': 2, 'status': 'up', 'tx': 3309, 'rx': 2100}, {'type': 'GigabitEthernet', 'id': 4, 'status': 'up', 'tx': 2000, 'rx': 1000}]
Ble.. ile kodu!! na prosta operacje, zobaczmy jak poradzi sobie list comprehension


b) List comprehension

Kod: Zaznacz cały

>>> def comprehensionFunc(status,interfaces):
...     return  [x for x in interfaces if x.get('status') == status]
... 
>>> 
>>> comprehensionFunc('up',interfaces)
[{'type': 'GitabitEthernet', 'id': 1, 'status': 'up', 'tx': 3000, 'rx': 2000}, {'type': 'GigabitEthernet', 'id': 2, 'status': 'up', 'tx': 3309, 'rx': 2100}, {'type': 'GigabitEthernet', 'id': 4, 'status': 'up', 'tx': 2000, 'rx': 1000}]
>>> 
Znacznie lepiej prawda :) No ale to jeszcze nie jest to co bysmy chcieli. Zobaczmy jak mozna to zrobic metoda filter

c) Metoda filter

Kod: Zaznacz cały

>>> interfacesStatus = list(filter(lambda x: x.get('status')=='up', interfaces))
>>> 
>>> interfacesStatus
[{'type': 'GitabitEthernet', 'id': 1, 'status': 'up', 'tx': 3000, 'rx': 2000}, {'type': 'GigabitEthernet', 'id': 2, 'status': 'up', 'tx': 3309, 'rx': 2100}, {'type': 'GigabitEthernet', 'id': 4, 'status': 'up', 'tx': 2000, 'rx': 1000}]
Dobra zgodze sie metoda filter jest znacznie bardziej magiczna, ale to tylko pierwsze wrazenie. Na pewno lambda moze spowodowac ze niektorzy zwatpia
w funkcyjne programowanie ale nie taki diabel straszny jak go maluja !. Lambda to nic innego jak nie nazwana funkcja ktora moze przyjac dowolna ilosc argumentow (wszystko przed :) a nastepnie poslugujac sie tymi argumentami wykonac okreslona operacje na strukturze ktora jest drugim argumentem do funkcji filter. Sam filter zwraca nam obiekt wiec musimy zamienic to na strukturke metoda list ktora zwroci nam liste.

Kolejny przyklad mam nadzieje pokaze wiecej prawdziwej mocy drzemiacej w programowaniu funkcyjnym.

Zadanie 2.

Zwrocic sume wszystkich RX :) Jako zadanko mozna sprobwoac metody tradycyjnej i list comprehension, ja natomiast chce pokazac jak zrobic to za pomoca pure funkcji
o nazwie map + sum.

Kod: Zaznacz cały

>>> rxSum = sum(list(map(lambda x: x.get('rx'), interfaces)))
>>> rxSum
5300
Sweet, jedna linia kodu a jaka operacja :)

Teraz.. nikt nei zabroni nam laczyc metod do bardziej wymyslonych warunkow. Zrobmy teraz tak, dodajmy tylko TX interfejsow ktore sa w stanie UP, nie inetresuja nas interfejsy ktore sa down tym razem

Kod: Zaznacz cały

>>> txSum = sum(list(map(lambda x: x.get('status') == 'up' and x.get('tx'), interfaces)))
>>>  
>>> txSum
8309
Bam ! jedna linia kodu !! Mam nadzieje ze zacheci to was do chociaz przemyslenia programowania funkcyjnego jako metody pracy ze strukturami. Przy dluzszym obcowaniu
i wprawie obiecuje wam ze nie bedziecie chcieli juz pisac tradycyjnych petli.

Jak ktos dokladnie chce wiedziec jak dziala list comprehension to zapraszam do mnie na bloga http://horac.pl

Pozdr

freel4ncer
wannabe
wannabe
Posty: 581
Rejestracja: 27 wrz 2007, 01:13

Re: Better python for network engineers

#2

#2 Post autor: freel4ncer »

naginasz troche rzeczywistosc bo jesli chcesz pokazac ze mozesz uzyc onelinera (btw to nie perl gdzie sie zawody w onelinerach robilo )
to czemu poprostu nie zorbisz interfacesStatus = [x for x in interfaces if x.get('status') == "up"] tylko tworzysz funkcje ? ;)

to samo z liczeniem rx
listcomprehension

Kod: Zaznacz cały

sum([x.get('rx',0) for x in interfaces])
jest krotsze niz

Kod: Zaznacz cały

sum(list(map(lambda x: x.get('rx'), interfaces)))
pozatym twoja lambda sie wysypie jesli ktorys z interfejsow nie bedzie bedzie mial klucza rx ;) list comprehension ktory podalem bedzie dzialal (ale to juz wina braku default value w funkcji get a nie samem lambdy;)

Tak wiec moje osobiste zdanie jest takie :
list comprehension jak najbardziej ! uzywam czesto i jest super
lambda not so much (chociaz czasami sie przydaje nie powiem ale unikalbym uzywania na sile) - ze wzgledu na redability wole rozpisac na named function dodac komentarze do tego ladniej wyglada w tracebacku gdy sie cos sypnie :)

horac
wannabe
wannabe
Posty: 72
Rejestracja: 18 kwie 2016, 21:04
Lokalizacja: CCIE

Re: Better python for network engineers

#3

#3 Post autor: horac »

Funkcja jest po to żebyś sobie status wrzucił jaki cię interesuje pod argumentem. To są banalne przykłady a lambda się przydaje gdy więcej argumentów podajesz. np wyciągając dane z różnych struktur i robiąc na nich operacje. Zastosowanie nie jest do konkursów tylko, zobacz na puree funkcje JavaScript tam to dopiero się kod kroi w postaci spread array i object operatora

A po to użyłem get żeby się nie wysypało bo get obsługuje wyjątek braku klucza w słowniku z automatu

Dobrze że nie napisałem tego w Ruby bo tam to dopiero można się czepiać różnych rozwiązań(lambda vs Proc, block itp) :) chodzi tylko o to żeby ludzie nie pisali w pythonie jak w C tylko skorzystali z możliwości jakie daje język wyższego poziomu

Po za tym na sile wyrzuciles 'rx' z jednego interfejsu zeby wywolac blad na lambda. Wiadomo ze edge case'y sie obsluguje ale nie o tym ten watek ;]

A zsumowac sie da tylko trzeba dodatkowy warunek wrzucic
Ostatnio zmieniony 08 mar 2017, 16:32 przez horac, łącznie zmieniany 2 razy.

freel4ncer
wannabe
wannabe
Posty: 581
Rejestracja: 27 wrz 2007, 01:13

Re: Better python for network engineers

#4

#4 Post autor: freel4ncer »

horac pisze:Funkcja jest po to żebyś sobie status wrzucił jaki cię interesuje pod argumentem. To są banalne przykłady a lambda się przydaje gdy więcej argumentów podajesz. np wyciągając dane z różnych struktur i robiąc na nich operacje. Zastosowanie nie jest do konkursów tylko, zobacz na puree funkcje JavaScript tam to dopiero się kod kroi w postaci spread array i object operatora

A po to użyłem get żeby się nie wysypało bo get obsługuje wyjątek braku klucza w słowniku z automatu
Aha i zwraca NoneType sproboj to zsumowac :D

Kod: Zaznacz cały

Traceback (most recent call last):
  File "test.py", line 7, in <module>
    print sum(list(map(lambda x: x.get('rx'), interfaces)))
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
Poczytaj sobie jaki stosunek do lamdy ma tworca pythona Guido
http://www.artima.com/weblogs/viewpost.jsp?thread=98196
Why drop lambda? Most Python users are unfamiliar with Lisp or Scheme, so the name is confusing; also, there is a widespread misunderstanding that lambda can do things that a nested function can't -- I still recall Laura Creighton's Aha!-erlebnis after I showed her there was no difference! Even with a better name, I think having the two choices side-by-side just requires programmers to think about making a choice that's irrelevant for their program; not having the choice streamlines the thought process



Powtorze jeszcze raz z perspektywy osoby ktora na codzien pisze w pythonie i robi review kodu polecam list comprehension , ale lambdy juz nie zabardzo (nie wykluczam uzywania tylko nie polecam sie napalac na nia ) - czyta sie i debugguje znacznie gorzej. Kod jest duzo czytelniejszy przy uzyciu funkcji. Znac oczywiscie nalezy. Moj post nie jest krytyka twojego tylko polemika , brawo za to ze chcialo ci sie przedstawic powyzsze zagadnienia ale uwazam ze nei mozna byc wobec nich bezkrytycznym . Podam przyklad ktory czesto obserwoje. Sa ludzie ktorzy pisza proceduralnei od jakiegos czasu i ktoregos dnia odkrywajja OOP od tego czasu wszedzie wciskaja objekty i klasy czesto tam gdzie nei ma to najmniejszego sensu ;) Czy OOP jest zle ? oczywiscie ze nie czy nalezy nagle porzucic proderulanosc i pisac wszystko obiektowo - not so much

horac
wannabe
wannabe
Posty: 72
Rejestracja: 18 kwie 2016, 21:04
Lokalizacja: CCIE

Re: Better python for network engineers

#5

#5 Post autor: horac »

Zgodze sie z tym ze dobiera sie metody pod wymagania i dobrze ze istnieje polemika bo swiat bylby nudny , ale widze duzo zalet programowania funkcyjnego. Niezliczona ilosc bledow trudnych do wychwycenia powstaje w tradycyjnym petleniu, off-by-one to chyba taka wisienka na torcie bugow w przypadku petli. Po za tym chodzi o to zeby pisac jak najmniej kodu a zeby robil jak najwiecej. Ok problemem moze byc debugowanie pozniejsze a czytelnosc to juz raczej zalezy od doswiadczenia i preferencji.

I bezkrytyczny na pewno nie jestem, wszystkiego programowaniem funkcyjnym zastapiac sie nie da, ale do obrobki danych wejsciowych jest swietne, zwlaszcza gdy wyciagasz cos po API i dostajesz bloba z JSONem. OOP glownie do tworzenia relacji miedzy obiektami, ale tez nie czysty OOP bo ja jestem zwolennikiem komponent programowania z roznymi jego wersjami.

Na sile wyrzuciles klucz wartosc rx z interfejsow.. i pokazujesz blad na NoneType ale to sie da nadal zsumowac tylko z dodatkowym warunkiem
freel4ncer pisze: Aha i zwraca NoneType sproboj to zsumowac :D

Kod: Zaznacz cały

>>> interfaces = [{'type': 'GitabitEthernet', 'id': 1, 'status': 'up', 'tx': 3000}, 
			{'type': 'GigabitEthernet', 'id': 2, 'status': 'up', 'tx': 3309, 'rx': 2100},
 			{'type': 'GigabitEthernet', 'id': 3, 'status': 'down', 'tx': 5000, 'rx': 200}, 
 			{'type': 'GigabitEthernet', 'id': 4, 'status': 'up', 'tx': 2000, 'rx': 1000}]

>>> sum(list(map(lambda x: x.get('rx') or x.get('rx') != None, interfaces)))
3300

freel4ncer
wannabe
wannabe
Posty: 581
Rejestracja: 27 wrz 2007, 01:13

Re: Better python for network engineers

#6

#6 Post autor: freel4ncer »

a tak nie ladniej?

Kod: Zaznacz cały

sum(list(map(lambda x: x.get('rx',0), interfaces)))

P.s uzylem zlego doboru slow z tym sproboj to zsumowac oczywisice ze sie da po modyfikacji lamdy chodizlo mi tylko o to ze Nonetype z int nie da rady ;)

horac
wannabe
wannabe
Posty: 72
Rejestracja: 18 kwie 2016, 21:04
Lokalizacja: CCIE

Re: Better python for network engineers

#7

#7 Post autor: horac »

co do NonType to jest cos co mnie w pythonie zawsze wkurza. Ze nigdy nie wiadomo co ci wypluje badz zwroci. Troche to jest wk w nietypowanych jezykach wlasnie.
W C dostajesz konkretnego typu dane na wejsciu, masz funkcje ktora zwraca pointer na int, to to ma zwrocic i nie ma zmiluj. W pythonie, zwlaszcza np w takim PyQt
gdzie bind jest zrobiony do C++ mozna spedzic godziny na debugowaniu;]

freel4ncer
wannabe
wannabe
Posty: 581
Rejestracja: 27 wrz 2007, 01:13

Re: Better python for network engineers

#8

#8 Post autor: freel4ncer »

ja dlatego czesto obchodze to uzywajac default value (oczywisicie nie zawsze sie da ) chociaz i tak powinnismy sie cieszyc ze NoneType ma boola False ;)

ODPOWIEDZ