La lliçó anterior va acabar amb tres preguntes sense resposta. Per què demanar_opcio pot llegir la constant PRIORITATS sense que la hi passem? Què passaria si dins d'una funció hi escrivíssim titol = "un altre": canviaria el titol del programa principal? I per què la variable valor que viu dins de demanar_text no existeix fora d'ella?

Les tres són la mateixa pregunta amb roba diferent: on viu cada nom i qui el pot veure. Això s'anomena àmbit (scope), i és una d'aquelles idees que, mentre no s'entenen, produeixen errors desconcertants: funcions que «no veuen» una variable que està clarament escrita més amunt, canvis que es perden sense deixar rastre, valors que apareixen on no haurien d'aparèixer.

Entendre l'àmbit no és un luxe teòric: és el que converteix una funció en una caixa segura, una cosa que pots fer servir sabent que no espatllarà res del que hi ha a fora. I és, a més, la base d'una regla d'or que aplicarem a la lliçó següent: una funció ben feta depèn només del que rep per paràmetres.

Contingut

  1. Àmbit local: néixer i morir amb la crida
  2. Àmbit global
  3. La regla LEGB
  4. Llegir una global no és el mateix que assignar-la
  5. La paraula global i per què es desaconsella
  6. Ombreig de noms
  7. Com es passen els arguments en Python
  8. Constants globals: l'excepció acceptada
  9. Funcions imbricades i nonlocal
  10. Àmbit i depuració
  11. TascaFàcil: auditoria de les funcions
  12. Errors comuns i consells
  13. Exercicis
  14. Conclusió

  1. Àmbit local: néixer i morir amb la crida

Tota variable creada dins d'una funció —sigui per assignació, sigui per ser un paràmetre— és local a aquella funció: existeix mentre la crida s'executa i desapareix tan bon punt acaba.

def calcular_cost(dies, tarifa):
    subtotal = dies * tarifa
    impostos = subtotal * 0.21
    return subtotal + impostos

print(calcular_cost(4, 110.0))
print(subtotal)
532.4
NameError: name 'subtotal' is not defined

La primera línia funciona; la segona no. subtotal i impostos van existir, van fer la seva feina i s'han esvaït: fora de la funció Python no té cap nom anomenat subtotal, i per això respon amb el NameError que ja coneixes de 04-01. Això, que sembla una limitació, és en realitat la millor propietat de les funcions. Significa que pots escriure una funció fent servir els noms que et vinguin de gust —i, valor, total— amb la garantia que no trepitjaràs res del que hi ha a fora. Sense àmbit local, cada nom que fessis servir dins d'una funció seria una mina per a la resta del programa, i no podries escriure ni cinquanta línies sense col·lidir amb tu mateix. Funciona també entre funcions diferents: si demanar_titol i demanar_prioritat fan servir totes dues una variable anomenada valor, no es destorben gens, perquè són dues variables diferents que casualment comparteixen nom, cadascuna a la seva pròpia caixa.

  1. Àmbit global

L'àmbit global és el del fitxer: tot allò que es defineix al marge esquerre, fora de qualsevol funció. Aquestes variables s'anomenen globals i són visibles des de qualsevol punt del mòdul, inclòs l'interior de les funcions.

NOM_ESTUDI = "Estudi Alba"                   # global

def mostrar_capcalera():
    print(f"TascaFacil - {NOM_ESTUDI}")      # lectura d'una global: funciona

mostrar_capcalera()                          # TascaFacil - Estudi Alba

Aquí tens la resposta a la primera pregunta de la lliçó: demanar_opcio podia llegir PRIORITATS perquè les constants són globals i les funcions poden llegir allò global sense demanar permís.

Ara bé, que es pugui no vol dir que convingui: una funció que llegeix variables globals canviants deixa de ser autònoma, perquè el seu resultat ja no depèn només del que rep sinó de l'estat en què estigui el programa en aquell moment, cosa que la torna impredictible en llegir-la i impossible de provar per separat. Hi tornarem a la secció 8, amb l'única excepció raonable: les constants.

  1. La regla LEGB

