Chapter 2: Język programowania Python

Język programowania Python

Python

O Pythonie

API

Python jest językiem programowania wysokiego poziomu o ogólnym przeznaczeniu. Filozofia jego zaprojektowania kladzie nacisk na produktywność programisty i czytelność kodu. Ma minimalistyczną składnię rdzenia z niewielu podstawowymi poleceniami i prostą semantyką, ale ma także wielkie i wszechstronne standardowe biblioteki, w tym interfejs programowania aplikacyjnego (Application Programming Interface - API) z wieloma podstawowymi funkcjami systemów operacyjnych (OS). Kod Pythona, z racji minimalizmu, definiuje wbudowane obiekty, takie jak listy (list), krotki (tuple), słowniki (dict) i dowolnie długie liczby (long).

Python obsługuje wiele paradygmatów programowania, w tym programowanie zorientowane obiektowo (class), programowanie proceduralne (def) i programowanie funkcjonalne (lambda). Python ma dynamiczny system typów i automatyczne zarządzanie pamięcią za pomocą zliczania referencji (podobnie jak Perl, Ruby i Scheme).

Python został wydany przez Guido van Rossuma w 1991 r. Język ma otwarty, społecznościowy model twórczy zarządzany przez organizację non-profit Python Software Foundation. Istnieje wiele interpreterów i kompilatorów języka programowania Python, w tym w Java (Jython), ale w tym przeglądzie odnosimy się do referencji implementacji języka C, stworzonego przez Guido.

Na oficjalnej stronie Pythona[python] można znaleźć wiele poradników, oficjalną dokumentację oraz informatory bibliotek.

Dodatkowe informacje o Pythonie można znaleźć polecanej przez nas książce ref.[guido] i ref.[lutz].

Można pominąć ten rozdział, jeśli jest się zaznajomionym z językiem Python.

Rozpoczynamy

shell

Binarne dystrybucje web2py dla Microsoft Windows lub Apple OS X zawierają już wbudowany interpreter Pythona.

Można go uruchomić na Windows za pomoca następującego polecenia (wpisz w wierszu poleceń DOSa):

web2py.exe -S welcome

Na Apple OS X wprowadź następujące polecenie w oknie terminala (będąc w tym samym katalogu co web2py.app):

./web2py.app/Contents/MacOS/web2py -S welcome

Na Linuksie lub innym systemie uniksopodobnym są szanse, że masz już zainstalowaną obsługę Pythona. Jeśli tak, wprowadź w powłoce polecenie:

python web2py.py -S welcome

Jeśli nie masz jeszcze zainstalowanego Pythona 2.7 (lub nowszej wersji 2.x), trzeba będzie pobrać i zainstalować go przed uruchomieniem web2py.

Opcja -S welcome polecenia instruuje web2py aby uruchomił interaktywną powłokę, tak aby polecenie zostało wykonane w kontrolerze dla aplikacji welcome - szkieletowej aplikacji web2py. Daje ona możliwość dostępu do prawie wszystkie klas, obiektów i funkcji web2py. Jest to jedyna różnica pomiędzy interaktywną linią poleceń web2py a zwykłą linią poleceń Pythona.

Interfejs administracyjny zapewnia również powłokę internetową dla każdej aplikacji. Możesz uzyskać dostęp do jeden z nich tak jak do aplikacji "welcome".

http://127.0.0.1:8000/admin/shell/index/welcome

Wypróbuj wszystkie przykłady w tym rozdziale wykorzystując zwykłą powłokę lub powłokę internetową.

help, dir

help
dir

Język Python dostarcza dwa polecenia umożliwiające uzyskanie informacji o zdefiniowanych obiektach w bieżącym zakresie, zarówno tych wbudowanych jak i zdefiniowanych przez użytkowników.

Możemy wykorzystać polecenie help dla uzyskania informacji o obiekcie dla przykładu "1":

>>> help(1)
Help on int object:

class int(object)
 |  int(x[, base]) -> integer
 |
 |  Convert a string or number to an integer, if possible.  A floating point
 |  argument will be truncated towards zero (this does not include a string
 |  representation of a floating point number!)  When converting a string, use
 |  the optional base.  It is an error to supply a base when converting a
 |  non-string. If the argument is outside the integer range a long object
 |  will be returned instead.
 |
 |  Methods defined here:
 |
 |  __abs__(...)
 |      x.__abs__() <==> abs(x)
...

a, ponieważ "1" jest liczbą, mamy opis o klasie int i wykazane wszystkie jego metody. Tutaj dane wyjściowe zostały obcięte, ponieważ wyjście jest badzo długie i szczegółowe.

Podobnie można uzyskać listę metod obiektu "1" przez polecenie dir:

>>> dir(1)
['__abs__', ..., '__xor__']

Typy

type

