# asignaciones de variables
x = 1.0
mi_variable = 12.2

"""
Explanation: <table width="100%" border="0">
  <tr>    
  <td><img src="./images/ing.png" alt="" align="left" /></td>
  <td><img src="./images/ucv.png" alt="" align="center" height="100" width="100" /></td>
  <td><img src="./images/mec.png" alt="" align="right"/></td>
  </tr>
</table>

<br>
<h1 style="text-align: center;"> Curso de Python para Ingenieros Mecánicos </h1>
<h3 style="text-align: center;"> Por: Eduardo Vieira</h3>
<br>
<br>
<h1 style="text-align: center;"> Programación básica en Python y el Jupyter Notebook </h1>
<br>
Markdown
Texto en cursiva, negrita o cursiva negrita.
Puedes crear listas numeradas o sin numerar:

Un elemento sun numerar
Sub elemento
Sub sub elemento




Sub elemento
        - Una cosa
        - Otra cosa
Segundo elemento sin numerar
Sub elemento
Tercero
Sub elemento

Numerada:

Punto uno
Sub punto uno
Sub punto dos


Punto dos
Punto tres

Super linea horizontal

Una cita (blockquote)

Hermoso es mejor que feo.
Explícito es mejor que implícito.
Simple es mejor que complejo.
Complejo es mejor que complicado.
Plano es mejor que anidado.
Escaso es mejor que denso.
Cuenta la legibilidad.
Los casos especiales no son lo suficientemente especial como para romper las reglas.
Aunque sentido práctico supera pureza.
Los errores nunca debe pasar en silencio.
A menos que explícitamente silenciados.
Ante la ambigüedad, rechaza la tentación de adivinar.
Debería haber una - y preferiblemente sólo una - manera obvia de hacerlo.
Aunque esa manera puede no ser obvia al principio a menos que seas holandés.
Ahora es mejor que nunca.
Aunque nunca es a menudo mejor que la * justo * ahora.
Si la implementación es difícil de explicar, es una mala idea.
Si la implementación es fácil de explicar, puede ser una buena idea.
Namespaces son una gran idea de fanfarria  - Vamos a hacer más de esos!
Zen de Python

Enlaces:
Google
Títulos:
Título 1
Título 2
Titulo 3
Código
Puedes escribir código que no se ejecutará:
def f(x):
    """Calcula el cuadrado de un número"""
    return x**2

O
python
def f(x):
        """Calcula el cuadrado de un número"""
        return x**2
Otro ejemplo en C
if (i=0; i&lt;n; i++) {
  printf("hello %d\n", i);
  x += 4;
}

O
C
if (i=0; i&lt;n; i++) {
      printf("hello %d\n", i);
      x += 4;
    }
Bonitas ecuaciones en Latex
Gracias a MathJax, podemos icluir fómulas escritas en Latex como
$e^{i\pi} + 1 = 0$  o
$$e^x=\sum_{i=0}^\infty \frac{1}{i!}x^i$$
Las ecuaciones en linea ven entre $:
$e^{i\pi} + 1 = 0$
Expresiones en su propia linea van entre $$:
$$e^x=\sum_{i=0}^\infty \frac{1}{i!}x^i$$
Tablas
| Esta | es   |
|------|------|
|  una  | tabla|
|$\theta$ |$x^2$ |
HTML
tambien podemos escribir en HTML
<table>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
<tr>
<td>row 1, cell 1</td>
<td>row 1, cell 2</td>
</tr>
<tr>
<td>row 2, cell 1</td>
<td>row 2, cell 2</td>
</tr>
</table>

Imagenes y videos
Imagen:

o con HTML
<img src="./images/ucv.png" />
Video
<video controls src="./images/big_buck_bunny.mp4" />

Introducción a la programación en Python
Variables y tipos
Nombres de símbolos
Los nombres de las variables en Python pueden contener los caracteres a-z, A-Z, 0-9 y algunos caracteres especiales como _. Los nombres de variables normales deben comenzar con una letra. 
Por convención, los nombres de las variables comienzan con letra minúscula, mientras que los nombres de las clases comienzan con una letra mayúscula. 
Además, existen algunos palabras claves Python que no pueden ser usados como nombres de variables. Éstas son:
and, as, assert, break, class, continue, def, del, elif, else, except, 
exec, finally, for, from, global, if, import, in, is, lambda, not, or,
pass, print, raise, return, try, while, with, yield

Nota: Atención con la palabra lambda, que podría fácilmente ser un nombre de variable natural en un programa científico. Sin embargo, como es una palabra clave, no puede ser usado como nombre de una variable.
Asignaciones
El operador para asignar valores en Python es el signo igual (=). Python es un lenguage de escritura dinámica, de modo que no necesitamos especificar el tipo de una variable cuando la creamos.
Al asignar un valor a una variable nueva se crea esa variable:
End of explanation
"""


