Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Python bàsic

Programes

Els programes consten de mòduls. Els mòduls, al seu torn, poden contenir instruccions, definicions de funcions i/o definicions de classe. Cada mòdul està associat a un fitxer font (extensió .py) i possiblement a un fitxer de bytecode (extensió .pyc).

Un compilador/intèrpret anomenat màquina virtual de Python (PVM) tradueix els fitxers font de Python a bytecode abans de l’execució. La PVM pot desar el bytecode corresponent en fitxers per a execucions posteriors.

Execució

Un programa en Python es pot executar com a script des d’una línia d’ordres de la manera següent:

python helloworld.py

Errors de sintaxi es detecten abans d’executar el programa.

Errors de tipus (TypeError) es detecten en temps d’execució. És millor aclarir-ho així.

Hola, món

La versió més senzilla d’aquest programa consisteix en una sola instrucció:

print("Hola món!")

L’ordre print converteix automàticament les dades en text, les mostra i mou el cursor a la línia següent:

print("Hola món!") print(34) print("Hola món!", end = "") # evita el salt de línia

Una altra versió integra aquesta instrucció en una funció principal que es crida al final del mòdul:

def main(): print("Hola món!") main()

També tenim entrada des del teclat:

nom = input("Introduïu el vostre nom: ") # cadena edat = int(input("Introduïu la vostra edat: ")) # sencer

Estructura i sintaxi

  • Els literals inclouen números, cadenes, tuples, llistes i diccionaris.

  • Els identificadors inclouen els noms de variables, classes, funcions i mètodes.

  • Les paraules reservades inclouen les de les principals sentències de control (if, while, for, import, etc.), operadors (in, is, etc.), definicions (def, class, etc.) i valors especials (True, False, None, etc.).

  • Els elements lèxics d’una sola línia estan separats per zero o més espais.

  • La sagnia és significativa i s’utilitza per marcar estructures sintàctiques, com ara blocs d’instruccions i codi dins de definicions de funcions, classes i mètodes.

  • Una frase es pot trencar i continuar a la línia següent després d’una coma o mitjançant el símbol ’'.

  • Les capçaleres de les instruccions de control i les definicions de funcions, classes i mètodes acaben amb dos punts (:).

  • Un comentari de final de línia comença amb el símbol #.

    # Això és un comentari de final de # línia # (en tres línies).

    Els docstring, que comencen amb “”“ i acaben amb “”“, són és la forma de documentar funcions, classes o mòduls.

    """ Això és una docstring amb diverses línies. """

Tipus de dades

Tots els valors de dades, incloses les funcions, són objectes. I totes les variables són referències a aquests objectes.

  • int representa nombres sencers.
  • float representa nombres de coma flotant amb precisió doble.

Cadenes

  • Una cadena és una seqüència de 0 o més caràcters.

  • Les cadenes són instàncies de la classe str. Les cadenes són objectes immutables.

  • La funció len retorna el nombre de caràcters d’una cadena.

  • L’operador de subíndex ([]) accedeix a un caràcter en una posició determinada.

    aString = "Hola món!" print(len(aString), aString[2])
  • Les cadenes es poden comparar utilitzant els operadors de comparació estàndard ==, <, etc.

  • Els literals de cadena es formen utilitzant cometes simples o cometes dobles com a delimitadors.

  • Les cadenes són instàncies de la classe str. Les cadenes són objectes immutables.

  • Els literals de caràcters són simplement cadenes que contenen un sol caràcter.

  • Una seqüència d’escapada es forma utilitzant el caràcter ’' seguit d’una lletra adequada com ara ‘n’ o ‘t’.

L’operador de concatenació + uneix dues cadenes per formar una tercera cadena nova. Els operands d’altres tipus s’han de convertir en cadenes abans de poder ser concatenats.

Exemples:

"35" + " pàgines de llargada." str(35) + " pàgines de llargada." # Si x i y són objectes, el codi str(x) + str(y) # concatena les seves representacions de cadena

Les f-strings són una manera moderna i elegant de formatar cadenes de text a Python. Permeten incrustar expressions directament dins de literals de cadena de manera més llegible i eficient que els mètodes anteriors:

nom = "Maria" edat = 25 missatge = f"Hola, em dic {nom} i tinc {edat} anys."

A dins de les claus pot haver-hi expressions.

Si són números, es poden formatar després dels dos punts.

Aritmètica i condicions

Els operadors aritmètics inclouen +, -, *, /, %, // i ** (exponenciació).

L’operació / retorna sempre un float, i l’operació // un sencer, sempre que els operands siguin sencers.

max, min, abs i round són funcions estàndard amb els efectes estàndard (round retorna un nombre sencer).

El mòdul matemàtic inclou funcions estàndard per a trigonometria, logaritmes, arrels quadrades, etc.

Exemples:

round(3.14) math.sqrt(2)

Els operadors de comparació són ==, !=, <, >, <= i >=.

Tots els operadors de comparació retornen True o False.

Només els objectes que són comparables, com ara nombres i seqüències d’objectes comparables (cadenes i llistes de nombres o cadenes), poden ser operands per a comparacions.

Exemple:

print("AAB" > "AAA")

El tipus booleà bool inclou els valors constants True i False (tingueu en compte les majúscules).

Altres valors, com ara 0, ‘’, [] i None, també signifiquen False. Pràcticament qualsevol altre valor també significa True.

Els operadors lògics són not, and i or.

Les expressions booleanes compostes consten d’un o més operands booleans i un operador lògic. L’avaluació en curtcircuit s’atura quan hi ha prou informació disponible per retornar un valor. not s’avalua abans de and, que s’avalua abans d’or.

Conversions

Els tipus numèrics es poden convertir a altres tipus numèrics mitjançant les funcions de conversió de tipus adequades. El nom de la funció és el nom del tipus de destinació.

Exemples:

int(3.14) float(3)

Les funcions ord i chr s’utilitzen per convertir entre sencers i caràcters, de la manera següent:

ord('A') chr(65)

La funció str converteix qualsevol objecte Python a la seva representació de cadena corresponent.

str(45) str(3.14)

Variables

Forma:

<variable> = <expressió>

Exemples:

x = 1 x = x + 3.14
  • Una variable s’introdueix i s’estableix a un valor inicial mitjançant una instrucció d’assignació.
  • Qualsevol variable pot anomenar qualsevol objecte i es pot reiniciar a qualsevol objecte.
  • Una variable selecciona el tipus de l’objecte al qual està actualment vinculada.
  • La comprovació de tipus i la comprovació de referències a variables no inicialitzades es realitzen en temps d’execució.

Les assignacions tenen la forma:

<variable> = <expressió>

Exemple:

x = 1 x = x + 3.14

Les variables en si mateixes no tenen tipus. Qualsevol variable pot anomenar qualsevol cosa i reiniciar-se a qualsevol cosa. L’objecte al qual fa referència una variable té un tipus.

Mètodes

Una crida a un mètode d’instància consisteix en una referència d’objecte (també anomenada objecte receptor), seguida d’un punt, seguit del nom del mètode i una llista d’arguments entre parèntesis.

Exemple:

aList.sort()

Una crida a un mètode de classe consisteix en un nom de classe, seguit d’un punt, seguit del nom del mètode i una llista d’arguments entre parèntesis.

Un mètode retorna el tipus de valor indicat per la seva instrucció return, si n’hi ha. Si un mètode no retorna explícitament un valor, per defecte es retorna el valor None. Les compatibilitats de tipus es resolen en temps d’execució.

Organització del codi

Els mòduls de Python s’utitzen per defecte en la forma de singletons, com els mòduls de JavaScript. Comparativament a llenguatges com Java, les classes només s’utilitzen quan cal estat individual.

Un exemple de singleton:

value = 0 def increment(): global value value += 1 return value

Mòduls

Un mòdul és un fitxer .py amb definicions (funcions, classes, constants…). S’importa mitjançant la forma:

import <nom del mòdul>

Els recursos d’aquest mòdul es referencian mitjançant la forma

<nom del mòdul>.<nom del recurs>

Exemple:

import math print(math.sqrt(2), math.pi)

Alternativament, es pot importar un recurs individual mitjançant la forma

from <nom del mòdul> import <nom del recurs>

Aleshores es referencia el recurs sense el nom del mòdul com a qualificador.

Exemples:

from math import sqrt, pi print(sqrt(2), pi) from random import * # Referencia tots els recursos des de random: NO RECOMMANABLE

Es pot assignar un àlies al mòdul o recurs per escurçar-lo o evitar conflictes:

from datetime import datetime as dt

Paquets

Un paquet és un directori que conté un arxiu especial __init__.py i altres mòduls. Això permet agrupar diversos mòduls sota un mateix “nom d’espai”.

mypackage/ __init__.py utils.py models.py

Es pot importar amb:

import mypackage.utils from mypackage import models

Funcions

A diferència dels mètodes, que són funcions associades a un objecte o una classe, Python disposa de funcions independents o funcions globals que no pertanyen a cap objecte i que es poden cridar directament.

Aquestes funcions es poden definir en un mòdul o directament en un script o a l’intèrpret.

Exemple:

def suma(a, b): return a + b resultat = suma(3, 4) print(resultat)

Python inclou moltes funcions built-in (integrades) que es poden utilitzar sense importar cap mòdul. Aquestes funcions permeten fer operacions comunes de manera senzilla i eficient.

Algunes funcions built-in importants:

FuncióDescripcióExemple
printEscriu dades a la sortida estàndardprint("Hola")
lenRetorna la longitud d’una col·lecció o cadenalen("abc") retorna 3
typeRetorna el tipus d’un objectetype(3) retorna <class 'int'>
intConverteix un valor a enterint("10") retorna 10
strConverteix un valor a cadenastr(5) retorna "5"
rangeGenera una seqüència d’enterslist(range(3)) retorna [0,1,2]
inputLlegeix entrada des del teclatinput("Escriu algo: ")

Àmbit de les variables

  • global: fa que una variable dins d’una funció faci referència a la definida al nivell global del mòdul. Sense global, una assignació dins d’una funció crea una variable local.

  • nonlocal: fa que una variable dins d’una funció interna (nested function) faci referència a la definida a la funció externa, no a l’àmbit global.

Python resol els noms seguint l’ordre “LEGB”:

  • Local (dins de la funció actual)
  • Enclosing (funció externa si hi ha funcions niades)
  • Global (del mòdul actual)
  • Built-in (funcions i noms predefinits de Python, com len, print)

Arguments per defecte i nominals

En Python, els paràmetres d’una funció poden tenir valors per defecte, que s’utilitzen quan la crida no proporciona un valor per a aquest paràmetre.

def saluda(nom="Món"): print(f"Hola, {nom}!") saluda() # Hola, Món! saluda("Julián") # Hola, Julián!

Els arguments també es poden passar per nom, independentment de l’ordre:

def mostra_info(nom, edat): print(f"Nom: {nom}, Edat: {edat}") mostra_info(edat=25, nom="Anna")

Funcions com a objectes

Les funcions a Python són objectes de primera classe, això significa que es poden:

  • Assignar a variables
  • Passar com a arguments a altres funcions
  • Retornar des de funcions
def quadrat(x): return x * x f = quadrat print(f(5)) # 25 def aplica_funcio(f, valor): return f(valor) print(aplica_funcio(quadrat, 7)) # 49

Les funcions lambda són funcions anònimes i petites, definides en una sola línia, útils quan es necessita una funció senzilla sense crear-ne una amb def.

suma = lambda a, b: a + b print(suma(3, 4)) # 7 # Usos habituals en funcions com map, filter, sorted llista = [1, 4, 2, 5] ordenada = sorted(llista, key=lambda x: -x) # ordre descendent print(ordenada) # [5, 4, 2, 1]