Python jest językiem o dynamicznym systemem typów, co oznacza że zmienne nie mają typu i nie muszą być deklarowane. Z drugiej strony, wartości mają typ. Można wypytać zmienną o typ wartości jaką zawiera:

>>> a = 3
>>> print type(a)
<type 'int'>
>>> a = 3.14
>>> print type(a)
<type 'float'>
>>> a = 'hello python'
>>> print type(a)
<type 'str'>

Python zawiera również struktury danych takie jak listy i słowniki.

Ciąg znakowy

ciag znakowy
ciąg znakowy
str
ASCII
UTF8
Unicode
encode

W tym podręczniku pojęcia ciąg znakowy i łańcuch znakowy oznaczaja ten sam typ zmiennej string i używane są zamiennie.

Python (2.x) obsługuje stosowanie dwóch różnych typów ciągów znakowych: ciągi znaków ASCII i ciągi znaków Unicode. Ciągi znaków ASCII są ujmowane w znaki cudzysłowów: '...', "..." albo w ograniczniki '..' lub """...""". Potrójne cudzysłowy ograniczają ciągi złożone. Ciąg znaków Unicode rozpoczyna się przedrostkiem u po którym następuje ciąg zawierający znaki Unicode. Ciągi znaków Unicode mogą być konwertowane do ciagów znaków ASCII przez użycie metody encode, na przykład:

>>> a = 'this is an ASCII string'
>>> b = u'This is a Unicode string'
>>> a = b.encode('utf8')

Po wykonaniu tych trzech poleceń wynikową wartością a jest ciąg zawierający zakodowane znaki UTF8. Zgodnie z założeniami web2py używa wewnętrznie kodowanych ciągów znaków UTF8.

Jest też możliwe dodawanie zmiennych do ciągów w różny sposób:

>>> print 'number is ' + str(3)
number is 3
>>> print 'number is %s' % (3)
number is 3
>>> print 'number is %(number)s' % dict(number=3)
number is 3

Ostatnia notacja jest bardziej wyrazista i jest mniej podatna na błędy, dlatego jest zalecana.

Wiele obiektów Pythona, na przykład liczby, mogą być serializowane do ciągów znakowych przy użyciu funkcji str lub repr. Te dwa polecenia są bardzo podobne ale dają trochę odmienne dane wyjściowe. Na przykład:

>>> for i in [3, 'hello']:
        print str(i), repr(i)
3 3
hello 'hello'

We własnych klasach str i repr można definiować lub redefiniować przy użyciu specjalnych operatorów __str__ i __repr__. Są one krótko opisane dalej, bo szczegóły można znaleźć w oficjalnej dokumentacji Pythona[pydocs] .

Inną ważną cechą ciągów znakowych Pythona jest to, że podobnie jak listy są obiektami iterowalnymi.

>>> for i in 'hello':
        print i
h
e
l
l
o

Lista

lista

W Pythonie główne metody klasy list to append, insert i delete:

>>> a = [1, 2, 3]
>>> print type(a)
<type 'list'>
>>> a.append(8)
>>> a.insert(2, 7)
>>> del a[0]
>>> print a
[2, 7, 3, 8]
>>> print len(a)
4

Listy moga być dzielone:

>>> print a[:3]
[2, 7, 3]
>>> print a[1:]
[7, 3, 8]
>>> print a[-2:]
[3, 8]

i łączone:

>>> a = [2, 3]
>>> b = [5, 6]
>>> print a + b
[2, 3, 5, 6]

Lista jest iterowalna – można wyszukiwać w niej elementy stosując pętlę:

>>> a = [1, 2, 3]
>>> for i in a:
        print i
1
2
3

Elementami listy nie muszą być tego samego typu – mogą być nimi dowolne obiekty Pythona.

Istnieje bardzo częsta sytuacja, w której można użyć listy składanej. Rozważmy następujący kod:

>>> a = [1,2,3,4,5]
>>> b = []
>>> for x in a:
        if x % 2 == 0:
            b.append(x * 3)
>>> b
[6, 12]

Kod ten jawnie przetwarza elementy listy , wybierając i modyfikując podzbiór wejściowy listy i tworząc nowa listę wynikową. Kod ten może być wyrażony krócej z użyciem tzw. listy składanej (ang. list comprehension):

>>> a = [1,2,3,4,5]
>>> b = [x * 3 for x in a if x % 2 == 0]
>>> b
[6, 12]

Krotka

krotka

Krotka jest podobna do listy, ale jej wielkość i elementy są niezmienne, podczas gdy w listy są modyfikowalne. Jeśli element krotki jest jest obiektem, to atrybuty obiektu są modyfikowalne. Krotki ogranicza się nawiasami okrągłymi.

>>> a = (1, 2, 3)

Więc choć krotka działa podobnie do listy:

>>> a = [1, 2, 3]
>>> a[1] = 5
>>> print a
[1, 5, 3]

