Biblioteka standardowa

Operacje plikowe

Otwieranie plików

open(path[, mode[, buffersize]])

otwieranie pliku zarówno do odczytu, jak i zapisywania

  • path – łańcuch znaków określający ścieżkę, wskazujący na dany plik

  • mode – tryb otwarcia pliku

  • buffersize – argument opcjonalny, określa, jaki tryb buforowania powinien być stosowany, kiedy uzyskuje się dostęp do pliku

file = open(inPath, 'rU')
file = open(outPath, 'wb')

Tryby plików

r

Otwiera istniejący plik do odczytu

w

Otwiera plik do zapisu. Jeśli plik już istnieje, jego zawartość jest kasowana. Jeśli plik jeszcze nie istnieje, tworzony jest nowy.

a

Otwiera istniejący plik do uaktualnienia, zachowując istniejącą część bez zmian i dodając do niej nową treść.

r+

Otwiera plik zarówno do odczytu, jak i zapisu. Istniejąca treść pozostaje bez zmian.

w+

Otwiera plik zarówno do zapisu, jak i odczytu. Istniejąca treść jest kasowana.

a+

Otwiera plik zarówno do odczytu, jak i zapisu. Istniejąca treść pozostaje bez zmian, a nowa treść jest dodawana do istniejącej.

b

Stosowany w dodatku do jednego z ww. trybów odczytu, zapisu lub dodawania. Otwiera plik w trybie binarnym.

Tryb buforowania

0

Plik nie powinien być buforowany

1

Buforowanie wierszy (line-buffering)

inna liczba dodatnia

Wskazuje konkretny rozmiar bufora, który ma być wykorzystywany w dostępie

brak argumentu lub liczba ujemna

Wykorzystywany jest domyślny rozmiar bufora systemu

Otwieranie i zamykanie plików

close()

metoda zamykająca plik

Przykład:

inPath = "input.txt"
outPath = "output.txt"

#  Otwarcie pliku do odczytu
file = open(inPath, 'r')
if file:
    #  Tutaj następuje odczytywanie zawartości pliku
    file.close()
else:
    print("Przy otwieraniu pliku nastąpił błąd.")

# Otwarcie pliku do zapisu
file = open(outPath, 'wb')
if file:
    # Tutaj następuje zapisywanie czegoś do pliku
    file.close()
else:
    print("Przy otwieraniu pliku nastąpił błąd.")

Od Pythona w wersji 2.5 można używać wyrażenia with, tzw. menedżera kontekstu. Gwarantuje on poprawne zamknięcie pliku nawet w sytuacji wystąpienia wyjątku.

Przykład:

with open("input.txt", 'r') as f:
    # Operacje na obiekcie pliku
    f.read()

# zamknięcie pliku

Menedżer kontekstu odpowiada następującej obsłudze wyjątków:

try:
    f = open("input.txt", 'r')
    f.read()
finally:
    f.close()

Wyrażenie with upraszcza pisanie kodu odpornego na wyjątki.

Odczyt

Odczytywanie całej zawartości pliku

read()

wczytuje pełną zawartość pliku aż do napotkania znacznika EOF, a następnie zwraca zawartość pliku w formie łańcucha znaków.

readlines()

wczytuje całą zawartość pliku, oddzielając każdy wiersz jako osobny łańcuch znaków, aż do napotkania znacznika EOF i zwraca listę łańcuchów znaków reprezentujących pojedyncze wiersze.

read(bytes)

wczytuje określoną liczbę bajtów z pliku i zwraca je w postaci łańcucha znaków. Jeśli pierwszy odczytany znak jest znacznikiem EOF, zwracany jest null.

Przykład:

filePath = "input.txt"

# Wczytanie całego pliku do bufora
buffer = "Bufor całego pliku:\n"
buffer += open(filePath, 'r').read()
print(buffer)

# Wczytanie wierszy do bufora
buffer = "Bufor wszystkich wierszy z pliku:\n"
lines = open(filePath, 'r').readlines()
print(lines)
for line in lines:
    buffer += line
print(buffer)

