post

Python ro:Rezolvarea problemelor

Am explorat diverse părţi din limbajul Python şi acum vom vedea cum conlucrează acestea prin proiectarea şi scrierea unui program care face ceva util. Ideea este de a învăţa cum să scriem un program Python propriu.

Contents

Problema

Problema este “Vreau un program care să facă un backup al tuturor fişierelor mele importante”.

Deşi aceasta este o problema simplă, nu avem destule informaţii pentru a începe găsirea unei soluţii. Se impune o analiză suplimentară. De exemplu, cum specificăm care fişiere trebuie salvate? Cum vor fi ele stocate? Unde vor fi stocate?

După o analiză corectă a problemei, proiectăm programul. Facem o listă cu lucruri care descriu cum trebuie să funcţioneze programul nostru. În acest caz, am creat lista următoare care descrie cum vreau EU să meargă. Dacă faceţi voi proiectarea s-ar putea să rezulte o altfel de analiză, întrucât fiecare face lucrurile în felul lui, deci e perfect OK.

  1. Fişierele şi directoarele care trebuie salvate sunt specificate într-o listă.
  2. Backup-ul trebuie stocat în directorul principal de backup
  3. Fişierele sunt stocate întro arhivă zip.
  4. Numele arhivei zip este data şi ora.
  5. Folosind comanda standard zip disponibilă implicit în toate distribuţiile Linux/Unix. Utilizatorii de Windows pot instala din pagina proiectului GnuWin32 şi adauga C:Program FilesGnuWin32bin la variabila de mediu PATH, similar modului în care am făcut pentru recunoaşterea însăşi a comenzii python. Reţineţi că puteţi folosi orice comandă de arhivare atât timp cât această are o interfaţă linie de comandă, ca să îi putem transmite argumente din programul nostru.

Soluţia

Întrucât designul programului nostru este relativ stabil, putem scrie codul care implementează soluţia noastră.

#!/usr/bin/python
# Fişier: backup_ver1.py

import os
import time

# 1. Fişierele şi directoarele de salvat sunt specificate într-o listă.
source = ['"C:\My Documents"', 'C:\Code']
# Observaţi că a fost nevoie de ghilimele duble în interiorul şirului pentru a proteja spaţiile din interiorul numelor.

# 2. Salvarea (engl. backup) trebuie stocată în directorul principal de backup
target_dir = 'E:\Backup' # Nu uitaţi să schimbaţi asta cu directorul folosit de voi

# 3. Fişierele sunt salvate întro arhivă zip.

# 4. Numele arhivei zip este data şi ora curentă
target = target_dir + os.sep + time.strftime('%Y%m%d%H%M%S') + '.zip'

# 5. Folosim comanda zip pentru a include fişierele şi directoarele de salvat în arhivă
zip_command = "zip -qr {0} {1}".format(target, ' '.join(source))

# Rulăm comanda de backup
if os.system(zip_command) == 0:
    print('Salvare reuşită în ', target)
else:
    print('Backup EŞUAT')

Rezultat:

   $ python backup_ver1.py
   Salvare reuşită în E:Backup20090208153040.zip

Acum suntem în faza de testare în care verificăm dacă programul nostru lucrează corect. Dacă nu se comportă cum trebuie, va fi nevoie de debugging adică eliminarea erorilor din program.

Dacă programul nu va merge, puneţi o declaraţie print(zip_command) imediat înainte de apelul os.system şi rulaţi programul. Acum copiaţi comanda zip_command la promptul shell-ului şi verificaţi dacă merge pe cont propriu. Dacă aceasta eşuează, citiţi manualul comenzii zip ca să aflaţi ce ar putea fi greşit. Dacă reuşeşte, verificaţi programul Python ca să vedeţi dacă este exact ca mai sus.

Cum funcţionează:

Veţi observa cum am transformat designul în cod întro manieră pas cu pas.

Utilizăm modulele os şi time importându-le de la început. Apoi specificăm directoarele care trebuie salvate în lista source. Directorul destinaţie este locul unde stocăm toate salvările şi acesta este specificat în variabila target_dir. Numele arhivei zip pe care o vom crea este “data curentă+ora curentă” pe care le găsim folosind funcţia time.strftime(). Arhiva va avea extensia .zip şi va fi stocată în directorul target_dir.

Observaţi folosirea variabilei os.sep – cea care ne dă separatorul de director al sistemului vostru de operare; acesta va fi '/' în Linux şi Unix, '\' în Windows şi ':' în Mac OS. Folosirea declaraţiei os.sep în locul acestor caractere va face programul mai portabil între aceste sisteme.