to przypisywanie elementów nie działa w krotce:

>>> a = (1, 2, 3)
>>> print a[1]
2
>>> a[1] = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Krotka, podobnie jak lista, jest iterowalna. Proszę zauważyć, że krotka z jednym elementem musi zawierać końcowy przecinek, tak jak pokazano to niżej:

>>> a = (1)
>>> print type(a)
<type 'int'>
>>> a = (1,)
>>> print type(a)
<type 'tuple'>

Krotki są bardzo przydatne dla efektywnego wypakowania obiektów do odrębnych zmiennych, gdyż są niezmienne i też dlatego, że nawiasy są często opcjonalne:

>>> a = 2, 3, 'hello'
>>> x, y, z = a
>>> print x
2
>>> print z
hello

Słownik

słownik

Słownik Pythona jest tablicą asocjacyjną, która odwzorowuje obiekty klucze na obiekty wartości. Na przykład:

>>> a = {'k':'v', 'k2':3}
>>> a['k']
v
>>> a['k2']
3
>>> a.has_key('k')
True
>>> a.has_key('v')
False

Kluczami mogą być dowolne niemodyfikowalne obiekty (int, string, tuple lub każdy obiekt, którego klasa implementuje metodę __hash__). Wartościami mogą być obiekty dowolnego typu. Różne klucze i wartości w tym samym słowniku nie muszą być tego samego typu. Jeśli klucze są znakami alfanumerycznymi, to słownik może być zadeklarowany w alternatywnej składni:

>>> a = dict(k='v', h2=3)
>>> a['k']
v
>>> print a
{'k':'v', 'h2':3}

Użytecznymi metodami są has_key, keys, values i items:

>>> a = dict(k='v', k2=3)
>>> print a.keys()
['k', 'k2']
>>> print a.values()
['v', 3]
>>> print a.items()
[('k', 'v'), ('k2', 3)]

Metoda items tworzy listę krotek, każda zawiera klucz i związaną z nim wartość.

Elementy słownika i elementy listy można usunąć za pomocą polecenia del:

>>> a = [1, 2, 3]
>>> del a[1]
>>> print a
[1, 3]
>>> a = dict(k='v', h2=3)
>>> del a['h2']
>>> print a
{'k':'v'}

Wewnętrznie Python wykorzystuje operator hash do konwersji obiektów na liczby całkowite i używa takiej liczby do określenia gdzie przechowywać daną wartość.

>>> hash("hello world")
-1500746465

O wcięciach

W Pythonie do rozdzielenia bloków kodu używa się wcięć. Blok rozpoczyna się od linii kończącej się w dwukropkiem i jest kontynuowany przez wszystkie linie mające takie same lub większe wcięcie w następnej linii. Na przykład:

>>> i = 0
>>> while i < 3:
>>>    print i
>>>    i = i + 1
>>>
0
1
2

Powszechnie stosuje się cztery spacje dla każdego wcięcia. Dobrą zasada jest nie mieszanie tabulatorów ze spacjami, bo prowadzi to do (niewidzialnych) błędów.

Instrukcja for...in

for

W Pythonie można iterować iterowalne obiekty w pętli:

>>> a = [0, 1, 'hello', 'python']
>>> for i in a:
        print i
0
1
hello
python

Jednym z popularnych skrótów generujących zakres iteratora bez zapisywania całych list elementów jest xrange.

>>> for i in xrange(0, 4):
        print i
0
1
2
3

Jest to równoważnik składni C/C++/C#/Java:

for(int i=0; i<4; i=i+1) { print(i); }

Innym użytecznym poleceniem jest enumerate, które zlicza pętle:

>>> a = [0, 1, 'hello', 'python']
>>> for i, j in enumerate(a):
        print i, j
0 0
1 1
2 hello
3 python

Istnieje również słowo kluczowe range(a, b, c), które zwraca listę liczb, począwszy od wartości a, powiększanych w każdym kroku o c i kończąc na liczbie mniejszej od b, domyślnie a ma wartość 0 a c wartość 1. Polecenie xrange jest podobne, ale faktycznie nie generuje lity, tylko iteruje po liście, więc lepsze jest przy iteracji.

Można też wyjść z pętli używając polecenia break

>>> for i in [1, 2, 3]:
         print i
         break
1

Można przejść do następnej iteracji pętli bez wykonywania całego bloku kodu stosując polecenie continue

>>> for i in [1, 2, 3]:
         print i
         continue
         print 'test'
1
2
3

Polecenie while

while

Pętla while w Pythonie działa podobnie jak ma to miejsce w innych językach programowania, poprzez zapętlanie nieskończona ilość razy i badanie warunku przed każdą iteracja. Jeśli warunek ma wartość False, pętla kończy się.

>>> i = 0
>>> while i < 10:
        i = i + 1