# Wczytanie bajtów do bufora
buffer = "Bufor odczytu:\n"
file = open(filePath, 'r')
while True:
    bytes = file.read(5)
    if bytes:
        buffer += bytes
    else:
        break
print(buffer)
Bufor całego pliku:
Wiersz 1
Wiersz 2
Wiersz 3
Wiersz 4
['Wiersz 1\n', 'Wiersz 2\n', 'Wiersz 3\n', 'Wiersz 4\n']
Bufor wszystkich wierszy z pliku:
Wiersz 1
Wiersz 2
Wiersz 3
Wiersz 4
Bufor odczytu:
Wiersz 1
Wiersz 2
Wiersz 3
Wiersz 4

Dostęp do każdego słowa w pliku

Słowa mogą być przetwarzane pojedynczo poprzez:
  • otwarcie pliku

  • wczytanie każdego wiersza do łańcucha znaków

  • podzielenie łańcuchów na znaki za pomocą funkcji split()

Przykład:

filePath = "input.txt"
wordList = []
wordCount = 0

# Wczytanie wierszy do pliku
file = open(filePath, 'r')
for line in file:
    for word in line.split():
        wordList.append(word)
        wordCount += 1

print(wordList)
print("Całkowita liczba słów {}".format(wordCount))
['Wiersz', '1', 'Wiersz', '2', 'Wiersz', '3', 'Wiersz', '4']
Całkowita liczba słów 8

Ustalenie liczby wierszy w pliku

readlines()

generowanie listy wierszy. Dla dużych plików używanie readlines() może być niepraktyczne ze względu na ilość pamięci i czas niezbędny do przetwarzania.

len()

ustalenie liczby wierszy w liście.

Przykład:

filePath = "input.txt"
lineCount = len(open(filePath, 'r').readlines())
print("Liczba wierszy w {} wynosi {}".format(filePath, lineCount))
Liczba wierszy w pliku input.txt wynosi 4

Zapisywanie do pliku

Metody zapisywania danych do pliku:

write(string)

zapisuje argument string do pliku w miejscu, w którym aktualnie znajduje się wskaźnik pliku

writelines(sequence)

funkcja zazwyczaj przyjmuje listę łańcuchów znaków sequence i zapisuje te łańcuchy do pliku

Można również przekierować funkcję print do pliku za pomocą argumentu file

Przykład:

wordList = ["Czerwony", "Niebieski", "Zielony"]
filePath = "output.txt"
# Zapisanie listy do pliku
fileOut = open(filePath, 'w')
fileOut.writelines(wordList)

# Zapisanie łańcucha znaków do pliku
fileOut.write("\n\nSformatowany tekst:\n")

# Zapisanie listy do pliku
fileOut = open(filePath, 'w')
fileOut.writelines(wordList)

# Zapisanie łańcucha znaków do pliku
fileOut.write("\n\nSformatowany tekst:\n")

# Drukowanie bezpośrednio do pliku
for word in wordList:
    print("\tKontrola koloru: {}".format(word), file=fileOut)
fileOut.close()
CzerwonyNiebieskiZielony
Sformatowany tekst:
    Kontrola koloru: Czerwony
    Kontrola koloru: Niebieski
    Kontrola koloru: Zielony

Przechodzenie drzewa katalogów

os.walk(path)

przechodzi drzewo katalogów i tworzy dla każdego z nich krotkę składającą się ze: ścieżki katalogu, listy nazw katalogów oraz listy nazw plików.

Po utworzeniu krotek mogą być one pojedynczo przetwarzane jako elementy listy z użyciem indeksów

  • 0 – dostęp do reprezentowanej bezpośrednio ścieżki katalogu

  • 1 – dostęp do listy podkatalogów

  • 2 – dostęp do plików zawartych w katalogu

Przykład:

import os
path = "/books/python"

def printFiles(dirList, spaceCount):
    for file in dirList:
        print("/".rjust(spaceCount+1) + file)

def printDirectory(dirEntry):
    print(dirEntry[0] + "/")
    printFiles(dirEntry[2], len(dirEntry[0]))

