Python este un limbaj ce oferă o serie de artificii pentru o scriere mai ușoară a codului. Cursul prezintă o trecere în revistă a principalelor moduri în care putem exprima mai ușor diferite construcții în Python.

List Comprehensions - Definire de liste

  • Vrem să constrim o funcție care primește o listă cu numere și întoarce o listă cu acestea triplate

Modul clasic

def triple_list(l):
    result = []
    for elem in l:
        result.append(elem * 3)
    return result

Putem scrie și mai simplu

Dacă puteam construi o listă cu elementele 4, 5 și 6 astfel: [4, 5, 6], mecanismul de list comprehension ne lasă să construim liste într-un mod descriptiv. Notația se bazează pe cea clasică din matematică și pune accent pe descrierea proprietaților pe care le au elementele din noua listă.

def triple_list(l):
    return [x * 3 for x in l]

În limbaj natural: pune x * 3 în listă, pentru fiecare x din l. O definire asemănătoare cu cea din matematică. Scrierea este echivalentă cu {x * 3 | x aparține l}


  • Vrem ca dintr-o listă să păstrăm doar elementele pare

Varianta clasică

def filter_even(l):
    result = []
    for elem in l:
        if elem % 2 == 0:
             result.append(elem)
    return result

Sau cu list comprehensions

def filter_even(l):
    return [x for x in l if x % 2 == 0]

În limbaj natural: pune x în listă, pentru fiecare x din l, dacă acesta e par.


  • Vrem toate perechile de numere i, j mai mari sau egale cu 0 și mai mici decât n
>>> n = 3
>>> [(i, j) for i in range(n) for j in range(n)]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
  • Vrem toate perechile de numere i, j mai mari sau egale cu 0 și mai mici decat n cu proprietatea că i <= j
>>> n = 3
>>> [(i, j) for i in range(n) for j in range(n) if i <= j]
[(0, 0), (0, 1), (0, 2), (1, 1), (1, 2), (2, 2)]
>>> # sau
>>> n = 3
>>> [(j, i) for i in range(n) for j in range(i + 1)]
[(0, 0), (0, 1), (1, 1), (0, 2), (1, 2), (2, 2)]

Dict Comprehensions

Într-un mod asemănător putem defini și dicționare.

  • Inițializăm cu 0 scorurile pentru o listă de persoane.
>>> nume = ["Ion", "Maria", "Gigel"]
>>> score = {n: 0 for n in nume}
>>> score
{'Ion': 0, 'Gigel': 0, 'Maria': 0}
>>> # Mai puțin pentru Ion
>>> nume = ["Ion", "Maria", "Gigel"]
>>> score = {n: 0 for n in nume if n != "Ion"}
>>> score
{'Gigel': 0, 'Maria': 0}
  • Putem transforma dintr-o listă de tuple într-un dicționar, sau invers.
>>> score = [('Gigel', 0), ('Maria', 10)]
>>> {n: s + 1 for n, s in score}
{'Gigel': 1, 'Maria': 11}

Observați construcția:

>>> score = [('Gigel', 0), ('Maria', 10)]
>>> for nume, scor in score:
...     print(nume, scor)
... 
Gigel 0
Maria 10
  • Vrem să inversăm operația anterioară.
>>> score = {'Gigel': 1, 'Maria': 11}
>>> [(s, n - 1) for s, n in score.items()]
[('Gigel', 0), ('Maria', 10)]

Observați metoda default de dicționar items

>>> score.items()
[('Gigel', 1), ('Maria', 11)]

Set comprehension

După aceleași reguli putem forma set-uri (structuri cu elemente unice) folosind construcția:

>>> dupplicates = [1, 1, 2, 2, 3, 3]
>>> {d for d in dupplicates}
set([1, 2, 3])
>>> # echivalenta cu
... 
>>> set(dupplicates)
set([1, 2, 3])

Iteratori