>>> print i
10

W Pythonie nie ma konstrukcji loop...until.

Instrukcja if...elif...else

if
elif
else
Korzystanie z instrukcji warunkowej w Pythonie jest intuicyjne:

>>> for i in range(3):
>>>     if i == 0:
>>>         print 'zero'
>>>     elif i == 1:
>>>         print 'one'
>>>     else:
>>>         print 'other'
zero
one
other

Słowo "elif" oznacza "else if". Zarówno elif jak else są klauzulami opcjonalnymi. Wyrażeń elif może być wiele ale tylko jedno else. Przy użyciu operatorów not, and i or można tworzyć skomplikowane warunki.

>>> for i in range(3):
>>>     if i == 0 or (i == 1 and i + 1 == 2):
>>>         print '0 or 1'

Instrukcja try...except...else...finally

try
except
finally
Exception
W Pythonie można zrzucać i zgłaszać wyjątki:

>>> try:
>>>     a = 1 / 0
>>> except Exception, e:
>>>     print 'oops: %s' % e
>>> else:
>>>     print 'no problem here'
>>> finally:
>>>     print 'done'
oops: integer division or modulo by zero
done

Jeśli wystąpi wyjątek, to wykonywana jest klauzula except obsługująca ten wyjatek, a klauzula else jest wówczas pomijana. Jeśli nie wystąpi wyjątek, to klauzula except jest pomijana, ale wykonywana jest klauzula else. Klauzula finally jest wykonywana zawsze.

Może być wiele klauzul except dla różnych możliwych wyjątków:

>>> try:
>>>     raise SyntaxError
>>> except ValueError:
>>>     print 'value error'
>>> except SyntaxError:
>>>     print 'syntax error'
syntax error

Klauzule else i finally są opcjonalne.

Oto lista wyjatków wbudowanych w Pythonie + HTTP (zdefiniowanych w web2py)

BaseException
 +-- HTTP (defined by web2py)
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- Exception
      +-- GeneratorExit
      +-- StopIteration
      +-- StandardError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |    |    +-- UnicodeError
      |    |         +-- UnicodeDecodeError
      |    |         +-- UnicodeEncodeError
      |    |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning

Szczegółowy opis każdego z nich można znaleźć w oficjalnej dokumentacji Pythona.

Platforma web2py dodaje tylko jeden nowy wyjątek, o nazwie HTTP. Gdy wyjątek ten wystąpi, spowoduje to powrót programu do strony błędu HTTP (więcej na ten temat znajduje się w rozdziale 4).

Jako wyjątek może być zgłoszony każdy obiekt, ale dobrą praktyka jest zgłaszanie obiektów, które rozszerzają jedna z wbudowanych klas wyjątku.

Instrukcja def...return

def
return

Funkcje deklarowane są za pomocą słowa def. Oto typowa funkcja Pythona:

>>> def f(a, b):
        return a + b
>>> print f(4, 2)
6

Nie ma potrzeby (i sposobu) określania typu argumentów i typu danych wyjściowych. W tym przykładzie funkcja f jest zdefiniowana z dwoma argumentami.

Funkcje są pierwszym elementem składni, opisywanym w tym rozdziale, w którym wprowadza się pojęcie zakresu lub przestrzeni nazw. W powyższym przykładzie identyfikatory a i b są zdefiniowane w zakresie funkcji f:

>>> def f(a):
        return a + 1
>>> print f(1)
2
>>> print a
Traceback (most recent call last):
  File "<pyshell#22>", line 1, in <module>
    print a
NameError: name 'a' is not defined

Identyfikatory zdefiniowane poza zakresem funkcji są dostępne w funkcji – proszę zwrócić uwagę jak identyfikator a jest obsługiwany w następującym kodzie:

>>> a = 1
>>> def f(b):
        return a + b
>>> print f(1)
2
>>> a = 2
>>> print f(1) # new value of a is used
3
>>> a = 1 # reset a
>>> def g(b):
        a = 2 # creates a new local a
        return a + b
>>> print g(2)
4
>>> print a # global a is unchanged
1

Jeśli a zostaje zmodyfikowane, to kolejne wywołania funkcji będą używać nową wartość globalną a, ponieważ definicja funkcji wiąże przechowywaną wartość lokalną identyfikatora a, nie wartość a, która została określona w czasie definiowania funkcji. Jednak jeśli a jest przypisane wewnątrz funkcji g, to wartość globalna a pozostaje bez zmian, ponieważ nowa wartość lokalna a przesłania wartość globalną. Odniesienia zakresu zewnętrznego mogą być wykorzystane przy tworzeniu domknięć:

>>> def f(x):
        def g(y):
            return x * y
        return g