tree = os.walk(path)
for directory in tree:
    printDirectory(directory)
/books/python/
             /Python Proposal.doc
             /Python_Phrasebook_TOC.doc
             /python_schedule.xls
             /template.doc
             /TOC_Notes.doc
/books/python/CH2/
                 /CH2.doc
/books/python/CH2/code/
                      /comp_str.py
                      /end_str.py
                      /eval_str.py
                      /format_str.py
                      /join_str.py
/books/python/CH3/
                 /CH3.doc

Zmiana nazwy pliku

os.remove(file)

wykrycie, czy plik o danej nazwie już istnieje, a następnie ewentualne usunięcie

os.rename(oldFile, newFile)

zmiana nazwy pliku

os.remove(newFileName)
os.rename(oldFileName, newFileName)

Przykład:

import os
oldFileName = "/books/python/CH4/code/output.txt"
newFileName = "/books/python/CH4/code/output.old"

# Nowy listing
for filename in os.listdir("/books/python/CH4/code/"):
    if filename.startswith("output"):
       print(filename)
# Usunięcie pliku, jeśli nowa nazwa już istnieje
if os.access(newFileName, os.X_OK):
    print("Usuwanie " + newFileName)
    os.remove(newFileName)
# Zmiana nazwy pliku
os.rename(oldFileName, newFileName)
# Stary listing
for filename in os.listdir("/books/python/CH4/code/"):
    if filename.startswith("output"):
       print(filename)
output.old
output.txt
Usuwanie /books/python/CH4/code/output.old
output.old

Rekurencyjne kasowanie plików i podkatalogów

Funkcja os.walk(path) automatycznie tworzy listę krotek, reprezentujących katalogi, które mają być usunięte. Aby rekurencyjnie skasować drzewo, należy przejść przez listę katalogów i usunąć każdy plik zawarty w liście plików (trzeci element krotki). Najpierw należy usunąć pliki, a następnie same katalogi w odwrotnej kolejności, rozpoczynając od najgłębiej zagnieżdżonych.

Przykład:

import os
emptyDirs = []
path = "/trash/deleted_files"

def deleteFiles(dirList, dirPath):
    for filename in dirList:
        print("Usuwanie " + filename)
        os.remove(dirPath + "/" + filename)

def removeDirectory(dirEntry):
    print("Usuwanie plików z " + dirEntry[0])
    deleteFiles(dirEntry[2], dirEntry[0])
    emptyDirs.insert(0, dirEntry[0])

# Sporządzenie listy wpisów w drzewie katalogów
tree = os.walk(path)
for directory in tree:
    removeDirectory(directory)
# Usunięcie pustych katalogów
for dir in emptyDirs:
    print("Usuwanie " + dir)
    os.rmdir(dir)
Usuwanie plików z /trash/deleted_files
Usuwanie 102.ini
Usuwanie 103.ini
Usuwanie 104.ini
Usuwanie 105.ini
Usuwanie 106.ini
Usuwanie plików z /trash/deleted_files\Test
Usuwanie 111.ini
Usuwanie 114.ini
Usuwanie 115.ini
Usuwanie plików z /trash/deleted_files\Test\Test2
Usuwanie 112.ini
Usuwanie 113.ini
Usuwanie plików z /trash/deleted_files\Test\Test2
Usuwanie plików z /trash/deleted_files\Test
Usuwanie plików z /trash/deleted_files

Wyszukiwanie plików w oparciu o rozszerzenie

Tworzymy listę rozszerzeń plików poprzez podzielenie łańcucha znaków wzorca za pomocą funkcji split(). Sprawdzamy, czy rozszerzenie pliku odpowiada rozszerzeniu znajdującemu się na liście używając funkcji endswith(string) na nazwie pliku.

Przykład:

import os
path = "/books/python"
pattern = "*.py;*.doc"

# Wydrukowanie plików, które odpowiadają wybranym rozszerzeniom
def printFiles(dirList, spaceCount, extension):
    for file in dirList:
        for ext in extension:
            if file.endswith(ext):
                print("/".rjust(spaceCount+1) + file)
                break

