Poprzedni wykład     Lista wykładów     Następny wykład

Budowa plików źródłowych (funkcje, widoczność zmiennych)

Plik źródłowy może zawierać komentarze, polecenia importu, definicje funkcji, definicje klas oraz kod do wykonania. Kolejność tych elementów jest w dużym stopniu dowolna. Pewne typy komentarzy (opisane niżej) muszą byc na początku pliku. Definicje funkcji i klas wykorzystywanych w kodzie muszą poprzedzać kod. Komentarze Import modułów Funkcje Widoczność zmiennych Opakowania funkcji i dekoratory

Komentarze

Komentarz zaczyna się znakiem # i ciągnie sie do końca wiersza. Dwa rodzaje komentarzy mają specjalne znaczenie:
Spis treści

Import modułów

Moduł to biblioteka funkcji, czasami moduł zawiera również stałe, np moduł math prócz funkcji matematycznych zawiera stałe math.e i math.pi. W pierwszym wykładzie opisałem użycie polecenia import do uruchamiania programów w trybie interaktywnym. Jest tam również informacja w jakich katalogach Python szuka importowanych modułów, i jak można te listę katalogów zmienić. Polecenie import ma cztery wersje:
  1. import nazwa_modułu,
  2. import nazwa_modułu as nazwa_aliasu,
  3. from nazwa_modułu import *,
  4. from nazwa_modułu import lista_obiektów_rodzielonych_przecinkami,
Dwie pierwsze wersje importują cały moduł, przy korzystaniu z obiektów modułu trzeba korzystać z pełnej nazwy obiektu: nazwa_modułu.nazwa_obiektu (a), lub nazwa_aliasu.nazwa_obiektu (b).
import math
math.cos(math.radians(45)) => 0.7071067811865476
import math as m
m.sin(m.radians(45)) => 0.7071067811865475
Funkcje trygonometryczne w Pythonie oczekują argumentu w radianach, dlatego konieczne jest przeliczenie stopni na radiany.
Trzecia wersja importuje z modułu wszystkie obiekty, nie trzeba, a nawet nie wolno, używać pełnej nazwy obiektu, wersja czwarta importuje tylko wymienione obiekty.
from math import e
e => 2.718281828459045
sqrt(3)
Traceback (most recent call last):
  File "", line 1, in 
    sqrt(3)
NameError: name 'sqrt' is not defined
from math import e,sqrt
sqrt(3) => 1.7320508075688772
Pełny import (wersje a i b) ma oczywistą wadę: więcej pisania. Skoro jest powszechnie stosowany, to musi mieć również zalety. Import jest przechodni, tzn. jeżeli w importowanym module jest polecenie import abrakadabra, to moduł abrakadabra też jest zaimportowany, ale nazwy obiektów z modułu abrakadabra się wydłużają.
Plik test.py zawiera wiersz import math.
import test
test.math.exp(3) => 20.085536923187668
Ani słowa test, ani słowa math nie można opuścić.

Importowany moduł jest kompilowany do kodu bajtowego - powstaje plik z rozszerzeniem pyc. Pliki te są niezależne od od platformy - plik pyc utworzony na Linuksie będzie działał w systemie Windows i na odwrót.
Zgodnie z dokumentacją, czas wykonywania modułów i programów skompilowanych jest taki sam jak nieskompilowanych. Krótszy jest natomiast czas ładowania do pamięci.
Kompilację możemy wymusić, kompilator znajduje sie w module compileall:

import compileall
compileall.compile_dir(nazwaKatalogu)
Metoda compile_dir ma kilka parametrów domyślnych. Przykład, wymuszona kompilacja wszytkich plików w katalogu test i jego bezpośrednich podkatalogach:
compileall.compile_dir('test',force=True,maxlevels=1)
Kolejność parametrów jest nieistotna, można napisać np. tak:
compileall.compile_dir(maxlevels=1,dir='test',force=True)
Tzn. przy wywoływaniu funkcji z wieloma argumentami nie musimy pamiętać w jakiej kolejności wpisać parametry. Wystarczy, że znamy nazwy argumentów.
Spis treści

Funkcje

Funkcja, to wydzielony i posiadający nazwę fragment programu, który coś robi i zwraca (lub nie) pewną wartość. W Pythonie funkcja zawsze może wystąpić po prawej stronie operatora przypisania, jeżeli funkcja nie zwraca żadnej wartości, to zmienna występująca po lewej stronie operatora przypisania nie ma żadnej wartości.
Definicja funkcji rozpoczyna się od słowa kluczowego def. Ciało funkcji musi mieć wcięcia - wszystkie wiersze takie samo wcięcie, chyba, że zawarte w ciele funkcji pętle i instrukcje sterujące wymuszą powiększenie wcięcia. Instrukcje znajdujące się w ciele funkcji wykonywane są kolejno od pierwszej do ostatniej (z uwzględnieniem instrukcji sterujących). Instrukcja return kończy wykonywanie funkcji, jeżeli za poleceniem return znajduje się wyrażenie, to jego wartość jest wyliczana. Wyliczona wartość jest wartością zwracaną przez funkcję.
W Pythonie nie ma przeciążania (nazw) funkcji, zdefiniowanie funkcji o istniejącej nazwie zastępuje dawną funkcją. Jest natomiast bardzo wygodny mechanizm domyślnych wartości argumentów.
def f(x,y=11,z=23):
    return x+y+z
