/Cw2

Z Brain-wiki

TI:WTBD/Ćwiczenia 2

Tutaj zajęliśmy się trochę wykorzystaniem w programach argumentów wywołania przekazanych na linii poleceń. Jest to odrobinę obok zasadniczego tematu, ale rzecz jest przydatna jeżeli chcemy tworzyć narzędzia wielokrotnego użytku, nadające się do udostępnienia komuś innemu niż sam autor.

Pierwszy przykład emuluje częściowo systemowe polecenie iconv (proszę zajrzeć do manuala). Analizę argumentów wykonujemy "ręcznie", aby się przekonać, że nie jest to trudne. Identyfikujemy opcje -f i -t jako poprzedzające nazwy kodowań: odpowiednio, początkowego i docelowego. Jeśli którakolwiek z tych opcji nie wystąpi, to nie wiadomo co robić -- więc zgłaszamy błąd i kończymy. Reszta argumentów to nazwy plików do przetworzenia. Wynik posyłamy jednym strumieniem na standardowe wyjście.

#! /usr/bin/python
# -*- coding: utf-8 -*-
# convtxt0.py

from sys import argv, stdin, stdout, stderr

progname = argv.pop(0)
args, from_enc, to_enc = list(), None, None

while argv:
    curr = argv.pop(0)
    if curr == '-f':
        from_enc = argv.pop(0)
    elif curr == '-t':
        to_enc = argv.pop(0)
    else:
        args.append(curr)
if not (from_enc and to_enc):
    stderr.write((u'Użycie: %s -f from_enc -t to_enc [plik1 ...]\n' % progname)
                    .encode('utf-8')
    )
    exit(1)
if args:
    for f in args:
        stdout.write(open(f).read().decode(from_enc).encode(to_enc))
else:
    stdout.write(stdin.read().decode(from_enc).encode(to_enc))

Słabością tej implementacji jest słabo określone zachowanie, gdy któraś z opcji (-f czy -t) wystąpi w linii poleceń więcej niż raz. Na zdrowy rozum, sens mają dwa rozwiązania: albo uznać to za błąd wywołania, albo pozwolić by -f występowało wielokrotnie i stosowało się do plików, których nazwy występują po tym wystąpieniu -f (czyli kolejne pojawienie się tej opcji "przełączałoby" kodowanie wg. którego interpretowana jest zawartość, dla dalszych plików). Zastosowanie tego podejścia do -t nie miałoby chyba wiele sensu, bo nie ma co mieszać kodowań w ramach jednego strumienia wyjściowego -- więc wielokrotne -t powinno raczej być błędem.

A jak się zachowuje kod powyżej tak jak stoi? I jak go zmienić, by działał wg. powyższych założeń?

Można też inaczej: skorzystać z "gotowca" dostarczonego w bibliotece standardowej:

#! /usr/bin/python
# coding: utf-8

from sys import argv, stdin, stdout, stderr
from optparse import OptionParser

parser = OptionParser()
parser.add_option('-f', '--from', type='string', dest='from_enc')
parser.add_option('-t', '--to', type='string', dest='to_enc')
opts, args = parser.parse_args()
if not opts.from_enc or not opts.to_enc:
    stderr.write(
        (u'Wywołanie: %s [-f|--from] kodowanie1 [-t|--to] kodowanie2 [plik1 ...]\n' % argv[0])
        .encode('utf-8')
    )
    exit(1)
if args:
    for f in args:
        stdout.write(open(f).read().decode(opts.from_enc).encode(opts.to_enc))
else:
    stdout.write(stdin.read().decode(opts.from_enc).encode(opts.to_enc))

Nie jest to może dużo krótsze, i nie rozwiązuje problemu, o którym mowa powyżej, ale za to daje obsługę tzw. długich nazw opcji (--from, ..), nieco większą elastyczność (dopuszcza parę wersji podawania argumentów do opcji), jest też możliwość autogenerowania "helpu" składanego i ładnie formatowanego z opisów, podawanych w wywołaniach add_option. Proszę znaleźć sposób zrobienia tego w dokumentacji i zastosować do powyższego przykładu.

W kolejnym kroku nauczyliśmy się korzystać metody translate wartości napisowych, na przykładzie programiku, który eliminuje "polskie litery" z danych tekstowych, wymieniając je na ich nieakcentowane odpowiedniki:

#! /usr/bin/python
# coding: utf-8

_asciify_pl_tab = (u'ĄĆĘŁŃÓŚŻŹąćęłńóśżź', u'ACELNOSZZacelnoszz')
asciify_pl_tab = dict()
for a, b in zip(*_asciify_pl_tab):
  asciify_pl_tab[ord(a)] = b
  
#print asciify_pl_tab
def test(ienc='utf-8', oenc='utf-8'):
  import fileinput
  from sys import stdout
  for line in fileinput.input():
    stdout.write(line.decode(ienc).translate(asciify_pl_tab).encode(oenc))
    
if __name__ == '__main__':
  test()

"Tablicę" (naprawdę -- słownik) translacji można było oczywiście wprowadzić od razu w postaci literalnego słownika, byłoby to jednak bardziej żmudne i chyba mniej czytelne.

Przy okazji dowiedzieliśmy się o module fileinput, który automatyzuje iterację po linijkach z całego ciągu plików (a nie tylko z jednego).