Quan Python troba un nom, el busca a quatre llocs i en aquest ordre, quedant-se amb el primer que trobi. La regla es coneix per les seves inicials en anglès: LEGB.

Capa Què és Exemple
L — Local Dins de la funció que s'està executant subtotal, un paràmetre
E — Enclosing La funció que envolta aquesta, si n'hi ha veure secció 9
G — Global El nivell del fitxer NOM_ESTUDI, PRIORITATS
B — Built-in El que Python porta de sèrie print, len, int, input
flowchart TD
    A["Local: dins de la funcio"] --> B["Enclosing: funcio que l'envolta"]
    B --> C["Global: el fitxer"]
    C --> D["Built-in: print, len, int, input"]
    D --> E["No es a cap capa: NameError"]

Llegeix-ho com una cerca que baixa esglaons: si el nom és a la capa local, es fa servir aquell i no es mira més avall; si no, es prova l'envolupant; després la global; després les funcions integrades; i si tampoc no hi és, NameError. Una conseqüència immediata i molt útil: el nom més proper guanya. Si una funció té una variable local total i a més existeix una global total, dins de la funció total es refereix sempre a la local. La global continua existint, intacta, però queda tapada mentre duri la crida.

  1. Llegir una global no és el mateix que assignar-la

Aquí hi ha el punt que més confusió genera de tota la lliçó. Acabem de veure que llegir una global des de dins d'una funció funciona sense més. Canviem una sola cosa: assignar en comptes de llegir.

comptador = 0

def incrementar():
    comptador = comptador + 1    # intenta ASSIGNAR
    print(comptador)

incrementar()
UnboundLocalError: cannot access local variable 'comptador' where it is not associated with a value

Sembla contradictori, però la regla és senzilla i no té excepcions: si en algun punt del cos d'una funció hi ha una assignació a un nom, aquest nom és local a tota la funció. Python ho decideix en compilar la funció, abans d'executar res. Així que comptador és local dins d'incrementar, i la línia comptador = comptador + 1 intenta llegir una local que encara no té valor. D'aquí ve l'UnboundLocalError, que és un NameError especialitzat.

I compte amb la variant que no dona error, encara més perillosa:

comptador = 0

def posar_a_deu():
    comptador = 10       # crea una variable LOCAL nova; la global no es toca
    print(f"Dins: {comptador}")

posar_a_deu()
print(f"Fora: {comptador}")
Dins: 10
Fora: 0

La funció sembla haver canviat el comptador, i fins i tot l'imprimeix canviat, però a fora tot continua igual. Aquest és l'error del qual parlàvem: no falla, no avisa, i no fa el que sembla. Si alguna vegada et preguntes «he assignat la variable dins de la funció i a fora no canvia», aquesta n'és l'explicació.

  1. La paraula global i per què es desaconsella

Python ofereix una manera de forçar que l'assignació afecti la variable global: declarar-la amb global al començament del cos.

comptador = 0

def incrementar():
    global comptador         # "quan digui comptador, em refereixo a la global"
    comptador = comptador + 1

incrementar()
incrementar()
print(comptador)             # 2

Funciona. I tot i així, la recomanació de pràcticament tota la comunitat Python és no fer-lo servir llevat de casos molt comptats. La raó s'entén millor veient el que trenca:

total_hores = 0

def registrar_jornada(hores):
    global total_hores
    total_hores = total_hores + hores

def calcular_mitjana(dies_treballats):
    global total_hores
    total_hores = total_hores / dies_treballats      # destrossa l'acumulat
    return total_hores

registrar_jornada(8)
registrar_jornada(6)
print(calcular_mitjana(2))      # 7.0
registrar_jornada(5)
print(total_hores)              # 12.0, un valor que ja no significa res

calcular_mitjana tenia un nom innocent —«calcular»— i ha modificat l'estat compartit. A partir d'aquí, total_hores ja no són hores totals ni una mitjana: és un número sense significat, i el programa continuarà funcionant sense queixar-se. Imagina't això en un fitxer de mil línies amb quinze funcions que escriuen la mateixa global: per saber quant val una variable en un punt hauries de llegir-te el programa sencer.