>>> doubler = f(2) # doubler to nowa funkcja
>>> tripler = f(3) # tripler to nowa funkcja
>>> quadrupler = f(4) # quadrupler to nowa funkcja
>>> print doubler(5)
10
>>> print tripler(5)
15
>>> print quadrupler(5)
20

Funkcja f tworzy nową funkcję. Proszę zauważyć, że zakres funkcji g jest całkowicie zawarty w funkcji f. Domknięcia są bardzo mocne.

Argumenty funkcji mogą mieć wartości domyślne a funkcje mogą zwracać wiele wyników:

>>> def f(a, b=2):
        return a + b, a - b
>>> x, y = f(5)
>>> print x
7
>>> print y
3

Argumenty funkcji mogą być przekazywane przez nazwę, co oznacza, że kolejność argumentów określonych w wywołaniu może być inna niż kolejność argumentów w definicji funkcji:

>>> def f(a, b=2):
        return a + b, a - b
>>> x, y = f(b=5, a=2)
>>> print x
7
>>> print y
-3

Funkcje mogą również pobierać w czasie wykonania zmienną liczbę argumentów:

>>> def f(*a, **b):
        return a, b
>>> x, y = f(3, 'hello', c=4, test='world')
>>> print x
(3, 'hello')
>>> print y
{'c':4, 'test':'world'}

Tutaj argumenty nie przekazane przez nazwę (3, 'hello') są przechowywane w krotce a, a argumenty przekazane przez nazwę (c i test) są przechowywane w słowniku b.

W przeciwnym razie lista lub krotka może być przekazywana do funkcji, która wymaga indywidualnego pozycjonowania argumentów przez ich wypakowanie:

>>> def f(a, b):
        return a + b
>>> c = (1, 2)
>>> print f(*c)
3

a słownik może być wypakowany w celu dostarczenia argumentów w postaci słów kluczowych:

>>> def f(a, b):
        return a + b
>>> c = {'a':1, 'b':2}
>>> print f(**c)
3

Wyrażenie lambda

lambda

Wyrażenie lambda dostarcza sposób na tworzenie bardzo krótkich funkcji nienazwanych w bardzo prosty sposób:

>>> a = lambda b: b + 2
>>> print a(3)
5

Wyrażenie "lambda [a]:[b]" czyta się dosłownie jako "funkcja z argumentami [a] zwracająca [b]". Same wyrażenie lambda jest nienazwane, ale funkcje przejmuje nazwę przez przypisanie do identyfikatora a. Zasady zasięgu dla def mają zastosowanie do równoważnika lambda i w rzeczywistości powyższy kod, w odniesieniu do a, jest identyczny z funkcją zadeklarowaną przy użyciu def:

>>> def a(b):
        return b + 2
>>> print a(3)
5

Jedyną korzyścią z używania wyrażenia lambda jest zwięzłość, ale zwięzłość jest bardzo wygodna w niektórych sytuacjach. Rozważmy funkcję o nazwie map, która stosuje funkcję do wszystkich elementów w liście, tworząc nową listę:

>>> a = [1, 7, 2, 5, 4, 8]
>>> map(lambda x: x + 2, a)
[3, 9, 4, 7, 6, 10]

Kod ten byłby dwukrotnie większy w przypadku zastosowania instrukcji def zamiast lambda. Główną wadą wyrażenia lambda, jest to że składnia (w implementacji Pythona) pozwala tylko na pojedyncze wyrażenia. Dla dłuższych funkcji trzeba użyć instrukcji def a dodatkowy nakład powodowany przez funkcję nazwaną zmniejszany jest wraz ze wzrostem długości funkcji. Podobnie jak def, wyrażenie lambda można stosować do funkcji curry: nowa funkcja może być użyta przez zawarcie istniejącej funkcji, tak że nowa funkcja posiada inny zestaw argumentów:

>>> def f(a, b): return a + b
>>> g = lambda a: f(a, 3)
>>> g(2)
5

Istnieje wiele sytuacji w których przydatna jest funkcja curry, ale jedna z nich dotyczy bezpośrednio web2py: buforowania. Załóżmy, że masz kosztowną funkcję, która sprawdza, czy jej argument jest liczbą pierwszą:

def isprime(number):
    for p in range(2, number):
        if (number % p) == 0:
            return False
    return True

Funkcja ta jest oczywiście czasochłonna.

Załóżmy teraz, że masz funkcję buforującą cache.ram, która pobiera trzy argumenty: klucz, funkcję i ilość sekund.

value = cache.ram('key', f, 60)

Przy pierwszym wywołaniu , wywołuje funkcję f(), zapisuje wyjście w słowniku w pamięci (powiedzmy "d") i zwraca go tak, że jej wartość to:

value = d['key']=f()

Przy drugim wywołaniu, jeśli klucz jest w słowniku i nie jest starszy niż liczba określonych sekund (60), to zwraca odpowiednią wartość bez wykonywania wywołania funkcji.

value = d['key']

