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.
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}
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.
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)]
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)]
Într-un mod asemănător putem defini și dicționare.
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}
>>> 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
>>> 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)]
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])
Î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ă.
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()
Î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
Î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}
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;Foarte utile în debugging sunt și următoarele funcții built-in:
dir
type
vars
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']
Aceasta indică tipul unui obiect.
>>> class X(object):
... pass
...
>>> x = X()
>>> type(x)
<class '__main__.X'>
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'}
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()
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]}]