type(x)

"""
Explanation: Aunque no se especifique explícitamente, cada variable sí tiene un tipo asociada a ella. El tipo es extraido del valor que le fue asignado.
End of explanation
"""


x = 1

type(x)

"""
Explanation: Si asignamos un nuevo valor a una variable, su tipo puede cambiar.
End of explanation
"""


print(y)

"""
Explanation: Si tratamos de unsar una variable que no ha sido definida obtenemo un mensaje de error (NameError):
End of explanation
"""


# enteros
x = 1
type(x)

# flotantes
x = 1.0
type(x)

# booleanos
b1 = True
b2 = False

type(b1)

# números complejos: note que se usa `j` para especificar la parte imaginaria
x = 1.0 - 1.0j
type(x)

print(x)

print(x.real, x.imag)

"""
Explanation: Tipos Fundamentales
End of explanation
"""


import types

# imprime todos los tipos definidos en el módulo `types`
print(dir(types))

x = 1.0

# verifica si la variable x es flotante
type(x) is float

# verifica si la variable x es un entero
type(x) is int

"""
Explanation: Funciones de relacionadas con Tipos
El módulo types contiene definiciones de nombres de tipo que pueden ser usadas para testear si las variables son de un cierto tipo:
End of explanation
"""


isinstance(x, float)

"""
Explanation: Podemos también usar el método isinstance para testear tipos de variables:
End of explanation
"""


x = 1.5

print(x, type(x))

x = int(x)

print(x, type(x))

z = complex(x)

print(z, type(z))

x = float(z)

"""
Explanation: Conversión de Tipo
End of explanation
"""


y = bool(z.real)

print(z.real, " -> ", y, type(y))

y = bool(z.imag)

print(z.imag, " -> ", y, type(y))

"""
Explanation: Un número complejo no puede ser convertido a un número flotante o a un entero. Necesitamos usar z.real, o bien z.imag, para extraer la parte que deseamos del número complejo z:
End of explanation
"""


1 + 2, 1 - 2, 1 * 2, 1 / 2

1.0 + 2.0, 1.0 - 2.0, 1.0 * 2.0, 1.0 / 2.0

# División entera de dos númenos flotantes
3.0 // 2.0

# Atención! El operador de potencia en Python no es ^, sino **
2 ** 2

"""
Explanation: Operadores y comparaciones
La mayoría de los operadores y las comparaciones en Python funcionan como one esperaría:

Operadores aritméticos +, -, *, /, // (división entera), '**' potencia
End of explanation
"""


True and False

not False

True or False

"""
Explanation: Los operadores booleanos se escriben como palabras: and, not, or.
End of explanation
"""


2 > 1, 2 < 1

2 > 2, 2 < 2

2 >= 2, 2 <= 2

# igualdad
[1,2] == [1,2]

# ¿objetos identicos?
l1 = l2 = [1,2]

l1 is l2

"""
Explanation: Operadores de comparación &gt;, &lt;, &gt;= (mayor o igual), &lt;= (menor o igual), == igualdad, es identico.
End of explanation
"""


s = "Hola mundo"
type(s)

# longitud de la cadena: el número de caracteres que contiene
len(s)

# reemplaza una subcadena de una cadena por cadena
s2 = s.replace("mundo", "universo")
print(s2)

"""
Explanation: Tipos compuestos: Cadenas, listas y diccionarios
Cadenas
Las cadenas son el tipo de variables que es usado para almacenar mensajes de texto.
End of explanation
"""


s[0]

"""
Explanation: Podemos aislar un carácter en una cadena usando []:
End of explanation
"""


s[0:5]

