Generadores

Generadores

Un generador es un objeto que nos permite iterar sobre una secuencia de valores con la particularidad de no tener que crear explícitamente dicha secuencia. Esta propiedad los hace idóneos para situaciones en las que el tamaño de las secuencias podría tener un impacto negativo en el consumo de memoria.

De hecho ya hemos visto algunos generadores y los hemos usado de forma directa. Un ejemplo es range() que ofrece la posibilidad de crear secuencias de números.

Básicamente existen dos implementaciones de generadores:

  • Funciones generadoras.
  • Expresiones generadoras.
Nota: A diferencia de las funciones ordinarias, los generadores tienen la capacidad de «recordar» su estado para recuperarlo en la siguiente iteración y continuar devolviendo nuevos valores.

Funciones generadoras

Las funciones generadoras se escriben como funciones ordinarias con el matiz de incorporar la sentencia yield que sustituye, de alguna manera, a return. Esta sentencia devuelve el valor indicado y, a la vez, «congela» el estado de la función para subsiguientes ejecuciones.

Veamos un ejemplo en el que escribimos una función generadora de números pares:

Generadores

Generadores

Una vez creado el generador, ya podemos iterar sobre él:

Generadores

Si queremos «explicitar» la lista de valores que contiene un generador, podemos hacerlo de la siguiente manera:

Generadores

Importante: Un detalle muy importante sobre los generadores es que «se agotan». Es decir, una vez que ya hemos consumido todos sus elementos ya no obtendremos nuevos valores.

Expresiones generadoras

Una expresión generadora es sintácticamente muy similar a una lista por comprensión, pero utilizamos paréntesis en vez de corchetes. Se podría ver como una versión acortada de una función generadora.

Podemos tratar de reproducir el ejemplo visto en funciones generadoras en el que creamos números pares hasta el 20:

Generadores

Nota: Las expresiones generadoras admiten condiciones y anidamiento de bucles, tal y como se vio con las listas por comprensión.

Ejercicio

Escriba una función generadora que devuelva los 100 primeros números enteros elevados al cuadrado.

Decoradores

Hay situaciones en las que necesitamos modificar el comportamiento de funciones existentes pero sin alterar su código. Para estos casos es muy útil usar decoradores.

Un decorador es una función que recibe como parámetro una función y devuelve otra función. Se podría ver como un caso particular de clausura.

Veamos un ejemplo en el que documentamos la ejecución de una función:

Generadores

Ahora vamos a definir una función ordinaria (que usaremos más adelante):

Generadores

Ahora aplicaremos el decorador definido previamente simple_logger() sobre la función ordinaria hi(). Se dice que que simple_logger() es la función decoradora y que hi() es la función decorada. De esta forma obtendremos mensajes informativos adicionales. Además el decorador es aplicable a cualquier número y tipo de argumentos e incluso a cualquier otra función ordinaria:

Generadores

Generadores

Usando @ para decorar

Python nos ofrece un «syntactic sugar» para simplificar la aplicación de los decoradores a través del operador @ justo antes de la definición de la función que queremos decorar:

Generadores

Podemos aplicar más de un decorador a cada función. Para ejemplificarlo vamos a crear dos decoradores muy sencillos:

Generadores

Ahora aplicaremos ambos decoradores sobre una función que realiza el producto de dos números:

Generadores

Importante: Cuando tenemos varios decoradores aplicados a una función, el orden de ejecución empieza por aquel decorador más «cercano» a la definición de la función.

Ejercicio

Escriba un decorador llamado fabs() que convierta a su valor absoluto los dos primeros parámetros de la función que decora y devuelva el resultado de aplicar dicha función a sus dos argumentos. El valor absoluto de un número se obtiene con la función abs().

A continuación probar el decorador con una función fprod() que devuelva el producto de dos valores, jugando con números negativos y positivos.

¿Podrías extender el decorador para que tuviera en cuenta un número indeterminado de argumentos posicionales?