Es poden definir funcions dins d’altres funcions. Aquestes funcions internes poden accedir a variables del seu entorn exterior. Quan una funció interna recorda l’estat de les variables externes encara que la funció exterior hagi acabat, es diu que és un closure.

Exemple:

def multiplicador(n): def multiplica(x): return x * n return multiplica per_3 = multiplicador(3) print(per_3(10)) # 30

Control de flux

while

Té la forma:

while <expressió booleana>: <instrucció> <instrucció>

Les instruccions del cos del bucle estan marcades amb sagnat.

L’instrucció break surt d’un bucle:

while True: break

L’instrucció return surt d’una funció o mètode. Si no s’especifica cap expressió, es retorna el valor None:

return 0

L’instrucció pass no fa res:

while True: pass

for

Només hi ha un tipus de bucle for, que visita cada element d’un objecte iterable, com ara una cadena o una llista.

Forma:

for <variable> in <iterable>: <instrucció> … <instrucció>

Exemple:

for s in aListOfStrings: print(s)

La variable recull el valor de cada element de l’objecte iterable i és visible al cos del bucle.

Les instruccions del cos del bucle estan marcades amb sagnat.

Els bucles simples controlats per recompte que iteren a través d’un rang d’enters tenen tres formes:

for <variable> in range(<límit superior>): <instrucció> … <instrucció> for <variable> in range(<límit inferior>, <límit superior>): <instrucció> … <instrucció> for <variable> in range(<límit inferior>, <límit superior>, <valor del pas>): <instrucció> … <instrucció>

if

Forma:

if <expressió booleana>: <instrucció> … <instrucció> elif <expressió booleana>: <instrucció> … <instrucció> else: <instrucció> … <instrucció>

Les sentències del conseqüent i de cada alternativa estan marcades amb sagnat.

Classes i objectes

La forma d’instanciar objectes és:

<nom de la classe>(<arguments>)

Exemple:

compte = CompteCorrent("Ken", "1111", 500.00)

Les variables i els paràmetres en si mateixos no tenen tipus. Qualsevol variable o paràmetre pot anomenar qualsevol cosa i reiniciar-se a qualsevol cosa. L’objecte al qual fa referència una variable o paràmetre té un tipus.

Col.leccions

TipusMutabilitatOrdenatPermet duplicats
list
tupleNo
setNoNo
frozensetNoNoNo
dictSí (>=3.7)No (claus)

Llistes

  • Una llista és una seqüència mutable de 0 o més objectes de qualsevol tipus.

  • Les llistes tenen una representació literal, de la forma:

    [e0, e1, …, en-1]

  • La funció len retorna el nombre d’elements d’una llista.

  • L’operador de subíndex ([]) accedeix a un element en una posició determinada.

Exemple:

aList = [45, 56, 67] print(len(aList), aList[2])
  • Les llistes d’objectes comparables es poden comparar utilitzant els operadors de comparació estàndard ==, <, etc.
  • La classe list inclou molts mètodes útils per a insercions, eliminacions, cerques, etc.

Sets

  • Un set és una col·lecció mutable de 0 o més objectes únics de qualsevol tipus.
  • La funció len retorna el nombre d’elements d’un conjunt.
  • La classe set inclou molts mètodes útils per a insercions, eliminacions, cerques, etc.
set1 = set() for x in range(10): set1.add(x) for x in set1: print(x) set2 = set([1,2,3]) set3 = set1.intersection(set2)

Diccionaris o mapes

  • Un diccionari és una col·lecció mutable de 0 o més parells clau/valor únics. Dit d’una altra manera, un diccionari conté un conjunt de claus, on cada clau està associada a un valor.
  • La funció len retorna el nombre d’elements d’un diccionari.
  • L’operador de subíndex accedeix a un valor en una clau existent. Aquest operador també es pot utilitzar per afegir una clau nova o per substituir un valor en una clau existent.
  • La classe dict inclou molts mètodes útils. Exemple:
# Associa 10 edats aleatòries entre 1 i 10 amb noms consecutius names = {} for i in range(1, 11): name = "Nom" + str(i) names[name] = random.randint(1, 10) # Imprimeix totes les claus i els seus valors for key in names.keys(): print(key, names[key])

Tuples

  • Una tuple és una seqüència immutable de 0 o més objectes de qualsevol tipus.
  • Les tuples són útils per agrupar valors que no han de canviar.
  • Es poden accedir per índex i utilitzar en bucles igual que les llistes.
  • Es poden usar com a claus de diccionaris, a diferència de les llistes (ja que són immutables).
tup1 = () # tupla buida tup2 = (1, 2, 3) # tres enters tup3 = ("a", 3.14, True) # valors heterogenis print(tup2[0]) # accés per índex for item in tup3: print(item) if 2 in tup2: print("2 està a tup2")

Iteradors

Un iterador és un objecte que admet el recorregut d’una col·lecció. La PVM utilitza automàticament un iterador sempre que veu un bucle for.

La funció iter espera una col·lecció iterable com a argument i retorna un objecte iterador per a aquesta col·lecció.

Example:

i = iter([1, 2, 3])

Un iterador registra un punter de posició actual a un element de la col·lecció. El mètode iterador next(i) avança aquest punter i retorna l’element visitat més recentment.

Example:

print (next(i), next(i), next(i))

Quan next ha retornat l’últim element de la col·lecció, qualsevol crida posterior de next generarà una excepció StopIteration. Quan el programador hagi acabat d’utilitzar un iterador, s’ha de tancar mitjançant el mètode close().

Per recórrer tots els elements amb un iterador, incrusteu la crida de next en una instrucció try/except, de la manera següent:

while True: try: element = next(i) except StopIteration: break

Comprehensions

Les comprehensions són formes compactes i llegibles de construir col·leccions a partir d’iterables. Substitueixen bucles for simples i són molt utilitzades en codi Python idiomàtic.