"""
Explanation: Atención usuarios de MATLAB: el indexado comienza en 0!
Podemos extraer una parte de una cadena usando la sintaxis [desde:hasta], que extrae caracteres entre los índices  desde y hasta:
End of explanation
"""


s[:5]

s[6:]

s[:]

"""
Explanation: Si omitimos desde o bien hasta de [desde:hasta], por defecto se entiende que se refiere al comienzo y/o al fin de la cadena, respectivamente:
End of explanation
"""


s[::1]

s[::2]

"""
Explanation: Podemos también definir el tamaño del paso usando la sintaxis [desde:hasta:paso] (el valor por defecto de paso es 1, como ya vismo):
End of explanation
"""


print("uno", "dos", "tres")  # El comando print puede desplegar varias cadenas

print("uno", 1.0, False, -1j)  # El comendo print convierte todos los argumentos a cadenas

print("uno" + "dos" + "tres") # cadenas "sumadas" con + son contatenadas sin espacio entre ellas

print("valor = %f" % 1.0)       # podemos usar formateo de cadenas en el estilo del lenguaje C

# este formateo crea una cadena
s2 = "valor1 = %.2f. valor2 = %d" % (3.1415, 1.5)

print(s2)

# forma alternativa, más intuitiva para formatear una cadena
s3 = 'valor1 = {0}, valor2 = {1}'.format(3.1415, 1.5)

print(s3)

"""
Explanation: Esta técnica es llamada slicing ("rebanado"). Puede leer más sobre la sintaxis aquí http://pyspanishdoc.sourceforge.net/lib/built-in-funcs.html y aquí (en inglés) http://docs.python.org/release/2.7.3/library/functions.html?highlight=slice#slice
Python tiene un rico conjunto de funciones para procesar texto. Ver por ejemplo http://docs.python.org/2/library/string.html (en inglés) para más información.
Ejemplos de formateo de cadenas
End of explanation
"""


l = [1,2,3,4]

print(type(l))
print(l)

"""
Explanation: Listas
Listas son muy similares a las cadenas, excepto que cada elemento puede ser de un tipo diferente.
La sintaxis para crear listas en Python es [..., ..., ...]:
End of explanation
"""


print(l)

print(l[1:3])

print(l[::2])

"""
Explanation: Podemos usar las mismas técnicas de "rebanado" que usamos en el caso de cadenas para manipular listas:
End of explanation
"""


l[0]

"""
Explanation: Atención usuarios de MATLAB: el indexado comienza en 0!
End of explanation
"""


l = [1, 'a', 1.0, 1-1j]

print(l)

"""
Explanation: Los elementos en una lista no requieren ser del mismo tipo:
End of explanation
"""


lista_anidada = [1, [2, [3, [4, [5]]]]]

lista_anidada

"""
Explanation: Las listas en Python pueden ser inhomogéneas y arbitrariamente anidadas:
End of explanation
"""


desde = 10
hasta = 30
paso = 2

range(desde, hasta, paso)

# en Python 3 range genera un interador, que puede ser convertido a una lista usando 'list(...)'. Esto no tiene efecto en Python 2
list(range(desde, hasta, paso))

list(range(-10, 10))

s

# convierte una cadena a una lista, por conversión de tipo:

s2 = list(s)

s2

# ordenando listas
s2.sort()

print(s2)

"""
Explanation: Las listas juegan un rol muy importante en Python y son, por ejemplo, usadas en bucles y otras estructuras de control de flujo (discutidas más abajo). Existan muchas funciones convenientes para generar listas de varios tipos, por ejemplo la función range:
End of explanation
"""


# crea una nueva lista vacía
l = []

# agrega un elemento usando `append`
l.append("A")
l.append("d")
l.append("d")

print(l)

"""
Explanation: Agregando, insertando, modificando, y removiendo elementos de listas
End of explanation
"""


l[1] = "p"
l[2] = "p"

print(l)

l[1:3] = ["d", "d"]

print(l)

"""
Explanation: Podemos modificar listas asignando nuevos valores a los elementos de la lista. En lenguaje técnico se dice que la lista es mutable.
End of explanation
"""


l.insert(0, "i")
l.insert(1, "n")
l.insert(2, "s")
l.insert(3, "e")
l.insert(4, "r")
l.insert(5, "t")

print(l)

"""
Explanation: Insertar un elemento en una posición específica insert
End of explanation
"""