Funcţia time.strftime() primeşte o specificaţie ca aceea folosită în program. Specificaţia %Y va fi înlocuită cu anul, fără secol. Specificaţia %m va fi înlocuită cu luna, ca numar zecimal între 01 şi 12 ş.a.m.d. Lista completă a specificaţiilor poate fi găsită în Manualul de referinţă Python.

Creăm numele directorului destinaţie folosind operatorul de adunare care concatenează şirurile adică alătură şirurile şi produce unul mai lung. Atunci noi creăm un şir zip_command care conţine comanda pe care o vom executa. Puteţi verifica dacă a rezultat o comandă corectă prin executarea ei de sine stătătoare într-un shell (terminal Linux sau DOS prompt).

Comanda zip pe care o folosim are câteva opţiuni şi parametri transmişi. Opţiunea -q este folosită pentru a indica modul de lucru tăcut (engl. quiet). Opţiunea -r specifică modul recursiv de parcurgere a directoarelor, adică trebuie să includă şi toate subdirectoarele şi subdirectoarele acestora etc. Cele două opţiuni se combină şi se specifică pe scurt -qr. Opţiunile sunt urmate de numele arhivei care va fi creată urmată de lista fişierelor şi directoarelor de salvat. Convertim lista source într-un şir folosind metoda join a şirurilor, pe care am învăţat deja s-o folosim.

În fine, rulăm comanda folosind funcţia os.system care execută comanda ca şi cum ar fi fost lansată din sistem adică în shell – ea întoarce 0 dacă comanda a fost executată cu succes, altfel întoarce un cod de eroare.

În funcţie de rezultatul comenzii, tipărim pe ecran mesajul adecvat, cum că salvarea a reuşit sau nu.

Asta e, am creat un script care să faca un backup al fişierelor importante din sistem!

Notă pentru utilizatorii de Windows
În locul secvenţelor de evadare cu dublu backslash, puteţi folosi şi şiruri brute. De exemplu, folosiţi 'C:\Documents' sau r'C:Documents'. În orice caz, nu folosiţi 'C:Documents' întrucât veţi ajunge să folosiţi o secvenţă de evadare necunoscută, D.

Acum că avem un script funcţional de salvare, îl putem rula de câte ori vrem să obţinem o salvare a fişierelor. Utilizatorii de Linux/Unix sunt sfătuiţi să folosească metode executabile aşa cum am discutat în capitolele precedente, astfel ca ele să poată rula de oriunde, oricând. Asta se numeşte faza de operare sau de distribuire a software-ului.

Programul de mai sus funcţionează corect, dar (de obicei) primul program nu funcţionează cum ne-am aştepta. De exemplu ar putea fi probleme dacă nu am proiectat corect programul sau dacă avem o eroare de dactilografiere în scrierea codului (engl. typo), etc. În modul adecvat, vă veţi întoarce la faza de design sau debuggigg pentru a rezolva problema.

A doua versiune

Prima versiune a scriptului nostru funcţionează. Totuşi, putem rafina programul pentru a lucra mai bine în utilizarea sa de zi cu zi. Asta se numeşte întreţinere sau mentenanţă a software-ului (engl. maintenance).

Unul din rafinamentele pe care le-am considerat eu utile a fost un mecanism mai bun de denumire a salvărilor, folosind ora ca nume al fişierului, iar data ca nume de subdirector al directorului de backup care să conţină salvările din aceeaşi data. Primul avantaj este că salvările vor fi stocate într-o manieră ierarhică şi vor fi mai uşor de gestionat. Al doilea avantaj este că lungimea numelor de fişier va fi mai mult mai mică. Al treilea avantaj este că se va putea verifica mai uşor dacă au fost făcute salvări zilnic (în ziua în care nu s-a facut, directorul având ca nume acea dată lipseşte, întrucât nu a fost creat).

#!/usr/bin/python
# Fişier: backup_ver2.py

import os
import time

# 1. Fişierele şi directoarele de salvat sunt specificate întro listă.
source = ['"C:\My Documents"', 'C:\Code']
# Observaţi că au fost necesare ghilimele duble pentru a proteja spaţiile din interiorul numelor.

# 2. Salvarea trebuie stocată în directorul principal de backup 
target_dir = 'E:\Backup' # Nu uitaţi să schimbaţi asta cu directorul pe care îl folosiţi voi

# 3. Fişierele sunt salvate în fişiere zip.
# 4. Data curentă este numele subdirectorului din folderul principal 
azi = target_dir + os.sep + time.strftime('%Y%m%d')
# Ora curentă este numele arhivei zip
acum = time.strftime('%H%M%S')

# Creăm subdirectorul, dacă nu exista înainte
if not os.path.exists(azi):
    os.mkdir(azi) # creăm directorul
    print('Am creat cu succes directorul ', azi)