Les llistes es fan amb [expressió for element in iterable if condició] (l’if és opcional).

# Nombres parells entre 0 i 9 pares = [x for x in range(10) if x % 2 == 0] # Quadrats dels nombres quadrats = [x**2 for x in range(5)]

Els conjunts tenen la mateixa forma però amb {}:

# Lletres úniques en una cadena vocals = {c for c in 'informàtica' if c in 'aeiou'}

Els diccionaris es fan amb {clau: valor for element in iterable if condició}:

# Taula de quadrats taula = {x: x**2 for x in range(5)} # Nom i llargada noms = ["Anna", "Joan", "Pau"] longituds = {nom: len(nom) for nom in noms}

Generadors

Un generador és una manera fàcil de crear iteradors personalitzats. En lloc d’implementar una classe amb els mètodes __iter__ i __next__, podem usar una funció que utilitza la instrucció yield per produir valors un a un.

Quan s’executa una funció generadora, no es retorna un valor immediatament, sinó un objecte generador que implementa el protocol d’iterador.

def comptar_fins(n): i = 1 while i <= n: yield i i += 1 gen = comptar_fins(3) print(next(gen)) # 1 print(next(gen)) # 2 print(next(gen)) # 3

Quan yield s’executa, la funció es pausa i el valor es retorna. L’execució es reprèn en el punt on es va deixar, mantenint l’estat local (com ara el valor de i).

També es poden utilitzar els generadors directament en bucles:

for x in comptar_fins(3): print(x)

Definició de classes

Les definicions de classes tenen la forma general:

class <name>(<superclass name>): <class variables> <class methods> <instance methods>

La superclasse en parèntesis s’omet per a classes bàsiques. Els ítems dins de la definició de la classe poden aparèixer en qualsevol ordre.

Example:

class Student: NUM_GRADES = 5 def __init__(self, name): self.name = name self.grades = [] for i in range(Student.NUM_GRADES): self.grades.append(0) def getName(self): return self.name def getGrade(self, i): return self.grades[i - 1] def setGrade(self, i, newGrade): self.grades[i - 1] = newGrade def __str__(self): """Format: Name on the first line and all grades on the second line, separated by spaces. """ result = self.name + '\n' result += ''.join(map(str, self.grades)) return result

Ús:

s = Student('Mary') for i in range(1, Student.NUM_GRADES + 1) s.setGrade(i, 100) print(s)

Visibilitat

Tots els elements definits dins d’una classe (variables o mètodes) són potencialment visibles per als programadors que utilitzen la classe.

Els implementadors d’una classe solen desaconsellar l’accés directe a les variables utilitzant el subratllat als seus noms.

Variables i constructors

Les variables d’instància sempre tenen com a prefix la paraula reservada self. Normalment s’introdueixen i s’inicialitzen en un mètode constructor anomenat __init__.

En l’exemple següent, les variables self.name i self.grades són variables d’instància, mentre que la variable NUM_GRADES és una variable de classe:

class Student: NUM_GRADES = 5 def __init__(self, name): self.name = name self.grades = [] for i in range(Student.NUM_GRADES): self.grades.append(0)

La PVM crida automàticament el mètode constructor quan el programador sol·licita una nova instància de la classe, de la següent manera:

s = Student('Mary')

El mètode constructor sempre espera almenys un argument, self. Quan es crida el mètode, l’objecte que s’està instanciant es passa aquí i, per tant, està vinculat a self al llarg del codi. Es poden donar altres arguments per proporcionar valors inicials per a les dades de l’objecte.

Un constructor per defecte no espera cap argument de la crida i assigna valors per defecte raonables a les variables d’instància d’un objecte. Altres constructors esperen un o més arguments que permetin al programador especificar aquests valors.

Com que no hi ha sobrecàrrega de mètodes, només hi pot haver un mètode __init__. Tanmateix, es poden emular diversos mètodes mitjançant paràmetres opcionals amb valors per defecte.

Per exemple, el mètode __init__ de la classe següent definirà el nom de l’estudiant com una cadena buida si la crida no proporciona cap nom:

class Student: NUM_GRADES = 5 def __init__(self, name = ''): self.name = name self.grades = [] for i in range(Student.NUM_GRADES): self.grades.append(0)

Ús:

s1 = Student('Mary') S2 = Student()

Mètodes d’instància

La forma d’una definició de mètode d’instància és

def <nom>(self, <altres arguments>): <instruccions>

Una definició de mètode no té un tipus de retorn explícit. Si un mètode no retorna un valor d’una instrucció return, retorna automàticament el valor None. En cas contrari, el mètode retorna el tipus de valor retornat per una instrucció return o el valor None si no hi ha aquest valor.

L’argument self és necessari per a un mètode d’instància. Quan es crida el mètode, el mètode cridat no passa explícitament un argument a la posició de self. En canvi, la PVM assigna a self l’objecte receptor.

Quan es crida un mètode, els seus arguments han de coincidir en nombre amb els paràmetres corresponents de la definició. Com de costum, la comprovació de tipus es difereix fins al punt en què es necessita un valor per a una operació específica del tipus.

Exemple:

class Student: # Variables and constructor def getName(self): return self.name def getGrade(self, i): return self.grades[i - 1] def setGrade(self, i, newGrade): self.grades[i - 1] = newGrade

La sobrecàrrega de mètodes s’aconsegueix mitjançant paràmetres opcionals i predeterminats o comprovant el tipus dels paràmetres i responent-hi adequadament.

Per exemple, una classe Student pot tenir tres mètodes anomenats resetGrades per restablir totes les qualificacions.

El primer mètode, que no espera cap argument, restableix cada qualificació a 0.

El segon mètode, que espera un únic argument enter, restableix cada qualificació a aquest enter.

El tercer mètode, que espera una llista d’enters com a argument, restableix les qualificacions a aquests enters a les posicions corresponents de la llista.