l.remove("A")

print(l)

"""
Explanation: Eliminar el primer elemento con un valor específico usando 'remove'
End of explanation
"""


punto = (10, 20)

print(punto, type(punto))

punto = 10, 20

print(punto, type(punto))

"""
Explanation: Eliminar un elemento en una posición específica usando del:
Puede introducir help(list) para más detalles, o leer la documentación en la red
Tuplas
Tuplas son similares a las listas, excepto que ellas no pueden ser modificadas una vez creadas, es decir, son inmutables. 
En Python, las tuplas son creadas usando la sintaxis (..., ..., ...), o incluso ..., ...:
End of explanation
"""


x, y = punto

print("x =", x)
print("y =", y)

"""
Explanation: Podemos separar una tupla asignandola a una lista de variables separadas por coma:
End of explanation
"""


punto[0] = 20

"""
Explanation: Si intentamos asignar un nuevo valor a un elemento de una tupla obtenemos un error:
End of explanation
"""


parametros = {"parametro1" : 1.0,
          "parametro2" : 2.0,
          "parametro3" : 3.0,}

print(type(parametros))
print(parametros)

print("parametro1 = " + str(parametros["parametro1"]))
print("parametro2 = " + str(parametros["parametro2"]))
print("parametro3 = " + str(parametros["parametro3"]))

parametros["parametro1"] = "A"
parametros["parametro2"] = "B"

# agrega una nueva entrada
parametros["parametro4"] = "D"

print("parametro1 = " + str(parametros["parametro1"]))
print("parametro2 = " + str(parametros["parametro2"]))
print("parametro3 = " + str(parametros["parametro3"]))
print("parametro4 = " + str(parametros["parametro4"]))

"""
Explanation: Dictionarios
Dictionarios son también como listas, excepto que cada elemento es un par clave-valor. La sintaxsis de los diccionarios es {clave1 : valor1, ...}:
End of explanation
"""


afirmacion1 = False
afirmacion2 = False

if afirmacion1:
    print("afirmacion1 es verdadera")
    
elif afirmacion2:
    print("afirmacion2 es verdadera")
    
else:
    print("afirmacion1 y afirmacion2 son falsas")

"""
Explanation: Control de flujo
Sentencias condicionales: if, elif, else
La sintaxis Python para la ejecución condicional de código usa las palabras clave if, elif (else if), else:
End of explanation
"""


afirmacion1 = afirmacion2 = True

if afirmacion1:
    if afirmacion2:
        print("tanto afirmacion1 como afirmacion2 son verdaderas")

# Mala indentación!
if afirmacion1:
    if afirmacion2:
        print("tanto afirmacion1 como afirmacion2 son verdaderas")  # esta línea está mal indentada

afirmacion1 = False 

if afirmacion1:
    print("afirmacion1 es verdadera")
    
    print("aun estamos dentro del bloque if")

if afirmacion1:
    print("afirmacion1 es verdadera")
    
print("ahora estamos fuera del bloque")

"""
Explanation: Aquí encontramos por primera un aspecto pecular e inusual del lenguaje Python: Los bloques del programa son definidos por su nivel de indentación (la cantidad de espacio antes de cada linea). 
Compare con el código equivalente en C:
if (afirmacion1)
{
    printf("afirmacion1 es verdadera\n");
}
else if (afirmacion2)
{
    printf("afirmacion1 es verdadera\n");
}
else
{
    printf("afirmacion1 y afirmacion2 son falsas\n");
}

En C los bloques son definidos por los paréntesis llaves { y }. El nivel de indentación (espacio en blanco antes del código) no importa (es completamente opcional). 
En Python, la extensión de un bloque de código es definido por el nivel de indentación (usualmente un tab o cuatro espacios en blanco). Esto significa que debemos ser cuidados@s de indentar nuestro código correctamente, de lo contrario tendremos errores de sintaxis. 
Ejemplos:
End of explanation
"""


for x in [1,2,3]:
    print(x)

"""
Explanation: Ciclos
En Python, los ciclos (loops) puede ser programados de varias maneras diferentes. La forma más común es usando un cicle for, que se usa junto con objetos iterables, como por ejemplos las listas. La sintaxis básica es:
Ciclos for:
End of explanation
"""


for x in range(4): # por defecto range comienza con 0
    print(x)