# Wydrukowanie każdego podkatalogu
def printDirectory(dirEntry, extension):
    print(dirEntry[0] + "/")
    printFiles(dirEntry[2], len(dirEntry[0]), extension)

# Konwersja łańcucha znaków wzorca na listę rozszerzeń plików
extensions = []
for extension in pattern.split(";"):
    extensions.append(extension.lstrip("*"))

# Przejście drzewa katalogów w celu wydrukowania plików
for directory in os.walk(path):
    printDirectory(directory, extensions)
/books/python/
          /Python Proposal.doc
          /Python_Phrasebook_TOC.doc
          /python_schedule.xls
          /template.doc
          /TOC_Notes.doc
/books/python/CH2/
                 /CH2.doc
/books/python/CH2/code/
                      /comp_str.py
                      /end_str.py
                      /eval_str.py
                      /format_str.py
                      /join_str.py
/books/python/CH3/
                 /CH3.doc

Współpraca z systemem operacyjnym

Moduł os

Moduł os udostępnia przenośny, niezależny od platformy interfejs dla dostępu do usług operacyjnych. Pozwala to na dodanie do programów obsługi na poziomie systemu operacyjnego.

os.path.abspath(path)

zwraca bezwzględną ścieżkę w formie łańcucha znaków.

>>> import os
>>> print(os.path.abspath("."))
/home/my_user/code/Python
>>> print(os.path.abspath(".."))
/home/my_user/code
os.exists(path)
os.isdir(path)
os.isfile(path)

funkcje sprawdzają istnienie plików i katalogów.

>>> os.path.exists("/home/InfoTraining")
True
>>> os.path.isdir("/home/InfoTrainingSzkolenia")
True
>>> os.path.isfile("/home/InfoTrainingSzkolenia/doc1.txt")
True
os.chdir(path)

umożliwia zmianę bieżącego katalogu roboczego dla programu.

>>> os.chdir("/home/user/books/python/ch1/code")
>>> os.path.abspath(".")
/home/user/books/python/ch1/code
os.environ

słownik zmiennych środowiskowych.

>>> os.environ['PATH']
'/usr/sbin:/sbin:/usr/local/bin:/usr/bin'

Moduł subprocess

subprocess.Popen(command, stdin=None, stdout=None)

wykona funkcję systemową command tak, jakby znajdowała się ona w podpowłoce.

import subprocess
popen = subprocess.Popen("make",stdout=subprocess.PIPE)
result = popen.communicate()
print(result)

Moduł sys

Moduł sys udostępnia interfejs dostępu do środowiska interpretera Pythona.

>>> import sys
>>> sys.argv
['/home/my_user/code/print_it.py', 'text']
>>> sys.argv[1]
'text'

Atrybut argv modułu sys jest listą. Pierwszy element na liście argv jest ścieżką do modułu. Reszta listy składa się z argumentów, które zostały przekazane do modułu na początku wykonania.

Atrybut stdin modułu sys jest obiektem pliku, który zostaje utworzony na początku wykonywania kodu.

>>> text = sys.stdin.readline()
Dane wejściowe
>>> print(text)
Dane wejściowe

Atrybuty stdout i stderr wskazują na pliki używane jako standardowe wyjście oraz standardowe wyjście błędów. Pliki te domyślnie są związane z ekranem.

>>> old_stdout = sys.stdout
>>> old_stderr = sys.stderr
>>> sys.stdout = open("output.txt", "w")
>>> sys.stderr = sys.stdout
>>> sys.stdout = old_stdout
>>> sys.stderr = old_stderr

Moduł platform

Moduł platform udostępnia przenośny interfejs do informacji o platformie, na której uruchamiany jest program.

platform.architecture()

zwraca krotkę (bits, linkage)

  • bits – liczba bitów wielkości słowa w systemie

  • linkage – powiązane informacje o pliku wykonywalnym Pythona

>>> import platform
>>> platform.architecture()
('64bit', 'ELF')
platform.python_version()