Amb global Amb paràmetres i return
De què depèn el resultat? De l'estat del programa Només dels arguments
Es pot provar aïllada? No Sí
Qui la pot trencar? Qualsevol funció Ningú més
Es veu a la crida què toca? No Sí

L'alternativa sempre és la mateixa: rebre per paràmetre i retornar amb return. La versió sana seria def registrar_jornada(total, hores): return total + hores, i que el programa principal desi el resultat. Una línia més i un problema menys.

  1. Ombreig de noms

Ombrejar (shadowing) és declarar un nom que tapa un altre d'una capa més externa. Passa contínuament i moltes vegades és inofensiu, però té una versió que fa mal de debò: ombrejar un built-in.

list = "Cartell fira del llibre"     # tapa la funcio integrada list()
print(list)                          # funciona: imprimeix el text
print(list("abc"))                   # TypeError: 'str' object is not callable

En assignar a list, la capa global s'ha quedat amb aquest nom i la cerca LEGB ja no arriba mai a la capa built-in. L'error, a més, apareix lluny del punt on es va causar, de vegades centenars de línies després. Aquests són els noms que més s'ombregen per accident:

Nom integrat Per a què serveix Alternativa segura
list Crear llistes (mòdul 5) llista_tasques, elements
input Llegir del teclat entrada, text_llegit
str, int, float Convertir tipus text, numero, import_total
type Consultar el tipus tipus_tasca, categoria
sum, max, min, len Càlculs sobre col·leccions total, major, menor

Ombrejar input és especialment cruel en un programa com TascaFàcil: escrius input = input("Titol: ") una vegada i la crida següent a input(...) dona TypeError: 'str' object is not callable, perquè input ja no és la funció sinó la cadena que vas teclejar. La defensa és simple: si el teu editor acoloreix un nom com si fos especial, no el facis servir per a una variable.

  1. Com es passen els arguments en Python

Ja saps que un argument s'assigna al paràmetre. Però què s'assigna exactament, el valor o «la variable»? En Python, el que es passa és la referència a l'objecte: el paràmetre passa a apuntar al mateix objecte que l'argument. La conseqüència pràctica, amb els tipus que coneixes —números, cadenes, booleans, tots immutables—, és tranquil·litzadora: reassignar el paràmetre a dins no afecta la variable de fora.

def allargar_termini(dies):
    print(f"  Rebo dies = {dies}")
    dies = dies + 5                 # reassigna el nom LOCAL 'dies'
    print(f"  Ara dins dies = {dies}")
    return dies

termini = 4
resultat = allargar_termini(termini)
print(f"Fora termini = {termini}, resultat = {resultat}")
  Rebo dies = 4
  Ara dins dies = 9
Fora termini = 4, resultat = 9

Traça del que ha passat: en cridar, el paràmetre dies apunta al mateix objecte 4 que termini. La línia dies = dies + 5 crea un objecte nou, 9, i fa que el nom local dies hi apunti; termini continua apuntant a 4, perquè el número 4 no ha canviat —no pot: els enters són immutables. L'única manera que l'exterior s'assabenti del nou valor és el return.

El mateix passa amb les cadenes. Si una funció normalitzar(text) fa text = text.strip().capitalize() i retorna el resultat, la variable que has passat des de fora continua amb els seus espais i les seves minúscules intactes: és el que vas veure a 02-01 sobre la immutabilitat de les cadenes, ara en el context d'una crida. Els mètodes de cadena retornen una cadena nova; no modifiquen l'original.