"""
Explanation: El ciclo for itera sobre los elementos de la lista suministrada y ejecuta el bloque suministrado una vez para cada elemento. Cualquier tipo de lista puede ser usada para un ciclo for. Por ejemplo:
End of explanation
"""


for x in range(-3,3):
    print(x)

for palabra in ["computación", "científica", "con", "Python"]:
    print(palabra)

"""
Explanation: Nota: range(4) no incluye el 4 !
End of explanation
"""


for clave, valor in parametros.items():
    print(clave + " = " + str(valor))

"""
Explanation: Para iterar sobre pares clave-valor en un diccionario:
End of explanation
"""


for idx, x in enumerate(range(-3,3)):
    print(idx, x)

"""
Explanation: Algunas veces es útil tener acceso a los índices de los valores mientras se itera sobre una lista. Podemos usar la función enumerate para esto:
End of explanation
"""


l1 = [x**2 for x in range(0,5)]

print(l1)

"""
Explanation: Listas: Creando listas usando ciclos for:
Una forma conveniente y compacta de inicializar listas:
End of explanation
"""


i = 0

while i < 5:
    print(i)    
    i = i + 1
    
print("listo")

"""
Explanation: Ciclos while:
End of explanation
"""


def func0():   
    print("test")

func0()

"""
Explanation: Note que el comando print("listo") no es parte del cuerpo del ciclo while, debido a su indentación.
Funciones
En Python una función es definida usando la palabra clave def, seguida de un nombre para la función, una variable entre paréntesis (), y el símbolo de dos puntos :. El siguiente código, con un nivel adicional de indentación, is el cuerpo de la función.
End of explanation
"""


def func1(s):
    """
    Imprime la cadena 's' y dice cuántos caracteres tiene
    """
    
    print(s + " tiene " + str(len(s)) + " caracteres")

help(func1)

func1("test")

"""
Explanation: En forma opcional, pero muy recomendada, podemos definir un "docstring", que es una descripción del propósito y comportamiento de la función. El docstring debería ser incluido directamente después de la definición de la función, antes del código en el cuerpo de la función.
End of explanation
"""


def cuadrado(x):
    """
    Calcula el cuadrado de x.
    """
    return x**2

cuadrado(4)

"""
Explanation: Funciones que retornan un valor usan la palabra clave return:
End of explanation
"""


def potencias(x):
    """
    Calcula algunas potencias de x.
    """
    return x**2, x**3, x**4

potencias(3)

x2, x3, x4 = potencias(3)

print(x3)

"""
Explanation: Podemos retornar múltiples valores desde una función usando las tuplas (ver más arriba):
End of explanation
"""


def mifunc(x, p=2, debug=False):
    if debug:
        print("evaluando mifunc para x = " + str(x) + " usando el exponente p = " + str(p))
    return x**p

"""
Explanation: Argumentos por defecto y argumentos de palabra clave
En la definición de una función, podemos asignar valores por defecto a los argumentos de la función:
End of explanation
"""


mifunc(5)

mifunc(5, debug=True)

"""
Explanation: Si no suministramos un valor para el argumento debug al llamar a la función mifunc se considera el valor definido por defecto:
End of explanation
"""


mifunc(p=3, debug=True, x=7)

"""
Explanation: Si listamos explícitamente el nombre de los argumentos al llamar una función, ellos no necesitan estar en el mismo orden usando en la definición de la función. Esto es llamado argumentos de palabra clave (keyword), y son a menudo muy útiles en funciones que requieren muchos argumentos opcionales.
End of explanation
"""


f1 = lambda x: x**2
    
# es equivalente a 

def f2(x):
    return x**2

f1(2), f2(2)

"""
Explanation: Funciones sin nombre (funciones lambda)
En Python podemos también crear funciones sin nombre, usando la palabra clave lambda:
End of explanation
"""


# map es una función predefinida en Python
map(lambda x: x**2, range(-3,4))

# in Python 3 podemos usar `list(...)` para convertir la iteración a una lista explícita
list(map(lambda x: x**2, range(-3,4)))

"""
Explanation: Esta técnica es útil, por ejemplo, cuando queremos pasar una función simple como argumento de otra función, como en este caso:
End of explanation
"""