zwraca wersję pliku wykonywalnego Pythona dla celów zgodności.

>>> platform.python_version()
'3.3.2'
platform.uname()

zwraca obiekt zawierający informację w formie (system, node, release, version, machine, processor)

  • system – aktualnie działający system operacyjny

  • node – nazwa hosta danej maszyny

  • release – główna wersja systemu operacyjnego

  • version – informacja o wydaniu systemu operacyjnego w formie łańcucha znaków

  • machine, processor – informacje sprzętowe danej platformy

>>> platform.uname()
uname_result(system='Linux', node='tpad', release='3.2.0-4-amd64',
version='#1 SMP Debian 3.2.46-1+deb7u1', machine='x86_64', processor='')

Moduł time

Moduł time udostępnia przenośny interfejs do funkcji związanych z czasem w systemie, na którym program jest wykonywany.

time.time()

Zwraca bieżący czas systemowy jako liczbę sekund, które upłynęły od 1.01.1970 zgodnie z UTC (Coordinated Universal Time)

time.localtime(secs)

Zwraca czas w sekundach od 1.01.1970 w formie krotki (year, month, day, hour, second, day of week, day of year, daylight savings)

time.ctime(secs)

Zwraca czas, określony w sekundach od 1.01.1970, w formie sformatowanego łańcucha znaków, nadającego się do wydruku

time.clock()

Zwraca aktualny czas procesora w postaci liczby zmiennoprzecinkowej, która może być używana w funkcjach mierzących czas

time.sleep(secs)

Zmusza bieżący proces do uśpienia przez liczbę sekund określoną przez liczbę zmiennoprzecinkową secs

Utrwalanie stanu: moduł pickle

Serializacja i trwałość obiektów może zostać zrealizowana przy pomocy wbudowanych narzędzi Pythona. Moduły pickle i cPickle serializują obiekty do i z plików.

import pickle
pickler = pickle.Pickler(file)    # file to otwarty obiekt pliku
pickler.dump(obj)                 # Zrzut obiektu

Aby odserializować obiekt, używamy funkcji unpickle.

unpickler = pickle.Unpickler(file)  # file to otwarty obiekt pliku
obj = unpickler.load()          # Ładowanie obiektu

Serializacja - wskazówki

  1. Większość typów wbudowanych można poddać serializacji.

  2. Można dokonać serializacji obiektu klasy. Klasa musi być dostępna w momencie odtwarzania obiektu. Sam kod klasy nie jest serializowany. Python automatycznie importuje moduł zawierający odtwarzaną klasę.

  3. Pewne typy obiektów nie mogą być serializowane np. sockety, pliki.

  4. Dowolny obiekt zapewniający metody write(), read() i readline() może być używany jako plik.

  5. Obiekty rekursywne można poddać serializacji.

  6. Dane po serializacji można przenosić między różnymi systemami operacyjnymi i architekturami sprzętowymi.

  7. Serializacja obiektów zastosowana w Pythonie nie jest dostępna z poziomu programów napisanych w innych językach programowania.

Wyrażenia regularne

Wyrażenie regularne (regex) definiuje prosty analizator, który sprawdza dopasowanie tekstu do danego wzorca.

Powody używania wyrażeń regularnych:
  • Przetwarzanie: identyfikacja i wyodrębniane fragmentów tekstu, które spełniają określone kryteria. Wyrażenia regularne są używane w celu tworzenia doraźnych analizatorów składni oraz przez zwykłe narzędzia analizatorów składni.

  • Wyszukiwanie: odnajdywanie łańcuchów znaków, które mogą mieć więcej niż jedną formę, na przykład znalezienie dowolnego z plików auto.png, auto.jpg lub auto.svg, ale unikanie pliku autobus.png.

  • Wyszukiwanie i zastępowanie: zastępowanie każdego łańcucha znaków dopasowanego przez wyrażenie regularne, na przykład wyszukanie „motor” lub „pojazd jednośladowy” i zastąpienie słowem „motocykl”.

  • Dzielenie łańcuchów znaków: podzielenie łańcucha znaków w każdym miejscu dopasowanym przez wyrażenie regularne, na przykład podział w każdym miejscu występowania dwukropka lub znaku równości.

  • Sprawdzanie poprawności: weryfikacja, czy dany fragment tekstu spełnia pewne kryteria, na przykład zawiera cyfry, a po nich symbol waluty.