# Numele fişierului arhiva zip
target = azi + os.sep + acum + '.zip'

# 5. Folosim comanda zip pentru a colecta fişierele în arhivă. 
zip_command = "zip -qr {0} {1}".format(target, ' '.join(source))

# Rulăm programul de salvare
if os.system(zip_command) == 0:
    print('Salvare reuşită în ', target)
else:
    print('Salvare EŞUATĂ')

Rezultat:

   $ python backup_ver2.py
   Am creat cu succes directorul E:Backup20090209
   Salvare reuşită în E:Backup20090209111423.zip

   $ python backup_ver2.py
   Salvare reuşită în E:Backup20090209111837.zip

Cum funcţionează:

Majoritatea codului ramâne neschimbat. Schimbările constau un testarea existenţei directorului având ca nume data curentă în interiorului directorului principal de backup folosind funcţia os.path.exists. Dacă acesta nu există, îl creăm noi folosind funcţia os.mkdir.

Versiunea a treia

A doua versiune merge bine când facem multe salvări, dar e greu de văzut ce este salvat în fiecare arhiva! De exemplu, poate am făcut o schimbare mare unui program sau unei prezentări şi aş vrea să asociez aceste schimbări cu numele programului sau prezentării şi arhiva zip. Aceasta se poate realiza uşor prin ataşarea unui comentariu furnizat de utilizator la numele arhivei zip.

Notă
Următorul program nu funcţionează, deci nu va alarmaţi, urmaţi-l totuşi, pentru că e o lecţie în el.
#!/usr/bin/python
# Fişier: backup_ver3.py

import os
import time

# 1. Fişierele şi directoarele de salvat sunt specificate întro listă.
source = ['"C:\My Documents"', 'C:\Code']
# Observaţi că a fost nevoie de ghilimele duble pentru a proteja spaţiile din interiorul numelor.

# 2. Salvarea trebuie stocată în directorul principal
target_dir = 'E:\Backup' # Nu uitaţi să schimbaţi asta cu ce folosiţi voi
# 3. Fişierele sunt salvate întro arhivă zip.
# 4. Data curentă este numele subdirectorului
azi = target_dir + os.sep + time.strftime('%Y%m%d')
# Ora curentă este numele arhivei zip
acum = time.strftime('%H%M%S')

# Luăm un comentariu de la utilizator pentru a crea numele
comentariu = input('Introduceţi un comentariu --> ')
if len(comentariu) == 0: # Verificaţi dacă a fost introdus
    target = azi + os.sep + acum+ '_' +
    comentariu.replace(' ', '_') + '.zip'

# Creăm subdirectorul dacă nu există deja
if not os.path.exists(azi):
    os.mkdir(azi) # Creăm directorul
    print('Am creat cu succes directorul ', azi)

# 5. Folosim comanda zip pentru a colecta fişierele în arhiva
zip_command = "zip -qr {0} {1}".format(target, ' '.join(source))

# Rulăm salvarea
if os.system(zip_command) == 0:
    print('Salvare reuşită în ', target)
else:
    print('Salvare EŞUATĂ')

Rezultat:

   $ python backup_ver3.py
     File "backup_ver3.py", line 25
       target = azi + os.sep + now + '_' +
                                           ^
   SyntaxError: invalid syntax

Cum (nu) funcţionează:

Acest program nu funcţionează! Python spune că e undeva o eroare de sintaxă ceea ce înseamnă că programul nu a fost bine scris, că nu respectă structura pe care se aşteaptă Python să o găsească acolo. Când vedem eroarea dată de Python, aflăm şi locul unde a detectat el eroarea. Deci începem eliminarea erorilor (engl. debugging) programului de la acea linie.

La o observaţie atentă, vedem ca o linie logică a fost extinsă pe două linii fizice fără să se specifice acest lucru. În esenţă Python a găsit operatorul de adunare (+) fără vreun operand în acea linie şi prin urmare nu ştie cum să continue. Vă amintiţi că putem specifica trecerea unei linii logice pe următoarea linie fizică folosind un backslash la sfârşitul liniei fizice. Astfel facem corectura la progrmul nostru. Aceasta corecţie a programului când gasim erori se numeşte depanare (engl. bug fixing).

Versiunea a patra

#!/usr/bin/python
# Fişier: backup_ver4.py

import os
import time

# 1. Fişierele de salvat sunt specificate întro listă.
source = ['"C:\My Documents"', 'C:\Code']
# Observaţi că a trebuit să punem ghilimele duble pentru a proteja spaţiile din interiorul numelor.