Argument y ma wartośc domyślną 11, a argument z ma wartość domyślną 23. Argumenty z wartością domyślną (keyword arguments, argumenty słownikowe) muszą być na końcu listy argumentów.
Przykłady wywołania powyższej funkcji:
f(1,2,3) => 6
f(1,2) => 26
f(1) => 35
f(1,z=4) => 16
Wywołanie funkcji zawsze wymaga napisania nawiasów, również wtedy gdy funkcja jest bezargumentowa lub wszystkie argumenty maja wartości domyślne. Napisanie nazwy funkcji bez nawiasów nie jest błędem, ale nie jest też wywołaniem funkcji.
def f(tekst="ma",ileRazy=2):
    'funkcja zwraca tekst*ileRazy, default: tekst="ma", ileRazy=2'
    return tekst*ileRazy
g() => 'mama'
g => <function g at 0x01230970>
g.__doc__ => 'funkcja zwraca tekst*ileRazy, default: tekst="ma", ileRazy=2'
Pierwszy wiersz definicji funkcji może być typu str. Nie należy on do ciała funkcji, jest on krótką dokumentacją funkcji. Można go odczytać poleceniem nazwaFunkcji.__doc__. Taką dokumentację ma każda standardowa funkcja Pythona.
len => <built-in function len>
len.__doc__ => 'len(object) -> integer\n\nReturn the number of items of a sequence or mapping.'

Można definiować funkcje, które przyjmują dowolną (nieznaną w czasie definiowania funkcji) ilość argumentów.

Widoczność zmiennych

Python korzysta z tzw. przestrzeni nazw (namespace). Każda przestrzeń nazw to słownik (typ dict), klucze to nazwy zmiennych, wartości to wartości zmiennych. Kiedy wiersz kodu potrzebuje wartość zmiennej, to przeszukiwane są (w podanej niżej kolejności) wszystkie dostępne przestrzenie nazw. Różnica między globalną przestrzenią nazw dla modułu a wbudowaną globalną przestrzenią nazw. W trybie interaktywnym (konsola Pythona) definiujemy funkcję:
def f():
    globals()['x'] = 1000 # słownik globals() zawiera zmienne zdefiniowane w konsoli
po czym wpisujemy polecenia:
x = 1
f()
print x => 1000

Plik a.py ma taką zawartość:
x = 10      
def f():
    globals()['x']=1000 # słownik globals() zawiera zmienne zdefiniowane w module a (tzn. w pliku a.py)
Wpisujemy w konsoli Pythona polecenia:
x = 1 
import a
a.f()
x => 1

Spis treści

Opakowania funkcji i dekoratory

W Pythonie argumentem funkcji może być inna funkcja, również wartością zwracaną przez funkcję może być funkcja. Załóżmy, że chcemy wiedzieć ile razy nasz program wywołuje pewne funkcje. "Opakujemy" funkcje, których wywołania chcemy liczyć. Zaczniemy od zdefiniowania słownika, w którym będą pamiętane ilości wywołań oraz napisania funkcji opakowującej:
counters={}
def licz_wywolania(f):
    def wrapper(*arguments,**keywords):
        key = 'counter_'+f.__name__
        if counters.has_key(key):
            counters[key] += 1
        else:
            counters[key] = 1   
        return f(*arguments,**keywords)
    return wrapper
Funkcja opakowująca licz_wywołania(f) dopisuje przed ciałem funkcji f kod liczący wywołania funkcji f. Drugim krokiem będzie "opakowanie" pewnych funkcji:
len = licz_wywolania(len)
str = licz_wywolania(str)
Wywołamy parokrotnie "opakowane" funkcje:
len('123') => 3
len(counters) => 1
len(range(33)) => 33 
str(34) => '34'
str(34.55) => '34.55'
i zajrzymy do słownika:
counters => {'counter_len': 3, 'counter_str': 2}
Jeżeli chcemy opakować funkcje biblioteczne, to opisany wyżej sposób jest jedyny. Jeżeli mamy dostęp do kodu źródłowego funkcji, to istnieje drugi sposób opakowania - użycie dekoratorów.
def foo():
    print 'Jestem Foo'
foo = licz_wywolania(foo)
for i in range(10):
    foo()

@licz_wywolania
def boo():
    print 'Jestem Boo'
for i in range(10):
    boo()
counters => {'counter_foo': 10, 'counter_len': 3, 'counter_boo': 10, 'counter_str': 2}
W Django każdy adres jest mapowany na pewną funkcję. Załóżmy, że adres .../save jest mapowany na funkcję save_page, a dostęp do tego adresu powinni mieć tylko zalogowani użytkownicy. Będziemy w takiej sytuacji korzystać z dekoratora:
@login_required(login_url="/login/")
def save_page(request):
    ...
Lub
@login_required
def save_page(request):
    ...
Obie wersje dekoratora opakowują funkcję save_page dodając sprawdzenie czy użytkownik jest zalogowany, jeśli nie jest, to jest kierowany na stronę logowania (jej adres podajemy jako parametr login_url w pierwszej wersji dekoratora, lub w pliku settings.py w wierszu postaci LOGIN_URL = '/login/'), a po pomyślnym logowaniu jest kierowany na stronę .../save. Identycznie "dekorujemy" wszystkie strony wymagające zalogowania.
Innym ważnym dekoratorem jest permission_required. Załózmy, że do wspomnianego adresu .../save powinni mieć dostęp tylko zalogowani użytkownicy, którzy są ponadto administratorami aplikacji sieciowej (w terminologii Django maja nazwę superuser):
@permission_required('user.is_superuser',login_url="/login/")
def save_page(request):
    ...
Dekorator ten opakowuje funkcję save_page dodając sprawdzenie czy użytkownik jest zalogowany i czy spełnia warunek będacy pierwszym argumentem funkcji permission_required, jeśli nie jest, to jest kierowany na stronę logowania, a po pomyślnym logowaniu jest kierowany na stronę .../save.
Spis treści
Poprzedni wykład     Lista wykładów     Następny wykład