Per aconseguir-ho, es defineix un únic mètode amb un únic paràmetre opcional. Quan es crida el mètode, el valor d’aquest paràmetre serà 0 (no s’ha donat cap argument), un enter (qualsevol nova qualificació per assignar totes les qualificacions) o una llista d’enters. Si el tipus del paràmetre és una llista, el mètode utilitza un índex per obtenir la qualificació de la llista. En cas contrari, el mètode utilitza el valor directament.

def resetGrades(self, value = 0): for i in range(Student.NUM_GRADES): if type(value) == list: self.grades[i] = value[i] else: self.grades[i] = value

Ús:

s = Student(); s.resetGrades(100) s.resetGrades() newGrades = [85, 66, 90, 100, 73] s.resetGrades(newGrades)

Mètodes i variables de classe

  • Les variables de classe anomenen dades que comparteixen totes les instàncies d’una classe.
  • Una variable de classe s’introdueix mitjançant una simple instrucció d’assignació dins d’una classe.
  • Les referències a variables de classe sempre tenen com a prefix el nom de la classe.
  • Les variables de classe s’escriuen en majúscules per convenció.

Exemple:

class Student: NUM_GRADES = 5 def __init__(self, name): self.name = name self.grades = [] for i in range(Student.NUM_GRADES): self.grades.append(0)

Altres usos:

print(Student.NUM_GRADES)

Els mètodes de classe són mètodes que no saben res sobre les instàncies d’una classe, però poden accedir a variables de classe i cridar altres mètodes de classe per a diversos propòsits. Per exemple, un mètode per convertir una nota numèrica en una nota amb lletra es pot definir com a mètode de classe a la classe Student.

Les crides a mètodes de classe sempre tenen com a prefix el nom de la classe.

Exemple:

class Student: # Instance method definitions @classmethod def getLetterGrade(cls, grade): if grade > 89: return 'A' elif grade > 79: return 'B' else: return 'F'

Ús:

s = Student() for i in range(1, Student.NUM_GRADES + 1): print(Student.getLetterGrade(s.getGrade(i)))

Totes les variables es poden reiniciar en qualsevol moment. No hi ha constants simbòliques. Els símbols com ara True, False i None són paraules reservades.

La funció str converteix qualsevol objecte a la seva representació de cadena.

Exemple:

str(3.14) # retorna '3.14'

Aquesta funció es pot personalitzar per retornar la cadena adequada per a objectes de qualsevol classe definida pel programador incloent-hi un mètode __str__.

Quan el mètode __str__ està disponible, operacions com ara imprimir l’utilitzen automàticament per obtenir la representació de cadena d’un objecte.

Exemple:

class Student: NUM_GRADES = 5 def __init__(self, name): self.name = name self.grades = [] for i in range(Student.NUM_GRADES): self.grades.append(0) def __str__(self): """Format: Name on the first line and all grades on the second line, separated by spaces. """ result = self.name + '\n' result += ''.join(map(str, self.grades)) return result

Igualtat

L’operador == compara dos objectes per verificar la igualtat. Aquest operador utilitza l’operador is per defecte. L’operador is comprova la igualtat de dues referències d’objectes: es refereixen exactament al mateix objecte a la memòria? Sovint, aquesta prova és massa restrictiva. Una versió més relaxada compararia un o més dels atributs dels objectes per verificar la igualtat. Per exemple, dos objectes Student podrien tenir el mateix nom i considerar-se iguals, tot i que també són objectes diferents. Aquest tipus d’igualtat s’anomena equivalència estructural, a diferència del tipus més restrictiu d’identitat d’objecte.

El programador pot anul·lar la definició per defecte de == incloent una definició del mètode __eq__ en una classe determinada. En realitat, hi ha tres proves a realitzar. L’objecte receptor (self) i el segon objecte de paràmetre (other) es comparen primer per la identitat mitjançant l’operador is. Si aquesta prova falla, es comparen els tipus dels dos objectes. Si aquesta prova té èxit, els seus atributs rellevants es comparen mitjançant l’operador ==.

El comportament de != també es pot modificar anul·lant el mètode __ne__.

Exemple:

class Student: NUM_GRADES = 5 def __init__(self, name): self.name = name self.grades = [] for i in range(Student.NUM_GRADES): self.grades.append(0) def __eq__(self, other): """ Compare the names for equality if the objects are not identical. """ if self is other: return True elif type(self) != type(other): return False else: return self.name == other.name def __ne__(self, other): """ Compare the names for inequality. """ return not self == other

Usage:

s1 = Student('Mary') s2 = Student('Bill') s3 = Student('Bill') print(s1 == s2) # displays False print(s2 == s3) # displays True print(s2 is s3) # displays False print(s2 != s3) # displays False

Comparable

Els objectes comparables reconeixen els operadors de comparació ==, !=, <, >, <= i >=. Les cadenes són comparables. La classe str inclou els mètodes __eq__ i __lt__. Utilitzant aquests dos mètodes, Python genera automàticament el codi per a les comparacions adequades quan troba qualsevol dels altres operadors de comparació que s’utilitzen amb cadenes.

Es pot ordenar una llista de cadenes mitjançant el mètode sort(), però no es pot ordenar una llista d’objectes Student. Tanmateix, el programador pot incloure els mètodes __eq__ i __lt__ a la classe Student per resoldre aquest problema. Cada mètode utilitza els noms dels estudiants com a atributs comparables.

Tingueu en compte que els conceptes de menor que i major que són més restrictius que la igualtat. És a dir, ara se suposa que els tipus d’objectes que es comparen són els mateixos, de manera que qualsevol error es generarà en temps d’execució.

class Student: NUM_GRADES = 5 def __init__(self, name): self.name = name self.grades = [] for i in range(Student.NUM_GRADES): self.grades.append(0) def __eq__(self, other): if self is other: return True elif type(self) != type(other): return False else: return self.name == other.name def __lt__(self, other): return self.name < other.name