class Punto:
    """
    Clase simple para representar un punto en un sistema de coordenadas cartesiano.
    """
    
    def __init__(self, x, y):
        """
        Crea un nuevo punto en x, y.
        """
        self.x = x
        self.y = y
        
    def traslada(self, dx, dy):
        """
        Traslada el punto en dx y dy en las direcciones x e y respectivamente.
        """
        self.x += dx
        self.y += dy
        
    def __str__(self):
        return("Punto en [%f, %f]" % (self.x, self.y))

"""
Explanation: Clases
Las clases son una característica clave de la programación orientada al objeto. Una clase es una estructura para representar un objeto y las operaciones que pueden ser realizadas sobre el objeto. 
En Python una clase puede contener atributos (variables) y métodos (funciones).
En Python una clase es definida casi como una función, pero usando la palabra clave class, y la definición de la clase usualmente contiene algunas definiciones de métodos (una función en una clase).


Cada método de una clase debería tener un argumento self como su primer argumento. Este objeto es una autoreferencia.


Algunos nombres de métodos de clases tienen un significado especial, por ejemplo:


__init__: El nombre del método que es invocado cuando el objeto es creado por primera vez.

__str__ : Un método que es invocado cuando se necesita una simple representación de cadena de la clase, como por ejemplo cuando se imprime.
Existen muchas más, ver http://docs.python.org/2/reference/datamodel.html#special-method-names
End of explanation
"""


p1 = Punto(0, 0) # eso invoca el método __init__ en la cláse Punto

print(p1)         # esto invoca el método __str__ 

"""
Explanation: Para crear una nuva instancia de una clase:
End of explanation
"""


p2 = Punto(1, 1)

p1.traslada(0.25, 1.5)

print(p1)
print(p2)

"""
Explanation: Para invocar un método en la instancia de clase p:
End of explanation
"""


import math

"""
Explanation: Note que llamar a métodos de clases puede modificar el estado de esa instancia de clase particular, pero no afecta otras instancias de la clase o alguna otra variable global.
Esto es una de las cosas buenas de un diseño orientado al objeto: código como las funciones y variables relacionadas son agrupadas en entidades separadas e independientes. 
Módulos
La mayoría de la funcionalidad en Python es provista por módulos. La Librería Estándar de Python es una gran colección de  módulos que proveen implementaciones multiplataforma de recursos tales como el acceso al sistema operativo, entrada/salido de archivos (file I/O), manejo de cadenas, comunicación en redes, y mucho más.
Para usar un módulo en un programa Python éste debe primero ser importado. Un módulo puede ser importado usando el comando import. Por ejemplo, para importar el módulo math, que contiene muchas funciones matemáticas estándar, podemos usar:
End of explanation
"""


import math

x = math.cos(2 * math.pi)

print(x)

"""
Explanation: Esto incluye el módulo completo y lo deja disponible para su uso en el programa. Por ejemplo, podemos escribir:
End of explanation
"""


from math import *

x = cos(2 * pi)

print(x)

"""
Explanation: Alternativamente, podemos elegir importar todos los símbolos (funciones y variables) en un módulo al espacio de nombres (namespace) actual (de modo que no necesitemos usar el prefijo "math." cada vez que usemos algo del módulo math:
End of explanation
"""


from math import cos, pi

x = cos(2 * pi)

print(x)

"""
Explanation: Esta forma de proceder puede ser muy conveniente, pero en programas largos que incluyen muchos módulos es a menudo una buena idea mantener los símbolos de cada módulo en sus propios espacios de nombres, usando import math. Esto elimina potenciales confusiones con eventuales colisiones de nombres.
Como una tercera alternativa, podemos importar sólo algunos símbolos seleccionados desde un módulo listando explícitamente aquellos símbolos que deseamos importar, en lugar de usar el carácter comodín *:
End of explanation
"""


import math

dir(math)

"""
Explanation: Mirando qué contiene un módulo, y su documentación
Luego que se ha cargado un módulo, podemos listar los símbolos que éste provee usando la función dir:
End of explanation
"""


help(math.log)

log(10) # calcula el logaritmo de 10 en base e

log(10, 2) # calcula el logaritmo de 10 en base 2

"""
Explanation: Usando la función help podemos obtener una descripción de cada función (casi... no todas las funciones tienen docstrings, como se les llama técnicamente. Sin embargo, la mayoría de las funciones están documentadas de esta forma).
End of explanation
"""