foo.* # Dopasowuje string rozpoczynający od foo
\d* # Dopasowuje dowolny ciąg znaków złożony z cyfr
[a-zA-Z]+ # Dopasowuje sekwencję jednej lub więcej liter

Moduł re

Moduł re umożliwia korzystanie z wyrażeń regularnych w Pythonie.

Znaki specjalne

Większość znaków może być używana jako literały. Istnieją jednak znaki specjalne, które muszą być poprzedzone ukośnikiem (), aby można ich było używać jako literałów. Wspomniane znaki specjalne to \ . ^ $ ? + * {} [] () |.

Klasy znaków

Jeśli zamiast jednego określonego znaku chcemy dopasować jeden z zestawów znaków, używamy klasy znaków. Klasa znaków to jeden lub większa liczba znaków ujętych w nawiasy kwadratowe. Klasa znaków jest wyrażeniem, więc jeśli nie jest wyraźnie podany kwantyfikator, to zostanie dopasowany dokładnie jeden znak (dowolny ze znaków zdefiniowanych w klasie znaków).

Dla często używanych klas znaków dostępne są formy skrótowe.

.

Dopasowuje dowolny znak oprócz znaku nowej linii

\d

Dopasowuje dowolną cyfrę

\D

Dopasowuje dowolny znak nie będący cyfrą

\s

Dopasowuje dowolny biały znak

\S

Dopasowuje dowolny znak nie będący białym znakiem

\w

Dopasowuje dowolny znak alfanumeryczny

\W

Dopasowuje znaki nie będące znakami alfanumerycznymi

Kwantyfikatory wyrażeń regularnych

Kwantyfikatory służą do określania powtórzeń znaków lub sekwencji we wzorcach.

Domyślnie wyrażenia regularne realizują tzw. zachłanne dopasowanie. Każdy podciąg we wzorcu wyrażenia regularnego jest dopasowywany do największej możliwej liczby znaków w ciągu. Dopiero potem następuje przejście do kolejnej części wyrażenia.

*

Dopasowuje 0 lub więcej wystąpień

+

Dopasowuje 1 lub więcej wystąpień

?

Dopasowuje 0 lub 1 wystąpienie

{m,n}

Dopasowuje m do n wystąpień

[...]

Dopasowuje zbiór znaków

[^...]

Dopasowuje znaki, które nie występują w zbiorze

A | B

Dopasowuje A lub B

(...)

Dopasowuje wyrażenie podane w nawiasie jako grupę

Dodanie znaku zapytania po kwantyfikatorze powoduje przekształcenie go w tzw. kwantyfikator leniwy. Kwantyfikator leniwy pobiera znak po znaku szukając dopasowania. Ewentualna próba dopasowanie całego tekstu następuje na samym końcu.

??

Dopasowuje 0 lub 1 wystąpienie, wersja leniwa

*?

Dopasowuje 0 lub więcej, wersja leniwa

+?

Dopasowuje 1 lub więcej, wersja leniwa

{m,n}?

Dopasowuje m do n wystąpień, wersja leniwa

Asercje wyrażeń regularnych

Asercja pozwala wyznaczyć miejsce w tekście, w którym musi pojawić się dopasowanie.

^

Dopasowuje początek stringa, także po każdym znaku nowego wiersza, jeśli włączono opcję re.MULTILINE

$

Dopasowuje koniec stringa, , także przed każdym znakiem nowego wiersza, jeśli włączono opcję re.MULTILINE

\A

Dopasowuje początek stringa

\b

Dopasowuje pusty string na początku lub na końcu słowa

\B

Dopasowuje pusty string, lecz nie na początku lub na końcu słowa

\Z

Dopasowuje na końcu stringa

