.:: KURS PROGRAMOWANIA W JĘZYKU PYTHON ::.




Rozdział XII obejmuje szereg dostępnych w Pythonie rozwiązań, które znacznie usprawniają pracę programisty. Znajomość ich nie jest wymagana do napisania jakiegokolwiek programu, każdy problem można bowiem rozwiązać w inny, nie sięgający po te rozwiązania sposób. Jednak właściwe zastosowanie tych rozwiązań sprawić może, że program stanie się krótszy i/lub będzie działał szybciej.



- Przypisywanie funkcji do zmiennych


Uruchommy Pythona w trybie interaktywnym. Funkcje mogą być traktowane jako pewien specyficzny typ danych (tzw. typ proceduralny). Wynika z tego, że dla istniejących funkcji można wymyślać nowe nazwy. Zdefiniujmy funkcję pierwiastek_kwadratowy:


>>> def pierwiastek_kwadratowy(x):
      return float(x)**0.5


>>> pierwiastek_kwadratowy(2)
1.4142135623730951


Prościej będzie posługiwać się krótszą nazwą:


>>> pk=pierwiastek_kwadratowy


Wypróbujmy:


>>> pk(2)
1.4142135623730951


Tak samo można przypisywać funkcje wbudowane Pythona, np. spróbujmy zdefiniować polskobrzmiącą nazwę dla wczytywania danych:


>>> czytaj=raw_input
>>> imie=czytaj('Imię?')
Imię?Alfredo
>>> imie
'Alfredo'


Z wypisywaniem danych jest nieco większy kłopot, gdyż instrukcja print nie jest funkcją:


>>> pisz=print
SyntaxError: invalid syntax


Ale instrukcji print odpowiada w przybliżeniu funkcja sys.stdout.write:


>>> import sys
>>> sys.stdout.write("la la la")
la la la


A zatem:


>>> pisz=sys.stdout.write
>>> pisz("la la la")
la la la
>>> pisz(imie)
Alfredo
>>> pisz("%.2f" % pk(5))
2.24



- Funkcje wykorzystujące zmienne globalne


Czasami zachodzi potrzeba, by funkcja odnosiła się do zmiennej globalnej. Załóżmy że mamy zmienną globalną a:


>>> a=5


Chcielibyśmy, by poniższa funkcja zwiększyła tę zmienną o wartość przekazanego parametru b:


>>> def dodaj(b):
      a+=b


Jednak ta funkcja tego nie zrobi:


>>> dodaj(7)

Traceback (most recent call last):
  File "&<pyshell&#35;183>", line 1, in -toplevel-
    dodaj(7)
  File "&<pyshell&#35;182>", line 2, in dodaj
    a+=b
UnboundLocalError: local variable 'a' referenced before assignment


Aby funkcja działała poprawnie, musimy wewnątrz niej opisać zmienną a jako globalną:


>>> def dodaj(b):
      global a
      a+=b
>>> a
5
>>> dodaj(7)
>>> a
12



- Elementy programowania funkcjonalnego w Pythonie


Język programowania Python opiera się na paradygmacie imperatywnym programowania, w którym program stanowi uporządkowana lista instrukcji przeznaczonych do wykonania przez komputer. Python udostępnia jednak kilka rozwiązań przejętych z języków programowania takich jak Lisp, opartych na paradygmacie funkcjonalnym, w których za program uważa się wyrażenie matematyczne, operujące na pewnych parametrach i zwracające pewien rezultat. Te rozwiązania to:


- forma lambda
- funkcja apply( )
- funkcja map( )
- funkcja zip( )
- funkcja filter( )
- funkcja reduce( )
- wytworniki list (list comprehensions) [proszę się nie dziwić, tłumaczenie terminu według polskiej dokumentacji Pythona]


- Forma lambda


Forma lambda służy do tworzenia małych, anonimowych funkcji. Jej składnia jest następująca:


lambda parametry: wyrażenie


Najpierw zdefiniujmy funkcję suma1 w klasycznym stylu:


>>> def suma1(x,y):
      return x+y
>>> suma1(1,2)
3


A tak będzie wyglądał odpowiadający jej zapis w formie lambda:


>>> suma2=lambda x,y: x+y
>>> suma2(1,2)
3


Jak widać, obie funkcje działają identycznie prawidłowo:


>>> suma1(3,4)
7
>>> suma2(3,4)
7


Zaletą formy lambda jest to, że możemy ją wstawić wszędzie tam, gdzie da się wstawić inne wyrażenie, np. do listy:


>>> a=[lambda x,y: x+y, lambda x,y: x-y]
>>> a[0](2,3)
5
>>> a[1](2,3)
-1


jako element dłuższego wyrażenia:


>>> print "%.2f" % (lambda x,y: x**y)(2,0.5)
1.41


albo jako parametr będący funkcją:


>>> t="1 12 7 20".split()
>>> t
['1', '12', '7', '20']
>>> t.sort() &#35; sortowanie wg alfabetu
>>> t
['1', '12', '20', '7']
>>> t.sort(lambda x,y: cmp(int(x),int(y))) &#35; sortowanie wg wartości
>>> t
['1', '7', '12', '20']


Podstawową wadą formy lambda to niemożność wykorzystania w niej instrukcji nie będących wyrażeniami, np. print, if, for, while, itp., choć istnieją sposoby na obejście tego problemu. Jakkolwiek forma lambda bywa wygodna, nie należy jej nadużywać, bo prowadzi to do nieprzejrzystego kodu źródłowego. Przykład programu, w którym nadużyto formy lambda można znaleźć tutaj: http://p-nand-q.com/python/lambdaizing_quicksort.html



- Funkcja apply( )


Funkcja apply( ) przyjmuje jako pierwszy argument funkcję, a jako drugi krotkę zawierającą listę jej parametrów. Spróbujmy wykorzystać ją do wyliczenia pola i obwodu prostokąta o bokach o długości 5 i 7:


>>> boki=(5,7)
>>> pole=lambda a,b: a*b
>>> obwod= lambda a,b: a+b
>>> apply(pole,boki)
35
>>> apply(obwod,boki)
12


Zauważmy, że wywołanie bez apply samej funkcji obwod z parametrem boki byłoby niepoprawne:


>>> obwod(boki)
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
TypeError: <lambda>() takes exactly 2 arguments (1 given)
>>> 


gdyż jej parametrami są 2 liczby całkowite, a nie pojedyncza krotka. Jeszcze jeden przykład:


>>> info=lambda n,i,s: n+" ma "+str(i)+" "+s
>>> cecha_stefana=[4,"rece"]
>>> apply(info,(["Stefan"]+cecha_stefana))
'Stefan ma 4 rece'


Jak widać, jeżeli drugi parametr apply( ) nie jest krotką, tylko innym typem sekwencyjnym (w tym wypadku był listą), zostanie na krotkę automatycznie skonwertowany.




- Funkcja map( )


Funkcja map( ) przyjmuje jako parametry funkcję oraz jedną lub więcej sekwencji. Jej działanie polega na wykonaniu funkcji dla każdego elementu sekwencji i zwróceniu listy otrzymanych rezultatów:


>>> map(int, [0.5, 2.3, 3.9])
[0, 2, 3]
>>> map(lambda x: x*x, range(1,11))
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> kwadrat=lambda x: x*x
>>> map(kwadrat,map(int, [0.5, 2.3, 3.9]))
[0, 4, 9]
>>> map(int,map(kwadrat, [0.5, 2.3, 3.9]))
[0, 5, 15]


Jeżeli przekażemy do funkcji map kilka sekwencji, z pierwszej pobierany będzie pierwszy parametr funkcji, z drugiej – drugi, itd.:


>>> srednia=lambda x,y: (x+y)*0.5
>>> map(srednia, [1, 5, 100], [2, 10, 100])
[1.5, 7.5, 100.0]
>>> map(min, range(1,10), range(9,0,-1))
[1, 2, 3, 4, 5, 4, 3, 2, 1]



- Funkcja zip( )


Funkcja zip( ) służy do konsolidacji danych. Funkcja zip( ) przyjmuje jako parametry funkcję oraz jedną lub więcej sekwencji, po czym zwraca listę krotek, których poszczególne elementy pochodzą z poszczególnych sekwencji.


>>> zip("abcdef",[1,2,3,4,5,6])
[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6)]
>>> zip(range(1,10),range(9,0,-1))
[(1, 9), (2, 8), (3, 7), (4, 6), (5, 5), (6, 4), (7, 3), (8, 2), (9, 1)]


W przypadku, gdy długości sekwencji są różne, wynikowa sekwencja jest skracana do najkrótszej spośród nich:


>>> zip("zip",range(0,9),zip(range(0,9)))
[('z', 0, (0,)), ('i', 1, (1,)), ('p', 2, (2,))]



- Funkcja filter( )


Funkcja filter( ) służy do filtrowania danych. Funkcja filter( ) przyjmuje jako parametry funkcję oraz sekwencję, po czym zwraca sekwencję zawierającą te elementy sekwencji wejściowej, dla których funkcja zwróciła wartość logiczną True. Tak na przykład filtruje się samogłoski:


