Cap. 12 - Módulos
Un módulo es un archivo que contiene definiciones y sentencias Python que se quieren usar en otros programas Python. Hay varios módulos de Python que vienen con el propio lenguaje como parte de la librería estándar (standard library). Ya vimos dos de estos módulos hasta ahora: string y turtle.
También hemos visto cómo acceder a la ayuda (help). El sistema de ayuda contiene una lista de todos los módulos estándar que están disponibles en Python. Vale la pena consultarlo!
12.1) Números aleatorios (random)
Es frecuente que necesitemos números aleatorios en programas, aquí hay una lista de casos típicos:
- Para jugar un juego de azar en que la computadora debe arrojar un dado, elegir un número o tirar una moneda.
- Para mezclar al azar un mazo de cartas.
- Para hacer que un barco enemigo aparezca en un sitio aleatorio y desde allí comience a disparar sobre el jugador.
- Para simular una lluvia si estamos haciendo una representación computarizada para simular el impacto ambiental de la construcción de una represa.
- Para encriptar sesiones bancarias en internet.
Python provee un módulo random que ayuda con tareas como éstas. Se puede aprender más sobre él leyendo la ayuda, pero lo que vamos a utilizar de este módulo se muestra aquí:
#Crear una caja negra que genere números aleatorios rng = random.Random() lanzamiento_de_dado = rng.randrange(1, 7) #Devuelve un entero de 1 a 6 espera_en_segundos = rng.random() * 5;
El método randrange genera un entero entre su primer y su segundo argumento, aplicando la misma semántica que range (por lo cual el extremo inferior es incluido, pero el superior es excluido). Todos los valores tienen la misma probabilidad de ocurrir (es decir, los resultados están uniformemente distribuidos). Como range, randrange también puede tomar un argumento step opcional. Por ejemplo, supongamos que necesitamos generar al azar un número impar menor que 100:
r_impar = rng.randrange(1, 100, 2)
Otros métodos permiten generar otras distribuciones, por ejemplo la distribución normal (la de forma de campana) que puede ser más apropiada para estimar lluvias estacionales o la concentración de un compuesto en el cuerpo después de tomar una dosis medicinal. (El módulo random de Python tiene incorporadas estas distribuciones como opciones vía parámetros? O hay que implementarlas/simularlas?)
El método random retorna un número de tipo float en el intervalo [0.0, 1.0), donde el corchete y el paréntesis indican que el intervalo es cerrado por la izquierda y abierto por la derecha, es decir, 0.0 es posible como resultado, pero 1.0 nunca va a ocurrir. Es habitual escalar el resultado después del llamado a este método, para obtener números al azar en cualquier intervalo deseado. En el caso mostrado en el código anterior multiplicamos por cinco para obtener una espera_en_segundos que fuera un número al azar en el intervalo [0.0, 5.0). Como en el caso anterior, estos son números uniformemente distribuidos (los números cercanos a 0.0 son tan probables como los cercanos a 0.5 o a 1.0).
El siguiente ejemplo muestra cómo mezclar una lista de 52 números (shuffle no puede trabajar directamente con una lazy promise, por lo cual tuvimos que convertir el objeto range usando el convertidor de tipos list primero). El efecto obtenido es: list(range(52)) es una lista completa con los 52 números desde 0 a 51.
cartas = list(range(52)) # Genera enteros [0 .. 51] rng.shuffle(cartas) # Mezcla el paquete de 52 "cartas"
12.1.1) Repetibilidad y Testeo
Los generadores de números aleatorios se implementan en realidad mediante un algoritmo determinístico (repetible y predecible). Por lo tanto se los llama generadores pseudo-aleatorios (pseudo-random), pues no son auténticamente aleatorios. Comienzan con un valor semilla. Cada vez que se pide un nuevo número aleatorio, se obtiene uno basado en el actual atributo semilla, y el estado de la misma (que es uno de los atributos del generador) es actualizado.
Para hacer debugging y testing de unidades de testeo, conviene tener repetibilidad (la capacidad de repetir exactamente los mismos casos en cada repetición del testeo). Podemos forzar este comportamiento haciendo que el generador aleatorio sea siempre inicializado con la misma semilla. Con frecuencia esto sólo es deseable durante el testeo - jugar un juego de cartas en que el mazo es mezclado siempre de la misma forma se volvería rápidamente muy aburrido!
drng = random.Random(123) # Crear un generador con un estado inicial conocido
Esta forma alternativa de crear un generador de números aleatorios da una semilla explícita al objeto. Si no se pasa este argumento, el sistema muy probablemente usará algo basado en la hora actual. Así que si se toman algunos números aleatorios de drng en sucesivos testeos se obtendrán una y otra vez los mismos resultados!
12.1.2) Tomando bolas de bolsas, tirando dados, mezclando un mazo de cartas
He aquí un ejemplo que genera una lista que contiene n enteros al azar entre un límite inferior y un límite superior:
def lista_enteros_aleatorios(num, borde_inferior, borde_superior): """ Genera una lista que contiene num enteros aleatorios entre borde_inferior y borde_superior, siendo este último un borde abierto. """ rng = random.Random() # Crear un generador de números aleatorios resultado = [] for i in range(num): resultado.append(rng.randrange(borde_inferior, borde_superior)) return resultado
El output que se obtiene por un llamado típico es:
>>> lista_enteros_aleatorios(20, 5, 25) [9, 7, 6, 17, 8, 7, 17, 18, 9, 24, 14, 12, 12, 6, 17, 6, 17, 22, 18, 11]
Observar que tuvimos duplicados en el resultado. Con frecuencia esto es aceptable (si tiramos un dado varias veces, esperamos que ocurran repeticiones).
Pero ¿qué pasa si no queremos duplicados? Si quisiéramos que todos los resultados fueran distintos, podemos hacer esto: generar la lista de posibilidades, mezclarla y luego tomar de allí la cantidad de elementos que se quieran. Por ejemplo, el siguiente programa devuelve 5 meses al azar, sin duplicados (representando los meses por números del 1 al 12)
def cinco_meses_sin_duplicados(): xs = list(range(1,13)) # Crear una lista con los valores 1..12 (No hay duplicados aquí) rng = random.Random() # Crear un generador de números aleatorios rng.shuffle(xs) # Mezclar la lista resultado = xs[:5] # Tomar los primeros 5 elementos return resultado
En cursos de estadística, el primer caso (permitir duplicados) se conoce como tomar bolas de una bolsa con reemplazo (se devuelve cada bola después de sacarla, por lo cual puede volver a salir). Mientras que el segundo caso, sin duplicados, se conoce como tomar bolas de una bolsa sin reemplazo (una vez que la bola es quitada de la bolsa, no es devuelta, y por lo tanto no puede volver a salir). Los juegos de lotería funcionan de esta manera.
El algoritmo que recién presentamos para generar números aleatorios sin repeticiones no sería buena idea si tuviéramos que elegir unos pocos elementos de un dominio muy grande. Supongamos que queremos 5 números elegidos al azar entre 1 y 10 millones, sin duplicados. Generar una lista de 10 millones de items, mezclarla, y luego cortar sus 5 primeros elementos sería un desperdicio de espacio en memoria y tiempo! Entonces podemos intentarlo de otra manera:
def lista_enteros_aleatorios_sin_duplicados(num, borde_inferior, borde_superior): """ Genera una lista que contiene num enteros aleatorios entre borde_inferior y borde superior, en que borde_superior es abierto. La lista de resultados no puede contener duplicados. """ resultado = [] rng = random.Random() for i in range(num): while True: candidato = rng.randrange(borde_inferior, borde_superior) if candidato not in resultado: break resultado.append(candidato) return resultado xs = lista_enteros_aleatorios_sin_duplicados(5, 1, 10000000) print(xs)
Esto produce 5 números aleatorios, sin duplicados:
[2995530, 7460010, 8456245, 9815177, 6300155]
Pero incluso esta función tiene sus puntos débiles. ¿Puedes imaginar qué ocurrirá en el siguiente caso?
xs = lista_enteros_aleatorios_sin_duplicados(10, 1, 6)
- Respuesta: Caerá en loop infinito, porque una vez que se hayan introducido 6 valores en la lista, el intento de obtener el 7° nunca permitirá hacer un break para salir del while True:
12.2) El módulo TIME
A medida que comenzamos a trabajar con algoritmos más sofisticados y programas más grandes, una preocupación natural es "es nuestro código eficiente"? Una forma de saberlo es tomar el tiempo que lleva ejecutar cada operación. El módulo time tiene una función time que se recomienda para hacer esto. Donde sea que se llame a time, devuelve un número de tipo float que representa cuántos segundos pasaron desde una fecha fija convencional conocida como epoch. Esa fecha puede depender de la plataforma, pero suele ser el 1 de enero de 1970 a las 00:00:00 (UTC) en Windows y la mayor parte de las plataformas Unix.
El modo de usarlo es llamar a time y asignar su valor a una variable, por ejemplo t0, justo antes de comenzar a ejecutar la parte del código que se quiere medir. Concluida la ejecución de dicha parte, volver a llamar a time y guardar el resultado en una variable t1. La diferencia t1 - t0 es el tiempo que transcurrió, y nos puede servir para medir qué tan rápido está corriendo nuestro pgrograma.
Veamos un pequeño ejemplo. Python tiene una función built-in sum que puede sumar los elementos de una lista. Podemos nosotros también crear nuestra propia función para hacer lo mismo. ¿Cuál de las dos será más eficiente? Trataremos de sumar los valores de una lista [0, 1, 2, ...] con ambas funciones, y veremos cuál es más eficiente.
def mi_suma_de_lista(xs): suma = 0 for v in xs: suma += v return suma sz = 10000000 # Vamos a testear con una lista con 10 millones de elementos testdata = range(sz) t0 = time.time() mi_resultado = mi_suma_de_lista(testdata) t1 = time.time() print("mi_resultado = {0} (tiempo requerido = {1:.4f} segundos".format(mi_resultado, t1 - t0)) t2 = time.time() su_resultado = sum(testdata) t3 = time.time() print("su_resultado = {0} (tiempo requerido = {1:.4f} segundos".format(su_resultado, t3 - t2))
En una laptop común y corriente, obtuve estos resultados:
mi_resultado = 49999995000000 (tiempo requerido = 1.1871 segundos su_resultado = 49999995000000 (tiempo requerido = 0.6406 segundos
Por lo cual nuestra función es un 85% más lenta que la que viene con Python. Generar y sumar 10 millones de elementos en menos de un segundo no es poca cosa!
12.3) El módulo MATH
El módulo math contiene el tipo de funciones matemáticas que típicamente se encuentran en una calculadora: sin, cos, sqrt, asin, log, log10, así como algunas constantes matemáticas como pi y e.
>>> import math >>> math.pi # Constante pi 3.141592653589793 >>> math.e # Constante e, base del logaritmo natural 2.718281828459045 >>> math.sqrt(2.0) # Función raíz cuadrada (sqrt) 1.4142135623730951 >>> math.radians(90) # Convertir 90 grados a radianes 1.5707963267948966 >>> math.sin(math.radians(90)) # Encontrar el seno de 90 grados 1.0 >>> math.asin(1.0) * 2 # Duplicar el arcoseno de 1.0 para obtener pi 3.141592653589793
Como en casi todos los lenguajes de programación, los ángulos se expresan en radianes y no en grados. Hay dos funciones radians y degrees que permiten convertir entre estas dos formas habituales de medir ángulos.
Observar una diferencia entre nuestro uso de este módulo y nuestro uso de random y turtle. En éstos creábamos objetos y llamábamos a métodos de estos objetos. Esto es debido a que los objetos tenían estado (una tortuga tiene color, posición, dirección hacia la que apunta, etc., y todo generador de números aleatorios tiene una semilla generadora que determina su resultado siguiente).
Las funciones matemáticas son "puras" y no tienen ningún estado (el cálculo de la raíz cuadrada de 2.0 no depende de ninguna clase de estado o de historia sobre qué es lo que ocurrió en el pasado). Por lo tanto las funciones no son métodos de un objeto - son simplemente funciones que están agrupadas en un módulo llamado math.
12.4) Creando tus propios módulos
Todo lo que necesitas para crear tus propios módulos es guardar tu script con la extensión .py. Supongamos, por ejemplo, que guardamos el siguiente script en el archivo herramientas_secuencias.py
def eliminar_de_posicion(pos, secuencia): return secuencia[:pos] + secuencia[pos + 1:]
Ahora podemos usar nuestro módulo, tanto en scripts que escribamos más adelante, o en el intérprete interactivo de Python. Para hacerlo, debemos comenzar por importar el módulo:
>>> import herramientas_secuencias >>> s = "Una frase!" >>> herramientas_secuencias.eliminar_de_posicion(5, s) 'Una fase!'
No incluimos la extensión .py del nombre del archivo al importar. Python espera que los archivos de los módulos terminen con .py, por lo cual no es necesario incluir la extensión en la sentencia import.
El uso de módulos permite descomponer programas muy grandes en partes de tamaño manejable, y mantener juntas partes relacionadas.
12.5) Namespaces (espacios de nombres)
Un namespace (espacio de nombres) es una colección de identificadores que pertenecen a un módulo, o a una función (y como pronto veremos, también a una clase). Generalmente, buscamos que el espacio de nombres guarde cosas "relacionadas", por ejemplo, todas las funciones matemáticas, o todas las cosas que típicamente haríamos con números aleatorios.
Cada módulo tiene su propio espacio de nombres, así que podemos utilizar el mismo nombre (para algún identificador) en distintos módulos sin provocar un problema de identificación.
Consideremos por ejemplo estos dos módulos:
# Modulo1.py pregunta = "Cuál es la fuente de toda sabiduría?" respuesta = 373 Y: # Modulo2.py pregunta = "Qué estás buscando?" respuesta = "Es lo que quisiera saber." Ahora podemos importar ambos módulos y acceder a la pregunta y la respuesta de cada uno: import modulo1 import modulo2 print(modulo1.pregunta) print(modulo2.pregunta) print(modulo1.respuesta) print(modulo2.respuesta)
El output de este programa será:
Cuál es la fuente de toda sabiduría? Qué estás buscando? 373 Es lo que quisiera saber.
Las funciones también tienen su propio espacio de nombres:
def f(): n = 7 print("valor de n dentro de f:", n) def g(): n = 42 print("valor de n dentro de g:", n) n = 11 print("valor de n antes de llamar a f:", n) f() print("valor de n después de llamar a f:", n) g() print("valor de n después de llamar a g:", n)
La ejecución del programa produce el siguiente output:
valor de n antes de llamar a f: 11 valor de n dentro de f: 7 valor de n después de llamar a f: 11 valor de n dentro de g: 42 valor de n después de llamar a g: 42
Las tres n no entran en conflicto porque cada una pertenece a un espacio de nombres distinto - son 3 nombres para 3 variables diferentes, del mismo modo como uno se puede encontrar con 3 instancias de persona que se llaman "Bruno".
Los espacios de nombres permiten a varios programadores trabajar en el mismo proyecto sin tener colisiones de nombres.
Cómo se relacionan espacios de nombres, archivos y módulos?
Python tiene un mapeo uno-a-uno muy sencillo: un módulo por archivo, que da pie a un espacio de nombres. También, Python toma el nombre del módulo del nombre del archivo y esto se convierte en el nombre del espacio de nombres. math.py es un archivo, el módulo se llama math y su espacio de nombres es math. Así que en Python los conceptos son más o menos intercambiables.
Sin embargo, en otros lenguajes (ejemplo, C#) se puede definir un módulo en múltiples archivos, o que un archivo tenga múltiples espacios de nombres, o que varios archivos compartan el mismo espacio de nombres. Es decir que en esos lenguajes el nombre del archivo no tiene por qué coincidir con el del espacio de nombres.
Por lo tanto, una buena idea es tratar de mantener una distinción entre ambos conceptos en nuestra mente (una cosa es el archivo, otra cosa es el espacio de nombres).
Los archivos y directorios organizan dónde las cosas se guardan en nuestra computadora. Por el otro lado, los espacios de nombres y módulos son conceptos de programación: nos ayudan a organizar cómo queremos agrupar funciones y atributos que se relacionan entre sí. No tienen que ver tanto con "dónde" se guardan las cosas, y a priori no tendrían por qué coincidir con la estructura de archivos y directorios.
Todo lo anterior implica que en Python, si le cambiáramos el nombre al archivo math.py, esto causaría el cambio del nombre del módulo, y nuestros llamados a import math deberían modificarse para reflejarlo, así como todos nuestros llamados a funciones y atributos del módulo a través del espacio de nombre math, cuyo nombre también habrá cambiado.
En otros lenguajes esto no ocurre necesariamente así. Así que no debemos mezclar los conceptos sólo porque Python los mezcla!
12.6) Reglas de alcance y búsqueda (lookup)
El alcance de un identificador es la región del código del programa en que el identificador puede ser accedido, o usado.
Hay tres tipos importantes de alcance en Python:
- Alcance local - se refiere a identificadores declarados dentro de una función. Estos identificadores se mantienen en el espacio de nombres que pertenece a dicha función y cada función tiene su propio espacio de nombres.
- Alcance global - se refiere a todos los identificadores declarados en el módulo o archivo actual.
- Alcance Built-in - se refiere a todos los identificadores de Python - por ejemplo range o min - que pueden ser utilizados sin importar nada, y están (casi) siempre disponibles. (Seguramente dice "casi" siempre porque las built-in pueden ser ocultas por las globales o locales)
Python (como casi todos los lenguajes de programación) usa reglas de precedencia: el mismo nombre puede ocurrir en varios alcances, pero el más interno, o alcance local, tendrá siempre precedencia sobre el alcance global, y el global siempre se usa en preferencia al built-in. Veamos un ejemplo sencillo:
def range(n): return (123*n) print(range(10))
Qué es mostrado por la función print? Acabamos de definir una función de nombre range, lo que introduce cierta ambigüedad. Cuando usamos range, ¿nos referimos a la nuestra, o a la built-in? La aplicación de las reglas de alcance determina que será nuestra versión de range la llamada, y no la built-in, porque nuestra función range está en el espacio de nombres global, que tiene precedencia sobre los nombres built-in.
Por lo tanto, si bien nombres como range y min son built-in, pueden ser "ocultados" de tu uso si eliges definir tus propias variables o funciones utilizando dichos nombres. Es una práctica confusa y poco recomendable redefinir nombres built-in - pero parte de ser un programador requiere comprender las reglas de alcance y comprender también que puedes hacer cosas turbias que causarán confusión, y que es mejor evitarlas.
Ahora, un ejemplo un poco más complejo:
n = 10 m = 3 def f(n): m = 7 return 2*n+m print(f(5), n, m)
Esto imprime 17 10 3. La razón es que las dos variables m y n en las líneas 1 y 2 están fuera de la función en el espacio de nombres global. Dentro de la función, nuevas variables n y m son definidas y existen sólo durante la ejecución de la función f. Éstas son creadas en el espacio de nombres local de la función f. Dentro del cuerpo de f, las reglas de alcance determinan que usamos las variables locales n y m. Por el contrario, cuando retornamos de f, los argumentos n y m pasados a la función print se refieren a las variables originales de las líneas 1 y 2, y éstas no fueron alteradas por la ejecución de la función f.
Observar también que el def pone al nombre f en el espacio de nombres global aquí. Por lo cual puede ser llamado en la línea print.
¿Cuál es el alcance de la variable n en la línea 1? Su alcance (la región en la que es visible) es en todas las líneas externas a la definición de f, y no es visible en las 3 líneas en que se define a f, porque en ellas se utiliza la definición local de la variable n.
12.7) Atributos y el operador punto
Las variables definidas dentro de un módulo se llaman atributos del módulo. Hemos visto que los objetos también tienen atributos: por ejemplo, la mayor parte de los objetos tienen un atributo __doc__, algunas funciones tienen un atributo __annotations__. Los atributos se acceden mediante el operador punto (.). El atributo pregunta de modulo1 y modulo2 en el ejemplo de la sección 12.5 era accedido usando modulo1.pregunta y modulo2.pregunta.
Los módulos también contienen funciones, y el operador punto permite acceder a ellas del mismo modo. En la sección 12.4 vimos que herramientas_secuencias.eliminar_de_posicion se refiere a la función eliminar_de_posicion del módulo herramientas_secuencias.
Cuando usamos un nombre con punto, muchas veces no referimos a él como nombre completamente cualificado, porque estamos diciendo exactamente a qué atributo pregunta nos queremos referir. Eliminamos así toda posible ambigüedad o error de interpretación.
12.8) Tres variantes de la sentencia IMPORT
Veremos aquí 3 formas distintas de importar nombres al espacio de nombres actual, y usarlos:
import math x = math.sqrt(10)
Aquí simplemente el identificador math es agregado al espacio de nombres. Si se quiere acceder a alguna de las funciones del módulo, es necesario utilizar la notación punto.
Aquí hay otra forma de hacerlo:
from math import cos, sin, sqrt x = sqrt(10)
Los nombres se agregan directamente al espacio de nombres actual, y pueden ser utilizados sin cualificación. El nombre math en sí mismo no es importado, por lo cual el intento de usar el nombre cualificado math.sqrt devolverá un mensaje de error.
Por último tenemos una abreviación conveniente:
from math import * # Importa todos los identificadores de math, # agregándolos al espacio de nombres actual x = sqrt(10) # Se pueden utilizar sin cualificación
De los 3, el primero suele ser el método preferido, incluso si requiere escribir un poco más cada vez que se llama a una función o atributo del módulo. De todas formas se puede abreviar esto, importando el módulo con un nombre más corto:
import math as m m.pi 3.141592653589793
Pero tampoco es tan complicado escribir el nombre del módulo completo, con los editores actuales que hacen auto-completar y muestran menúes con listas emergentes de atributos y funciones.
Finalmente, observemos este caso:
def area(radio): import math return math.pi * radio * radio x = math.sqrt(10) # Esto da un error
Aquí hemos importado math, pero lo hicimos dentro de un espacio de nombres local (el de la función area). Así que el nombre se puede utilizar dentro del cuerpo de la función, pero no en el script que la incluye, porque no es visible en el espacio de nombres global.
12.9) Convierte tu tester de unidades en un módulo
Cerca del final del capítulo 6 (cuando vimos funciones fructíferas) introdujimos el concepto de testeo de unidades, e implementamos nuestra propia función test, y tuvimos que copiarla dentro de cada módulo para el cual escribimos tests. Ahora podemos poner esa definición dentro de un módulo propio, por ejemplo unit_tester.py, y simplemente usar una línea en cada nuevo script:
from unit_tester import test
12.10) Glosario
- atributo (variable definida dentro de un módulo, o de una clase o instancia - los atributos de módulos se pueden acceder usando el operador punto)
- operador punto, nombre totalmente cualificado, sentencia import, método, módulo, espacio de nombres
- colisión de nombres (situación en que dos o más nombres en un espacio de nombres no pueden ser resueltos sin ambigüedad - el uso de la forma "import módulo" evita estas colisiones)
- librería estándar (standard library) (una librería es una colección de software utilizada como herramientas en el desarrollo de software - la librería estándar de un lenguaje de programación es el conjunto de dichas herramientas que son distribuidas con el lenguaje en sí - Python incluye una extensa librería estándar)
12.11) Ejercicios
1) Abre la ayuda sobre el módulo calendar.
- a. Intenta lo siguiente:
import calendar cal = calendar.TextCalendar() # Crea una instancia cal.pryear(2012) # ¿Qué pasa aquí?
- Respuesta: lo que sucede es que se imprime el calendario completo del año 2012.
- b. Observa que la semana empieza en Lunes. Un inquieto estudiante considera que sería mejor comenzar la semana en Martes. Lee la documentación de TextCalendar y fíjate cómo puedes ayudarlo a conseguir lo que se propone. (hacerlo)
- c. Encuentra una función para imprimir sólo el mes de tu cumpleaños. (hacerlo)
- d. Intenta esto:
d = calendar.LocaleTextCalendar(6, "SPANISH") d.pryear(2012)
- Intenta con algún otro lenguaje, incluido alguno que no funcione, y observa qué sucede.
- Respuesta: en el caso "SPANISH", se muestra el calendario en español, curiosamente con las semanas comenzando en domingo (correctamente), y no en lunes.
- En el caso de un idioma inexistente, obtenemos un mensaje de error
- e. Experimenta con calendar.isleap. Qué espera como argumento? Qué devuelve como resultado? Qué tipo de función es ésta?
- Respuesta: espera un año como argumento, devuelve un booleano como resultado; es una función fructífera.
- Toma nota, detalladamente, de lo que has aprendido en este ejercicio. (Posiblemente porque será utilizado más adelante?)
2) Abre la ayuda sobre el módulo math: (hacerlo)
- a. Cuántas funciones hay en ese módulo?
- b. Qué hace math.ceil? Y math.floor? (pista: tanto ceil como floor esperan un argumento float)
- c. Explica cómo hemos estado calculando el mismo valor que math.sqrt sin usar el módulo math.
- Respuesta: elevando a la 1/2
- d. Cuáles son las dos constantes del módulo math?
- Respuesta: e y pi
- Toma nota detallada de tus investigaciones en este ejercicio.
3) Investigar el módulo copy. Qué hace deepcopy? En qué ejercicios del último capítulo podría haber ayudado deepcopy? (hacerlo)
4) Crear un módulo mimodulo1.py. Agregarle atributos miedad para guardar tu edad actual, y año para guardar el año actual. Crea otro módulo mimodulo2.py. Agrega atributos miedad con el valor 0 y año con el valor del año en que naciste. Ahora crea un archivo de nombre namespace_test.py, importa ambos módulo anteriores y escribe la siguiente sentencia: (hacerlo)
print( (mimodulo2.miedad - mimodulo1.miedad) == (mimodulo2.año - mimodulo1.año) )
- Cuando vayas a ejecutar namespace_test.py verás o bien True o False según haya ocurrido o no tu cumpleaños este año.
- Lo que ilustra este ejemplo es que distintos módulos pueden tener el mismo atributo miedad o año. Al estar en distintos espacios de nombres, no hay conflicto entre ellos. Cuando escribismo namespace_test.py, cualificamos totalmente a cuál de las variables año o miedad nos estamos refiriendo.
5) Agrega la siguiente sentencia a mimodulo1.py, mimodulo2.py y namespace_test.py del ejercicio anterior: (hacerlo)
print("My name is", __name__)
- Ejecuta namespace_test.py. ¿Qué sucede? ¿Por qué? Ahora agrega lo siguiente al final de mimodulo1.py
if __name__ == "__main__": print("This won't run if I'm imported.")
- Ejecuta mimodulo1.py y namespace_test.py otra vez. ¿En qué caso ves que se ejecuta la sentencia print?
6) En una línea de comandos Python, intenta lo siguiente:
>>> import this
- ¿Qué dice Tim Peters sobre los espacios de nombres?
- Respuesta: en la última línea de "The Zen of Python, by Tim Peters" se lee: "Namespaces are one honking great idea -- let's do more of those!"
7) Describe la respuesta del intérprete Python a cada una de las siguientes sentencias en una sesión continuada en el intérprete: (hacerlo)
>>> s = "If we took the bones out, it wouldn't be crunchy, would it?" >>> s.split() >>> type(s.split()) >>> s.split("o") >>> s.split("i") >>> "0".join(s.split("o"))
- Asegúrate de comprender por qué obtienes cada resultado. Entonces aplica lo que has aprendido a llenar el body de la función que sigue mediante los métodos join y split de los objetos str:
def myreplace(old, new, s): """ Replace all occurrences of old with new in s. """ ... test(myreplace(",", ";", "this, that, and some other thing") == "this; that; and some other thing") test(myreplace(" ", "**", "Words will now be separated by stars.") == "Words**will**now**be**separated**by**stars.")
- Tu solución debería pasar los tests.
8) Crea un módulo llamado wordtools.py ya preparado para utilizar el módulo de testeo. Luego implementa funciones que pasen los tests siguientes: (hacerlo)
test(cleanword("what?") == "what") test(cleanword("'now!'") == "now") test(cleanword("?+='w-o-r-d!,@$()'") == "word") test(has_dashdash("distance--but")) test(not has_dashdash("several")) test(has_dashdash("spoke--")) test(has_dashdash("distance--but")) test(not has_dashdash("-yo-yo-")) test(extract_words("Now is the time! 'Now', is the time? Yes, now.") == ['now','is','the','time','now','is','the','time','yes','now']) test(extract_words("she tried to curtsey as she spoke--fancy") == ['she','tried','to','curtsey','as','she','spoke','fancy']) test(wordcount("now", ["now","is","time","is","now","is","is"]) == 2) test(wordcount("is", ["now","is","time","is","now","the","is"]) == 3) test(wordcount("time", ["now","is","time","is","now","is","is"]) == 1) test(wordcount("frog", ["now","is","time","is","now","is","is"]) == 0) test(wordset(["now", "is", "time", "is", "now", "is", "is"]) == ["is", "now", "time"]) test(wordset(["I", "a", "a", "is", "a", "is", "I", "am"]) == ["I", "a", "am", "is"]) test(wordset(["or", "a", "am", "is", "are", "be", "but", "am"]) == ["a", "am", "are", "be", "but", "is", "or"]) test(longestword(["a", "apple", "pear", "grape"]) == 5) test(longestword(["a", "am", "I", "be"]) == 2) test(longestword(["this","supercalifragilisticexpialidocious"]) == 34) test(longestword([ ]) == 0)
- Guarda este módulo para que puedas usar sus herramientas en programas futuros.