# 2. Salvarea trebuie stocată în directorul principal de backup
target_dir = 'E:\Backup' # Nu uiţati să schimbaţi asta cu ceea ce folosiţi voi

# 3. Salvările se fac în arhive zip.

# 4. Ziua curentă este numele subdirectorului din directorul principal de backup
azi = target_dir + os.sep + time.strftime('%Y%m%d')
# Ora curentă este numele arhivei zip
acum = time.strftime('%H%M%S')

# Acceptăm un comentariu de la utilizator
comentariu = input('Introduceţi un comentariu --> ')
if len(comentariu) == 0: # Verificăm dacă a fost introdus un comentariu
    target = azi + os.sep + acum + '.zip'
else:
    target = azi + os.sep + acum + '_' + 
        comentariu.replace(' ', '_') + '.zip'

# Creăm subdirectorul, dacă nu exista deja
if not os.path.exists(azi):
    os.mkdir(azi) # creăm directorul
    print('Am creat cu succes directorul ', today)

# 5. Folosim comanda zip pentru a colecta fişierele în arhivă zip
zip_command = "zip -qr {0} {1}".format(target, ' '.join(source))

# Rulăm comanda de backup
if os.system(zip_command) == 0:
    print('Salvare reuşită în ', target)
else:
    print('Salvare EŞUATĂ')

Rezultat:

   $ python backup_ver4.py
   Introduceţi un comentariu --> noi exemple adăugate
   Salvare reuşită în E:Backup20090209162836_noi_exemple_adăugate.zip

   $ python backup_ver4.py
   Introduceţi un comentariu -->
   Salvare reuşită în E:Backup20090209162916.zip

Cum functionează:

Acest program funcţionează, acum! Să parcurgem îmbunătăţirile pe care i le-am adus în versiunea 3. Acceptăm un comentariu de la utilizator folosind funcţia input şi apoi testăm dacă s-a introdus ceva sau nu calculând lungimea şirului introdus cu ajutorul funcţiei len. Dacă utilizatorul a dat ENTER fără să introducă ceva (poate era doar un backup de rutină şi n-au fost făcute modificări anume), apoi continuăm ca mai înainte.

Totuşi, dacă a fost introdus un comentariu, acesta este ataşat numelui arhivei zip, chiar înaintea extensiei .zip. Observaţi că înlocuim spaţiile în comentariu cu underscore – ca să fie mai uşoară gestiunea fişierelor cu nume lungi.

Alte rafinamente

A patra versiune este una satisfăcătoare pentru majoritatea utilizatorilor, dar este mereu loc pentru mai bine. De exemplu se poate introduce un nivel de logoree (engl. verbosity) pentru program, cu ajutorul opţiunii -v Pentru a face programul mai vorbăreţ.

Altă îmbunătăţire posibilă ar fi să permitem ca fişierele şi directoarele de salvat să fie transmise scriptului la linia de comandă. Noi le putem culege din lista sys.argv şi le putem adăuga la variabila listă source folosind metoda extend a clasei list.

Cea mai importanta extindere ar fi să nu folosim os.system ci direct modulele predefinite zipfile sau tarfile pentru a crea aceste arhive. Ele sunt parte a bibliotecii standard şi sunt deja disponibile pentru a scrie un program fără dependente externe pentru programul de arhivare.

Totuşi, am folosit os.system pentru crearea salvărilor din motive pedagogice, pentru ca exemplul să fie destul de simplu de înteles, dar şi util.

Puteţi încerca să scrieţi a cincea variantă folosind modulul zipfile în locul apelului os.system?

Procesul de dezvoltare de software

Am trecut prin diverse faze în procesul de scriere a unui program. Aceste faze pot fi rezumate astfel:

  1. Ce (Analiza)
  2. Cum (Design)
  3. Executare (Implementare)
  4. Test (Testare şi eliminare erori)
  5. Utilizare (Operare sau distribuire)
  6. Mentenanţă (Rafinare)

O cale recomandată de a scrie programe este procedura urmată de noi la scrierea scriptului de salvare: facem analiza şi designul. Începem implementarea cu o versiune simplă. O testăm şi o depanăm. O folosim pentru a ne asigura că merge cum ne-am propus. Acum îi adăugăm noi facilităţi pe care le dorim şi parcurgem ciclul FACI – TESTEZI – UTILIZEZI de câte ori este nevoie. Reţineţi, Software-ul este crescut, nu construit.

Rezumat

Am văzut cum se creează un program/script Python şi diferite stadii implicate de scrierea unui program. Aţi putea considera utilă scrierea de programe proprii, atât pentru acomodarea cu Python cât şi pentru a rezolva probleme.

În continuare vom discuta despre programarea orientată pe obiecte.


Advertisements