Jak buforować wyjście funkcji isprime dla dowolnego wejścia? Właśnie tak:

>>> number = 7
>>> seconds = 60
>>> print cache.ram(str(number), lambda: isprime(number), seconds)
True
>>> print cache.ram(str(number), lambda: isprime(number), seconds)
True

Wyjście jest zawsze takie samo, ale za pierwszym razem wywoływane są funkcje cache.ram i isprime, za drugim razem tak nie jest.

Funkcje Pythona, tworzone z użyciem instrukcji def lub wyrażenia lambda umożliwiają ponowne fabrykowanie istniejących funkcji z innym zestawem argumentów. Funkcje cache.ram i cache.disk są funkcjami buforującymi web2py.

Instrukcja class

class

Ponieważ Python jest językiem o typach dynamicznych, klasy i obiekty Pythona mogą wydawać się dziwne. W rzeczywistości nie ma potrzeby definiowania zmiennych składowych (atrybutów) podczas deklarowania klas a różne instancje tej samej klasy mogą mieć różne atrybuty. Atrybuty są na ogół związane z instancją, a nie z klasą (z wyjątkiem gdy deklarowane są jako "atrybuty klasy", które są takie same jak "statyczne zmienne składowe" w C++/Java).

Oto przykład:

>>> class MyClass(object): pass
>>> myinstance = MyClass()
>>> myinstance.myvariable = 3
>>> print myinstance.myvariable
3

Proszę zauważyć, ze polecenie pass jest poleceniem pustym (nic nie robiącym). W tym przypadku jest zastosowane do zdefiniowania klasy MyClass, która nic nie zawiera. MyClass() wywołuje konstruktora klasy (w tym przypadku domyślny konstruktor) i zwraca obiekt (instancję klasy). Wyrażenie (object) w definicji klasy wskazuje, że nasza klasa rozszerza wbudowaną klasę object. Nie jest to wymagane, ale jest dobrą praktyką.

Oto bardziej skomplikowany przykład:

>>> class MyClass(object):
>>>    z = 2
>>>    def __init__(self, a, b):
>>>        self.x = a
>>>        self.y = b
>>>    def add(self):
>>>        return self.x + self.y + self.z
>>> myinstance = MyClass(3, 4)
>>> print myinstance.add()
9

Funkcje zadeklarowane wewnątrz klasy są metodami. Niektóre metody mają specjalne, zarezerwowane nazwy. Na przykład, __init__ jest konstruktorem. Wszystkie zmienne są zmiennymi lokalnymi metody, z wyjątkiem metod zadeklarowanych poza metodami. Na przykład z jest zmienną klasy, równoważną ze statyczną zmienna składową w C++, która posiada taką sama wartość dla wszystkich instancji klasy.

Proszę zauważyć, że metoda __init__ pobiera 3 argumenty a add jedną, jednak wywołujemy te metody odpowiednio z 2 i 0 argumentów. Pierwszy argument reprezentuje, zgodnie z konwencją, lokalną nazwę używana wewnątrz metody do odniesienia się do bieżącego obiektu. Tutaj stosujemy self do odniesienia się do bieżącego obiektu, ale można by użyć innej nazwy. Zmienna self spełnia taką samą rolę jak *this w C++ lub this w Java, ale self nie jest słowem kluczowym.

Taka składnia jest niezbędna aby uniknąć dwuznaczności podczas deklarowania zagnieżdżonych klas, takich jak klasa, która jest lokalna w stosunku do metody wewnątrz innej klasy.

Specjalne atrybuty, metody i operatory

Atrybuty klas, metody i operatory rozpoczynające się podwójnym podkreśleniem są zazwyczaj przeznaczone do bycia prywatnymi (np. aby uwidaczniać je wewnątrz, ale nie na zewnątrz klasy), ale jest to konwencja nie respektowana przez interpreter.

Niektóre z nich są zarezerwowane jako słowa kluczowe i mają specjalne znaczenia.

Oto dla przykładu trzy z nich:

  • __len__
  • __getitem__
  • __setitem__

Mogą one być użyte, na przykład, do utworzenia obiektu kontenera, który działa jak lista:

>>> class MyList(object):
>>>     def __init__(self, *a): self.a = list(a)
>>>     def __len__(self): return len(self.a)
>>>     def __getitem__(self, i): return self.a[i]
>>>     def __setitem__(self, i, j): self.a[i] = j
>>> b = MyList(3, 4, 5)
>>> print b[1]
4
>>> b.a[1] = 7
>>> print b.a
[3, 7, 5]

Inne specjalne operatory zawierają __getattr__ i __setattr__ definiujące atrybuty get i set dla klas oraz __sum__ i __sub__ nadpisujące operacje arytmetyczne. W celu poznania działania tych operatorów odsyłamy czytelnika do bardziej zaawansowanych książek w tym temacie. Wspominaliśmy już o specjalnych operatorach __str__ i __repr__.