>>> samogloski='aeiou'
>>> samogloska=lambda x: x.lower() in samogloski
>>> samogloska('a')
True
>>> samogloska('A')
True
>>> samogloska('z')
False
>>> filter(samogloska,"Ala ma kota, kot ma Ale")
'AaaoaoaAe'


A tak liczby parzyste:


>>> filter(lambda x: x%2-1, range(0,11))
[0, 2, 4, 6, 8, 10]



- Funkcja reduce( )


Funkcja reduce( ) służy do agregowania danych. Funkcja reduce( ) przyjmuje jako parametry funkcję oraz sekwencję, zwraca pojedynczą wartość. Na początek wykonuje funkcję dla dwóch pierwszych elementów sekwencji, następnie wykonuje funkcję dla otrzymanego w pierwszym kroku rezultatu i trzeciego elementu sekwencji, następnie wykonuje funkcję dla otrzymanego w drugim kroku rezultatu i czwartego elementu sekwencji, itd., aż dojdzie do końca sekwencji. Np.: suma elementów:


>>> reduce(lambda x,y: x+y, [1,2,3])
6


Np.: iloczyn elementów:


>>> reduce(lambda x,y: x*y, [1,2,3,4])
24


Np.: suma kwadratów elementów:


>>> reduce(lambda x,y: x+y, map(lambda x: x*x, range(1,10)))
285



- Ciągi "surowe"


Domyślnie Python podmienia w ciągach tekstowych kombinacje znaków rozpoczynające się od przeciwukośnika na kody sterujące, np. linia:


a="ala\nma kota"


nada zmiennej a zawartość: "ala", znak końca linii, "ma kota", co można sprawdzić:


>>> print a
ala
ma kota


Aby zapobiec podmianie, używamy notacji ciągów "surowych" (raw strings), w której cudzysłów (lub apostrof) poprzedza się literą r:


>>> b=r"ala\nma kota"
>>> print b
ala\nma kota


Jak widać, w ciągach surowych sekwencje sterujące nie są podmieniane na odpowiadające im kody sterujące, czyli można do woli używać znaku slash. Notacja taka jest użyteczna do zapisu ścieżek dostępu do plików i wyrażeń regularnych (patrz niżej).



- Pobieranie danych z Internetu


Podstawową obsługę Internetu w Pythonie umożliwia biblioteka urllib:


>>> import urllib


Spróbujmy zapisać stronę główną portalu onet.pl w pliku lokalnym onet.html. Wpiszmy:


>>> urllib.urlretrieve("http://www.onet.pl",r"c:\onet.html")


Po chwili (uzależnionej od szybkości łącza) zobaczymy:


('c:\\onet.html', <httplib.HTTPMessage instance at 0x00BC6738>)


co oznacza, że strona została pomyślnie załadowana z Internetu.



- Wywoływanie innych programów z poziomu Pythona


Podstawową obsługę systemu operacyjnego w Pythonie umożliwia biblioteka os:


>>> import os


Spróbujmy teraz otworzyć w domyślnej przeglądarce www zapisaną wcześniej stronę onet.html. Wpiszmy:


>>> os.system(r'c:\onet.html')


Po chwili zapisana strona zostanie otwarta w nowym oknie. Jej wygląd może nieco odbiegać od tego, jaki znamy, ze względu na to, że zapisaliśmy jedynie sam plik HTML, bez dołączonych plików stylów i obrazków.



- Wyrażenia regularne


Wyrażenia regularne (zwane gwarowo regexpami) pozwalają odnaleźć w dłuższym tekście poszukiwany wzorzec. Aby wypróbować ich działanie, zaczniemy od załadowania dłuższego tekstu, na którym będziemy ćwiczyć. Będzie nim zapisana wcześniej strona onet.html:


>>> inp=open(r'c:\onet.html','r')
>>> txt=inp.read()
>>> inp.close()


W tej chwili znajduje się ona w zmiennej txt. Obsługę wyrażeń regularnych w Pythonie umożliwia biblioteka re:


>>> import re


Wyrażenia regularne zbudowane są ze znaków wyszukiwanych (dokładnie takich, jakie mają być znalezione) oraz symboli specjalnych (definiujących reguły).



- Wyszukiwanie wzorca w tekście


Do wyszukiwania w tekście wzorców służy funkcja findall. Aby znaleźć wszystkie wystąpienia słowa "poczta", wpiszmy:


>>> re.findall(r"poczta",txt)
['poczta']


