class: center, middle, inverse, title-slide .title[ # Function Scope ] .subtitle[ ## MDS ] .author[ ### Daniel Miles Touya ] .date[ ### 2025-06-23 ] --- <style> .scroll-output { max-height: 300px; overflow-y: auto; background: #f9f9f9; border: 1px solid #ccc; padding: 10px; font-family: monospace; font-size: 70%; white-space: pre-wrap; } </style> # FuncTion Scope - El **scope** (o ámbito) define **dónde** una variable es **reconocida** y **puede ser usada** en el código. - En Python, el scope determina **desde qué partes del programa** se puede acceder a una variable. -- > No todas las variables son **accesibles** desde todas las partes de nuestro programa > No todas las variables **existen** al mismo tiempo --- # Function Scope **Tipos de scope en Python** (regla LEGB): .pull-left[ <img src="scope.jpg" width="400" height="400"> ] -- .pull-right[ 1. **Local**: Dentro de una función. 2. **Enclosing**: En funciones anidadas (una función dentro de otra). 3. **Global**: En el nivel superior del archivo (fuera de funciones). 4. **Built-in**: Palabras reservadas y funciones propias de Python (`len`, `print`, etc.). > Python busca los nombres en este orden: **Local → Enclosing → Global → Built-in** ] --- # Function Scope **¿Por qué es importante entender el Scope?** - **Evita errores comunes**: como `NameError` cuando una variable no está definida en el scope correcto. - **Facilita el diseño de funciones**: puedes usar variables locales sin afectar otras partes del código. - **Permite usar funciones anidadas** y técnicas avanzadas como *closures*. - **Mejora la legibilidad y mantenimiento** del código. - **Te ayuda a depurar problemas** de forma más rápida y precisa. --- # Function Scope Dos tipos de __scope__ generales: **Global** Todo objeto definido en el **cuerpo principal de un file** es accesible en el scope global: será visible en todo el file _Remark_: es posible definir una variable explícitamente como global. -- Vamos a definir una variable global .pull-left[ ````python x = "x global" "x" in globals() ```` ] -- Es accesible hasta dentro de una función .pull-right[ ````python def fx_global(): tipo_de_variable = x print(tipo_de_variable) fx_global() ```` ] --- **Local** Una variable definida **dentro de una función** es local a esa función: es accesible desde el momento en que se define hasta el final de la función, y **existe mientras** la función se esté ejecutando. _Remark_: siempre que se llame a una función, se crea un _scope_ .pull-left[ ````python def fx_local(): x_l = "x_local" print(x_l) fx_local() y = x_l ```` ] -- .pull-right[ ````python x = "global" def fx(): x = "x_local" print(x) fx() print(x) ```` ] --- # Function Scope > Definition global type .pull-left[ ````python x = "global" def fx(): global x x = "x_local" print(x) fx() print(x) ```` ] -- .pull-right[ <b style="color:brown">NO SE RECOMIENDA</b> Estamos modificando variables `globales` accesibles en cualquier parte del programa Funciona tb si no tenemos definido la variable, i.e., defimos la global dentro de la funión ````python def fx(): global x x = "x_local" print(x) fx() print(x) ```` ] --- # Function Scope: Local RESUMEN .pull-left[ Caso 1: Donde se busca a `x` ````python x = 'global x' def test(): y = 'local y' print(x) test() print(x) print(y) ```` `x` no está definida dentro de la función: la busca fuera. ] -- .pull-right[ Caso 2: `x` definida en la función ````python x = 'global x' def test(): x = 'local x' print(x) test() print(x) ```` ] --- # Function Scope: Local Parámetros vinculados a argumentos como variable local: >El argumento va a ser una variable local dentro de la función: no existe fuera .pull-left[ ````python def test(z): print(locals()) print(z) test('local z') ```` El argumento `z` define una variable local ] -- .pull-right[ ````python def test(z, d = "Default"): print(locals()) print(d) test('local z') ```` El argumento `z` y el `d` definen variables locales ] --- # Function Scope: Enclosing Cuando no encuentra en el entorno local, busca en el _enclosing_ scope .pull-left[ Caso 1: Donde se busca a `x` ````python def outer(): x = 'outer x' def inner(): x = 'inner x' print(x) inner() print(x) outer() ```` ] -- .pull-right[ Caso 2: Donde se busca a `x` ````python def outer(): x = 'outer x' def inner(): #x = 'inner x' print(x) inner() print(x) outer() ```` ] Esto es el enclosing: busca la `x` in any of the enclosing function. --- # Function Scope: Enclosing El mismo concepto de global y local se aplica en las funciones: podemos decirle a la función que queremos trabajar con la `x` global en el enclosing scope. __nonlocal__ nos permite trabajar con variables locales de las enclosing functions y modificarlas .pull-left[ ````python def outer(): x = 'outer x' def inner(): nonlocal x x = 'inner x' print(x) inner() print(x) outer() ```` ] -- .pull-left[ ````python x = "x global" def outer(): x = 'outer x' def inner(): nonlocal x x = 'inner x' print(x) inner() print(x) outer() print(x) ```` ] -- > Se modifica la enclosing variable. --- # Function Scope: Visualizar .pull-left[ Caso 1 ````python a = 0 def my_function(): print(a) my_function() ```` ] -- .pull-right[ Caso 2: Donde se busca la `a` ````python a = 0 def my_function(): a = 3 print(a) my_function() print(a) ```` ] -- .pull-left[ Caso 3 ````python a = 0 def my_function(): print(a) a = 3 my_function() print(a) ```` ] -- .pull-right[ <small> - Si se **asigna** una variable dentro de una función (como `a = 3`), → Python **automáticamente la trata como local** en esa función. - **Todas** las referencias a `a` dentro de la función se consideran **locales**. - Al llegar a `print(a)`, Python cree que estás tratando de acceder a la **versión local de `a`**, pero **todavía no ha sido creada**. </small> ]