From 2c79243054ae96d98f2e7e4b84322485f0c421a1 Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga Date: Thu, 28 Nov 2019 12:18:13 +0100 Subject: Scope dinámico MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/04_funciones.md | 166 ++++++++++++++++++++++++++++------------------------ 1 file changed, 91 insertions(+), 75 deletions(-) (limited to 'src') diff --git a/src/04_funciones.md b/src/04_funciones.md index c99ef04..669e796 100644 --- a/src/04_funciones.md +++ b/src/04_funciones.md @@ -195,70 +195,6 @@ pero sí al revés. Los casos son los siguientes: [^generator-expression]: -### Global - -Hemos hablado de qué sentencias crean nuevos contextos, pero no hemos hablado -de qué pasa si esos nuevos contextos crean referencias cuyo nombre es idéntico -al de las referencias globales. - -Partiendo de lo que se acaba de explicar, y antes de adentrarnos en ejemplos, -si se crea una función (o cualquiera de las otras estructuras) python creará un -contexto para ella. Una vez creado, al crear una variable en este nuevo -contexto, python añadirá una nueva entrada en su tabla hija con el nombre de la -variable. Al intentar consultarla, python encontrará que en su tabla hija -existe la variable y tomará el valor con el que la declaramos. Cuando la -función termine, la tabla de contexto asociada a la función será eliminada. -Esto siempre es así, independientemente del nombre de referencia que hayamos -seleccionado. Por tanto, si el nombre ya existía en alguno de los contextos -padre, lo ocultaremos, haciendo que dentro de esta función se encuentre el -nombre recién declarado y no se llegue a buscar más allá. Cuando la función -termine, como el contexto asociado a ésta no está en la zona de búsqueda de la -función madre, en la función madre el valor seguirá siendo el que era. - -Ilustrándolo en un ejemplo: - -``` python ->>> a = 1 ->>> def f(): -... a = 2 -... print(a) -... ->>> a -1 ->>> f() -2 -``` - -Aunque el nombre de la referencia declarada en el interior sea el mismo que el -de una referencia externa su declaración no afecta, lógicamente, al exterior -ya que ocurre en un contexto independiente. - -Para afectar a la referencia externa, python dispone de la sentencia `global`. -La sentencia `global` afecta al bloque de código actual, indicando que los -identificadores listados deben interpretarse como globales. De esta manera, si -se reasigna una referencia dentro de la función, no será el contexto propio el -que se altere, sino el contexto padre que la albergue. - -``` python ->>> a = 1 ->>> def f(): -... global a -... a = 2 -... print(a) -... ->>> f() -2 ->>> a -2 -``` - -> NOTA: Te recomiendo, de todas formas, que no edites valores globales desde -> el cuerpo de funciones. Es más elegante y comprensible si los efectos de las -> funciones sólo se aprecian en los argumentos de entrada y salida. - -Para más detalles sobre limitaciones y excepciones, puedes buscar en la ayuda -ejecutando `help("global")`. - ### Scope léxico, Closures Hemos dicho que las funciones pueden declararse dentro de funciones, pero no @@ -332,6 +268,87 @@ A nivel práctico, las *closures* son útiles para muchas labores que iremos desgranando de forma accidental. Si tienes claro el concepto te darás cuenta dónde aparecen en los futuros ejemplos. +### Scope dinámico: `global` y `nonlocal` + +Hemos hablado de qué sentencias crean nuevos contextos, pero no hemos hablado +de qué pasa si esos nuevos contextos crean referencias cuyo nombre es idéntico +al de las referencias que aparecen en contextos superiores. + +Partiendo de lo que se acaba de explicar, y antes de adentrarnos en ejemplos, +si se crea una función (o cualquiera de las otras estructuras) python creará un +contexto para ella. Una vez creado, al crear una variable en este nuevo +contexto, python añadirá una nueva entrada en su tabla hija con el nombre de la +variable. Al intentar consultarla, python encontrará que en su tabla hija +existe la variable y tomará el valor con el que la declaramos. Cuando la +función termine, la tabla de contexto asociada a la función será eliminada. +Esto siempre es así, independientemente del nombre de referencia que hayamos +seleccionado. Por tanto, si el nombre ya existía en alguno de los contextos +padre, lo ocultaremos, haciendo que dentro de esta función se encuentre el +nombre recién declarado y no se llegue a buscar más allá. Cuando la función +termine, como el contexto asociado a ésta no está en la zona de búsqueda de la +función madre, en la función madre el valor seguirá siendo el que era. + +Ilustrándolo en un ejemplo: + +``` python +>>> a = 1 +>>> def f(): +... a = 2 +... print(a) +... +>>> a +1 +>>> f() +2 +``` + +Aunque el nombre de la referencia declarada en el interior sea el mismo que el +de una referencia externa su declaración no afecta, lógicamente, al exterior +ya que ocurre en un contexto independiente. + +Para afectar a la referencia global, python dispone de la sentencia `global`. +La sentencia `global` afecta al bloque de código actual, indicando que los +identificadores listados deben interpretarse como globales. De esta manera, si +se reasigna una referencia dentro de la función, no será el contexto propio el +que se altere, sino el contexto global, el padre de todos los contextos. + +``` python +>>> a = 1 +>>> def f(): +... global a +... a = 2 +... print(a) +... +>>> f() +2 +>>> a +2 +``` + +> NOTA: Te recomiendo, de todas formas, que no edites valores globales desde +> el cuerpo de funciones. Es más elegante y comprensible si los efectos de las +> funciones sólo se aprecian en los argumentos de entrada y salida. + +Para más detalles sobre limitaciones y excepciones, puedes buscar en la ayuda +ejecutando `help("global")`. + +El caso de `nonlocal` es similar, sin embargo, está diseñado para trabajar en +contextos anidados. Es decir, en lugar de saltar a acceder a una variable +global, `nonlocal` la busca en cualquier contexto que no sea el actual. +`nonlocal` comienza a buscar las referencias en el contexto padre y va saltando +hacia arriba en la jerarquía en busca de la referencia. Para saber más: +`help("nonlocal")`. + +La diferencia principal entre ambas es que `global` puede crear nuevas +referencias, ya que se sabe a qué contexto debe afectar: al global. Sin +embargo, `nonlocal` necesita que la referencia a la que se pretende acceder +esté creada, ya que no es posible saber a qué contexto se pretende acceder. + +Las sentencias `global` y `nonlocal` son tramposas, ya que son capaces de +alterar el comportamiento del *scope léxico* y convertirlo en *scope dinámico*. +El *scope dinámico* es el caso opuesto al léxico, en el que las funciones +acceden a valores definidos en el contexto donde se ejecutan, no donde se +crean. ## Argumentos de entrada y llamadas @@ -610,26 +627,25 @@ sencilla, y todos los conceptos importantes relacionados con ellas. Un conocimiento que es útil en python, pero que puede ser extendido a casi cualquier lenguaje. -Partiendo del *scope*, has aprendido la diferencia entre lo *local* y lo -*global*. Después, has comprendido que las funciones en python son sólo un -valor más, como puede ser un `int`, y que pueden declararse en cualquier lugar, -lo que te abre la puerta a querer declarar funciones sencillas sin nombre, que -se conocen como funciones *lambda*. +Tras un sencillo acercamiento al *scope*, has comprendido que las funciones en +python son sólo un valor más, como puede ser un `int`, y que pueden declararse +en cualquier lugar, lo que te abre la puerta a querer declarar funciones +sencillas sin nombre, que se conocen como funciones *lambda*. Una vez has aclarado que las funciones son ciudadanos de primera clase (*first-class citizens*) ya estabas preparado para afrontar la realidad del *scope* donde has tratado los contextos y cómo funcionan definiendo el concepto del *scope léxico* que, colateralmente, te ha enseñado lo que es una *closure*, -un método para implementarlo. +un método para implementarlo. Pero también has tenido ocasión de aprender que +en python es posible crear casos de *scope dinámico* mediante las sentencias +`global` y `nonlocal`, que pueden ser útiles, pero es mejor no abusar de ellas. Pero no había quedado claro en su momento cómo funcionaban los argumentos de entrada y las llamadas a las funciones, así que has tenido ocasión de ver por primera vez lo que es un *callable* en python, aunque se te ha prometido -analizarlo en el futuro. - -El último detalle que has aprendido son los tipos de argumentos de entrada que -pueden recibir las funciones, *positional* y *keyword*, y cómo se utilizan en -todas sus posibles formas. +analizarlo en el futuro. Lo que sí que has tenido ocasión de tratar son los +argumentos *positional* y *keyword*, y cómo se utilizan en todas sus posibles +formas. Finalmente, para agrupar todo esto en un único concepto, se te han mostrado los *decorators*, aunque de forma muy general, con el fin de que vieras que todo lo -- cgit v1.2.3