Usage:

s1 = Student('Mary') s2 = Student('Bill') print(s1 < s2) # displays False print(s1 > s2) # displays True

Classes internes

Una classe es pot definir només per al seu ús en una altra definició de classe. Per exemple, una classe LinkedStack podria utilitzar una classe OneWayNode. Idealment, la definició de OneWayNode s’inclouria dins de LinkedStack, però això no està permès. A tall de comparació, aquí teniu una implementació d’aquestes dues classes:

class OneWayNode: def __init__(self, data, next): self.data = data self.next = next class LinkedStack: def __init__(self): self.items = None self.size = 0 def push(self, element): self.items = OneWayNode(element, self.items) self.size += 1 def pop(self): element = self.items.data self.items = self.items.next self.size -= 1 return element def peek(self): return self.items.data def isEmpty(self): return len(self) == 0 def __len__(self): return self.size

Definició d’iteradors

Per convenció, el mètode iter() retorna un iterador sobre un objecte iterable. L’usuari d’un iterador pot esperar que el mètode next() retorni el següent objecte d’una iteració, fins que next genera una excepció StopIteration. En aquest punt, es tanca l’iterador mitjançant el mètode close().

Suposem que la classe LinkedStack ara inclou un mètode iter. Aleshores, es poden visitar els objectes d’una pila, de dalt a baix, de qualsevol de les maneres següents:

stack = LinkedStack() i = stack.iter() while True: try: element = next(i) # process element except StopIteration: break for element in stack: # process element

El mètode iter crea i retorna un objecte generador. El codi d’aquest generador s’executa en un procés independent, de manera simultània amb el procés que utilitza l’iterador. Un generador pot mantenir l’estat intern, com per exemple un punter que indica la posició actual dins dels elements de la col·lecció. En l’exemple actual, aquesta referència apunta inicialment al primer node de la llista enllaçada de la pila.

El codi del generador conté un bucle while True. Si la posició actual és None, vol dir que s’ha arribat a l’últim node, i el generador llança una excepció StopIteration. En cas contrari, retorna l’element del node actual. La instrucció yield pausa l’execució del generador fins que es crida el mètode next(), que retorna l’element generat. Quan el control torna al generador, el punter s’actualitza al següent node. Aquest procés pot continuar indefinidament, excepte si l’usuari crida el mètode close() del generador.

Exemple:

class OneWayNode: def __init__(self, data, next): self.data = data self.next = next class LinkedStack: def __init__(self): self.items = None self.size = 0 def push(self, element): self.items = OneWayNode(element, self.items) self.size += 1 def pop(self): element = self.items.data self.items = self.items.next self.size -= 1 return element def peek(self): return self.items.data def isEmpty(self): return len(self) == 0 def __len__(self): return self.size def __iter__(self): curPos = self.items while True: if curPos is None: raise StopIteration yield curPos.data curPos = curPos.next

Herència

En un llenguatge de programació orientat a objectes, es pot definir una nova classe que reutilitza el codi d’una altra classe. La nova classe esdevé una subclasse de la classe existent, que també s’anomena classe pare. La subclasse hereta tots els atributs, inclosos els mètodes i les variables, de la seva classe pare i de qualsevol classe ancestral de la jerarquia.

La subclassificació és una manera convenient de proporcionar funcionalitats addicionals o més especialitzades a un recurs existent. Per exemple, una cua garanteix l’accés als seus elements en ordre de primer a entrar, primer a sortir. Una cua de prioritat es comporta en la majoria dels aspectes igual que una cua, excepte que els elements de més prioritat s’eliminen primer. Si els elements tenen la mateixa prioritat, s’eliminen en ordre FIFO estàndard. Una cua de prioritat és, doncs, una subclasse de cua, amb un mètode especialitzat per a insercions que garanteix l’ordre adequat dels elements. La cua de prioritat obté tots els seus altres mètodes de la classe de cua gratuïtament.

Aquí teniu la definició d’una classe LinkedQueue per a cues ordinàries:

class OneWayNode: def __init__(self, data, next): self.data = data self.next = next class LinkedQueue: def __init__(self): self.front = self.rear = None self.size = 0 def enqueue(self, element): n = OneWayNode(element, None) if self.isEmpty(): self.rear = self.front = n else: self.rear.next = n self.rear = n self.size += 1 def dequeue(self): element = self.front.data self.front = self.front.next self.size -= 1 if self.isEmpty(): self.rear = None return element def peek(self): return self.front.data def isEmpty(self): return len(self) == 0 def __len__(self): return self.size

La forma per definir una subclasse és:

class <nom de la subclasse>(<nom de la classe pare>): <variables i mètodes>

La classe LinkedPriorityQueue és una subclasse de LinkedQueue. La nova classe inclou només dos mètodes, __init__ i enqueue. El mètode __init__ de LinkedPriorityQueue simplement crida el mateix mètode a la seva classe pare, utilitzant la forma:

<nom de la classe pare>.__init__(self)

Això fa que la classe pare inicialitzi les seves variables d’instància.

El nou mètode enqueue primer comprova si hi ha una cua buida i, si això és cert, crida el mètode enqueue a la classe pare utilitzant la forma:

<nom de la classe pare>.<nom del mètode>(<arguments>)

En cas contrari, el mètode busca la posició adequada del nou element i l’insereix allà. Com que la part frontal de la cua es troba al capdavant de l’estructura enllaçada, la cerca s’atura quan l’element entrant és estrictament menor que l’element de la cua. Així, un element entrant es col·loca darrere de tots els elements de la mateixa prioritat.

Here is the definition of LinkedPriorityQueue:

class LinkedPriorityQueue(LinkedQueue): def __init__(self): LinkedQueue.__init__(self) def enqueue(self, element): if self.isEmpty(): LinkedQueue.enqueue(self, element) else: probe = self.front while probe != None and element >= probe.data: trailer = probe probe = probe.next if probe == None: # At rear of queue self.rear.next = OneWayNode(element, None) self.rear = self.rear.next elif probe == self.front: # At front of queue self.front = Node(element, self.front.next) else: # Betwixt two nodes trailer.next = Node(element, probe) self.size += 1