Wejście i wyjście pliku

file.read
file.write

W Pythonie można otwierać pliki i dokonywać w nich zapisu przy użyciu:

>>> file = open('myfile.txt', 'w')
>>> file.write('hello world')
>>> file.close()

Podobnie można dokonywać z powrotem odczytu z pliku stosując:

>>> file = open('myfile.txt', 'r')
>>> print file.read()
hello world

Alternatywnie można dokonywać odczytu w trybie binarnym stosując "rb", zapisywać w trybie binarnym stosując "wb" i otwierać plik w trybie dopisywania stosując "a", wykorzystując standardową notację C.

Polecenie read pobiera opcjonalny argument, który jest liczbą bajtów. Można również przeskoczyć do określonego miejsca w pliku wykorzystując atrybut seek.

file.seek

Można z powrotem odczytać dane z pliku stosując polecenie read:

>>> print file.seek(6)
>>> print file.read()
world

i można zamknąć plik stosując:

>>> file.close()

W standardowej dystrybucji Pythona, znanej pod nazwą CPython, zmienne są zliczanymi referencjami (odniesieniami do obiektów), dotyczy to też przechowywanie uchwytów do plików, tak więc CPython wie, że gdy liczba referencji (uchwytów) do otwartych plików spada do zera, to plik może zostać zamknięty a zmienne zbyte. Jednak w innej implementacji Pythona, takiej jak PyPy, zamiast zliczania referencji stosowane jest "odśmiecanie kolekcji" (ang. garbage collection - gc), a to oznacza, że możliwe jest skumulowanie w tym samym czasie za dużej ilości uchwytów do otwartych plików. W rezultacie, w wyniku błędu gc ma szansę zamknąć wszystkie pliki i pozbyć się zmiennych. Dlatego najlepiej jest zamykać uchwyty do plików, gdy nie są one już potrzebne. Platforma web2py dostarcza dwie funkcje pomocnicze, read_file() i write_file() wewnątrz przestrzeni nazw gluon.fileutils, która hermetyzuje dostęp do pliku i zapewnia właściwe zamykanie uchwytów.

Podczas używania web2py nie wiesz gdzie znajduje się bieżący katalog, ponieważ zależy to od konfiguracji web2py. Zmienna request.folder zawiera ścieżkę do bieżącej aplikacji. Ścieżki można łączyć z poleceniem os.path.join, co jest omówione poniżej.

Funkcje exec, eval

exec
eval

W przeciwieństwie do Java, Python jest językiem rzeczywiście iterpretowanym. Oznacza to, że ma zdolność do wykonywania wyrażeń Pythona przechowywanych w ciągach znakowych. Na przykład:

>>> a = "print 'hello world'"
>>> exec(a)
'hello world'

Co się stało? Funkcja exec powiadamia interpreter, aby ją wywołał i wykonał zawartość ciągu znaków przekazanym jako argument. Jest możliwe wykonanie zawartości ciągu w kontekście zdefiniowanym przez symbol w słowniku:

>>> a = "print b"
>>> c = dict(b=3)
>>> exec(a, {}, c)
3

Tutaj interpreter podczas wykonywania ciagu a widzi symbole zdefiniowane w c (w tym przykładzie b), ale nie widzi całego c i a. Jest to inaczej niż w ograniczonym środowisku, ponieważ exec nie ogranicza tego, co można zrobić wewnętrzny kod. Po prostu określa zestaw zmiennych widzianych przez kod.

Funkcją związaną jest eval, która działa podobnie jak exec, ale oczekuje argumentów do ewaluacji wartości i zwraca tą wartość.

>>> a = "3*4"
>>> b = eval(a)
>>> print b
12

Instrukcja import

import
random
Prawdziwa siła Python leży w jego modułach bibliotecznych. Dostarczają one duży i spójny zbiór interfejsów programowania aplikacji (Application Programming Interfaces - API) dla wielu bibliotek systemowych (często w sposób niezależny od systemu operacyjnego).

Na przykład, jeśli chce się skorzystać z generatora liczb losowych, można to zrobić tak:

>>> import random
>>> print random.randint(0, 9)
5

Drukuje to losową liczbę całkowitą z przedziału 0 do 9 (w tym 9), na przykład 5. Funkcja randint jest zdefiniowana w module random. Jest też możliwe zaimportowanie obiektu z modułu do bieżącej przestrzeni nazw:

>>> from random import randint
>>> print randint(0, 9)

lub zaimportowanie wszystkich obiektów z modułu do bieżącej przestrzeni nazw:

>>> from random import *
>>> print randint(0, 9)

lub zaimportowanie wszystkiego w nowo definiowanej przestrzeni nazw:

>>> import random as myrand
>>> print myrand.randint(0, 9)