%more mimodulo.py

"""
Explanation: También podemos usar la función help directamente sobre los módulos: 
help(math)

Algunos módulos muy útiles de la librería estándar de Python son os (interfaz con el sistema operativo), sys (Parámetros y funciones específicas del sistema), math (funciones matem'aticas), shutil (operaciones con archivos), subprocess, multiprocessing, threading. 
Una lista completa de los módulos estándar para Python 2 y Python 3 está disponible (en inglés) en http://docs.python.org/2/library/ y http://docs.python.org/3/library/, respectivamente. Una versión en español está disponible en http://pyspanishdoc.sourceforge.net/lib/lib.html.
Creación de Módulos
Uno de los conceptos más importantes en programación es el de reusar código para evitar repeticiones.
La idea es escribir funciones y clases con un propósito y extensión bien definidos, y reusarlas en lugar de repetir código similar en diferentes partes del programa (programación modular). Usualmente el resultado es que se mejora ostensiblemente la facilidad de lectura y de mantención de un programa. En la práctica, esto significa que nuestro programa tendrá menos errores, y serán más fáciles de extender y corregir. 
Python permite programación modular en diferentes niveles. Las funciones y las clases son ejemplos de herramientas para programación modular de bajo nivel. Los módulos Python son construcciones de programación modular de más alto nivel, donde podemos colectar variables relacionadas, funciones y clases. Un módulo Python es definido en un archivo Python (con extensión .py), y puede ser accequible a otros módulos Python y a programas usando el comendo import. 
Considere el siguiente ejemplo: el archivo mimodulo.py contiene una implementación simple de una variable, una función y una clase:
End of explanation
"""


import mimodulo

"""
Explanation: Podemos importar el módulo mimodulo a un programa Python usando import:
End of explanation
"""


help(mimodulo)

mimodulo.mi_variable

mimodulo.mi_function() 

mi_clase = mimodulo.MiClase() 
mi_clase.set_variable(10)
mi_clase.get_variable()

"""
Explanation: Use help(module) para obtener un resumen de lo que suministra el módulo:
End of explanation
"""


raise Exception("descripción del error")

"""
Explanation: Excepciones
En Python los errores son manejados con una construcción especial de lenguaje llamada "Exceptions" (excepciones). Cuando ocurre un error, una excepción puede ser hecha, que interrumpe el flujo normal del programa y retorna a algún otro lugar del código donde se definan los comandos try-except más cercanos.
Para generar una excepción podemos usar el comando raise, que toma un argumento que debe ser una instancia de la clase  BaseExpection o una clase derivada de ella.
End of explanation
"""


try:
    print("test")
    # genera un error: ya que la variable test no está definida
    print(test)
except:
    print("Encontré una excepción")

"""
Explanation: Un úso típico de las excepciones es para abortar funciones cuando ocurre algún error, por ejemplo:
def mi_funcion(argumentos):

    if not verify(argumentos):
        raise Expection("Argumentos invalidos")

    # el resto del código sigue aquí

Para capturar los errores que son generados por funciones y métodos de clases, o por el mismo intérprete Python, use los comandos try y except:
try:
    # aquí va el código normal
except:
    # el código para manejar el error va aquí
    # Este código no se ejecuta a menos que 
    # el código de arriba genere un error

Por ejemplo:
End of explanation
"""


try:
    print("test")
    # genera un error: ya que la variable test no está definida
    print(test)
except Exception as e:
    print("Encontré una excepción:" + str(e))

"""
Explanation: Para obtener información sobre un error, podemos accesar la instancia de clase Exception que describe la excepción usando por ejemplo:
except Exception as e:
End of explanation
"""


# Esta celda da el estilo al notebook
from IPython.core.display import HTML
css_file = './css/aeropython.css'
HTML(open(css_file, "r").read())

"""
Explanation: Lectura adicional

http://www.python.org - The official web page of the Python programming language.
http://www.python.org/dev/peps/pep-0008 - Guía de estilo para la programación en Python. Altamente recomendada (en inglés).
http://www.greenteapress.com/thinkpython/ - Un libro gratuito sobre Python.
Python Essential Reference - Un buen libro de referencia sobre programación en Python.
End of explanation
"""