Ejemplo

  • Entrada: -3 y 7
  • Salida: 21

Funciones recursivas

La recursividad es el mecanismo por el cual una función se llama a sí misma:

Generadores

Generadores

 

Veamos ahora un ejemplo más real en el que computar el enésimo término de la Sucesión de Fibonacci utilizando una función recursiva:

Generadores

Función generadora recursiva

Si tratamos de extender el ejemplo anterior de Fibonacci para obtener todos los términos de la sucesión hasta un límite, pero con la filosofía recursiva, podríamos plantear el uso de una función generadora:

Generadores

Generadores

Ejercicio

Escriba una función recursiva que calcule el factorial de un número:

n! = n * (n — 1) * (n — 2) * … * 1

Ejemplo

  • Entrada: 5
  • Salida: 120
6.1.6 Espacios de nombres

Como bien indica el Zen de Python:

Namespaces are one honking great idea – let’s do more of those!

Que vendría a traducirse como: «Los espacios de nombres son una gran idea – hagamos más de eso». Los espacios de nombres permiten definir ámbitos o contextos en los que agrupar nombres de objetos.

Los espacios de nombres proporcionan un mecanismo de empaquetamiento, de tal forma que podamos tener incluso nombres iguales que no hacen referencia al mismo objeto (siempre y cuando estén en ámbitos distintos).

Cada función define su propio espacio de nombres y es diferente del espacio de nombres global aplicable a todo nuestro programa.

Generadores

Figura 6: Espacio de nombres global vs espacios de nombres de funciones

Acceso a variables globales

Cuando una variable se define en el espacio de nombres global podemos hacer uso de ella con total transparencia dentro del ámbito de las funciones del programa:

Generadores

Creando variables locales

En el caso de que asignemos un valor a una variable global dentro de una función, no estaremos modificando ese valor. Por el contrario, estaremos creando una variable en el espacio de nombres local:

Generadores

Forzando modificación global

Python nos permite modificar una variable definida en un espacio de nombres global dentro de una función. Para ello debemos usar el modificador global:
Generadores

Contenido de los espacios de nombres

Python proporciona dos funciones para acceder al contenido de los espacios de nombres: locals() Devuelve un diccionario con los contenidos del espacio de nombres local. globals() Devuelve un diccionario con los contenidos del espacio de nombres global.

Generadores

Generadores

Generadores

EJERCICIOS DE REPASO

1. Escriba una función en Python que indique si un número está en un determinado intervalo •

  • Entrada: valor=3; lim_inferior=2; lim_superior=5
  • Salida: True

2. Escriba una función en Python que reciba una lista de valores enteros y devuelva otra •

  • Entrada: [1, 2, 3, 4, 5, 6, 7, 8, 9]
  • Salida: [2, 4, 6, 8]

3. Escriba una función en Python que indique si un número es perfecto. Utilice una función •

  • Entrada: 8128
  • Salida: True

4. Escriba una función en Python que determine si una cadena de texto es un palíndromo

  • Entrada: ana lava lana
  • Salida: True

5. Escriba una función en Python que determine si una cadena de texto es un pangrama •

  • Entrada: The quick brown fox jumps over the lazy dog
  • Salida: True

AMPLIAR CONOCIMIENTOS

  • Comparing Python Objects the Right Way: «is» vs «==»
  • Python Scope & the LEGB Rule: Resolving Names in Your Code
  • Defining Your Own Python Function
  • Null in Python: Understanding Python’s NoneType Object
  • Python “!=” Is Not “is not”: Comparing Objects in Python
  • Python args and kwargs: Demystified
  • Documenting Python Code: A Complete Guide
  • Thinking Recursively in Python
  • How to Use Generators and yield in Python
  • How to Use Python Lambda Functions
  • Python Decorators 101
  • Writing Comments in Python
  • Introduction to Python Exceptions
  • Primer on Python Decorators

Publicaciones Similares