260 likes | 444 Views
Čo objaví Pascalista v Pythone. Peter Borovansk ý, KAI, I-18, borovan (a)ii.fmph.uniba.sk. Cie ľom prednášky: je ukázať zaujímavé jazykové konštrukcie jazyka Python ( procedurálny, objekt ový a funkc ionálny štýl , list-comprehension, generátory, ... )
E N D
Čo objaví Pascalista v Pythone Peter Borovanský, KAI, I-18, borovan(a)ii.fmph.uniba.sk Cieľom prednášky: • je ukázať zaujímavé jazykové konštrukcie jazyka Python (procedurálny, objektový a funkcionálny štýl, list-comprehension, generátory, ...) • neurobiť ďalší tutorial, ani prehľad metód akejkoľvek triedy • skúsenosti s jazykom (čo ma zaujalo a prekvapilo) Literatúra: • Python docshttp://docs.python.org/py3k/ • Python in Educationhttp://www.python.org/community/sigs/current/edu-sig/ • Learning with Python (2.x) http://openbookproject.net//thinkCSpy • Programovací jazyk Pythonhttp://www.py.cz (http://howto.py.cz/index.htm) Cvičenie: • malá pythoniáda - jednoduché pythonovské programy • interpreter konečného automatu/Turingového/Minského stroja
Guido van Rossum v roku 1996 napísal: "Pred šiestimi rokmi, v decembri 1989, som hľadal zábavný programátorský projekt, ktorý by ma zamestnal cez týždeň počas Vianoc. Moja kancelária … bola zavretá, ale mal som domáci počítač a nič iného na práci. Rozhodol som sa, že napíšem interpreter pre nový skriptovací jazyk, o ktorom som už skôr premýšľal: nasledovníka jazyka ABC, ktorý by zaujal programátorov v Unixe/C. Ako pracovný názov som zvolil Python, lebo som bol v nevážnej nálade (a tiež som veľkým fanúšikom Monty Pythonovho Lietajúceho cirkusu)." http://sk.wikipedia.org/wiki/Guido_van_Rossum
Rozdiel medzi dynamicky a staticky typovaným jazykom Jednoduché typy • interpretovaný jazyk • poskytuje typy podobné int, long, float, complex, bool, str(ing), … • implicitné číselné konverzie až na konverziu z/do string (1j*1j+1 == 0) • dynamicky typovaný jazyk • každá hodnota je asociovaná s typom, avšak typ premennej/konštrukcie nie je známy počas kompilácie, ale je známy až v čase výpočtu def dynatest(flag): if flag: var = 1234 else: var = "retazec" print(var,':',type(var)) if flag: var = 3.4 else: var = flag print(var,':', type(var)) dynatest(True) dynatest(False) 1234 : <class 'int'> 3.4 : <class 'float'> retazec : <class 'str'> False : <class 'bool'> Analógia null a void z C++ None : <class 'NoneType'>
...toto sme nechceli robiť, ale niečo z toho môže byť na rozcvičke Operácie nad základnými typmi pozri si poriadne http://docs.python.org/library/stdtypes.html ani Pascal ani C... Pascal: • x and y, x or y, not z, True, False • predsa pozná <> ako anachronizmus • int(x), float(x), long(x), str(x) sú typové konverzie à la Pascal C: • case sensitive, True != true • ==, !=, = ((ne)rovnosť, priradenie) • +, -, *, /, //, %, ** sú operátory sčítania, ... , celočíselné delenie, modulo, umocnenie • nepozná i++, --i, len i += 1, i -= 1 • bitové operácie |, &,^,~,>>,<< • indexujeme C-éčkovsky, od 0 po size-1 Nápady tretích strán: • divmod(x,y) vráti dvojicu (div,mod) • math.ceil(x), math.floor(x)à la Java
analógia kolekcií napr. z Java Kolekcie - sekvencie • heterogénne zoznamy (môžu obsahovať objekty rôznych typov) • [1,2,3] – trojprvkový zoznam s prvkami 1,2,3 • [] – prázdny zoznam • [[1,2,3]] – jednoprvkový zoznam, ktorého jediný prvok je trojprovkový zoznam • [1,3.1415,['a','b']]: <class 'list'>- typ zoznamu • reťazce – "retazec", 'string': <class 'str'> • n-tice - (True, 3.1415, "retazec", [1,2,3,4]), (), (1,): <class 'tuple'> • for-cyklus cez sekvencie: for elem in sekvencia: # elem je premenná prebiehajúca sekvenciou … for e in [1,3.1415,['a','b']]:for chr in"retazec": print(e,':',type(e)) print(chr,’:’,type(chr)) 1 : <class 'int'> r : <class 'str'> 3.1415 : <class 'float'> e : <class 'str'> ['a', 'b'] : <class 'list'> t : <class 'str'> …
Operácie so sekvenciami(najbežnejšie) • x in s, x not in s nachádza/nenachádza sa v sekvencii • len(s) dĺžka sekvencie • min(s), max(s) minimálny/maximálny prvok • s.index(i) prvý výskyt i v sekvencii s, inak -1 • s.count(i) počet výskytov i v sekvencii s • s + t zreťazenie dvoch sekvencií • s * n, n * s s+s+s+…+s a to presne n-krát • s[i] i-ty prvok s • s[i:j] podsekvencia s[i,i+1,…,j-1] • s[i:j:k] podsekvencia s[i,i+k,…,???] Príklady: 't' in 'retazec‘True 't' not in 'retazec‘False len('retazec')7 min('retazec')'a' 'retazec'.index('t')2 'r'*10'rrrrrrrrrr' 2*'r'*3'rrrrrr' 4 in [0,1,2,3,4,5] True 4 not in range(6) False len(range(10)) 10 max(range(10)) 9 [0]*10 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] (0,)*3 (0, 0, 0)
Indexovanie sekvencia[indexOdVrátane:indexPo:krok] ktorýkoľvek z parametrov môžeme vynechať: "retazec"[3] == 'a' "retazec"[3:] == 'azec' "retazec"[3::2] == 'ae' "retazec"[:5] == 'retaz' "retazec"[-1] == 'c' "retazec"[::-1] == 'cezater' "retazec"[-1:-6:-2] == 'czt‘ • reťazce 'retazec'.upper() == 'RETAZEC' 'stav,pismenko,novepismenko,novystav'.split(',') == ['stav', 'pismenko', 'novepismenko', 'novystav'] ': '.join(["janko","marienka"]) == 'janko:marienka'
v jave Map<K,V> Zložitejšie kolekcie – slovník slovníkový typ s kľúčom Key a hodnotou Value je zobrazenie Key→Value Príklad: ilustrujeme slovník typu str → int, aj keď slovník tiež môže byť hetero • vytvorenie slovníka slovnik = dict({"janko":1, "marienka":2, "jezibaba":10}): <class 'dict'> • indexovanie kľúčom: slovnik["marienka"] • modifikácia: slovnik["janko"]=99 • odstránenie prvku: del slovnik["jezibaba"] • tlač: print(slovnik) {'marienka': 2, 'janko': 99} • cyklus cez kolekciu: for k,v in slovnik.items(): print(k,v)
dovolí programovať: • procedurálne (štruktúrovane), • objektovo, • funkcionálne Multiparadigmový(štruktúrovane) • unikátnosť: indetovanie (odsadenie začiatku riadku od ľavého okraja) je dôležité a nahrádza blok {…}/begin…end • riadiace príkazy (if…elif…elif…else, for, while, break a continue) • nepozná repeat…until/do…while, ani case/switch • má výnimky (try…except…except…except:) • if cond: • ... • elif cond: • ... • else: • … • while cond: • ... • for var in sekvencia: • ... # funnySort zoznam= [4,3,2,5,3,1,2,4,6,8,9,2] size = len(zoznam) for i in range(size): # i = 0, 1, …, size-1 for j in range(1, size): # j = 1, …, size-1 if zoznam[j] < zoznam[j-1]: zoznam[j-1], zoznam[j] = zoznam[j], zoznam[j-1] # paralelné :=
def rekurzivny(m, n): if m == 0: return n + 1 if n == 0: return rekurzivny(m - 1, 1) return rekurzivny(m - 1, rekurzivny(m, n - 1)) riešenie anonymného študenta Prémia Ackerman fromcollectionsimportdeque# z modulu collection zober triedu deque def acker(m, n): """ riešenie ... - dokumentačný reťazec, ktorý sa patrí napísať """ parametre, vysledky = deque(), deque() # dva zasobníky na držanie stavu parametre.append( (m, n,) ) # vloženie (m, n) do zásobníka whilelen(parametre): m, n = parametre.pop() # vyber(m, n) zo zásobníka if n is None: n = vysledky.pop() # tretie pravidlo – časť 2 if m == 0:# prvé pravidlo vysledky.append(n + 1) continue if n == 0:# druhé pravidlo parametre.append( (m - 1, 1,) ) continue parametre.append( (m - 1, None,) ) # tretie pravidlo – časť 1 parametre.append( (m, n - 1,) ) return vysledky.pop()
S jedným zásobníkom n m A(m,n) … … def ackermann(m,n): """ idea: zasobnik [zvysok,m,n<-] sa časom zmení na [zvysok,A(m,n)<-] """ stack = [m,n,] while len(stack) > 1:# pokiaľ sú na zásobníku 2 argumenty n = stack.pop() # vyber ich ... m = stack.pop() # ... v správnom poradí if m == 0:# [zvysok,0,n<-] => [zvysok,n+1<-] stack.append(n+1) elif m > 0 and n > 0: # [zvysok,m,n<-] => ([zvysok,m-1,m,n-1<-] stack.extend([m-1,m,n-1]) else:# [zvysok,m,0<-] => [zvysok,m-1,1<-] stack.extend([m-1,1]) return stack.pop()# na zásobníku zostal už len výsledok http://en.wikipedia.org/wiki/Ackermann_function
Neštruktúrovane(goto in Python) • goto a comefrom boli pridané ako rozšírenia importované z modulu goto (1.apríla 2004) from goto import comefrom, label def bigFunction(): setUp() if not doFirstTask(): label .failed if not doSecondTask(): label .failed if not doThirdTask(): label .failed comefrom .failed cleanUp() from goto import goto, label for i in range(1, 10): for j in range(1, 20): for k in range(1, 30): print i, j, k if k == 3: goto .end label .end print "Finished\n" http://mail.python.org/pipermail/python-announce-list/2004-April/002982.html
Stratila sa idea C,Java…, žefunkcie/ procedúry/metódy sa definujú rovnako, ako sa aplikujú. Tu sa dokonca volajú inak... Objektovo • triedy, • objekty, • preťažovanie operátorov • dedenie (aj viacnásobne) Definícia triedy a konštruktora: class BinTreeNode: """ definícia triedy s lokálnymi (triednymi) premennými """ left, right, value = None, None, None def __init__(self, left, value, right): # jediný konštruktor BinTreeNode self.value = value# volanie konštruktora self.left = left# tree = BinTreeNode(None,5,None) self.right = right prvý argument metódy je • v definícii explicitne self, t.j. objekt (self), na ktorý sa metóda aplikuje • pri volaní implicitne objekt (self), na ktorý sa metóda aplikuje
Preťažovanie operátorov Niektoré operátory sú naviazané na metódy s preddefinovanými menami tvaru __xyz__ a tie môžeme predefinovať, napr.: def __str__(self):# textová reprezentácia objektu self return str(self.value)+"("+str(self.left)+","+str(self.right)+")" Vyhľadávanie v binárnom vyváženom strome: def__contains__(self, elem):# elem in self ifself.value == elem: # deep compare – porovná celé štruktúry return True# našiel sa elifself.value < elem:# musí byť v pravo ifself.right == None:# ak vpravo nie je nič return False else: return elem in self.right # rekurzia vpravo else:# musí byť vľavo ifself.left == None:# ak vľavo nie je nič return False else: return elem inself.left # rekurzia vľavo
If it looks like a duck and quacks like a duck, it must be a duck Duck typing pes macka je forma dynamického typovania, dynamická náhrada virtuálnych metód class pes(): # definujeme dve triedy bez akejkoľvek defičnosti def zvuk(self): # obe definujú metódu zvuk() return "haw-haw" class macka(): def zvuk(self): return "mnau-mnau" def zvuk(zviera):# otázkou (statického programátora) je, akého typu je print(zviera.zvuk())# premenná zviera, keď na ňu aplikujeme .zvuk() # odpoveď: uvidí sa v RT podľa hodnoty premennej farma = [pes(), macka()]# heterogénny zoznam objektov for zviera in farma: zvuk(zviera) haw-haw mnau-mnau
Ako by to pascalista s dedičnosťou animal dog cat cow class animal(): # nadtrieda def zvuk(self):# náznak virtuálnej metódy return "no sound" # neviem aký zvuk, resp. pass class dog(animal): # dog ako podtrieda animal def zvuk(self):# override metódy zvuk z animal return "haw-haw" class cat(animal): # cat ako podtrieda animal def zvuk(self): # override metódy zvuk z animal return "mnau-mnau" class cow(animal): # cow ako podtrieda animal pass# nemá vlastnú metódu zvuk # pass znamená prázdny príkaz for animal in [dog(), cat(), cow()] : print(animal.zvuk()) haw-haw mnau-mnau no sound
List comprehension(množinová notácia) Táto téma sa ešte objaví v Haskelli V Pythone definujeme zoznam: [2*x for x in range(1,100)] [x*x for x in range(1,100) if x%2==0] [(x,x*x) for x in range(1,10)] [ [i for i in range(1,n+1)] fornin range(1,10)] V matematike definujeme množinu: M1= { 2x | x ε [1;100) } M2= { x2 | x ε [1;100), 2|x } M3= { (x, x2) | x ε [1;10) } M4= { {1..n} | n ε [1;10) }= { {1}, {1,2}, {1,2,3},… {1,2,3,4,…,9} } M5 = { (a,b,c) | a ε [1;n), b ε [1;n), c ε [1;n), a+b+c<=n, a2+b2=c2 } """ pythagorejske trojuholniky s obvodom najviac n """ [(a,b,c) for a in range(1,n) for b in range(1,n) for c in range(1,n) if a+b+c <= n if a*a + b*b == c*c ] [(3, 4, 5), (4, 3, 5), (5, 12, 13), (6, 8, 10), (7, 24, 25), (8, 6, 10), (8, 15, 17), (9, 12, 15), (9, 40, 41), (10, 24, 26),…
Použitie list-comprehension(príklad Quicksort) list-comprehension je veľmi expresívna syntaktická konštrukcia, keďže v (matematikovi) blízkej notácii ukrýva jeden, resp. viacero vnorených cyklov s podmienkami. Dá sa bez nej žiť, avšak dostaneme rozsiahlejší a textovo menej prehľadný kód, ktorý používa rekurzívne/cyklické procedúry namiesto list-comprehension. Preto ju používajte ... ;-) Príklad (elegantného použitia list-comprehension): def quicksort(L): if len(L) <= 1: return L else: pivot = L[0] # prvý prvok triedeného zoznamu bude pivot ostatne = L[1:] # zvyšné triedené prvky okrem pivota return quicksort([x for x in ostatne if x<pivot])+\ # lilliputs [pivot]+\ # pivot quicksort([x for x in ostatne if x >= pivot]) # maxiputs print(quicksort([4,3,2,5,7,4,5,6,3,1,2,3,5]))
Funkcionálne Python je inšpirovaný funkciami a funkcionálmi à la Haskell Python má lambda konštrukciu: dovolí definovať anonymné funkcie v tvare lambda <argumenty>:<telo>, napr.lambda x:x*x, t.j. x→x2 Je anonymná podoba pomenovanej funkcie def square(x): return x*x Rozdieľ (pre Pascalistu) spočíva v tom, že funkcie môžeme tvoriť v run-time, dynamicky, teda ich môžeme vytvoriť ľubovoľný počet... Funkcia je regulárna hodnota v jazyku, ktorú môžeme napr. • aplikovať (na argumenty) (lambda x:x*x)(5) 25 • mapovať (aplikovať fciu na každý element sekvencie) list(map(lambda n : n*n, [1,2,3,4,5,6,7,8,9,10])) [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] • filtrovať (vybrať zo sekvencie len prvky spĺňajúce daný predikát) list(filter(lambda n: n%2>0, [1,2,3,4,5,6,7,8,9,10])) [1, 3,5,7,9]
matrix = [ [1,2,3], [4,5,6], [7,8,9] ] map & filter(príklady) • list(map(f,data)) znamená [f(x) for x in data] symetrie matice reprezentovanej ako zoznam riadkov matrix[::-1] [ [7,8,9], [4,5,6], [1,2,3] ] list(map(lambda row:row[::-1],matrix)) [ [3,2,1], [6,5,4], [9,8,7] ] • list(filter(p,data)) == [x for x in data if p(x)] prvočísla list( filter( (lambda n:0== sum( (1 for i in range(2,n) if n % i == 0) ) ), range(2,100) ) ) list(map(lambda i: n%i, range(2,n))).count(0),
Closures(len pre fajnšmeckerov) def addN(n): # výsledkom addN je funkcia, return (lambda x:n+x) # ktorá k argumentu pripočína N add5 = addN(5) # toto je jedna funkcia x→5+x add1 = addN(1) # toto je iná funkciay→1+y # … môžem ich vyrobiť neobmedzene veľa print(add5(10))# 15 print(add1(10))# 11 def iteruj(n,f):# výsledkom je funkcia fn if n == 0: return(lambda x:x)# identita else: return(lambda x:f(iteruj(n-1,f)(x)))# f(fn-1) = fn add5SevenTimes = iteruj(7,add5)# +5(+5(+5(+5(+5(+5(+5(100))))))) print(add5SevenTimes(100)) # 135
Python sa snaží byť lenivý Ale skutočná lenivosť príde až Haskellom Generátory(coroutiny) Generátor je procedúra/funkcia, ktorá má v istom bode prerušený (a odložený) zvyšok svojho výpočtu. Generátor odovzdáva výsledky volajúcej procedúre pomocou príkazu yield hodnota. Na obnovenie výpočtu (a pokračovanie v ňom) generátora slúži funkcia next(gener) def gen(n):# generátor generuje postupne čísla 0,1,2,...,n-1 for i in range(n):# cyklus pre i z intervalu yield i# yield vždy preruší výpočet cyklu a urobí return i print([x*x for x in gen(5)]) # for cyklus beží nad generátorom, [0,1,4,9,16] print(sum(gen(5)))# agregátor sum nad generátorom 10 print(list(gen(5)) # list nad generátorom pozbiera jeho výsledky g = gen(5) print(next(g)),print(next(g)),print(next(g)),print(next(g)),print(next(g)),print(next(g)),… 0 1 2 3 4 Exception
Nekonečné generátory def integers(n):# generuje nekonečne veľa výsledkov tvaru while True:# n, n+1, n+2, … yield n n += 1 print(list(integers(1))) # toto nemôže nikdy vytvoriť celý zoznam print(min(integers(1))) # hoc minimum je zrejmé, ani toto nedobehne [n*2 for n inintegers(1)]# tu by už Haskell niečo dal, ale Python nie ... def take(n,g):# zober prvých n generovaných hodnôt gen. g for i in range(n): yield next(g)# next(g) vyprovokuje výpočet ďalšej hodnoty g print(list(take(10,integers(1)))) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Eratosten s nekonečnom sa dá pracovať len lenivo def sieve(d, sieved):# osievací generátor for x in sieved:# z generátora sieved prepúšťa len if (x % d != 0):# hodnoty nedeliteľné d yield x def eratosten(ints):# eratostenovo sito (prvočísla :-) while True: # zober generátor ints=integers(2) first = next(ints) # prvé číslo predstavuje prvočíslo yield first# toto sa reportuje výsledok eratosten ints = sieve(first, ints)# preosejeme čísla tak, že vyhádžeme # všetky deliteľné týmto prvočíslom # a pokračujeme v cykle print(list(take(100,eratosten(integers(2))))) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541]
Fibonacci def zip(gen1, gen2): # zipovač dvoch nekonečných generátorov while True: # do nekonečna pýtaj výsledok gen1 a gen2 yield next(gen1), next(gen2) # a vráť dvojicu týchto výsledkov ako # výsledok generatora zip print(list(take(10,zip(integers(1), integers(2))))) def tail(gen): # tento generátor vracia hodnoty rovnaké next(gen) # gen, akurát prvý výsledok zahodí, ignoruje... while True: # potom už vráti, to čo od gen dostane yield next(gen) def fib(): # netradičná generátorová definícia Fibonacciho čísel yield 1 # prvý prvok postupnosti yield 1 # druhý prvok postupnosti for (x,y) in zip(fib(),tail(fib())):# zozipujeme fib a fib okrem prvého prvku yield x+y # z každej dvojice vyrobíme súčet a ten # prezentujeme ako ďalšie fib číslo print(list(take(20,fib())))
exec& eval exec reťazec - je príkaz, ktorý z reťazca vykoná príkaz v ňom uložený eval(reťazec) - je funkcia, ktorá z reťazca vyhodnotí výraz Táto funkcionalita sa podľa očakávania nachádza v interpreteri. >>>exec('a=1; print("hodnota a=",a)')# hurá, pascalovská ; je tu späť hodnota a= 1 >>>eval('a*a+a') 2