Symboli specjalny "." (kropka) zastępuje dowolny znak. Aby znaleźć wszystkie sekwencje znaków "200", po których następuje dowolny znak, wpiszmy:


>>> re.findall(r"200.",txt)
['2005', '2005', '2005', '2005', '2007', '2006', '2008', '2003', '2005']


Nawiasy kwadratowe pozwalają podać listę znaków, które mają być wyszukane. Aby znaleźć wszystkie sekwencje znaków "200", po których następuje cyfra od 6 do 9, wpiszmy:


>>> re.findall(r"200[6789]",txt)
['2007', '2006', '2008']


lub prościej:


>>> re.findall(r"200[6-9]",txt)
['2007', '2006', '2008']


Circumflex (^) pozwala podać znak, który NIE ma być wyszukany. Aby znaleźć wszystkie sekwencje znaków "200", po których NIE następuje cyfra od 6 do 9, wpiszmy:


>>> re.findall(r"200[^6-9]",txt)
['2005', '2005', '2005', '2005', '2003', '2005']


Symboli specjalny "*" (gwiazdka) zastępuje dowolną liczbę znaków takiego typu, jakim jest poprzedzona. Aby znaleźć wszystkie frazy zaczynające się od "Ci" a kończące na "ki", wpiszmy:


>>> re.findall(r'Ci[a-z]*ki',txt)
['Ciekawostki']


Nawiasy klamrowe "[]" pozwalają podać, ile dokładnie znaków ma się powtórzyć. Aby znaleźć wszystkie sekwencje czterocyfrowe mające w środku dwa zera, wpiszmy:


>>> re.findall(r'[0-9]0{2}[0-9]',txt)


Przeciwukośnik "\" pozwala używać symboli specjalnych jako znaków wyszukiwanych:


>>> re.findall(r'\*',txt)
['*']


Kreska pionowa "|" pozwala podać jednocześnie kilka wzorców do wyszukiwania:


>>> re.findall(r'Poczta|Sport',txt)
['Poczta', 'Sport', 'Poczta', 'Sport', 'Sport', 'Poczta', 'Sport']


Kombinacja (?=...) pozwala znaleźć tylko takie sekwencje, po których następuje podany ciąg.
Kombinacja (?<=...) pozwala znaleźć tylko takie sekwencje, przed którymi następuje podany ciąg.
Przykładowo, aby odnaleźć wszystkie hiperłącza, napiszemy:


>>> re.findall(r'(?<=href=").*(?=")',txt)



- Podmiana fragmentów tekstu


Wyrażenia regularne mogą być także pomocne do zastąpienia określonych fragmentów tekstu innymi. Do ćwiczeń posłużymy się teraz krótszym tekstem:


>>> txt="Ala ma malego kota, ale to nie kot ma Ale"


Do podmiany wzorców w tekście służy funkcja sub, która jako swój wynik zwraca podmieniony tekst, nie zmieniając przy tym tekstu oryginalnego (w naszym przypadku zmiennej txt). Wypróbujmy i przeanalizujmy:


>>> re.sub('Al','Kasi',txt)
'Kasia ma malego kota, ale to nie kot ma Kasie'


Symbol specjalny \s oznacza dowolny znak biały; \S - dowolny znak nie będący białym:


>>> re.sub(r'ma(?=\s)','posiada',txt)
'Ala posiada malego kota, ale to nie kot posiada Ale'
>>> re.sub(r'(?<=\s)k','ocel',txt)
'Ala ma malego ocelota, ale to nie ocelot ma Ale'
>>> re.sub(r'ma\S','zl',txt)
'Ala ma zlego kota, ale to nie kot ma Ale'
>>> re.sub(r'm\S*o\s','',txt)
'Ala ma kota, ale to nie kot ma Ale'
>>> re.sub(r'(?=kot\s)','maly ',txt)
'Ala ma malego kota, ale to nie maly kot ma Ale'



- Ćwiczenia kontrolne


I. Używając wytwornika zbuduj listę zawierającą wszystkie liczby podzielne przez 3 z zakresu od 1 do 33.
a) Używając funkcji filter() usuń z niej wszystkie liczby parzyste
b) Używając notacji lambda i funkcji map() podnieś wszystkie elementy tak otrzymanej listy do sześcianu
c) Odpowiednio używając funkcji reduce( ) i len() oblicz średnią arytmetyczną z elementów tak otrzymanej listy

II. Przy użyciu wyrażenia regularnego, wyświetl wszystkie adresy e-mail zawarte na stronie http://ppcg.eu.org/redakcja.php/





-- back