În Python, există multe obiecte ce pot fi parcurse într-un for. De exemplu, pentru liste, obținem fiecare element din listă, pentru string-uri, fiecare caracter. În cazul unui dicționar, obținem perechi de cheie, valoare și pentru un fișier fiecare linie a fișierului respectiv. Aceste obiecte peste care putem să iterăm se numesc iterabile.

Un obiect este considerat iterabil dacă implementează două metode: iter și next. Funcția iter primește ca parametru un obiect iterabil și întoarce un iterator. Metoda next primește iteratorul ca parametru și întoarce următorul element din parcurgere. La sfârșitul iterației aruncă o excepție StopIteration.

>>> def iterator_example():
...     color_list = ['blue', 'yellow', 'red', 'green']
...     iterator_color_list = iter(color_list)
...     for i in range(0,5):
...         print next(iterator_color_list)

iterator_example()

După cum se poate observa, iteratorii sunt destul de complicat de utilizat. Dar aceștia au o mare utilitate ăn salvarea memoriei utilizate.

Aceștia nu evaluează fiecare valoare a fiecărui element din obiect în momentul în care sunt inițializați. O evaluează numai în momentul în care este cerută valoarea respectivă. Acest lucru se numește lazy evaluation. Această evaluare întârziată este utilă când avem un set de date de dimensiuni foarte mari.Permite utilizarea imediată a datelor în timp ce întregul set este evaluat.

Iteratorul poate fi utilizat o singură dată.

Generatori

Generatorii sunt funcții ce ne permit să cream iteratori mai ușor. Aceștia introduc un cuvânt cheie yield, ce funcționează, într-un fel, precum return. Diferența este că acesta salvează starea funcției. În momentul în care chemăm funcția a doua oară, execuția continuă de unde a rămas, cu aceleași valori pentru variabile.

>>>def generator_example():
...    def generate_unsigned():
...        i = 0
...        while True:
...            print("before yield ", i)
...            yield i
...            print("after yield ", i)
...            i += 1
    
>>>generator_unsigned = generate_unsigned() 
>>>print(next(generator_unsigned))
>>>print(next(generator_unsigned))
>>>generator_example()
>>>def generator_expresion():
...    a = (x * x for x in range(10))
...    print(next(a))
...    print(next(a))
...    print(next(a))
    
>>>generator_expresion()

Obiecte callable

În Python o funcție este, de asemenea, și o valoare. De exemplu, putem atribui unei variabile valoarea unei funcții.

>>> def plus_one(x):
...     return x + 1
... 
>>> # valoarea lui plus_one este
... 
>>> plus_one
<function plus_one at 0x7f7b8c662e60>
>>> # o atribuim lui f
... 
>>> f = plus_one
>>> f
<function plus_one at 0x7f7b8c662e60>
>>> f(12)
13

De asemena, putem construi clase ale căror instanță se comportă precum o funcție. Aceste obiecte se numesc callable și se creează implementând metoda call. Generalizând exemplul de mai sus:

>>> class PlusN(object):
...     def __init__(self, plus_what):
...             self.plus_what = plus_what
...     def __call__(self, x):
...             return x + self.plus_what
... 
>>> plus_one = PlusN(1)
>>> plus_one(12)
13

Funcții anonime

În unele cazuri, funcțiile pot fi destul de scurte și ar fi inutil să poluăm spațiul de nume cu o funcție scurtă pe care o folosim doar de câteva ori.

Din acest motiv există funcții anonime sau lambda. O astfel de funcție se creează în felul următor:

>>> plus_one = lambda x: x + 1
>>> plus_one
<function <lambda> at 0x7f7b8c662e60>
>>> plus_one(12)
13
>>> # Sau cu mai multe argumente
... 
>>> f = lambda x, y: x + y 
>>> f(1, 2)
3

Funcțiile lambda sunt folosite, de obicei, în funcții care au ca parametru obiecte ce pot fi apelate.