Surowe ciągi znaków

Surowe ciągi znaków (raw strings) ułatwiają pisanie wyrażeń regularnych w Pythonie. Ciąg poprzedza się literą r, która wyłącza specjalne znaczenie backslash’a w kolejnym ciągu znaków.

number_pattern = r'(\d+)\.(\d*)' # Dopasowuje liczby jak 3.4772

opis modułu re

  • Wyrażenia regularne są definiowane z użyciem opisanej składni

  • Kompilowany do wyrażenia regularnego „obiekt”

  • Używany do wykonywania operacji dopasowywania i zastępowania

import re
text = '12.34'
number_pattern = r'(\d+)\.(\d*)'       # Mój wzór
compiled_pattern = re.compile(number_pattern)         # Kompilacja
is_matched = compiled_pattern.match(text)                  # Sprawdzam
if is_matched:
    print("pasuje")
else:
    print("nie pasuje")

Obiekty wyrażeń regularnych

Obiekty wyrażeń regularnych stworzone przez re.compile() posiadają następujące metody:

r.search(s [,pos [,endpos]])

Szuka dopasowania

r.match(s [,pos [,endpos]])

Sprawdza dopasowanie stringu

r.split(s)

Dzieli według dopasowania regex

r.findall(s)

Znajduje wszystkie dopasowania

r.sub(repl,s)

Zamienia wszystkie dopasowania na repl

  • Jeśli znaleziono dopasowanie, zwracany jest obiekt dopasowania ’MatchObject’, który zawiera informację, gdzie znaleziono dopasowanie oraz o grupie

  • Metoda search szuka dopasowania w dowolnym miejscu stringa

  • Metoda match szuka dopasowania zaczynając od pierwszego znaku

  • Parametry pos i endpos określają początkową i końcową pozycję do wyszukania/dopasowania

Obiekty dopasowania

Obiekty dopasowania zawierają informację o samym dopasowaniu. Oparte są na pojęciu grup i stosują reguły grupowania.

Wyrażenie regularne (\d+)\.(\d*) ma 3 grupy: * Grupa 0 : całe wyrażenie regularne * Grupa 1 : część (\d+) * Grupa 2 : część (\d*)

Numerowanie grup we wzorcu przebiega od lewej do prawej.

Uzyskiwanie informacji o dopasowaniu:

m.group(n)  # Zwraca tekst dopasowany dla grupy n
m.start(n)  # Zwraca indeks początkowy dla grupy n
m.end(n)    # Zwraca indeks końcowy dla grupy n

Przykłady dopasowania

import re
compiled_pattern = re.compile(r'(\d+)\.(\d*)')
match = compiled_pattern.match("42.37")
matched_part = match.group(0)  # Zwraca ’42.37’
first_group = match.group(1)  # Zwraca ’42’
second_group = match.group(2)  # Zwraca ’37’
print(match.start(2))    # Drukuje 3
# Zastępowanie URL (np.: http://www.python.org) hiperłączem
url_pattern = r'(http://[\w-]+(\.[\w-]+)*((/[\w-~]*)?))'
compiled_pattern = re.compile(url_pattern)
compiled_pattern.sub('<a href="\\1">\\1</a>', text) # Zastępowanie w stringu

Testowanie kodu - moduł unittest

Wprowadzenie

Testowanie programu pozwala sprawdzić, czy program zachowuje się w pożądany sposób. Manualne testowanie programu jest czasochłonne. Dlatego dobrą praktyką jest pisanie automatycznych testów, które mogą zostać szybko wykonane przez komputer. Testuje się nie tylko całe programy, ale też pojedyncze funkcje czy klasy.

Standardowa biblioteka Pythona posiada moduł unittest ułatwiający pisanie takich testów.