Classes abstractes

Quan dues o més classes contenen codi redundant, es pot factoritzar en una classe pare comuna. Aquesta classe normalment s’anomena classe abstracta, perquè no està instanciada.

Considereu dues implementacions de piles, ArrayStack i LinkedStack. Els seus contenidors interns d’elements són diferents, així com els seus mètodes per afegir, eliminar i iterar a través d’aquestes estructures. Tanmateix, si suposem que cada pila fa un seguiment de la seva mida de la mateixa manera, els mètodes len i isEmpty seran els mateixos per a qualsevol implementació. I si suposem que cada implementació inclou un mètode iter, podem implementar diversos altres mètodes, com ara str i addAll, només una vegada per a totes les piles.

L’exemple següent defineix una classe AbstractStack que inclou la informació comuna a totes les implementacions de pila. Per accedir a aquests recursos, una implementació de pila, com ara ArrayStack o LinkedStack, simplement estén AbstractStack.

class AbstractStack: def __init__(self): self.size = 0 def isEmpty(self): return len(self) == 0 def __len__(self): return self.size def __str__(self): result = '' for element in self: result += str(element) + '\n' return result def addAll(self, otherIterable): for element in otherIterable: self.push(element)

AbstractStack és responsable d’inicialitzar només una variable d’instància, size. Cal tenir en compte que el mètode str utilitza un bucle for sobre self, cosa que implica que l’objecte de pila és iterable. En conseqüència, cada implementació de pila ha d’incloure la seva pròpia definició del mètode iter. Finalment, cal tenir en compte que el mètode addAll copia elements d’un altre objecte interable a la pila, utilitzant el mètode push. Per tant, el mètode push també s’ha d’incloure a cada implementació de pila.

La primera implementació, ArrayStack, inclou un mètode init que permet al programador especificar un objecte iterable opcional com a argument. Si aquest argument no és None, es passa al mètode addAll per copiar els seus elements a la nova pila. També es pot cridar addAll per afegir diversos elements en qualsevol moment posterior. ArrayStack també és responsable de mantenir la seqüència d’elements. Per tant, inclou les definicions de peek, pop, push i iter.

Exemple:

class ArrayStack(AbstractStack): def __init__(self, otherIterable = None): AbstractStack.__init__(self) self.items = [] if otherIterable != None: self.addAll(otherIterable) def push(self, element): self.items.append(element) self.size += 1 def pop(self): self.size -= 1 return self.items.pop() def peek(self): return self.items[-1] def __iter__(self): probe = len(self.items) - 1 while True: if probe < 0: raise StopIteration yield self.items[probe] probe -= 1

Els mateixos mètodes es defineixen a LinkedStack, però és clar que accedeixen a elements en un tipus d’estructura interna molt diferent.

class OneWayNode: def __init__(self, data, next): self.data = data self.next = next class LinkedStack(AbstractStack): def __init__(self, otherIterable = None): AbstractStack.__init__(self) self.items = None if otherIterable != None: self.addAll(otherIterable) def push(self, element): self.items = OneWayNode(element, self.items) self.size += 1 def pop(self): element = self.items.data self.items = self.items.next self.size -= 1 return element def peek(self): return self.items.data def __iter__(self): probe = self.items while True: if probe is None: raise StopIteration yield probe.data probe = probe.next

Gestió d’errors

Python permet capturar i gestionar errors mitjançant blocs try i except. Això evita que el programa s’aturi de manera inesperada i permet reaccionar davant d’errors de manera controlada.

try: # codi que pot provocar una excepció x = int(input("Introdueix un nombre: ")) resultat = 10 / x except ValueError: print("No has introduït un nombre enter.") except ZeroDivisionError: print("No pots dividir per zero.") finally: print("Això s'executa sempre, amb o sense error.")
  • try: conté el codi que pot generar una excepció.
  • except: captura un error específic i executa el codi corresponent.
  • finally: (opcional) conté codi que s’executa sempre, hagi passat o no una excepció. Es poden afegir múltiples blocs except per tractar diferents tipus d’errors.
  • Es pot capturar l’excepció en una variable amb as:
try: 1 / 0 except ZeroDivisionError as e: print(f"S'ha produït un error: {e}")

Es poden provocar errors manualment mitjançant raise, per exemple per indicar que alguna condició no es compleix:

def divideix(a, b): if b == 0: raise ValueError("No es pot dividir per zero.") return a / b

Tipus d’excepcions comunes:

ExcepcióQuan apareix
ValueErrorConversió incorrecta de tipus (int("abc"))
ZeroDivisionErrorDivisió entre zero
TypeErrorOperacions entre tipus incompatibles
IndexErrorÍndex fora de rang en una seqüència
KeyErrorClau inexistent en un diccionari
FileNotFoundErrorArxiu inexistent

Context managers

Un gestor de context és un objecte que defineix accions a fer abans i després d’un bloc de codi. S’utilitzen habitualment per gestionar recursos que s’han d’obrir i tancar, com ara fitxers, connexions de xarxa o bases de dades.

La forma més comuna d’utilitzar-los és mitjançant la instrucció with.

with open("notes.txt", "r") as fitxer: contingut = fitxer.read()
  • open(...) obre el fitxer i retorna un objecte.
  • El bloc dins de with llegeix el contingut.
  • Quan acaba el bloc, el fitxer es tanca automàticament, fins i tot si hi ha un error.
  • Això evita haver d’escriure fitxer.close() manualment i millora la seguretat i neteja del codi.

Podem definir els nostres propis gestors de context mitjançant una classe amb dos mètodes especials:

  • __enter__: s’executa al començar el bloc.
  • __exit__: s’executa al final, fins i tot si hi ha excepcions.