>>> def apply_in_dict_vals(d, f):
...     return {key: f(value) for key, value in d.items()}
... 
>>> score = {'Gigel': 1, 'Maria': 11}
>>> apply_in_dict_vals(score, lambda x: x - 1)
{'Gigel': 0, 'Maria': 10}
>>> # Sau cu obiect callable
... 
>>> apply_in_dict_vals(score, PlusN(-1))
{'Gigel': 0, 'Maria': 10}
>>> # Sau cu o functie definita anterior
... 
>>> apply_in_dict_vals(score, plus_one)
{'Gigel': 2, 'Maria': 12}

Debugging (pdb)

Pentru detecția erorilor într-un program apelăm la debugging, care în Python se face printr-un modul dedicat: pdb. Cea mai comună utilizare a acestui modul este amplasarea unui breakpoint în cod, înainte de linia care generează o eroare:

import pdb; pdb.set_trace()

Execuția programului se va opri la această linie, permițând inspecția obiectelor curente, prin deschiderea unui interpretor interactiv. Comenzile ce pot fi folosite în debugger sunt:

  • h(elp) - printează lista de comenzi disponibile;
  • l(ist) - afișează un bloc de cod (11 linii) în jurul liniei curente;
  • n(ext) - trece la următoarea instrucțiune;
  • c(ontinue) - contină execuția codului până la sfârșit/următorul breakpoint;
  • s(tep) - intră în corpul funcției;
  • b(reak) linenum - setează un nou breakpoint la linia linenum;
  • q(uit) - oprește execuția programului;

Funcții de inspecție în interpretor

Foarte utile în debugging sunt și următoarele funcții built-in:

  • dir
  • type
  • vars

dir

Funcția dir(obj) returnează o listă cu toate atributele (date sau metode) unui obiect.

>>> s = 'hello'
>>> dir(s)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

type

Aceasta indică tipul unui obiect.

>>> class X(object):
...     pass
... 
>>> x = X()
>>> type(x)
<class '__main__.X'>

vars

vars întoarce valoarea atributului __dict__ pentru orice modul, clasă sau instanță care are acest atribut.

>>> class X(object):
...     def __init__(self):
...         self.greeting = 'Hello'
...         self.name = 'John'
... 
>>> x = X()
>>> vars(x)
{'greeting': 'Hello', 'name': 'John'}

Try/except

Când se produce o excepție, programul nostru python se va opri și va genera o eroare. Aceste erori pot fi prinse și tratate utilizănd blocul try.

>>>def exception_example():
...    try:
...        print(x)
...        int('hello')
...    except NameError:
...        print ('There was a name error exception')
...    except ValueError:
...        print ('There was a value error exception')
...    except (NameError, ValueError):
...        print('There was an error')
...    else:
...        print('Everything worked fine.')
...    finally:
...        print('The block was executed.')

>>>exception_example()

Manipulare date în diferite formate - json, csv

json

JSON este un format de reprezentare și interschimb de date între aplicații informatice. Modulul json pune la dispoziție funcții pentru serializarea unui obiect în format JSON și pentru deserializarea unui string care conține un document JSON în obiect Python, conform tabelului de conversie.

  • dumps(obj) - returnează string-ul cu obiectul obj formatat JSON;
  • loads(s) - returnează obiectul Python obținut în urma deserializării string-ului s;

Exemple:

>>> import json                                                             
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])                     
'["foo", {"bar": ["baz", null, 1.0, 2]}]'  
                             
>>> json.dumps({'c': 1, 'a': 2, 'b': 3}, sort_keys=True)                    
'{"a": 2, "b": 3, "c": 1}'  
                                                              
>>> print(json.dumps({'foo': [3, 4, 1], 'bar': {'a': 0, 'b': ['baz', 2]}}, indent=4))
{                                                                           
    "foo": [                                                                
        3,                                                                  
        4,                                                                  
        1                                                                   
    ],                                                                      
    "bar": {                                                                
        "a": 0,                                                             
        "b": [                                                              
            "baz",                                                          
            2                                                               
        ]                                                                   
    }                                                                       
}  

>>> json.loads('["foo", {"bar": ["baz", null, 1.0, 2]}]')                   
[u'foo', {u'bar': [u'baz', None, 1.0, 2]}]