unittest jest najpopularniejszą biblioteką stosowaną do pisania testów jednostkowych. Wynika to jej następujących cech:

  • unittest jest częścią standardowej biblioteki Pythona, co oznacza, że jest dostępna wszędzie, gdzie jest zainstalowany Python.

  • unittest jest inspirowana biblioteką JUnit z Javy. Osoby, które pisały wcześniej testy w JUnit bardzo szybko nauczą się pisać testy w Pythonie. Ponadto, wykorzystano sprawdzony interfejs. Z drugiej strony, wzorowanie się na bibliotece Javy powoduje, że testy są dosyć rozwlekłe i „rozgadane”.

  • unittest potrafi automatycznie znaleźć wszystkie testy i wykonać je.

Przykład

Testy grupuje się w klasy dziedziczące po unittest.TestCase. Każda metoda zaczynająca się od test_ to jeden test. Wywołanie unittest.main() powoduje uruchomienie wszystkich testów (nie jest potrzebne ręczne wymienianie nazw wszystkich testów).

Powszechnie przyjętą konwencją jest separacja testów i testowanego kodu, poprzez umieszczenie ich w osobnych plikach. Dodatkowo, testy umieszcza się w osobnym katalogu, a nie w tym samym katalogu co testowany moduł. Dzięki temu możliwa jest instalacja pakietu bez instalowania bibliotek wykorzystywanych tylko przez testy.

# converters.py
import re

def url_converter(url):
    if not url:
        raise ValueError("Empty url")

    url_pattern = r'(http://[\w-]+(\.[\w-]+)*((/[\w-]*)?))'
    compiled_pattern = re.compile(url_pattern)
    return compiled_pattern.sub('<a href="\\1">\\1</a>', url)

# test_converters.py
import unittest

from converters import url_converter

class UrlConverterTests(unittest.TestCase):

    def test_converts_url_to_ahref(self):
        url = "http://www.python.org"
        expected_ahref = '<a href="http://www.python.org">http://www.python.org</a>'
        result = url_converter(url)
        self.assertEqual(result, expected_ahref)

if __name__ == "__main__":
    unittest.main()

Ostatnie dwie linie powyższego kodu gwarantują uruchomienie testów, ale tylko gdy plik z kodem zostanie bezpośrednio uruchomiony, a nie zaimportowany.

$ python test_converters.py

setUp i tearDown

Jeżeli na początku lub na końcu każdego testu wykonujemy operacje, które powtarzają się w innych testach, wówczas można je umieścić w metodach setUp i tearDown. setUp jest metodą wykonywaną na początku każdego testu, natomiast tearDown – po wykonaniu testu. Jest to świetne miejsce na:

  • uzyskanie zasobów, które są potrzebne w każdym teście (np. połączenie z bazą danych),

  • skonfigurowanie środowiska w ten sam sposób dla każdego testu (np. w setUp – utworzenie przykładowych tabel i rekordów w bazie danych, a w tearDown – „posprzątanie” po teście, tzn. wyczyszczenie testowej bazy danych przez usunięcie tych tabel).

class TestAdd(unittest.TestCase):
    def setUp(self):
        print("setUp")

    def tearDown(self):
        print("tearDown")

    def test_one(self):
        print("test one")

    def test_two(self):
        print("test two")
setUp
test one
tearDown
setUp
test two
tearDown

self.assert*

W środku każdego testu możemy wykorzystać szereg metod zaczynających się od assert, np. assertEqual. Test przechodzi, jeżeli wszystkie takie asercje są prawdziwe. Jeżeli chociaż jedna taka asercja nie będzie spełniona, wówczas wykonywanie testu jest natychmiast przerywane i wykonywana jest metoda tearDown.

Najczęściej wykorzystywane asercje to:

  • self.assertTrue(condition) i self.assertFalse(condition),

  • self.assertEqual(got, expected) i self.assertNotEqual(got, expected),

  • self.assertIn(element, collection) i self.assertNotIn(element, collection),

  • self.assertIsInstance(obj, class_) i self.assertNotIsInstance(obj, class_).

Do sprawdzenia, czy dany blok kodu rzuca wyjątek, można użyć self.assertRaises(ExceptionType):

class UrlConverterTests(unittest.TestCase):

    def test_raises_exception_for_empty_string(self):
        url = ""

        with self.assertRaises(ValueError):
            url_converter(url)