Anotacions de tipus

Python permet indicar els tipus d’arguments i valors retornats d’una funció mitjançant anotacions de tipus. Aquestes anotacions no són obligatòries i no es comproven en temps d’execució, però ajuden a documentar el codi i milloren l’autocompletat i la detecció d’errors per part d’eines com mypy, Pyright o editors com VSCode.

def saluda(nom: str, edat: int) -> str: return f"Hola, {nom}. Tens {edat} anys."

Els tipus s’especifiquen amb el format:

def nom_funció(paràmetre: Tipus) -> TipusRetorn:

El mòdul typing permet descriure tipus més complexos:

TipusDescripció
List[T]Llista d’elements del tipus T
Dict[K, V]Diccionari amb claus de tipus K i valors V
Optional[T]Valor que pot ser de tipus T o None
Tuple[T1, T2]Tupla amb elements de tipus T1, T2, etc.
Union[T1, T2]Valor que pot ser de tipus T1 o T2
AnyQualsevol tipus

Exemple amb List i Dict:

from typing import List, Dict def suma_llista(nums: List[int]) -> int: return sum(nums) def traduccions(cat2eng: Dict[str, str]) -> None: for cat, eng in cat2eng.items(): print(f"{cat}{eng}")

Exemple amb Optional:

from typing import Optional def busca(llista: List[int], valor: int) -> Optional[int]: if valor in llista: return valor return None

Aquest exemple indica que la funció pot retornar un enter o None.

Consells:

  • Per a col·leccions a partir de Python 3.9, es poden usar tipus natius: list[int], dict[str, int], etc.
  • En versions anteriors, cal usar typing.List, typing.Dict, etc.
  • Les anotacions ajuden els humans i eines automàtiques, però no canvien el comportament del codi.

Python idiomàtic

Aquesta secció recull construccions, funcions i idiomes habituals de Python que apareixen constantment quan es treballa amb dades, machine learning (ML) i data science (DS).

  • enumerate(iterable) → índex i valor alhora

    Quan recorrem una col·lecció, sovint necessitem tant la posició com el valor. enumerate genera parells (índex, element) automàticament.

    for i, valor in enumerate(["a", "b", "c"]): print(i, valor)
    # 0 a / 1 b / 2 c
  • zip → agrupar iterables en paral·lel

    Permet recórrer múltiples col·leccions simultàniament, agrupant elements per posició. S’atura quan s’esgota la col·lecció més curta.

    noms = ["A", "B", "C"] notes = [7, 8, 9] for nom, nota in zip(noms, notes): print(nom, nota)
  • all, any → comprovar condicions sobre col·leccions

    all retorna True si tots els elements compleixen una condició. any retorna True si almenys un element la compleix. Útils per validacions ràpides.

    notes = [7, 8, 9] print(all(n > 5 for n in notes)) # True print(any(n == 10 for n in notes)) # False
  • sorted(iterable, key=...)

    Retorna una nova llista ordenada sense modificar l’original. El paràmetre key permet especificar una funció per determinar el criteri d’ordenació.

    paraules = ["cotxe", "aigua", "lluna"] print(sorted(paraules, key=len)) # ['aigua', 'lluna', 'cotxe']
  • Desempaquetament múltiple

    Permet assignar múltiples valors en una sola instrucció. L’operador * captura elements restants en una llista.

    a, b = (1, 2) a, *rest, b = [1, 2, 3, 4, 5] # a=1, rest=[2,3,4], b=5
  • Slicing avançat (essencial per arrays i tensors)

    La notació [inici:final:pas] permet extreure subseqüències. És fonamental per manipular arrays de NumPy i tensors en ML, on sovint es treballa amb porcions de dades.

    llista = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print(llista[2:7]) # [2, 3, 4, 5, 6] print(llista[::2]) # [0, 2, 4, 6, 8] (pas de 2) print(llista[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] (invertir) print(llista[1:8:2]) # [1, 3, 5, 7]
  • map, filter, reduce → programació funcional

    Apliquen funcions a col·leccions sense bucles explícits. map transforma cada element, filter selecciona elements que compleixen una condició, i reduce acumula valors.

    from functools import reduce nums = [1, 2, 3, 4] quadrats = list(map(lambda x: x**2, nums)) # [1, 4, 9, 16] parells = list(filter(lambda x: x % 2 == 0, nums)) # [2, 4] suma = reduce(lambda x, y: x + y, nums) # 10
  • *args, **kwargs → arguments variables

    Permeten que una funció accepti un nombre variable d’arguments. *args captura arguments posicionals i **kwargs captura arguments amb nom (keyword arguments).

    def mostra(*args, **kwargs): print(args, kwargs) mostra(1, 2, 3, nom="Anna", edat=25) # (1, 2, 3) {'nom': 'Anna', 'edat': 25}
  • Gestió de paths amb pathlib

    La llibreria pathlib ofereix una manera orientada a objectes i multiplataforma de treballar amb rutes de fitxers. L’operador / construeix paths de forma elegant.

    from pathlib import Path fitxer = Path("dades") / "train.csv" if fitxer.exists(): contingut = fitxer.read_text()
  • Lectura de fitxers amb with

    El context manager with garanteix que els fitxers es tanquin automàticament, fins i tot si hi ha errors. Evita problemes de memòria i recursos no alliberats.

    with open("dades.txt") as f: for linia in f: print(linia.strip())
  • Mòdul csv

    Proporciona funcions per llegir i escriure fitxers CSV de manera robusta, gestionant automàticament cometes, delimitadors i formats especials.

    import csv with open("dades.csv") as f: lector = csv.reader(f) for fila in lector: print(fila)
  • Walrus operator (Python 3.8+)

    L’operador := permet assignar i utilitzar un valor en la mateixa expressió. Útil per evitar càlculs duplicats i fer codi més compacte.

    # Útil per processar dades línia a línia while (line := f.readline()): process(line)

Referències