/Cw1
TI:WTBD/Ćwiczenia 1
Do zabawy używaliśmy m. in. plików Plik:PanTadeusz1.txt i Plik:PanTadeusz1 Latin2.txt.
Dla rozgrzewki i jako narzędzie do dalszej zabawy napisaliśmy moduł z funkcjami: konwertującą liczbę (int) na "bity" (jej zapis dwójkowy jako napis składający się z zer i jedynek), i funkcję do niej odwrotną. Oto pełny tekst kodu modułu:
#! /usr/bin/python
# coding: utf-8
# util.py
def tobits(N):
s = list()
while N:
N, r = N / 2, N % 2
s.append('%i' % r)
s.reverse()
return ''.join(s)
def frombits(bits):
res = 0
for c in bits:
if c not in '01':
raise ValueError('not bits!')
res *= 2
res += int(c)
return res
Funkcji tych użyliśmy do przyjrzenia się, jak wyglądają sekwencje wielobajtowe reprezentujące znaki spoza zakresu ASCII w kodowaniu UTF-8. Informacje o zasadzie kodowania UTF-8 można znaleźć np. w podręczniku systemowym (man utf-8).
Oczywiście tak naprawdę nie było potrzeby pisania tych funkcji samemu. Proszę znaleźć sposób uzyskania tych konwersji wprost za pomocą wbudowanych funkcji/metod.
Napisaliśmy dwie funkcje odtwarzające zasadniczą funkcjonalność standardowego polecenia wc, różnią się one sposobem wykonania operacji wejścia. Obie nic nie wiedzą o kodowaniach tekstu, traktując zawartość pliku jako strumień bajtów.
def wc0(plik):
'''zwraca (linie, słowa, znaki);
ta wersja wc nic nie wie o kodowaniach, po prostu liczy bajty.
'''
try:
s = plik.read()
except AttributeError:
s = open(plik).read()
#return len(s.split('\n')), len(s.split()), len(s)
# tradycyjne wc liczy wystąpienia '\n', co często nie zgadzałoby się o 1
return s.count('\n'), len(s.split()), len(s)
def wc1(plik):
'''na sposób linijka-po-linijce'''
try:
f = open(plik)
except TypeError:
f = plik
l, w, c = 0, 0, 0
for line in f:
l += 1
w += len(line.split())
c += len(line)
return l, w, c
Warto czasami sprawdzić, czy "ulepszona" implementacja nie daje innych wyników, niż oryginalna. Zwłaszcza, że z komentarza w pierwszej wersji wc wynika, że czasami wyniki tych funkcji mogą różnić się o 1 (dla liczby linijek) -- w jakich okolicznościach, i jak to poprawić?
def test(plik):
if wc0(plik) == wc1(plik):
print 'OK'
else:
print 'ERROR!'
Kolejna wersja zakłada, że plik jest zapisem tekstu w kodowaniu UTF-8.
def wc(plik, enc='utf-8'):
'''ta wersja zakłada że plik jest w kodowaniu utf-8
'''
try:
s = plik.read()
except AttributeError:
s = open(plik).read()
s = s.decode(enc)
return s.count('\n'), len(s.split()), len(s)
Ta wersja cierpi na tę słabość, że rzuci wyjątkiem jeśli treść pliku nie jest legalnym strumieniem UTF-8.
Jeżeli chcemy stworzyć program bezpośrednio wykonywalny, to potrzebny jest jeszcze blok mówiący o tym, co ma się stać w wyniku jego wywołania. Do tego musimy oczywiście dołączyć jeszcze w treści pliku programu którąś z wersji definicji funkcji wc.
if __name__ == '__main__':
from sys import argv, stdin
args = argv[1:]
if args:
reslines = list(wc(plik) + (plik,) for plik in args)
else:
reslines = [wc(stdin) + ('-',)]
if len(reslines) > 1:
reslines.append(tuple(sum(t) for t in zip(*reslines)[:3]) + ('RAZEM',))
#print reslines # jeszcze trzeba to umiejętnie sformatować
reslines = list(tuple(str(x) for x in line) for line in reslines)
maxl = tuple(max(len(s) for s in col) + 1 for col in zip(*reslines))
fmt = '%%%ds%%%ds%%%ds %%s' % maxl[:3]
for line in reslines:
print fmt % line
Co tu się dzieje?
- Dobieramy się do listy argumentów z linii poleceń (pomijając zerowy, będący nazwą samego programu) i traktujemy te argumenty jako nazwy plików.
- Iterujemy po elementach listy argumentów jeśli jest niepusta. WPP czytamy zawartość stdin i pracujemy na niej. Uwaga, iteracja jest wykonana nie za pomocą pętli for, a generatora.
- W przypadku gdy lista wynikowa ma długość > 1, dołączamy jeszcze jeden wiersz z podsumowaniem.
- Formatujemy wynik w zgrabną tabelkę o wyrównanych kolumnach. W tym celu obliczamy, jaką szerokość trzeba zarezerwować na każdą z 3 pierwszych kolumn, i na tej podstawie tworzymy napis formatujący fmt, służący do sformatowania linijek.