Una avançada honesta: amb objectes mutables —les llistes del mòdul 5— la història té un capítol més, perquè una funció sí que pot modificar el contingut de l'objecte rebut i que el canvi es vegi fora. El que acabes d'aprendre continua sent cert (reassignar el nom mai no afecta l'exterior), però caldrà distingir entre reassignar i modificar. Avui, amb números i cadenes, no hi ha ambigüitat possible.

  1. Constants globals: l'excepció acceptada

Després de tant advertiment contra les globals, per què PRIORITATS i EQUIP viuen al nivell global i les llegim des de qualsevol funció sense remordiment? Perquè són constants, i una constant no té els defectes d'una global canviant: no canvia mai durant l'execució, així que el resultat de la funció continua sent predictible; es declara en un sol lloc, a dalt del fitxer, on qualsevol la troba; es distingeix a simple vista pel conveni de MAJÚSCULES de 02-01; i documenta el domini del problema, perquè PRIORITATS = ("alta", "mitjana", "baixa") diu quines prioritats existeixen a Estudi Alba, i això és informació del negoci, no estat del programa.

Constant global (AMPLE, EQUIP) Variable global (titol, comptador)
Canvia durant l'execució? No Sí
Qui la modifica? Ningú Qualsevol funció
Complica raonar sobre el codi? No Molt
És acceptable llegir-la des d'una funció? Sí Evita-ho

Un matís d'estil: encara que llegir una constant global sigui legítim, de vegades convé passar-la igualment per paràmetre. És el que vam fer amb demanar_opcio(missatge, opcions): passant-li les opcions, la funció serveix per a prioritats, per a responsables i per al que vingui; llegint PRIORITATS directament, només serviria per a prioritats. Regla pràctica: si la constant forma part del que la funció fa, passa-la; si és un detall de presentació compartit per tot el programa, com AMPLE, llegeix-la.

  1. Funcions imbricades i nonlocal

Falta explicar la E de LEGB. En Python pots definir una funció dins d'una altra, i la de dins veu els noms de la de fora, que formen el seu àmbit envolupant (enclosing).

def preparar_informe(client):
    capcalera = f"Informe per a {client}"

    def linia(text):
        return f"{capcalera} | {text}"        # llegeix 'capcalera' de l'ambit envolupant

    print(linia("Tasca completada"))

preparar_informe("Forn Sole")    # Informe per a Forn Sole | Tasca completada

I si la funció interna necessités assignar a un nom de l'envolupant, existeix la paraula nonlocal, germana de global però apuntant una capa més amunt en comptes del fitxer sencer. No la farem servir al curs: amb el que saps avui, la solució neta continua sent passar la dada per paràmetre i retornar-la. N'hi ha prou que reconeguis nonlocal quan el vegis i sàpigues que es refereix a la capa envolupant. Les funcions imbricades sí que tornaran a aparèixer, amb un propòsit molt concret, a Funcions com a valors.

  1. Àmbit i depuració

Hi ha una raó molt pràctica per prendre's l'àmbit seriosament: l'estat global dispers multiplica el temps que trigues a trobar un error. Quan una funció depèn només dels seus paràmetres, diagnosticar una fallada és un problema tancat: mires els arguments, mires el valor retornat i l'error hi és a dins o no hi és. Quan depèn de globals que qualsevol pot modificar, la pregunta «per què aquí total_hores val 12?» no es respon mirant la funció: cal reconstruir tota la història de l'execució. És la diferència entre revisar una habitació i escorcollar un edifici.

Per això, quan alguna cosa no quadri, la primera pregunta útil és: de què depèn aquesta funció? Si la resposta és «només del que rep», ja has acotat el problema. Les tècniques concretes per investigar —traces, punts d'interrupció, lectura d'un traceback— són el tema de Depuració i gestió d'errors; el que l'àmbit et dona és un programa en què aquestes tècniques funcionen de pressa.

  1. TascaFàcil: auditoria de les funcions

Toca revisar el que vam escriure a 04-02 amb la pregunta de la secció anterior: de què depèn cada funció? A més de les quatre que ja coneixes, n'havíem esbossat una cinquena, mostrar_resum(), per pintar la línia d'estat de la tasca.

# La versio defectuosa de mostrar_resum
titol = ""
prioritat = ""
dies = 0

def mostrar_resum():
    """Pinta el resum de la tasca... llegint l'estat global."""
    print(f"{titol} | {prioritat} | {dies} dies")
Funció Depèn de Veredicte
demanar_text(missatge, obligatori=True) Els seus paràmetres Correcta
demanar_opcio(missatge, opcions) Els seus paràmetres Correcta
demanar_enter(missatge, minim, maxim) Els seus paràmetres Correcta
classificar_urgencia(prioritat, dies) Els seus paràmetres Correcta
mostrar_resum() Globals titol, prioritat, dies Defectuosa

Les quatre primeres estan netes: les pots copiar a un altre fitxer i funcionen tal qual. mostrar_resum() no: fora de TascaFàcil no serveix per a res, no es pot provar amb dades inventades i, si demà el programa gestionés dues tasques, no hi hauria manera de dir-li quina ha de pintar. La correcció és mecànica —convertir cada global llegida en un paràmetre:

def mostrar_resum(titol, prioritat, dies):
    """Pinta en una linia el resum de la tasca indicada."""
    print(f"{titol} | {prioritat} | {dies} dies | {classificar_urgencia(prioritat, dies)}")

mostrar_resum("Cartell fira del llibre", "alta", 2)   # ... | 2 dies | CRITICA
mostrar_resum("Menu Forn Sole", "baixa", 9)           # ... | 9 dies | Normal

Fixa't en les dues crides seguides amb dades diferents: això, que ara sembla trivial, era impossible amb la versió que llegia globals. I observa que mostrar_resum sí que crida classificar_urgencia, que és una funció global: això no és un problema d'àmbit, perquè les funcions, com les constants, es defineixen una vegada i no canvien. Amb això, tascafacil.py queda en un estat sanejat: constants globals a dalt, funcions que depenen només dels seus paràmetres, i l'estat de la tasca encara en variables soltes del programa principal. Aquest estat solt és l'últim cap que queda, i el lligarem a la lliçó següent.

Errors Comuns i Consells

Fer servir a fora una variable local. NameError. El que neix dins d'una funció mor amb ella: si necessites aquest valor a fora, retorna'l amb return.

Assignar a una global creient que la canvies. comptador = 10 dins d'una funció crea una local nova i deixa la global intacta, sense donar cap error. Si el valor de fora «no s'actualitza», és això.

UnboundLocalError. Apareix quan llegeixes un nom que també assignes dins de la funció: l'assignació l'ha fet local a tot el cos. Solució: rep-lo per paràmetre i retorna'l.

Ombrejar un built-in. Anomenar list, input, str o sum una variable trenca el programa molt després i en un altre lloc. Afegeix-hi una paraula: llista_tasques, text_entrada. I no abusis de global: funciona, i per això tempta, però cada global que escrius és una funció que ja no pots raonar per separat.

Consell: fes la prova del retall. Copia una funció a un fitxer buit. Si funciona sense arrossegar res més, és autònoma; si li falten noms, aquests noms haurien de ser paràmetres. I posa les constants a dalt i en MAJÚSCULES: és el senyal visual de «això és global a propòsit», i fa que qualsevol altra global salti a la vista com a sospitosa.

Exercicis

Exercici 1: Predir tres sortides

Digues què imprimeix cada bloc, i si escau quin error es produeix i per què.

# Bloc A
x = 5
def f():
    print(x)
f()

# Bloc B
y = 5
def g():
    y = 99
    print(y)
g()
print(y)

# Bloc C
z = 5
def h():
    print(z)
    z = 99
h()

Exercici 2: Convertir globals en paràmetres

Reescriu aquest programa perquè cap funció no depengui de variables globals canviants, fent servir paràmetres i return. Les constants en MAJÚSCULES es poden quedar on són.

TARIFA = 110.0
hores_acumulades = 0

def afegir_hores(hores):
    global hores_acumulades
    hores_acumulades = hores_acumulades + hores

def facturar():
    global hores_acumulades
    return hores_acumulades * (TARIFA / 8)

afegir_hores(8)
afegir_hores(4)
print(facturar())

Exercici 3: Trobar el sabotatge

Aquest programa imprimeix Hola i després es trenca. Explica exactament què passa, a quina línia es causa el dany i a quina es manifesta, i arregla'l.

def saludar(text):
    print(text)

saludar("Hola")
str = "Cartell fira del llibre"
print(str)
saludar(str(2026))

Solucions

Solució 1.

Bloc Sortida Explicació
A 5 La funció només llegeix la global: la cerca LEGB no la troba a local, baixa a global i la fa servir
B 99 i després 5 L'assignació crea una local y que tapa la global durant la crida; la global no es toca
C UnboundLocalError Hi ha una assignació a z al cos, així que z és local a tota la funció, inclòs el print anterior a l'assignació

El bloc C és el més instructiu: la línia que falla (print(z)) és anterior a la línia culpable (z = 99). Python decideix quins noms són locals en compilar la funció, no en executar-la línia a línia.

Solució 2.

TARIFA = 110.0

def afegir_hores(acumulades, hores):
    """Retorna el nou total d'hores despres d'afegir les indicades."""
    return acumulades + hores

def facturar(acumulades, tarifa=TARIFA):
    """Retorna l'import corresponent a les hores acumulades."""
    return acumulades * (tarifa / 8)

hores = 0
hores = afegir_hores(hores, 8)
hores = afegir_hores(hores, 4)
print(f"{facturar(hores):.2f} EUR")     # 165.00 EUR

L'acumulador continua existint, però ara viu al programa principal i viatja explícitament per paràmetres i valors retornats: a cada línia es veu qui canvia què. A més, facturar accepta una tarifa diferent si algun dia cal, sense tocar la constant.

Solució 3. La línia str = "Cartell fira del llibre" ombreja la funció integrada str, deixant aquest nom apuntant a una cadena. Encara no falla res: print(str) imprimeix el text sense problema. El dany es manifesta dues línies després, a str(2026), que intenta cridar una cadena com si fos una funció i produeix TypeError: 'str' object is not callable. La correcció és reanomenar la variable:

titol = "Cartell fira del llibre"
print(titol)
saludar(str(2026))

La moralitat és la distància entre la causa i el símptoma: en un fitxer llarg, aquestes dues línies podrien estar separades per dues-centes, i el missatge d'error apuntaria a la innocent.

Conclusió

Ja saps on viu cada nom. Les variables creades dins d'una funció són locals: neixen amb la crida, moren en acabar i no existeixen a fora, cosa que garanteix que una funció no trepitgi res de la resta del programa. Els noms es busquen seguint la regla LEGB —local, envolupant, global, integrat—, i sempre guanya el més proper. Des de dins d'una funció es pot llegir una global sense més, però assignar-la crea una variable local nova; forçar el contrari amb global funciona i gairebé mai no convé, perquè converteix cada funció en una cosa que només s'entén llegint el programa sencer. Ombrejar noms integrats com list o input produeix errors lluny d'on es van causar. I en passar arguments, Python lliura la referència a l'objecte: amb números i cadenes, immutables, reassignar a dins mai no afecta el de fora, així que l'única via de comunicació cap a l'exterior és el return.

La regla que resumeix tot això cap en una línia: una funció hauria de dependre només dels seus paràmetres, i comunicar-se amb l'exterior només pel seu valor retornat. Les constants en MAJÚSCULES són l'excepció acceptada, perquè no canvien. Aplicant-la, l'auditoria de TascaFàcil ha deixat netes les quatre funcions de validació i ha corregit mostrar_resum(), que llegia l'estat global i ara rep el que necessita. Ja tens les tres peces de l'enginyeria de funcions: definir i cridar (04-01), paràmetres i return (04-02) i àmbit (04-03). El que falta és el criteri per fer-les servir a escala: com partir un programa sencer de cent línies en funcions de la mida adequada, com decidir què va a cadascuna i en quin ordre escriure-les. Això és Descompondre un programa en funcions, on TascaFàcil passarà per fi de la v0.6 a la v0.7.

© Copyright 2026. Tots els drets reservats