Introducere în Python
Varianta rulabilă a fișierului curent poate fi accesată aici: Introducere în Python
Documentația completă Python poate fi accesată aici: Documentație Python
Informații generale
Toate variabilele din Python sunt considerate obiecte:
my_int = 1
my_float = 2.0
my_string = "my string"
my_bool = True
Putem afisa folosind functia print():
print(my_int)
print('nlp')
print(13)
print('----------------')
Output:
1
nlp
13
----------------
Fiecare obiect are un tip, chiar dacă nu trebuie declarat:
print(f"{my_int} - {type(my_int)}")
print(f"{my_float} - {type(my_float)}")
print(f"{my_string} - {type(my_string)}")
print(f"{my_bool} - {type(my_bool)}")
Output:
1 - <class 'int'>
2.0 - <class 'float'>
my string - <class 'str'>
True - <class 'bool'>
Litera 'f' inaintea unui string formateaza acel string si permite scrierea valorilor unor variabile.
De exemplu: a scrie f"{a} + {b} = {a + b}" este echivalent cu a scrie str(a) + " " + str(b) + " = " + str(a + b)
Unde str() converteste variabilele din tipul lor in string
Sintaxă
Operatorii sunt similari cu cei din majoritatea limbajelor de programare:
a = 5
b = 2
print(f"{a} + {b} = {a + b}")
print(f"{a} - {b} = {a - b}")
print(f"{a} * {b} = {a * b}")
print(f"{a} / {b} = {a / b}")
print(f"{a} % {b} = {a % b}")
Output:
5 + 2 = 7
5 - 2 = 3
5 * 2 = 10
5 / 2 = 2.5
5 % 2 = 1
Avem, în plus, ridicarea la putere și împărțirea întreagă:
print(f"{a} ** {b} = {a ** b}")
print(f"{a} // {b} = {a // b}")
Output:
5 ** 2 = 25
5 // 2 = 2
Indentarea este foarte importantă. În locul acoladelor din C++, aici avem sintagme de forma:
if a % 3 == 0:
print("Restul 0")
Desigur, putem avea mai multe cazuri:
if a % 3 == 0:
print("Restul 0")
elif a % 3 == 1:
print("Restul 1")
else:
print("Restul 2")
Output: Restul 2
Operații repetitive
Cu număr cunoscut de pași (for)
for i in range(4, 10):
print(i)
Output:
4
5
6
7
8
9
Observați utilizarea funcției range pentru a stabili intervalul. Aceasta are ca parametri start, end și step (default 1) și iterează de la start până la end - 1.
Pentru a itera descrescător, putem porni de la un start mai mare decât end cu pas negativ (aici -1):
for i in range(10, 4, -1):
print(i)
Output:
10
9
8
7
6
5
Cu număr necunoscut de pași (while)
i = 4
while i < 10:
print(i)
i += 1
Output:
4
5
6
7
8
9
Python nu are definită instrucțiunea do ... while, dar aceasta poate fi simulată:
i = 4
while True:
print(i)
i += 1
if i >= 10:
break
Output:
4
5
6
7
8
9
Operații cu șiruri de caractere
Șirurile de caractere se scriu între ghilimele sau apostrof (de exemplu "sir").
Putem scrie un șir pe mai multe rânduri daca folosim 3 apostrofuri sau 3 ghilimele la inceput și la finalul lui.
Atenție: dacă am deschis șirul cu apostrof sau ghlimele trebuie să îl închidem cu același tip de semn!
Putem accesa caracterele unui șir direct prin indici. Șirurile sunt immutable, așadar putem accesa un caracter prin indice, dar nu îl putem modifica. Prin urmare, metodele șirurilor nu modifică șirul pe care se aplică ci returnează un șir nou.
Dacă adunăm două șiruri obținem concatenarea lor:
"py" + "thon"
Output: 'python'
Dacă înmulțim un șir cu un număr n, obținem un șir nou în care se repetă șirul inițial de n ori:
"abc" * 5
Output: 'abcabcabcabcabc'
Putem concatena elementele unei liste de șiruri de caractere folosind funcția join:
"->".join(['a', 'b', 'c'])
Output: 'a->b->c'
Metode utile
Eliminarea spațiilor din capete:
sir = " abc "
sir.lstrip()
Output: 'abc '
sir.rstrip()
Output: ' abc'
sir.strip()
Output: 'abc'
Creează o listă cu subșirurile șirului inițial
"ab#cd#efgh#".split("#")
Output: ['ab', 'cd', 'efgh', '']
Operații cu liste
Listele sunt seturi ordonate de elemente și se enumeră între paranteze pătrate.
Dacă adunăm două liste obținem concatenarea lor:
[1,2] + [3,4]
Output: [1, 2, 3, 4]
Dacă înmulțim o listă cu un număr n, obținem o listă nouă în care se repetă șirul inițial de n ori:
[1,2] * 4
Output: [1, 2, 1, 2, 1, 2, 1, 2]
Crearea unei liste
Se enumeră elementele între paranteze drepte, sau se folosește constructorul list care primește un obiect prin care se poate itera (precum un sir de caractere)
l1 = [1,2,3]
l2 = list("abc")
print(l1)
print(l2)
Output:
[1, 2, 3]
['a', 'b', 'c']
Lungimea unei liste
Lungimea unei liste se calculează cu len(lista). De exemplu:
len([10, 5, 1])
Output: 3
Indicii unei liste
Indicii unei liste încep de la 0. Pentru lista l = [10,5,1], l[0] este 10, iar l[2] este 1.
Putem folosi și indici negativi. Indicii negativi îi putem considera pornind de la drepta spre stânga începând cu -1, și tot avansând cu 1 spre stânga.
De exemplu, pentru lista de mai sus, l[-1] este 1, l[-2] este 5 și l[-3] este 10.
Subliste
Pentru a obține o sublistă dintr-o listă putem folosi notația l[inidiceStart:indiceFinal] care va oferi sublista cu elementele din lista inițială cuprinse între pozițiile indiceStart inclusiv și indiceFinal exclusiv.
Dacă dorim un mod de parcurgere a listei diferit de cel implicit, pentru a crea sublista, putem adăuga și parametrul de iterare: l[inidiceStart:indiceFinal:iterator].
Astfel se va porni de la indicele de Start, punând elementul corespunzător în sublistă, si la indiceStart se va aduna apoi iteratorul, generând urmatorul element din sublistă. Procedeul se va repeta până se ajunge la un indice mai mare sau egal cu indicele final (pentru această ultimă valoare care depășește limita dată de indicele final nu se mai generează element în sublistă).
Oricare dintre cele trei argumente ale scrierilor l[inidiceStart:indiceFinal] și l[inidiceStart:indiceFinal:iterator] poate lipsi, caz în care se iau valorile implicite - indiceStart ia valoarea 0, indiceFinal ia lungimea listei și iteratorul devine 1.
Exemple de utilizări ale acestor scrieri:
l = list(range(10))
l
Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l[-1]
Output: 9
l[2:8]
Output: [2, 3, 4, 5, 6, 7]
l[2:8:2]
Output: [2, 4, 6]
l[-1:-7]
Output: []
l[-1:-7:-1]
Output: [9, 8, 7, 6, 5, 4]
l[8:2:-1]
Output: [8, 7, 6, 5, 4, 3]
l[:5]
Output: [0, 1, 2, 3, 4]
l[4:]
Output: [4, 5, 6, 7, 8, 9]
l[:]
Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l[::-1]
Output: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Observați că putem folosi și indici negativi, dar dacă vrem o parcurgere de la dreapta la stânga, trebuie să folosim un iterator negativ.
Iterarea printr-o listă
def proceseaza(x):
return x + 1
Putem itera în mai multe moduri. Operatorul in permite iterarea prin fiecare element al listei fără a cunoaște indicele elementului:
for elem in l:
proceseaza(elem)
Dacă avem nevoie și de indicele elementului putem parcurge lista folosindu-ne de accesul direct al elementului prin indice - insă nu e recomandată această parcurgere când modificăm lista în acest mod (adăugăm sau ștergem elemente), deoarece trebuie să fim atenți la actualizarea indicelui.
for i in range(len(l)):
proceseaza(l[i])
Putem itera printr-o listă folosind enumerate(lista) care este un generator prin care obținem pe rând tupluri de forma (indice, lista[indice]) - indicele din listă și elementul corespunzător acestuia.
for i, elem in enumerate(l):
proceseaza(elem)
Matrici
În Python nu avem în mod direct o structură pentru matrici (printre modulele implicite), în schimb putem simula o matrice printr-o listă de liste.
De exemplu:
l = [[1, 2, 3], [4, 5, 6]]
este o matrice de 2 linii și 3 coloane.
Adăugarea elementelor într-o listă
La finalul listei - metoda append(element):
l = [10, 7, 3, 5]
l.append(2)
print(l)
Output: [10, 7, 3, 5, 2]
La o poziție dată - metoda insert(indice, element):
l = [10, 7, 3, 5]
l.insert(1, 122)
print(l)
Output: [10, 122, 7, 3, 5]
Ștergerea elementelor dintr-o listă
Se folosește metoda pop(indice) care șterge din listă elementul de pe poziția dată. Utilizarea fără argumente este echivalentă apelării cu indicele -1, așadar va șterge ultimul element.
l = [10, 7, 3, 5]
l.pop(2)
print(l)
Output: [10, 7, 5]
Fără parametri:
l = [10, 7, 3, 5]
l.pop()
print(l)
Output: [10, 7, 3]
Putem șterge un anumit element cu metoda remove(element):
l = [10, 7, 3, 5]
l.remove(7)
print(l)
Output: [10, 3, 5]
Sortarea unei liste
Sortarea unei liste se poate face simplu prin metoda sort():
l = [2, 1, 10, 4, 100, 17, 23]
l.sort()
print(l)
Output: [1, 2, 4, 10, 17, 23, 100]
Sortarea implicită este cea crescătoare. Pentru a sorta descrescător, putem folosi parametrul reverse al funcției sort cu valoarea True:
l = [2, 1, 10, 4, 100, 17, 23]
l.sort(reverse = True)
print(l)
Output: [100, 23, 17, 10, 4, 2, 1]
Putem sorta oricum dorim folosind aceeași funcție. De exemplu, pentru sortarea după ultima cifră a numerelor vom folosi funcția lambda:
l = [2, 1, 10, 4, 100, 17, 23]
l.sort(key = lambda x: x % 10)
print(l)
Output: [10, 100, 1, 2, 23, 4, 17]
Modificarea listei în timpul parcurgerii
Dacă dorim să modificăm o listă (de exemplu să ștergem toate elementele pare) este comună următoarea greșeală:
l = [3, 2, 4, 5, 10, 12]
for i in range(len(l)):
if l[i] % 2 == 0:
l.pop(i)
i -= 1
Output:
1 2
3 10
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-4-2ff85a8985d8> in <module>
1 l = [3, 2, 4, 5, 10, 12]
2 for i in range(len(l)):
----> 3 if l[i] % 2 == 0:
4 print (i, l[i])
5 l.pop(i)
IndexError: list index out of range
Vom primi o eroare penru metoda pop() care va da un "IndexError: list index out of range" pentru că i-ul nu este decrementat la ștergerea elementului, ci continuă parcurgerea range-ului.
O altă greșeală:
l = [3, 2, 4, 5, 10, 12]
for elem in l:
if elem % 2==0:
l.remove(elem)
print(l)
Output: [3, 4, 5, 12]
Chiar dacă acum programul se oprește, nici acest caz nu este corect deoarece i-ul nu este actualizat și sare peste elemente.
Modul corect. Sunt mai multe moduri prin care putem realiza cerința de mai sus, având grijă să nu sărim elementele. De exemplu, putem folosi while - în felul acesta limita pentru i nu mai e rigidă, precalculată, cum era în cazul lui range:
l = [3, 2, 4, 5, 10, 12]
i = 0
while i < len(l):
if l[i] % 2 == 0:
l.pop(i)
i -= 1
i += 1
print(l)
Output: [3, 5]
Modul corect și elegant. Folosind comprehensions:
l = [3, 2, 4, 5, 10, 12]
rez = [x for x in l if x % 2 == 1]
print(rez)
Output: [3, 5]
Operații cu dicționare
Dicționarele reprezintă un set de perechi de chei și valori asociate. Cheile sunt unice în dicționar și trebuie să fie de tip immutable (pot fi stringuri, numere, tupluri etc., dar nu pot fi liste).
Pentru a crea un dicționar vid, folosim:
d = {}
Pentru a adăuga chei noi în dicționar, putem pur și simplu să le atribuinduim o valoare. Sintaxa este: dictionar[cheie] = valoare.
d["a"] = 100
d["b"] = 200
d["c"] = 300
print(d)
Output: {'a': 100, 'b': 200, 'c': 300}
Pentru a itera printr-un dicționar putem folosi operatorul in:
for k in d:
print(k, d[k])
Output:
a 100
b 200
c 300
Pentru a verifica dacă o cheie se găsește într-un dicționar, putem folosi același operator:
print("a" in d)
print(100 in d)
Output:
True
False
sau să folosim metoda items() care returnează o listă cu tupluri de forma (cheie, valoare):
for k, v in d.items():
print(k, v)
Output:
a 100
b 200
c 300
Pentru a obține lista de chei putem folosi metoda keys() iar pentru lista de valori metoda values().
Mulțimi
Mulțimile reprezintă seturi de elemente neordonate care nu acceptă duplicate.
Crearea unei mulțimi
multime_vida = set()
multime = {2, 3, 10, 8}
print(multime)
Output: {8, 3, 10, 2}
Observați că nu s-a păstrat ordinea din inițializarea mulțimii, fiindcă mulțimile sunt neordonate. Cu toate acestea, ordinea va fi actualizată la primul update sau querry pe mulțime:
multime.add(5)
multime
Output: {2, 3, 5, 8, 10}
Cardinalul unei mulțimi
Pentru a obține cardinalul unei mulțimi se folosește funcția len(). De exemplu:
len({2, 4, 10})
Output: 3
List comprehensions
Au sintaxa de forma:
[expresie for element in obiect_iterabil]
caz in care se va genera o listă cu același număr de elemente precum obiectul iterabil
sau
[expresie for element in obiect_iterabil if conditie]
caz în care va avea în listă doar elementele din obiectul iterabil care îndeplinesc condiția.
Exemple:
l = [2, 7, 5, 23, 10]
Lista cu dublul elementelor lui l:
l1 = [2*x for x in l]
l1
Output: [4, 14, 10, 46, 20]
Lista cu perechile de vecini din l:
l2 = [[l[i], l[i + 1]] for i in range(len(l) - 1)]
l2
Output: [[2, 7], [7, 5], [5, 23], [23, 10]]
Lista produsului cartezian:
l3 = [[x, y] for x in l for y in l]
l3
Output:
[[2, 2],
[2, 7],
[2, 5],
[2, 23],
[2, 10],
[7, 2],
[7, 7],
[7, 5],
[7, 23],
[7, 10],
[5, 2],
[5, 7],
[5, 5],
[5, 23],
[5, 10],
[23, 2],
[23, 7],
[23, 5],
[23, 23],
[23, 10],
[10, 2],
[10, 7],
[10, 5],
[10, 23],
[10, 10]]
Lista elementelor pare:
l4 = [x for x in l if x % 2 == 0]
l4
Output: [2, 10]
Crearea unei matrici folosind comprehensions
Putem folosi un comprehension cu for dublu.
De exemplu dacă dorim o matrice formată doar din 0-uri:
l = [[0] * 5 for _ in range(10)]
print(l)
Output: [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
Atenție, frecvent se face greșeala următoare:
l = [[0] * 5] * 10
print(l)
l[0][0] = 111
print(l)
Output:
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
[[111, 0, 0, 0, 0], [111, 0, 0, 0, 0], [111, 0, 0, 0, 0], [111, 0, 0, 0, 0], [111, 0, 0, 0, 0], [111, 0, 0, 0, 0], [111, 0, 0, 0, 0], [111, 0, 0, 0, 0], [111, 0, 0, 0, 0], [111, 0, 0, 0, 0]]
Observați cum s-a schimbat primul element în fiecare listă?
Atunci cand apelăm lista * n, unde n e un număr natural nenul, se copiază elemente din listă de n ori. Problema apare când avem o listă de obiecte, deoarece se copiază referențele către acele obiecte. Practic am avut [ lista_de_0] * 10, care a dus la o listă cu 10 referințe către aceeași lista de 0-uri, deci când am schimbat primul element din prima listă am văzut modificarea în toate cele 10 liste fiindcă de fapt sunt toate același obiect.
import copy
a = copy.deepcopy(l)
a
Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Operații cu fișiere
Pentru a deschide un fișier folosim metoda open(cale_fisier, mod_deschidere).
De exemplu, pentru a citi fisierul input.txt, putem folosi:
f = open("input.txt", "r")
sir = f.read()
caz în care vom avea în șir tot conținutul fișierului.
O altă variantă este să folosim metoda readlines() care returnează o listă de stringuri cu liniile fișierului.
Pentru a scrie într-un fișier putem deschide fișierul cu "w" pentru a fi suprascris, sau cu "a" pentru a adăuga la final de fișier.
Pentru a scrie într-un fișier putem folosi metoda write().
Clase
Pentru a defini o clasă folosim cuvântul cheie class, urmat de numele clasei.
Clasele au o metodă specială prin care se construiesc instanțele clasei, numită __init__. În această funcție vom trimite argumentele necesare pentru a completa proprietățile noii instanțe a clasei.
Orice metodă proprie instanțelor are ca prim parametru chiar o referință către instanță respectivă (metoda __init__ nu face excepție). De obicei acest prim parametru este numit self, dar nu este obligatoriu. Parametrul self nu va avea un argument corespunzător în apelul metodei, practic metodele se apelează cu argumente corespunzătoare tuturor parametrilor (mai puțin self).
Proprietățile de instanță nu se definesc direct în clasă, ci sunt create în constructor atunci când le folosim numele prima oară. De exemplu, putem face o inițializare: self.proprietate = valoare.
class Cls:
def __init__(self, aa, bb):
self.a = aa
self.b = bb
def incrementeaza_a(self):
self.a += 1
c1 = Cls(2,5)
c1.incrementeaza_a()
print(c1.a)
Output: 3
Observați cum în exemplul de mai jos, la crearea instanței c1, s-au dat valori doar pentru parametrii aa și bb din __init__, primul parametru fiind self, pentru care nu se oferă argument. Metoda incrementeaza_a() nu se apelează cu argumente deoarece are ca unic parametru self (adică instanța).
Pentru a asigura o afișare frumoasă a elementelor dintr-o clasă putem defini metodele __str__ și __repr__, ambele având ca rol returnarea unui string reprezentativ pentru instanța curentă.
Când apelăm print(obiect) se scrie ce returnează __str__. Când apelăm print(lista_de_obiecte) se afișează lista aplicând __repr__ pentru fiecare obiect.
Dacă apelăm str(obiect) obținem stringul returnat de __str__, iar cu repr(obiect) stringul returnat de __repr__.
class Cls:
n = 100
def __init__(self, aa, bb):
self.a = aa
self.b = bb
def __str__(self):
return "a = {} b = {}".format(self.a, self.b)
def __repr__(self):
return "({}, {})".format(self.a, self.b)
c1 = Cls(2, 5)
print(c1)
Output: a=2 b=5
print(str(c1))
Output: a=2 b=5
print(repr(c1))
Output: (2, 5)
c2 = Cls(3, 3)
c3 = Cls(4, 1)
print([c1, c2, c3])
Output: [(2, 5), (3, 3), (4, 1)]
Operatori
Se pot defini operatori pentru elementele unei clase. De exemplu, putem defini operatori de egalitate sau care să determine dacă un element se află într-o relație de ordine față de altul.
Pentru clasa de mai sus am putea considera că elementele se ordonează întâi după proprietatea a, apoi după b. Avem operatorii:
- __eq__ operația de egalitate
- __lt__ operatorul "<"
- __le__ operatorul "<="
- __gt__ operatorul ">"
- __ge__ operatorul pentru ">="
class Cls:
def __init__(self, aa, bb):
self.a = aa
self.b = bb
def __eq__(self, elem):
return (self.a, self.b) == (elem.a, elem.b)
def __lt__(self, elem):
return (self.a, self.b) < (elem.a, elem.b)
def __le__(self, elem):
return (self.a, self.b) <= (elem.a, elem.b)
def __gt__(self, elem):
return (self.a, self.b) > (elem.a, elem.b)
def __ge__(self, elem):
return (self.a, self.b) >= (elem.a, elem.b)
c1 = Cls(2,5)
c2 = Cls(2,5)
c3 = Cls(2,4)
Output:
print(c1 < c3)
Output: False
print(c1 >= c3)
Output: True
Proprietăți și metode de clasă
Proprietățile clasei se definesc direct în clasă, de obicei la început.
Funcțiile obișnuite se numesc metode de instanțiere.
Metodele de clasă vor fi precedate de decoratorul @classmethod.
Instanțele pot accesa proprietăți și metode de clasă.
În momentul în care o instanță încearcă să modifice o proprietate de clasă prin scrierea instanta.proprietate_clasa = valoare, nu se va modifica proprietatea clasei ci se va crea o proprietate a instanței cu acelasi nume. Din acel moment instanța nu mai poate accesa (în mod direct) decât propia proprietate cu acel nume:
class Cls:
n = 100
def __init__(self, aa, bb):
self.a = aa
self.b = bb
@classmethod
def incrementeaza_n(cls):
cls.n += 1
print(Cls.n)
Output: 100
c1 = Cls(2, 5)
print(c1.n)
Output: 100
c1.n = 17
print(c1.n)
print(Cls.n)
Output:
17
100
c2 = Cls(2,5)
print(c2.n)
print(Cls.n)
Output:
100
100
Cls.incrementeaza_n()
print(c1.n)
print(Cls.n)
Output:
17
101
c1.incrementeaza_n()
print(c1.n)
print(c2.n)
print(Cls.n)
Output:
17
102
102
Modulul math
Modulul math este folosit pentru funcții matematice utilizate frecvent:
- floor(numar) - returneaza partea întreagă inferioară
- ceil(numar) - returnează partea întreagă superioară
- sqrt(numar) - returnează rădăcina pătrată a numărului
- funcții trigonometrice: sin(numar), cos(numar) etc.
import math
math.sqrt(8)
Output: 2.8284271247461903
Modulul time
Uneori avem nevoie să calculăm cât a durat o anumită zonă de cod. Pentru asta putem folosi funcția time() din modulul time. Exemplu:
import time
t1 = time.time()
# zona de cod pentru care dorim sa calculam timpul
t2 = time.time()
print(t2 - t1) # in secunde
Output: 2.5987625122070312e-05