W pozostałej części tej książki będziemy korzystać z obiektów określonych w modułach os, sys, datetime, time i cPickle.

Wszystkie obiekty web2py są dostępne poprzez moduł o nazwie gluon i to jest przedmiotem dalszych rozdziałów. Platforma web2py wykorzystuje wiele modułów Pythona (na przykład thread), ale bardzo rzadko trzeba się do nich odwoływać bezpośrednio.

W kolejnych podrozdziałach omówimy te moduły, które są najbardziej użyteczne.

Moduł os

os
os.path.join
os.unlink

Moduł ten dostarcza interfejs do API systemu operacyjnego. Na przykład:

>>> import os
>>> os.chdir('..')
>>> os.unlink('filename_to_be_deleted')

W web2py NIE MOŻNA używać niektórych funkcji os, takich jak chdir, ponieważ nie są one bezpieczne dla wątku.

Polecenie os.path.join jest bardzo użyteczne – pozwala na łączenie ścieżek w sposób niezależny od systemu operacyjnego:

>>> import os
>>> a = os.path.join('path', 'sub_path')
>>> print a
path/sub_path

Do zmiennych środowiskowych systemu można uzyskać dostęp poprzez:

>>> print os.environ

który jest katalogiem tylko do odczytu.

Moduł sys

sys
sys.path

Moduł sys zawiera wiele zmiennych i funkcji, ale ta, która jest najbardziej dla nas przydatna, to sys.path. Zawiera listę ścieżek na których Python wyszukuje moduły. Gdy próbujemy zaimportować moduł, Python odszukuje go we wszystkich folderach wykazanych przez sys.path. Jeśli instalujesz moduły w jakimś miejscu i chcesz, aby Python je znalazł, to musisz dołączyć tą lokalizację do sys.path.

>>> import sys
>>> sys.path.append('path/to/my/modules')

Podczas uruchamiania web2py, Python zostaje wczytany do pamięci i rezyduje w niej przez cały czas działania web2py, tak więc w pamięci jest tylko jeden zbiór sys.path, podczas gdy może istnieć wiele wątków obsługujących żądania HTTP. Aby uniknąć przepełnienia pamięci, najlepiej jest sprawdzić, czy ścieżka już istnieje, przed jej ewentualnym dodaniem:

>>> path = 'path/to/my/modules'
>>> if not path in sys.path:
        sys.path.append(path)

Moduł datetime

date
datetime
time

Zastosowanie modułu datetime najlepiej jest zilustrować na przykładach:

>>> import datetime
>>> print datetime.datetime.today()
2008-07-04 14:03:90
>>> print datetime.date.today()
2008-07-04

Czasem może być konieczne ustalenie znacznika czasu na podstawie czasu UTC zamiast czasu lokalnego. W takim przypadku można skorzystać z następującej funkcji:

>>> import datetime
>>> print datetime.datetime.utcnow()
2008-07-04 14:03:90

Moduł datetime zawiera różne klasy: date, datetime, time i timedelta. Różnicą pomiędzy dwoma obiektami date lub dwoma obiektami datetime lub dwoma obiektami time jest timedelta:

>>> a = datetime.datetime(2008, 1, 1, 20, 30)
>>> b = datetime.datetime(2008, 1, 2, 20, 30)
>>> c = b - a
>>> print c.days
1

W web2py moduły date i datetime są używane do przechowywania odpowiednich typów SQL podczas przekazywania do lub zwracania z bazy danych.

Moduł time

time

Moduł time różni się od modułów date i datetime, ponieważ reprezentuje czas liczony w sekundach od początku epoki UNIX (początek 1970).

>>> import time
>>> t = time.time()
1215138737.571

Proszę zapoznać się z dokumentacją Pythona w zakresie konwersji pomiędzy czasem w sekundach a czasem traktowanych jako datetime.

Moduł cPickle

cPickle
peklowanie

Jest to bardzo ważny moduł. Dostarcza funkcji, które mogą serializować prawie dowolny obiekt Pythona, w tym obiekty samo się odwołujące. W Pytonie taki proces serializacji nosi nazwę peklowania. Na przykład, zbudujmy dziwny obiekt:

>>> class MyClass(object): pass
>>> myinstance = MyClass()
>>> myinstance.x = 'something'
>>> a = [1 ,2, {'hello':'world'}, [3, 4, [myinstance]]]

a teraz:

>>> import cPickle
>>> b = cPickle.dumps(a)
>>> c = cPickle.loads(b)

W tym przykładzie b jest reprezentacją łańcuchową a, a c jest kopią a generowaną przez zdeserializowany obiekt b.

cPickle może serializować i deserializować obiekt pliku:

>>> cPickle.dump(a, open('myfile.pickle', 'wb'))
>>> c = cPickle.load(open('myfile.pickle', 'rb'))
 top