├── 01_introduccion ├── 00_introduccion.md ├── 01_que_es_python.md ├── 02_instalar_python.md ├── 03_primeros_pasos.md ├── 04_sintaxis_basica.md ├── 05_nombrando_variables_1.md ├── 06_palabras_reservadas.md ├── 07_todo_alcance_variables.md ├── 10_ejecutando_scripts.md ├── duck_typing.md ├── todo_funciones_builtin.md └── unpacking.md ├── 02_estructurasdecontrol ├── 00_estructurasdecontrol.md ├── 01_if.md ├── 02_for.md ├── 03_range.md ├── 04_while.md ├── 05_switch.md ├── 06_match.md ├── 07_break.md ├── 08_continue.md ├── 09_zip.md ├── 10_enumerate.md ├── 11_list_comprehensions.md └── 12_iterables.md ├── 03_tiposyestructuras ├── 00_tiposyestructuras.md ├── 01_enteros.md ├── 02_booleanos.md ├── 03_float.md ├── 04_complejos.md ├── 05_cadenas.md ├── 06_estructuras_listas.md ├── 07_estructuras_sets.md ├── 09_estructuras_tuplas.md ├── 10_estructuras_diccionarios.md ├── 11_estructuras_frozensets.md ├── 12_castings.md ├── 13_colecciones.md └── 14_mutabilidad.md ├── 04_funciones ├── 00_funciones.md ├── 01_introduccion_funciones.md ├── 02_paso_valor_referencia.md ├── 03_args_kwargs.md ├── 04_documentando_funciones.md ├── 05_funciones_lambda.md ├── 06_recursividad.md ├── 07_decoradores.md ├── 09_generadores.md ├── 10_corrutinas.md ├── 11_caching_funciones.md └── 12_prog_funcional.md ├── 05_operadores ├── 00_operadores.md ├── 01_operadores_asignacion.md ├── 02_operadores_aritmeticos.md ├── 03_operadores_relacionales.md ├── 04_operadores_logicos.md ├── 05_operadores_bitwise.md ├── 06_operadores_identidad.md ├── 07_operadores_membresia.md └── 08_operador_walrus.md ├── 06_programacionorientadaobjetos ├── 00_programacionorientaaobjetos.md ├── 01_poo_intro.md ├── 02_poo_tipos_metodos.md ├── 03_poo_herencia.md ├── 04_poo_property.md ├── 05_todo_dunder_methods.md ├── 06_todo_op_overloadig.md ├── 07_clase_abstracta.md ├── exclude_abstraccion.md ├── exclude_acoplamiento.md ├── exclude_crear_clase.md ├── exclude_encapsulamiento.md ├── exclude_polimorfismo.md └── exclude_poo_cohesion.md ├── 07_excepciones ├── 00_excepciones.md ├── 01_excepciones_excep.md ├── 02_excepciones_assert.md ├── 03_excepciones_definiendo.md └── 04_context_managers.md ├── 08_ficheros ├── 00_ficheros.md ├── 01_ficheros_leer.md └── 02_ficheros_escribir.md ├── 09_testing_doc ├── 00_testing.md ├── 01_pep8_python.md ├── 02_nombrando_variables.md ├── 03_crear_cli.md ├── 04_errores_comunes.md ├── 05_codigo_pythonico.md └── 06_testing.md ├── LICENSE ├── README.md ├── img ├── complejo1.png ├── el-libro-de-python.png ├── hash.png ├── install-config.png ├── install-python.gif ├── jup-notebook.png ├── jupyter-gui.png ├── jupyter-helloworld.png ├── naive_vs_horner.png ├── pycharm-helloworld.png ├── pycharm-init.png ├── pycharm-newfile.png ├── pycharm-run.png ├── pycharm-venv.png ├── python-conf.png └── python-evolution.png ├── order_files.py ├── xx_ejemplosyejercicios ├── 01_numeros_aleatorios.md ├── 02_numeros_primos.md ├── 03_medir_tiempo_exec.md ├── 04_funciones_hash.md ├── 05_bubble_sort.md ├── 06_polinomios.md ├── 07_criptografia_asimetrica.md └── 08_calcular_letra_dni.md ├── xx_modulosypaquetes ├── 01_publicar_paquete_pip.md └── modulos.md └── xx_usandopaquetesexternos └── 01_moviepy.md /01_introduccion/00_introduccion.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 🕺🏻 01. Introducción 4 | order: 1 5 | has_children: true 6 | nav_order: a 7 | permalink: /introduccion-python 8 | --- 9 | 10 | # Introducción a Python 11 | 12 | -------------------------------------------------------------------------------- /01_introduccion/03_primeros_pasos.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Hola Mundo en Python 4 | title_nav: 📗 Hola Mundo en Python 5 | parent: 🕺🏻 01. Introducción 6 | description: Te explicamos de forma fácil y sencilla como escribir tu primer programa en Python, el famoso Hola Mundo. Para ello basta con usar la función print() y pasarle como argumento de entrada la cadena Hola Mundo entre comillas. 7 | order: 4 8 | nav_order: c 9 | permalink: /hola-mundo-python 10 | --- 11 | 12 | # Primeros pasos en Python 13 | 14 | A continuación te ayudamos a dar tus primeros pasos en Python. ¡Allá vamos! 15 | 16 | ## Hola mundo en Python 17 | 18 | En cualquier introducción a un nuevo lenguaje de programación, no puede faltar el famoso *Hola Mundo*. Se trata del primer programa por el que se empieza, que consiste en programar una aplicación que muestra por pantalla ese texto. Si ejecutas el siguiente código, habrás cumplido el primer hito de la programación en Python. 19 | 20 | ```python 21 | print("Hola Mundo") 22 | ``` 23 | 24 | Por lo tanto ya te puedes imaginar que la función `print()` sirve para imprimir valores por pantalla. Imprimirá todo lo que haya dentro de los paréntesis. Fácil ¿verdad? A diferencia de otros lenguajes de programación, en Python se puede hacer en 1 línea. 25 | 26 | 27 | ## Definiendo variables en Python 28 | 29 | Vamos a complicar un poco más las cosas. Creemos una variable que almacene un número. A diferencia de otros lenguajes de programación, no es necesario decirle a Python el tipo de dato que queremos almacenar en `x`. En otros lenguajes es necesario especificar que `x` almacenará un valor entero, pero no es el caso. Python es muy listo y al ver el número `5`, sabrá de que tipo tiene que ser la `x`. 30 | 31 | ```python 32 | x = 5 33 | ``` 34 | 35 | Ahora podemos juntar el `print()` que hemos visto con la `x` que hemos definido, para en vez de imprimir el *Hola Mundo*, imprimir el valor de la `x`. 36 | 37 | ```python 38 | print(x) 39 | # Salida: 5 40 | ``` 41 | 42 | En el anterior fragmento habrás visto el uso `#`. Se trata de la forma que tiene Python de crear los denominados comentarios. Un comentario es un texto que acompaña al código, pero que no es código propiamente dicho. Se usa para realizar anotaciones sobre el código, que puedan resultar útiles a otras personas. En nuestro caso, simplemente lo hemos usado para decirte que la salida de ese comando será `5`, ya que `x` valía 5. 43 | 44 | 45 | ## Sumando variables en Python 46 | 47 | Vamos a sumar dos variables e imprimir su valor. Lo primero vamos a declararlas, con nombres `a` y `b`. Declarar una variable significa "crearla". 48 | 49 | ```python 50 | # Declaramos las variables a, b 51 | # y asignamos dos valores 52 | a = 3 53 | b = 7 54 | ``` 55 | 56 | Ahora Python ya conoce `a` y `b` y sus respectivos valores. Podemos hacer uso de `+` para sumarlos, y una vez más de `print()` para mostrar su valor por pantalla. 57 | 58 | ```python 59 | print(a+b) 60 | ``` 61 | 62 | Es importante que sólo usemos variables que hayan sido definidas, porque de lo contrario tendremos un error. Si hacemos: 63 | 64 | ```python 65 | print(z) 66 | # NameError: name 'z' is not defined 67 | ``` 68 | Tendremos un error porque Python no sabe que es `z`, ya que no ha sido declarada con anterioridad. 69 | 70 | 71 | ## Ejemplo condicional 72 | 73 | Podemos empezar a complicar un poco más las cosas con el uso de una sentencia condicional. Te lo explicamos más adelante en [este post sobre el if](/if-python/ "este post sobre el if"). 74 | 75 | El siguiente código hace uso del **if** para comprobar si la `a` es igual `==` a 10. Si lo es, se imprimirá "Es 10" y si no lo es "No es 10". Es importante el uso de `==`, que es el [operador relacional que veremos en otros posts](/operadores-relacionales/ "operador relacional que veremos en otros posts"). 76 | 77 | ```python 78 | a = 10 79 | if a == 10: 80 | print("Es 10") 81 | else: 82 | print("No es 10") 83 | ``` 84 | 85 | ## Decimales y cadenas 86 | 87 | De la misma forma que hemos visto que una variable puede almacenar un valor entero como `10`, es posible también almacenar otros tipos como [decimales](/tipos-numericos-float/ "decimales") o incluso [cadenas de texto](/cadenas-python/ "cadenas de texto"). 88 | 89 | Si queremos almacenar un valor decimal, basta con indicarlo usando la separación con `.` 90 | 91 | ```python 92 | valor_decimal = 10.3234 93 | ``` 94 | 95 | Y si queremos almacenar una cadena, es necesario indicar su contenido entre comillas simples `'`o dobles `"`. 96 | 97 | ```python 98 | mi_cadena = "Hola Mundo" 99 | ``` 100 | 101 | Esperamos que te haya resultado útil esta introducción, y con ella ya estas list@ para continuar al siguiente tutorial, donde veremos más acerca de la sintaxis de Python. 102 | -------------------------------------------------------------------------------- /01_introduccion/05_nombrando_variables_1.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Nombrando variables I 4 | parent: 🕺🏻 01. Introducción 5 | description: Las variables en Python pueden ser creadas con el nombre de la variable igual al valor. A diferencia de en otros lenguajes de programación, no es necesario declararla anteriormente. 6 | order: 6 7 | nav_order: e 8 | permalink: /variables-python 9 | --- 10 | 11 | # Nombrando variables 12 | 13 | 14 | ## Crear variables 15 | Las variables en Python se pueden crear asignando un valor a un nombre sin necesidad de declararla antes. 16 | 17 | ```python 18 | x = 10 19 | y = "Nombre" 20 | z = 3.9 21 | ``` 22 | 23 | 24 | ## Nombres de variables 25 | Podemos asignar el nombre que queramos, respetando no usar las palabras reservadas de Python ni espacios, guiones o números al principio. 26 | 27 | ```python 28 | # Válido 29 | _variable = 10 30 | vari_able = 20 31 | variable10 = 30 32 | variable = 60 33 | variaBle = 10 34 | ``` 35 | 36 | Los siguientes ejemplos no son permitidos. 37 | ```python 38 | # No válido 39 | 2variable = 10 40 | var-iable = 10 41 | var iable = 10 42 | ``` 43 | 44 | ## Asignar múltiples valores 45 | Se pueden asignar múltiples variables en la misma línea. 46 | ```python 47 | x, y, z = 10, 20, 30 48 | ``` 49 | 50 | ## Imprimir variables 51 | Una variable puede ser impresa por pantalla usando `print()` 52 | 53 | ```python 54 | x = 10 55 | y = "Nombre" 56 | 57 | print(x) 58 | print(y) 59 | ``` -------------------------------------------------------------------------------- /01_introduccion/07_todo_alcance_variables.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Alcance variables 4 | parent: 🕺🏻 01. Introducción 5 | description: El alcance de variables en Python se refiere a la región específica del código donde una variable puede ser accedida o utilizada. Esto depende de dónde y cómo se declara la variable dentro del programa. 6 | order: 8 7 | nav_order: g 8 | permalink: /alcance-variables-python 9 | --- 10 | 11 | # Alcance variables en Python 12 | 13 | Python clasifica los alcances de acuerdo con el modelo `LEGB`, que determina el orden en que se buscan las variables. Las siglas `LEGB` corresponden a: 14 | 15 | Local: Variables definidas dentro de una función o bloque, accesibles únicamente dentro de ese contexto. Se crean al inicio de la ejecución de la función y desaparecen cuando esta termina. 16 | 17 | ```python 18 | def saludo(): 19 | mensaje = "¡Hola, mundo!" # Variable local 20 | print(mensaje) 21 | 22 | saludo() 23 | print(mensaje) # Esto generará un error porque 'mensaje' es local a la función. 24 | ``` 25 | En este caso, la variable `mensaje` se define y utiliza dentro de la función `saludo`. Fuera de esta función, la variable no existe y no se puede acceder. 26 | 27 | Enclosing: Se refiere a las variables definidas en una función exterior que encapsula otra función. Estas variables no son locales para la función más interna, pero tampoco son globales. 28 | 29 | ```python 30 | def funcion_exterior(): 31 | mensaje = "Hola desde la función exterior" # Variable en el alcance 'enclosing' 32 | 33 | def funcion_interior(): 34 | print(mensaje) # La función interior accede a la variable de la exterior 35 | 36 | funcion_interior() 37 | 38 | funcion_exterior() 39 | ``` 40 | Salida: `Hola desde la función exterior` 41 | 42 | En este caso, mensaje no es ni local para `funcion_interior` ni global, sino que pertenece al alcance de `funcion_exterior`. Este es un ejemplo de una variable con alcance enclosing. 43 | 44 | Global: Variables definidas en el nivel superior del script(/ejecutar-script-python) o programa, fuera de cualquier función. Son accesibles en todo el archivo, pero pueden ser modificadas dentro de una función solo si se usa la palabra clave `global`. 45 | 46 | ```python 47 | mensaje = "Hola desde el alcance global" # Variable global 48 | 49 | def imprimir_mensaje(): 50 | print(mensaje) # Accediendo a la variable global 51 | 52 | imprimir_mensaje() 53 | 54 | print(mensaje) # También accesible fuera de las funciones 55 | ``` 56 | Salida: `Hola desde el alcance global` 57 | `Hola desde el alcance global` 58 | 59 | En este caso, `mensaje` se define fuera de cualquier función(/funciones-python), por lo que es global y puede ser utilizada tanto dentro como fuera de las funciones(/funciones-python). 60 | 61 | Built-in: Variables y funciones predefinidas de Python que están disponibles en cualquier parte del código, como `len()`, `range()` y `print()`. 62 | 63 | ```python 64 | from math import sqrt # 'sqrt' es una función built-in del módulo 'math' 65 | 66 | numero = 16 67 | raiz_cuadrada = sqrt(numero) # Usamos la función built-in para calcular la raíz cuadrada 68 | print(f"La raíz cuadrada de {numero} es {raiz_cuadrada}") 69 | ``` 70 | Salida: `La raíz cuadrada de 16 es 4.0` 71 | 72 | En este caso, utilizamos una función(/funciones-python) built-in del módulo estándar `math`, que amplía las funcionalidades predefinidas de Python. 73 | 74 | -------------------------------------------------------------------------------- /01_introduccion/10_ejecutando_scripts.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Ejecutando scripts 4 | parent: 🕺🏻 01. Introducción 5 | description: Ejecutar script Python 6 | order: 9 7 | nav_order: h 8 | permalink: /ejecutar-script-python 9 | --- 10 | 11 | # Ejecutar Script en Python 12 | 13 | Próximamente 14 | 15 | ## Argumentos script 16 | -------------------------------------------------------------------------------- /01_introduccion/duck_typing.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Tipado dinámico y duck typing 4 | parent: 🕺🏻 01. Introducción 5 | description: El duck typing es una característica de ciertos lenguajes de programación orientados a objetos. En Python es posible el duck typing, lo que significa que el tipo de los objetos no importa tanto como sus métodos. Si un objeto tiene el método que va a ser llamado, da igual su tipo, simplemente se ejecutará. Es importante notar que otros lenguajes con tipado estático como Java no lo soportan. 6 | order: 10 7 | nav_order: i 8 | permalink: /duck-typing-python 9 | --- 10 | 11 | # Duck Typing en Python 12 | 13 | El [duck typing](https://docs.python.org/3/glossary.html#term-duck-typing) o tipado de pato es un concepto relacionado con la programación que aplica a ciertos lenguajes [orientados a objetos](/programacion-orientada-a-objetos-python), y que tiene origen en la siguiente frase: 14 | 15 | > If it walks like a duck and it quacks like a duck, then it must be a duck 16 | 17 | Lo que se podría traducir al español como. **Si camina como un pato y habla como un pato, entonces tiene que ser un pato**. 18 | 19 | ¿Y qué relación tienen los patos con la programación? Pues bien, se trata de un símil en el que los **patos son objetos** y **hablar/andar métodos**. Es decir, que si un determinado objeto tiene los métodos que nos interesan, nos basta, siendo su tipo irrelevante. 20 | 21 | Dicho de otra manera, no mires si es un pato. Fíjate si habla como un pato, camina como un pato, etc. Si cumple con todas estas características, ¿no podríamos acaso decir que se trata de un pato? 22 | 23 | > Don’t check whether it is-a duck: check whether it quacks-like-a duck, walks-like-a duck, etc, etc, depending on exactly what subset of duck-like behavior you need to play your language-games with. (comp.lang.python, Jul. 26, 2000) — Alex Martelli 24 | 25 | El concepto de *duck typing* se fundamenta en el [razonamiento inductivo](https://es.wikipedia.org/wiki/Razonamiento_inductivo), donde una serie de premisas apoyan la conclusión, pero no la garantizan. Si vemos a un animal que parece un pato, habla como tal y anda como tal, sería razonable pensar que se trata de un pato, pero sin un test de ADN nunca estaríamos al cien por cien seguros. 26 | 27 | Una vez entendido el origen del concepto, veamos lo que realmente significa esto en Python. En pocas palabras, **a Python le dan igual los tipos de los objetos, lo único que le importan son los métodos**. 28 | 29 | Definamos una clase `Pato` con un método `hablar()`. 30 | 31 | ```python 32 | class Pato: 33 | def hablar(self): 34 | print("¡Cua!, Cua!") 35 | ``` 36 | 37 | Y llamamos al método de la siguiente forma. 38 | 39 | ```python 40 | p = Pato() 41 | p.hablar() 42 | # ¡Cua!, Cua! 43 | ``` 44 | 45 | Hasta aquí nada nuevo, pero vamos a definir una función `llama_hablar()`, que llama al método `hablar()` del objeto que se le pase. 46 | 47 | ```python 48 | def llama_hablar(x): 49 | x.hablar() 50 | ``` 51 | 52 | Como puedes observar, en Python **no es necesario especificar los tipos**, simplemente decimos que el parámetro de entrada tiene el nombre `x`, pero no especificamos su tipo. 53 | 54 | Cuando Python entra en la función y evalúa `x.hablar()`, le da igual el tipo al que pertenezca `x` siempre y cuando tenga el método `hablar()`. Esto es el *duck typing* en todo su esplendor. 55 | 56 | ```python 57 | p = Pato() 58 | llama_hablar(p) 59 | # ¡Cua!, Cua! 60 | ``` 61 | 62 | ¿Y qué pasa si usamos otros objetos que no son de la clase `Pato`? Pues bien, como hemos dicho, a la función `llama_hablar()` le da igual el tipo. Lo único que el importa es que el objeto tenga el método `hablar()`. 63 | 64 | Definamos tres clases de animales distintas que implementan el método `hablar()`. Nótese que no existe [herencia](/herencia-en-python) entre ellas, son clases totalmente independientes. De haberla estaríamos hablando de [polimorfismo](/polimorfismo-en-programacion). 65 | 66 | ```python 67 | class Perro: 68 | def hablar(self): 69 | print("¡Guau, Guau!") 70 | 71 | class Gato: 72 | def hablar(self): 73 | print("¡Miau, Miau!") 74 | 75 | class Vaca: 76 | def hablar(self): 77 | print("¡Muuu, Muuu!") 78 | ``` 79 | 80 | Y como es de esperar la función `llama_hablar()` funciona correctamente con todos los objetos. 81 | 82 | ```python 83 | llama_hablar(Perro()) 84 | llama_hablar(Gato()) 85 | llama_hablar(Vaca()) 86 | 87 | # ¡Guau, Guau! 88 | # ¡Miau, Miau! 89 | # ¡Muuu, Muuu! 90 | ``` 91 | 92 | Otra forma de verlo, es iterando una lista con diferentes animales, donde `animal` va tomando los valores de cada objeto animal. Todo un ejemplo del *duck typing* y del tipado dinámico en Python. 93 | 94 | ```python 95 | lista = [Perro(), Gato(), Vaca()] 96 | for animal in lista: 97 | animal.hablar() 98 | 99 | # ¡Guau, Guau! 100 | # ¡Miau, Miau! 101 | # ¡Muuu, Muuu! 102 | ``` 103 | 104 | # Ejemplos Duck Typing 105 | 106 | ## Ejemplo len() 107 | Podemos ver el *duck typing* en todo su esplendor con la función `len()`. Dicha función lo único que realiza por debajo es llamar al [método mágico](/metodos-magicos-python) `__len__()`. Definamos dos clases: 108 | 109 | * `Foo` implementa el método `__len__()`. 110 | * `Bar` no lo implementa. 111 | 112 | ```python 113 | class Foo(): 114 | def __len__(self): 115 | return 99 116 | 117 | class Bar(): 118 | pass 119 | ``` 120 | 121 | Como ya hemos explicado, a la función `len()` no le importa el tipo del objeto que se le pase, siempre y cuando tenga el método `__len__()` implementado. Por ello, en el segundo caso falla. 122 | 123 | ```python 124 | print(len(Foo())) # 99 125 | print(len(Bar())) # Error 126 | ``` 127 | 128 | ## Ejemplo multiplicar 129 | 130 | Por otro lado, cuando hacemos una multiplicación utilizando el [operador aritmético](/operadores-aritmeticos) `*` el resultado depende de los tipos que estemos usando. 131 | 132 | No es lo mismo multiplicar dos [enteros](/entero-en-python) que un entero y [cadena](/cadenas-python). 133 | 134 | ```python 135 | print(3*3) # 9 136 | print(3*"3") # 333 137 | ``` 138 | 139 | Una vez más, podemos ver el *duck typing* en Python. Simplemente se busca que los objetos a la izquierda y derecha del `*` tengan implementado el `__rmul__` o `__mul__`. 140 | 141 | # Conclusiones 142 | 143 | * Python es un lenguaje que soporta el *duck typing*, lo que hace que el tipo de los objetos no sea tan relevante, siendo más importante lo que pueden hacer (sus métodos). 144 | * Otros lenguajes como Java no soporta el *duck typing*, pero se puede conseguir un comportamiento similar cuando los objetos comparten un interfaz (si existe herencia entre ellos). Este concepto relacionado es el [polimorfismo](/polimorfismo-en-programacion). 145 | * El *duck typing* está en todos lados, desde la función `len()` hasta el uso del operador `*`. -------------------------------------------------------------------------------- /01_introduccion/todo_funciones_builtin.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Funciones built-in 4 | parent: 🕺🏻 01. Introducción 5 | description: xx 6 | order: 11 7 | nav_order: j 8 | permalink: /python-built-in 9 | --- 10 | 11 | # Funciones built-in Python 12 | -------------------------------------------------------------------------------- /01_introduccion/unpacking.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Unpacking en Python 4 | parent: 🕺🏻 01. Introducción 5 | description: A través del uso de args y kwargs en Python, podemos hacer lo que es denominado como unpacking, y consiste en asignar un iterable o diccionario a varias variables. 6 | order: 12 7 | nav_order: k 8 | permalink: /unpacking-python 9 | --- 10 | 11 | # Unpacking en Python 12 | 13 | El *unpacking* en Python nos permite asignar una [lista](https://ellibrodepython.com/listas-en-python) a múltiples variables en una única línea de código. 14 | 15 | ```python 16 | a, b, c = [1, 2, 3] 17 | print(a) # 1 18 | print(b) # 2 19 | print(c) # 3 20 | ``` 21 | 22 | También es posible hacerlo con otros iterables como las [tuplas](https://ellibrodepython.com/tuplas-python). 23 | 24 | ```python 25 | a, b, c = (1, 2, 3) 26 | print(a) # 1 27 | print(b) # 2 28 | print(c) # 3 29 | ``` 30 | 31 | Y como es de esperar, el número de variables debe coincidir con la longitud. Obtenemos `not enough values to unpack` si proporcionamos menos. 32 | 33 | ```python 34 | a, b, c = (1, 2) 35 | # ValueError: not enough values to unpack (expected 3, got 2) 36 | ``` 37 | 38 | Y `too many values to unpack` si proporcionamos de más. 39 | 40 | ```python 41 | a, b = (1, 2, 3, 4) 42 | # ValueError: too many values to unpack (expected 2) 43 | ``` 44 | 45 | Se pueden dar casos curiosos como el siguiente, ya que en realidad funciona con cualquier [iterable](https://ellibrodepython.com/iterator-python). 46 | 47 | ```python 48 | a, b, c = "123" 49 | print(a) # 1 50 | print(b) # 2 51 | print(c) # 3 52 | ``` 53 | 54 | De hecho funciona también con [diccionarios](https://ellibrodepython.com/diccionarios-en-python), siendo la *key* lo usado por defecto. 55 | 56 | ```python 57 | a, b, c = {'uno': 1, 'dos':2, 'tres': 3} 58 | print(a) # uno 59 | print(b) # dos 60 | print(c) # tres 61 | ``` 62 | 63 | Aunque también podemos usar los *values*. 64 | 65 | ```python 66 | a, b, c = {'uno': 1, 'dos':2, 'tres': 3}.values() 67 | print(a) # 1 68 | print(b) # 2 69 | print(c) # 3 70 | ``` 71 | 72 | Dado que `range` devuelve un iterador, también lo podemos usar. 73 | 74 | ```python 75 | a, b, c = range(3) 76 | print(a) # 0 77 | print(b) # 1 78 | print(c) # 2 79 | ``` 80 | 81 | 82 | ## Operador de Unpacking 83 | 84 | Relacionado con el *unpacking* existe el operador `*`, que nos permite realizar asignaciones cuando el número de elementos es distinto. Tanto de esta manera. 85 | 86 | ```python 87 | *a, b = (1, 2, 3) 88 | print(a) # [1, 2] 89 | print(b) # 3 90 | ``` 91 | 92 | Como de esta otra. 93 | 94 | ```python 95 | a, *b = (1, 2, 3) 96 | print(a) # 1 97 | print(b) # [2, 3] 98 | ``` 99 | 100 | Podemos usarlo para unir listas. Aunque es importante notar que esto se puede hacer de otras formas como usando `+` o `.extend()`. 101 | 102 | ```python 103 | a = [1, 2] 104 | b = [3, 4] 105 | c = [*a, *b] 106 | 107 | print(c) 108 | # [1, 2, 3, 4] 109 | ``` 110 | 111 | También tenemos el operador definido para diccionarios, usando `**`. 112 | 113 | ```python 114 | a = {"uno": 1, "dos": 2} 115 | b = {"tres": 3, "cuatro": 4} 116 | 117 | c = {**a, **b} 118 | 119 | print(c) 120 | # {'uno': 1, 'dos': 2, 'tres': 3, 'cuatro': 4} 121 | ``` 122 | 123 | Ten cuidado si tienes *keys* duplicados, ya que el segundo sobrescribirá al primero. 124 | 125 | ```python 126 | a = {"uno": 1, "dos": 2} 127 | b = {"uno": 0, "dos": 0} 128 | 129 | c = {**a, **b} 130 | 131 | print(c) 132 | # {'uno': 0, 'dos': 0} 133 | ``` 134 | 135 | Y por último también podemos hacer cosas interesantes con bucles [for](https://ellibrodepython.com/for-python). 136 | 137 | ```python 138 | for primero, *resto in [(1, 2, 3), (4, 5, 6, 7)]: 139 | print("Primero:", primero) 140 | print("Resto:", resto) 141 | 142 | # Primero: 1 143 | # Resto: [2, 3] 144 | # Primero: 4 145 | # Resto: [5, 6, 7] 146 | ``` 147 | 148 | ## Unpacking para Swapping 149 | 150 | La principal aplicación práctica del *unpacking* es para intercambiar o hacer *swap* de variables en una única línea de código. Aunque pueda parecer algo sencillo, no todos los lenguajes permiten esto. 151 | 152 | ```python 153 | a, b = (1, 2) 154 | print(a, b) 155 | # 1 2 156 | 157 | a, b = b, a 158 | print(a, b) 159 | # 2 1 160 | ``` 161 | 162 | ## Unpacking en Funciones 163 | 164 | Por último, aunque lo vemos más en detalle en [args y kwargs](https://ellibrodepython.com/args-kwargs-python), el *unpacking* nos permite pasar un número de argumentos variables a una función. 165 | 166 | ```python 167 | def funcion(a, *args, **kwargs): 168 | print(f"args={a} args={args} kwargs={kwargs}") 169 | 170 | funcion(1) 171 | # args=1 args=() kwargs={} 172 | 173 | funcion(1, 2) 174 | # args=1 args=(2,) kwargs={} 175 | 176 | funcion(1, 2, 3, cuatro=4, cinco=5) 177 | # args=1 args=(2, 3) kwargs={'cuatro': 4, 'cinco': 5} 178 | ``` 179 | -------------------------------------------------------------------------------- /02_estructurasdecontrol/00_estructurasdecontrol.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 🏄🏻‍♀️ 02. Estructuras de control 4 | order: 13 5 | has_children: true 6 | nav_order: b 7 | permalink: /estructuras-control-python 8 | --- 9 | 10 | # Estructuras de Control en Python 11 | 12 | 13 | Antes de entrar a explicar las estructuras de control, vamos a ponernos un poco en contexto. 14 | 15 | Un código es una secuencia de instrucciones, que por norma general son ejecutadas una tras otra. Podemos verlo como una receta de cocina, en la que tenemos unos pasos a seguir. Empezamos por el principio, vamos realizando cada tarea, y una vez hemos llegado al final, hemos terminado. Nuestra receta en código podría ser la siguiente: 16 | 17 | ```python 18 | poner_agua_hervir() 19 | echar_arroz_hervir() 20 | cortar_pollo() 21 | freir_pollo() 22 | mezclar_pollo_arroz() 23 | ``` 24 | 25 | Sin embargo, en muchas ocasiones no basta con ejecutar las instrucciones una tras otra desde el principio hasta llegar al final. 26 | 27 | Puede ser que ciertas instrucciones se tengan que ejecutar si y sólo si se cumple una determinada condición. ¿Que pasa si nuestro comensal es vegetariano? No hay problema, podemos usar el condicional **if**. Si no es vegetariano usamos pollo, de lo contrario zanahoria. 28 | 29 | ```python 30 | poner_agua_hervir() 31 | echar_arroz_hervir() 32 | if not vegetariano: 33 | cortar_pollo() 34 | freir_pollo() 35 | mezclar_pollo_arroz() 36 | else: 37 | cortar_zanahoria() 38 | freir_zanahoria() 39 | mezclar_zanahoria_arroz() 40 | ``` 41 | 42 | Por otro lado, la receta que teníamos era para una persona. ¿Qué pasa si queremos cocinar para 3? ¿Tenemos que escribir el código repetido 3 veces? 43 | 44 | ```python 45 | # Cocinamos para la primera persona 46 | poner_agua_hervir() 47 | echar_arroz_hervir() 48 | cortar_pollo() 49 | freir_pollo() 50 | mezclar_pollo_arroz() 51 | 52 | # Volvemos a cocinar para la segunda 53 | poner_agua_hervir() 54 | echar_arroz_hervir() 55 | cortar_pollo() 56 | freir_pollo() 57 | mezclar_pollo_arroz() 58 | 59 | # Y para la tercera 60 | poner_agua_hervir() 61 | echar_arroz_hervir() 62 | cortar_pollo() 63 | freir_pollo() 64 | mezclar_pollo_arroz() 65 | ``` 66 | 67 | Pero ¿y si queremos para 100? Te puedes ya imaginar que repetir el código tantas veces no parece ser la mejor idea. Es aquí donde entran en juego el **for** y el **while**. 68 | 69 | Estas estructuras de control nos permiten repetir un determinado bloque de código tantas veces como queramos. 70 | 71 | ```python 72 | # Repite lo que hay dentro 100 veces 73 | for i in range(100): 74 | # Cocinamos la receta 75 | poner_agua_hervir() 76 | echar_arroz_hervir() 77 | cortar_pollo() 78 | freir_pollo() 79 | mezclar_pollo_arroz() 80 | ``` 81 | 82 | Como puedes ver, el código anterior haría lo mismo que copiar y pegar la receta 100 veces, pero es mucho más compacto y elegante. 83 | 84 | Sabido esto, ya estas en condiciones de empezar a leer este capítulo, donde aprenderás básico conceptos como el if/else/for/while y también algo más avanzados, como lo son los iteradores, clases iterables y uso del break/continue/try. -------------------------------------------------------------------------------- /02_estructurasdecontrol/03_range.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Range en Python 4 | title_nav: 📗 Range 5 | parent: 🏄🏻‍♀️ 02. Estructuras de control 6 | description: El uso de range en Python nos permite generar secuencias de números, dados un inicio, un final y un salto. 7 | order: 16 8 | nav_order: c 9 | permalink: /range-python 10 | --- 11 | 12 | # Range en Python 13 | 14 | ## Uso del range 15 | 16 | Uno de las iteraciones mas comunes que se realizan, es la de iterar un número entre por ejemplo 0 y `n`. Si ya programas, estoy seguro de que estas cansado de escribir esto, aunque sea en otro lenguaje. Pongamos que queremos iterar una variable `i` de 0 a 5. Haciendo uso de lo que hemos visto anteriormente, podríamos hacer lo siguiente. 17 | 18 | 19 | ```python 20 | for i in (0, 1, 2, 3, 4, 5): 21 | print(i) #0, 1, 2, 3, 4, 5 22 | ``` 23 | 24 | Se trata de una solución que cumple con nuestro requisito. El contenido después del `in` se trata de una clase que como ya hemos visto antes, es iterable, y es de hecho una tupla. Sin embargo, hay otras formas de hacer esto en Python, haciendo uso del `range()`. 25 | 26 | 27 | ```python 28 | for i in range(6): 29 | print(i) #0, 1, 2, 3, 4, 5 30 | ``` 31 | 32 | 33 | El `range()` genera una secuencia de números que van desde 0 por defecto hasta el número que se pasa como parámetro menos 1. En realidad, se pueden pasar hasta tres parámetros separados por coma, donde el primer es el inicio de la secuencia, el segundo el final y el tercero el salto que se desea entre números. Por defecto se empieza en 0 y el salto es de 1. 34 | 35 | 36 | ```python 37 | #range(inicio, fin, salto) 38 | ``` 39 | 40 | Por lo tanto, si llamamos a `range()` con `(5,20,2)`, se generarán números de 5 a 20 de dos en dos. Un truco es que el `range()` se puede convertir en `list`. 41 | 42 | 43 | ```python 44 | print(list(range(5, 20, 2))) 45 | ``` 46 | 47 | 48 | Y mezclándolo con el `for`, podemos hacer lo siguiente. 49 | 50 | 51 | ```python 52 | for i in range(5, 20, 2): 53 | print(i) #5,7,9,11,13,15,17,19 54 | ``` 55 | 56 | Se pueden generar también secuencias inversas, empezando por un número mayor y terminando en uno menor, pero para ello el salto deberá ser negativo. 57 | 58 | 59 | ```python 60 | for i in range (5, 0, -1): 61 | print(i) #5,4,3,2,1 62 | ``` 63 | -------------------------------------------------------------------------------- /02_estructurasdecontrol/06_match.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Match en Python 4 | title_nav: 📙 Match 5 | parent: 🏄🏻‍♀️ 02. Estructuras de control 6 | description: El match en Python se introdujo en la version 3.10. Es similar al switch pero mucho mas potente. 7 | order: 18 8 | nav_order: f 9 | permalink: /match-python 10 | --- 11 | 12 | ## Match en Python 13 | 14 | Python ofrece algo similar al *switch* de otros lenguajes de programación desde la versión `3.10`. 15 | 16 | Se trata del `match`. Cada `case` define un camino posible. El `_` es la opción por defecto, que se ejecuta si la entrada no coincide con ningún caso. 17 | 18 | ```python 19 | hora = 8 20 | match hora: 21 | case 8: 22 | print("Desayuno") 23 | case 14: 24 | print("Comida") 25 | case 21: 26 | print("Cena") 27 | case _: 28 | print("No toca comer") 29 | # Desayuno 30 | ``` 31 | 32 | El `match` nos permite realizar lo mismo que con múltiples `if/elif` como hemos visto anteriormente. Ambos códigos son equivalentes. 33 | 34 | ```python 35 | if hora == 8: 36 | print("Desayuno") 37 | elif hora == 14: 38 | print("Comida") 39 | elif hora == 21: 40 | print("Cena") 41 | else: 42 | print("No toca comer") 43 | ``` 44 | 45 | También podemos tener en nuestros `case` múltiples condiciones, donde `|` es interpretado como un `or`. 46 | 47 | ```python 48 | mes = 4 49 | match mes: 50 | case 12 | 1 | 2: print("Invierno") 51 | case 3 | 4 | 5: print("Primavera") 52 | case 6 | 7 | 8: print("Verano") 53 | case 9 | 10 | 11: print("Otoño") 54 | case _: print("Error") 55 | # Primavera 56 | ``` 57 | 58 | Aunque no acaba ahí la cosa. Podemos hacer *matching* de prácticamente cualquier cosa como una tupla. 59 | 60 | ```python 61 | coordenada = (0, 1) 62 | match coordenada: 63 | case (0, 0): 64 | print("Coordenada en origen") 65 | case (x, 0): 66 | print(f"Coordenada en eje x: {x}") 67 | case (0, y): 68 | print(f"Coordenada en eje y: {y}") 69 | case (x, y): 70 | print(f"Coordenada en: {x}, {y}") 71 | case _: 72 | print("Error") 73 | 74 | # Coordenada en eje y: 1 75 | ``` -------------------------------------------------------------------------------- /02_estructurasdecontrol/07_break.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Break en Python 4 | title_nav: 📙 Break 5 | parent: 🏄🏻‍♀️ 02. Estructuras de control 6 | description: El uso del break() en Python nos permite terminar prematuramente la ejecución de un bucle for o while. 7 | order: 19 8 | nav_order: g 9 | permalink: /break-python 10 | --- 11 | 12 | # Sentencia break Python 13 | 14 | ## Introducción al break 15 | 16 | La sentencia `break` nos permite alterar el comportamiento de los bucles [while](/while-python) y [for](/for-python). Concretamente, permite terminar con la ejecución del bucle. 17 | 18 | Esto significa que una vez se encuentra la palabra `break`, el bucle se habrá terminado. 19 | 20 | ## Break con bucles for 21 | 22 | Veamos como podemos usar el `break` con bucles [for](/for-python). El `range(5)` generaría 5 iteraciones, donde la `i` valdría de 0 a 4. Sin embargo, en la primera iteración, terminamos el bucle prematuramente. 23 | 24 | El `break` hace que nada más empezar el bucle, se rompa y se salga sin haber hecho nada. 25 | 26 | 27 | ```python 28 | for i in range(5): 29 | print(i) 30 | break 31 | # No llega 32 | 33 | # Salida: 0 34 | ``` 35 | 36 | Un ejemplo un poco más útil, sería el de buscar una letra en una palabra. Se itera toda la palabra y en el momento en el que se encuentra la letra que buscábamos, se rompe el bucle y se sale. 37 | 38 | Esto es algo muy útil porque si ya encontramos lo que estábamos buscando, no tendría mucho sentido seguir iterando la lista, ya que desperdiciaríamos recursos. 39 | 40 | 41 | ```python 42 | cadena = 'Python' 43 | for letra in cadena: 44 | if letra == 'h': 45 | print("Se encontró la h") 46 | break 47 | print(letra) 48 | 49 | # Salida: 50 | # P 51 | # y 52 | # t 53 | # Se encontró la h 54 | ``` 55 | 56 | 57 | ## Break con bucles while 58 | 59 | El `break` también nos permite alterar el comportamiento del [while](/while-python). Veamos un ejemplo. 60 | 61 | La condición `while True` haría que la sección de código se ejecutara indefinidamente, pero al hacer uso del `break`, el bucle se romperá cuando `x` valga cero. 62 | 63 | 64 | ```python 65 | x = 5 66 | while True: 67 | x -= 1 68 | print(x) 69 | if x == 0: 70 | break 71 | print("Fin del bucle") 72 | 73 | #4, 3, 2, 1, 0 74 | ``` 75 | 76 | Por norma general, y salvo casos muy concretos, si ves un `while True`, es probable que haya un `break` dentro del bucle. 77 | 78 | ## Break y bucles anidados 79 | 80 | Como hemos dicho, el uso de `break` rompe el bucle, pero sólo aquel en el que está dentro. 81 | 82 | Es decir, si tenemos dos bucles anidados, el `break` romperá el bucle anidado, pero no el exterior. 83 | 84 | ```python 85 | for i in range(0, 4): 86 | for j in range(0, 4): 87 | break 88 | #Nunca se realiza más de una iteración 89 | # El break no afecta a este for 90 | print(i, j) 91 | 92 | # 0 0 93 | # 1 0 94 | # 2 0 95 | # 3 0 96 | ``` 97 | -------------------------------------------------------------------------------- /02_estructurasdecontrol/08_continue.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Continue en Python 4 | title_nav: 📙 Continue 5 | parent: 🏄🏻‍♀️ 02. Estructuras de control 6 | description: La sentencia continue en Python nos permite modificar el comportamiento de los bucles, saltando el código restante de la iteración actual y pasando a la siguiente en el caso de que haya. 7 | order: 20 8 | nav_order: h 9 | permalink: /continue-python 10 | --- 11 | 12 | # Sentencia continue 13 | 14 | ## Introducción al continue 15 | 16 | El uso de `continue` al igual que el ya visto [break](/break-python), nos permite modificar el comportamiento de de los bucles [while](/while-python) y [for](/for-python). 17 | 18 | Concretamente, `continue` se salta todo el código restante en la iteración actual y vuelve al principio en el caso de que aún queden iteraciones por completar. 19 | 20 | La diferencia entre el `break` y `continue` es que el `continue` no rompe el bucle, si no que pasa a la siguiente iteración saltando el código pendiente. 21 | 22 | En el siguiente ejemplo vemos como al encontrar la letra `P` se llama al continue, lo que hace que se salte el `print()`. Es por ello por lo que no vemos la letra `P` impresa en pantalla. 23 | 24 | 25 | ```python 26 | cadena = 'Python' 27 | for letra in cadena: 28 | if letra == 'P': 29 | continue 30 | print(letra) 31 | # Salida: 32 | # y 33 | # t 34 | # h 35 | # o 36 | # n 37 | ``` 38 | 39 | A diferencia del `break`, el `continue` no rompe el bucle sino que finaliza la iteración actual, haciendo que todo el código que va después se salte, y se vuelva al principio a evaluar la condición. 40 | 41 | En el siguiente ejemplo podemos ver como cuando la `x` vale 3, se llama al `continue`, lo que hace que se salte el resto de código de la iteración (el `print()`). Por ello, vemos como el número 3 no se imprime en pantalla. 42 | 43 | 44 | ```python 45 | x = 5 46 | while x > 0: 47 | x -= 1 48 | if x == 3: 49 | continue 50 | print(x) 51 | 52 | #Salida: 4, 2, 1, 0 53 | ``` 54 | -------------------------------------------------------------------------------- /02_estructurasdecontrol/09_zip.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Zip en Python 4 | title_nav: 📙 Iterar con zip 5 | parent: 🏄🏻‍♀️ 02. Estructuras de control 6 | description: El uso de la función zip en Python nos permite iterar clases iterables en paralelo. Para ello, acepta varios iterables como entrada y nos devuelve un iterador. 7 | order: 21 8 | nav_order: i 9 | permalink: /zip-python 10 | --- 11 | 12 | # Iterar con zip en Python 13 | 14 | La función `zip()` de Python viene incluida por defecto en el *namespace*, lo que significa que puede ser usada sin tener que importarse. 15 | 16 | De acuerdo con la documentación [oficial](https://docs.python.org/3/library/functions.html#zip): 17 | 18 | > Returns an iterator of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables. The iterator stops when the shortest input iterable is exhausted. 19 | 20 | Dicho de otra manera, si pasamos dos listas a `zip` como entrada, el resultado será una tupla donde cada elemento tendrá todos y cada uno de los elementos i-ésimos de las pasadas como entrada. 21 | 22 | Veamos un ejemplo. Como podemos ver, el resultado tras aplicar `zip` es una lista con `a[0]b[0]` en el primer elemento y `a[1]b[1]` como segundo. 23 | 24 | ```python 25 | a = [1, 2] 26 | b = ["Uno", "Dos"] 27 | c = zip(a, b) 28 | 29 | print(list(c)) 30 | # [(1, 'Uno'), (2, 'Dos')] 31 | ``` 32 | 33 | *A priori* puede parecer una función no muy relevante, pero es realmente útil combinada con un [for](/for-python) para iterar dos [listas](/listas-en-python) en paralelo. 34 | 35 | ```python 36 | a = [1, 2] 37 | b = ["Uno", "Dos"] 38 | c = zip(a, b) 39 | 40 | for numero, texto in zip(a, b): 41 | print("Número", numero, "Letra", texto) 42 | 43 | # Número 1 Letra Uno 44 | # Número 2 Letra Dos 45 | ``` 46 | 47 | ## zip() con n argumentos 48 | 49 | Ya hemos visto el uso de `zip` con dos listas, pero dado que está definida como `zip(*iterables)`, es posible pasar un número arbitrario de [iterables](/iterator-python) como entrada. 50 | 51 | Veamos un ejemplo con varias [listas](/listas-en-python). Es importante notar que todas tienen la misma longitud, dos. 52 | 53 | ```python 54 | numeros = [1, 2] 55 | espanol = ["Uno", "Dos"] 56 | ingles = ["One", "Two"] 57 | frances = ["Un", "Deux"] 58 | c = zip(numeros, espanol, ingles, frances) 59 | 60 | for n, e, i, f in zip(numeros, espanol, ingles, frances): 61 | print(n, e, i, f) 62 | 63 | # 1 Uno One Un 64 | # 2 Dos Two Deux 65 | ``` 66 | 67 | ## zip() con diferentes longitudes 68 | 69 | También podemos usar `zip` usando iterables de diferentes longitudes. En este caso lo que pasará es que el iterador para cuando la lista más pequeña se acaba. 70 | 71 | ```python 72 | numeros = [1, 2, 3, 4, 5] 73 | espanol = ["Uno", "Dos"] 74 | 75 | for n, e in zip(numeros, espanol): 76 | print(n, e) 77 | 78 | # 1 Uno 79 | # 2 Dos 80 | ``` 81 | 82 | Resulta lógico que este sea el comportamiento, porque de no ser así y se continuara, no tendríamos valores para usar. 83 | 84 | ## zip() con un argumento 85 | 86 | Como cabría esperar, dado que `zip` está definido para un número arbitrario de parámetros de entrada, es posible también posible usar un único valor. El resultado son [tuplas](/tuplas-python) de un elemento. 87 | 88 | ```python 89 | numeros = [1, 2, 3, 4, 5] 90 | zz = zip(numeros) 91 | print(list(zz)) 92 | 93 | # [(1,), (2,), (3,), (4,), (5,)] 94 | ``` 95 | 96 | ## zip() con diccionarios 97 | 98 | Hasta ahora nos hemos limitado a usar `zip` con listas, pero la función está definida para cualquier clase [iterable](/iterator-python). Por lo tanto podemos usarla también con [diccionarios](/diccionarios-en-python). 99 | 100 | Si realizamos lo siguiente, `a,b` toman los valores de las key del [diccionario](/diccionarios-en-python). Tal vez algo no demasiado interesante. 101 | 102 | ```python 103 | esp = {'1': 'Uno', '2': 'Dos', '3': 'Tres'} 104 | eng = {'1': 'One', '2': 'Two', '3': 'Three'} 105 | 106 | for a, b in zip(esp, eng): 107 | print(a, b) 108 | 109 | # 1 1 110 | # 2 2 111 | # 3 3 112 | ``` 113 | 114 | Sin embargo, si hacemos uso de la función [items](https://ellibrodepython.com/diccionarios-en-python#items), podemos acceder al *key* y *value* de cada elemento. 115 | 116 | ```python 117 | esp = {'1': 'Uno', '2': 'Dos', '3': 'Tres'} 118 | eng = {'1': 'One', '2': 'Two', '3': 'Three'} 119 | 120 | for (k1, v1), (k2, v2) in zip(esp.items(), eng.items()): 121 | print(k1, v1, v2) 122 | 123 | # 1 Uno One 124 | # 2 Dos Two 125 | # 3 Tres Three 126 | ``` 127 | 128 | Nótese que en este caso ambas *key* `k1` y `k2` son iguales. 129 | 130 | ## Deshacer el zip() 131 | 132 | Con un pequeño truco, es posible deshacer el `zip` en una sola línea de código. Supongamos que hemos usado `zip` para obtener `c`. 133 | 134 | ```python 135 | a = [1, 2, 3] 136 | b = ["One", "Two", "Three"] 137 | c = zip(a, b) 138 | 139 | print(list(c)) 140 | # [(1, 'One'), (2, 'Two'), (3, 'Three')] 141 | ``` 142 | 143 | ¿Es posible obtener `a` y `b` desde `c`? Sí, tan sencillo como: 144 | 145 | ```python 146 | c = [(1, 'One'), (2, 'Two'), (3, 'Three')] 147 | a, b = zip(*c) 148 | 149 | print(a) # (1, 2, 3) 150 | print(b) # ('One', 'Two', 'Three') 151 | ``` 152 | 153 | Nótese el uso de `*c`, lo que es conocido como [unpacking](/unpacking-python) en Python. 154 | -------------------------------------------------------------------------------- /02_estructurasdecontrol/10_enumerate.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Enumerate Python 4 | title_nav: 📙 Iterar con enumerate 5 | parent: 🏄🏻‍♀️ 02. Estructuras de control 6 | description: Python nos da herramientas muy útiles para iterar estructuras de datos, como zip y enumerate. 7 | order: 22 8 | nav_order: j 9 | permalink: /enumerate-python 10 | --- 11 | 12 | # Enumerate en Python 13 | 14 | El uso del [for](/for-python) en Python nos permite iterar colecciones, recorriendo todos los elementos de la misma. 15 | 16 | ```python 17 | lista = ["A", "B", "C"] 18 | 19 | for l in lista: 20 | print(l) 21 | 22 | # Salida: 23 | # A 24 | # B 25 | # C 26 | ``` 27 | 28 | Sin embargo, existen situaciones en las que no solo queremos acceder al elemento *i-ésimo* de la colección, sino que **además queremos el índice**. Una forma de hacerlo sería la siguiente. 29 | 30 | ```python 31 | lista = ["A", "B", "C"] 32 | 33 | indice = 0 34 | for l in lista: 35 | print(indice, l) 36 | indice += 1 37 | 38 | # Salida: 39 | # 0 A 40 | # 1 B 41 | # 2 C 42 | ``` 43 | 44 | Aunque se trata de una forma perfectamente válida, no es demasiado *pythónica*, y es precisamente donde entra en juego el `enumerate()`. Su uso nos permite ahorrar alguna que otra línea de código, obteniendo un resultado mucho más limpio y claro. 45 | 46 | ```python 47 | lista = ["A", "B", "C"] 48 | 49 | for indice, l in enumerate(lista): 50 | print(indice, l) 51 | 52 | # Salida: 53 | # 0 A 54 | # 1 B 55 | # 2 C 56 | ``` 57 | 58 | Por último, es importante notar que su uso no se limita únicamente a bucles `for`. Podemos convertir el tipo `enumerate` en una [lista](/listas-en-python) de [tuplas](/tuplas-python), donde cada una contiene un elemento de la colección inicial y el índice asociado. 59 | 60 | ```python 61 | lista = ["A", "B", "C"] 62 | 63 | en = list(enumerate(lista)) 64 | print(en) 65 | 66 | # Salida; 67 | # [(0, 'A'), (1, 'B'), (2, 'C')] 68 | ``` 69 | 70 | Por lo tanto recuerda, la próxima vez que quieras acceder a los índices de una colección, piensa si tal vez `enumerate` puede resolver tu problema de manera más clara y con menos código. -------------------------------------------------------------------------------- /02_estructurasdecontrol/11_list_comprehensions.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 List comprehensions 4 | parent: 🏄🏻‍♀️ 02. Estructuras de control 5 | description: Las list comprehension o comprensión de listas son una herramienta de Python que nos permite modificar colecciones iterables en una sola línea de código. Al igual que existen para las listas, también pueden ser usadas sobre otros tipos como sets o diccionarios. 6 | order: 23 7 | nav_order: k 8 | permalink: /list-comprehension-python 9 | --- 10 | 11 | # List comprehensions 12 | 13 | Una de las principales ventajas de Python es que una misma funcionalidad puede ser escrita de maneras muy diferentes, ya que su sintaxis es muy rica en lo que se conoce como expresiones idiomáticas o *idiomatic expressions*. Las **list comprehension** o comprensión de listas son una de ellas. 14 | 15 | Vayamos al grano, las *list comprehension* nos permiten crear listas de elementos en una sola línea de código. Por ejemplo, podemos crear una lista con los cuadrados de los primeros 5 números de la siguiente forma 16 | 17 | 18 | ```python 19 | cuadrados = [i**2 for i in range(5)] 20 | #[0, 1, 4, 9, 16] 21 | ``` 22 | 23 | De no existir, podríamos hacer lo mismo de la siguiente forma, pero necesitamos alguna que otra línea más de código. 24 | 25 | 26 | ```python 27 | cuadrados = [] 28 | for i in range(5): 29 | cuadrados.append(i**2) 30 | #[0, 1, 4, 9, 16] 31 | ``` 32 | 33 | El resultado es el mismo, pero resulta menos claro. Antes de continuar, veamos la sintaxis general de las comprensiones de listas. 34 | 35 | 36 | ```python 37 | # lista = [expresión for elemento in iterable] 38 | ``` 39 | 40 | Es decir, por un lado tenemos el `for elemento in iterable`, que itera un determinado iterable y "almacena" cada uno de los elementos en `elemento` [como vimos en este otro post sobre el for](/for-python/). Por otro lado, tenemos la `expresión`, que es lo que será añadido a la lista en cada iteración. 41 | 42 | La expresión puede ser una operación como hemos visto anteriormente `i**2`, pero también puede ser un valor constante. El siguiente ejemplo genera una lista de cinco unos. 43 | 44 | 45 | ```python 46 | unos = [1 for i in range(5)] 47 | #[1, 1, 1, 1, 1] 48 | ``` 49 | 50 | La expresión también puede ser una llamada a una función. Se podría escribir el ejemplo anterior del cálculo de cuadrados de la siguiente manera. 51 | 52 | 53 | ```python 54 | def eleva_al_2(i): 55 | return i**2 56 | 57 | cuadrados = [eleva_al_2(i) for i in range(5)] 58 | #[0, 1, 4, 9, 16] 59 | ``` 60 | 61 | Como puedes observar, las posibilidades son bastante amplias. Cualquier elemento que sea iterable puede ser usado con las *list comprehensions*. Anteriormente hemos iterado `range()` pero podemos hacer lo mismo para una lista. En el siguiente ejemplo vemos como dividir todos los números de una lista entre 10. 62 | 63 | 64 | ```python 65 | lista = [10, 20, 30, 40 , 50] 66 | nueva_lista = [i/10 for i in lista] 67 | #[1.0, 2.0, 3.0, 4.0, 5.0] 68 | ``` 69 | 70 | ## Añadiendo condicionales 71 | 72 | En el apartado anterior hemos visto como modificar todos los elementos de un iterable (como una lista) de diferentes maneras. La primera elevando cada elemento al cuadrado, y la segunda dividiendo cada elemento por diez. 73 | 74 | Pero, ¿y si quisiéramos realizar la operación sobre el elemento sólo si una determinada condición se cumple? Pues tenemos buenas noticias, porque es posible añadir un condicional `if`. La expresión genérica sería la siguiente. 75 | 76 | 77 | ```python 78 | # lista = [expresión for elemento in iterable if condición] 79 | ``` 80 | 81 | Por lo tanto la `expresión` sólo se aplicará al `elemento` si se cumple la `condición`. Veamos un ejemplo con una frase, de la que queremos saber el número de erres que tiene. 82 | 83 | 84 | ```python 85 | frase = "El perro de san roque no tiene rabo" 86 | erres = [i for i in frase if i == 'r'] 87 | #['r', 'r', 'r', 'r'] 88 | ``` 89 | 90 | Lo que hace el código anterior es iterar cada letra de la frase, y si es una `r`, se añade a la lista. De esta manera el resultado es una lista con tantas letras `r` como la frase original tiene, y podemos calcular las veces que se repite con `len()`. 91 | 92 | 93 | ```python 94 | print(len(erres)) 95 | #4 96 | ``` 97 | 98 | 99 | ## Sets comprehension 100 | 101 | Las *set comprehensions* son muy similares a las listas que hemos visto con anterioridad. La única diferencia es que debemos cambiar el `()` por `{}`. Como resulta evidente, dado que los [set](/sets-python/) no permiten duplicados, si intentamos añadir un elemento que ya existe en el set, simplemente no se añadirá. 102 | 103 | 104 | ```python 105 | frase = "El perro de san roque no tiene rabo" 106 | mi_set = {i for i in frase if i == "r"} 107 | #{'r'} 108 | ``` 109 | 110 | ## Dictionary comprehension 111 | 112 | Y por último, también tenemos las comprensiones de [diccionarios](/diccionarios-en-python/). Son muy similares a las anteriores, con la única diferencia que debemos especificar la `key` o llave. Veamos un ejemplo. 113 | 114 | 115 | ```python 116 | lista1 = ['nombre', 'edad', 'región'] 117 | lista2 = ['Pelayo', 30, 'Asturias'] 118 | 119 | mi_dict = {i:j for i,j in zip(lista1, lista2)} 120 | #{'nombre': 'Pelayo', 'edad': 30, 'región': 'Asturias'} 121 | ``` 122 | 123 | 124 | Como se puede ver, usando `:` asignamos un valor a una llave. Hemos usado también `zip()`, que nos permite iterar dos listas paralelamente. Por lo tanto, en este ejemplo estamos convirtiendo dos listas a un diccionario. 125 | 126 | ## Conclusiones 127 | 128 | Las comprensiones de listas, sets o diccionarios son una herramienta muy útil para hacer que nuestro código resulte más compacto y fácil de leer. Siempre que tengamos una colección iterable que queramos modificar, son una buena opción para evitar tener que escribir bucles for. 129 | 130 | Las comprensiones están también muy relacionadas con el concepto de programación funcional y otra funciones que Python nos ofrece como `filter` o `map`, por lo que si no las conoces te recomendamos que leas sobre ellas. 131 | 132 | En ciertas ocasiones, las comprensiones no resultan sólo útiles por que puedan ser escritas en una sola línea de código, sino que también pueden llegar a ser más rápidas que otros métodos. Es muy importante por lo tanto medir su tiempo de ejecución para saber si son una buena elección. 133 | 134 | Y por último, aunque su uso resulte de lo más Pythónico y elegante (algo que a muchos programadores de Python les encanta), hay que tener cuidado con su uso y no abusar de ellas. Resulta fácil caer en la tentación de acabar escribiendo comprensiones que son tan largas que prácticamente son imposibles de leer, algo que puede no ser muy buena idea. 135 | -------------------------------------------------------------------------------- /03_tiposyestructuras/00_tiposyestructuras.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📦 03. Tipos y estructuras 4 | order: 25 5 | has_children: true 6 | nav_order: c 7 | permalink: /tipos-python 8 | --- -------------------------------------------------------------------------------- /03_tiposyestructuras/01_enteros.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Entero en Python 4 | title_nav: 📗 Entero o int 5 | parent: 📦 03. Tipos y estructuras 6 | description: Los enteros en Python o también conocidos como int, son un tipo de datos que permite representar números enteros, es decir, positivos y negativos no decimales. 7 | order: 26 8 | nav_order: a 9 | permalink: /entero-en-python 10 | --- 11 | 12 | # Entero o int 13 | 14 | > Los enteros en Python o también conocidos como int, son un tipo de datos que permite representar números enteros, es decir, positivos y negativos no decimales. Si vienes de otros lenguajes de programación, olvídate de los `int8`, `uint16` y demás. Python nos da un sólo tipo que podemos usar sin preocuparnosdel tamaño del número almacenado por debajo. 15 | 16 | ## Introducción a int 17 | 18 | Los tipos enteros o `int` en Python permiten almacenar un valor numérico no decimal ya sea positivo o negativo de **cualquier valor**. La función `type()` nos devuelve el tipo de la variable, y podemos ver com efectivamente es de la clase `int`. 19 | 20 | 21 | ```python 22 | i = 12 23 | print(i) #12 24 | print(type(i)) # 25 | ``` 26 | 27 | En otros lenguajes de programación, los `int` tenían un valor máximo que pueden representar. Dependiendo de la longitud de palabra o `wordsize` de la arquitectura del ordenador, existen unos números mínimos y máximos que era posible representar. Si por ejemplo se usan enteros de 32 bits el rango a representar es de -2^31 a 2^31–1, es decir, -2.147.483.648 a 2.147.483.647. Con 64 bits, el rango es de -9.223.372.036.854.775.808 hasta 9.223.372.036.854.775.807. Una gran ventaja de Python es que ya no nos tenemos que preocupar de esto, ya que por debajo se encarga de asignar más o menos memoria al número, y podemos representar prácticamente cualquier número. 28 | 29 | 30 | ```python 31 | x = 250**250 32 | print(x) 33 | print(type(x)) 34 | #305493636349960468205197939321361769978940274057232666389361390928129162652472045770185723510801522825687515269359046715531785342780428396973513311420091788963072442053377285222203558881953188370081650866793017948791366338993705251636497892270212003524508209121908744820211960149463721109340307985507678283651836204093399373959982767701148986816406250000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 35 | # 36 | ``` 37 | 38 |

39 | Para saber más: Si vienes de otras versiones de Python, tal vez eches de menos el tipo long. Puedes leer la PEP237, donde se explica porqué se unificó los long y los int. 40 |

41 | 42 | ## Ejemplos 43 | En el primer ejemplo hemos asignado un número decimal a una variable, pero también es posible asignar valores en `binario`, `hexadecimal` y `octal`. El prefijo `0b` indica que lo que viene a continuación será interpretado como un número binario. Para el caso hexadecimal es con `0x` y octal con `0c`. Al imprimir, el número se convierte en decimal para todos los casos. 44 | 45 | 46 | ```python 47 | a = 0b100 48 | b = 0x17 49 | c = 0o720 50 | print(a, type(a)) #4 51 | print(b, type(b)) #23 52 | print(c, type(c)) #464 53 | ``` 54 | 55 | 56 | Con el siguiente ejemplo podemos ver como Python asigna diferente número de `bits` a las variables en función del número que quieren representar. La función `getsizeof()` devuelve el tamaño de una variable en memoria. 57 | 58 | 59 | ```python 60 | import sys 61 | x = 5**10000 62 | y = 10 63 | print(sys.getsizeof(x), type(x)) 64 | print(sys.getsizeof(y), type(y)) 65 | ``` 66 | 67 | Aunque como hemos dicho, Python puede representar casi cualquier número, hay casos límite en los que nos podemos encontrar con una excepción como la que mostramos a continuación. 68 | 69 | ```python 70 | print(5e200**2) 71 | # OverflowError 72 | ``` 73 | 74 | Un caso curioso es que si intentamos representar un número aún mayor, nos encontraremos con lo siguiente en vez de con una excepción. 75 | 76 | ```python 77 | print(2e2000**2) 78 | # inf 79 | ``` 80 | 81 | ## Convertir a int 82 | El posible convertir a `int` otro tipo. Como hemos explicado, el tipo `int` no puedo contener decimales, por lo que si intentamos convertir un número decimal, se truncará todo lo que tengamos a la derecha de la coma. 83 | 84 | ```python 85 | b = int(1.6) 86 | print(b) #1 87 | ``` 88 | -------------------------------------------------------------------------------- /03_tiposyestructuras/02_booleanos.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Boolean Python 4 | title_nav: 📗 Booleano 5 | parent: 📦 03. Tipos y estructuras 6 | description: Los tipos booleanos en Python, o también conocidos como bool en Inglés, son un tipo de datos que se usa para representar un valor binario, verdadero o falso (True o False). Por lo tanto sólo pueden almacenar dos valores distintos. Son uno de los tipos más usados en Python. 7 | order: 27 8 | nav_order: b 9 | permalink: /booleano-python 10 | --- 11 | 12 | # Booleanos en Python 13 | Al igual que en otros lenguajes de programación, en Python existe el tipo bool o booleano. Es un tipo de dato que permite almacenar dos valores `True` o `False`. 14 | 15 | ## Declarar variable bool 16 | Se puede declarar una variable booleana de la siguiente manera. 17 | ```python 18 | x = True 19 | y = False 20 | ``` 21 | 22 | ## Evaluar expresiones 23 | Un valor booleano también puede ser el resultado de evaluar una expresión. Ciertos operadores como el mayor que, menor que o igual que devuelven un valor bool. 24 | 25 | ```python 26 | print(1 > 0) #True 27 | print(1 <= 0) #False 28 | print(9 == 9) #True 29 | ``` 30 | 31 | ## Función bool 32 | También es posible convertir un determinado valor a bool usando la función bool(). 33 | 34 | ```python 35 | print(bool(10)) # True 36 | print(bool(-10)) # True 37 | print(bool("Hola")) # True 38 | print(bool(0.1)) # True 39 | print(bool([])) # False 40 | ``` 41 | 42 | ## Uso con if 43 | Los condicionales if evalúan una condición que es un valor bool. 44 | 45 | ```python 46 | a = 1 47 | b = 2 48 | if b > a: 49 | print("b es mayor que a") 50 | ``` 51 | 52 | La expresión que va después del if es siempre evaluada hasta que se da con un booleano. 53 | ```python 54 | if True: 55 | print("Es True") 56 | ``` 57 | 58 | ## Bool como subclase de int 59 | Es importante notar que aunque estemos listando el tipo `bool` como si fuese un tipo más, es en realidad una subclase del `int` visto anteriormente. De hecho lo puedes comprobar de la siguiente manera. 60 | 61 | ```python 62 | isinstance(True, int) 63 | #True 64 | issubclass(bool, int) 65 | #True 66 | ``` -------------------------------------------------------------------------------- /03_tiposyestructuras/03_float.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Float Python 4 | title_nav: 📗 Float 5 | parent: 📦 03. Tipos y estructuras 6 | description: Los float en Python son un tipo numérico que permite representar datos positivos y negativos decimales. En Python tienen la particularidad de ser en realidad lo que en otro lenguajes de programación se llama double. 7 | order: 28 8 | nav_order: c 9 | permalink: /float-python 10 | --- 11 | 12 | # Float 13 | 14 | El tipo numérico `float` permite representar un número positivo o negativo con decimales, es decir, números reales. Si vienes de otros lenguajes, tal vez conozcas el tipo `doble`, lo que significa que tiene el doble de precisión que un `float`. En Python las cosas son algo distintas, y los `float` son en realidad `double`. 15 | 16 |

17 | Para saber más: Los valores float son almacenados de una forma muy particular, denominada representación en coma flotante. En el estándar IEEE 754 se explica con detalle. 18 |

19 | 20 | Por lo tanto si declaramos una variable y le asignamos un valor decimal, por defecto la variable será de tipo `float`. 21 | 22 | 23 | ```python 24 | f = 0.10093 25 | print(f) #0.10093 26 | print(type(f)) # 27 | ``` 28 | 29 | ## Conversión a float 30 | 31 | También se puede declarar usando la notación científica con `e` y el exponente. El siguiente ejemplo sería lo mismo que decir `1.93` multiplicado por diez elevado a `-3`. 32 | 33 | 34 | ```python 35 | f = 1.93e-3 36 | ``` 37 | 38 | También podemos convertir otro tipo a float haciendo uso de `float()`. Podemos ver como `True` es en realidad tratado como `1`, y al convertirlo a float, como `1.0`. 39 | 40 | 41 | ```python 42 | a = float(True) 43 | b = float(1) 44 | print(a, type(a)) #1.0 45 | print(b, type(b)) #1.0 46 | ``` 47 | 48 | ## Rango representable 49 | 50 | Alguna curiosidad es que los `float` no tienen precisión infinita. Podemos ver en el siguiente ejemplo como en realidad `f` se almacena como si fuera `1`, ya que no es posible representar tanta precisión decimal. 51 | 52 | 53 | ```python 54 | f = 0.99999999999999999 55 | print(f) #1.0 56 | print(1 == f) #True 57 | ``` 58 | 59 | Los `float` a diferencia de los `int` tienen unos valores mínimo y máximos que pueden representar. La mínima precisión es `2.2250738585072014e-308` y la máxima `1.7976931348623157e+308`, pero si no nos crees, lo puedes verificar tu mismo. 60 | 61 | 62 | ```python 63 | import sys 64 | print(sys.float_info.min) #2.2250738585072014e-308 65 | print(sys.float_info.max) #1.7976931348623157e+308 66 | ``` 67 | 68 | 69 | De hecho si intentas asignar a una variable un valor mayor que el `max`, lo que ocurre es que esa variable toma el valor `inf` o infinito. 70 | 71 | 72 | ```python 73 | f = 1.7976931348623157e+310 74 | print(f) #inf 75 | ``` 76 | 77 | Si quieres representar una variable que tenga un valor muy alto, también puedes crear directamente una variable que contenga ese valor `inf`. 78 | 79 | 80 | ```python 81 | f = float('inf') 82 | print(f) #inf 83 | ``` 84 | 85 | ## Precisión del float 86 | 87 | Desafortunadamente, los ordenadores no pueden representar cualquier número, y menos aún si este es uno irracional. Debido a esto, suceden cosas curiosas como las siguientes. 88 | 89 | Dividir `1/3` debería resultar en 0.3 periódico, pero para Python es imposible representar tal número. 90 | 91 | ```python 92 | print("{:.20f}".format(1.0/3.0)) 93 | # 0.33333333333333331483 94 | ``` 95 | 96 | Por otro lado, la siguiente operación debería tener como resultado cero, pero como puedes ver esto no es así. 97 | 98 | ```python 99 | print(0.1 + 0.1 + 0.1 - 0.3) 100 | # 5.551115123125783e-17 101 | ``` 102 | 103 | Afortunadamente, salvo aplicaciones muy concretas esto no resulta un problema. Al fin y al cabo, ¿a quién le importa lo que haya en la posición decimal 15? 104 | -------------------------------------------------------------------------------- /03_tiposyestructuras/04_complejos.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Números Complejos Python 4 | title_nav: 📗 Números complejos 5 | parent: 📦 03. Tipos y estructuras 6 | description: Los números complejos son aquellos que se representan como una tupla de valores, uno real y otro imaginario. En Python se define un tipo para su representación. 7 | order: 29 8 | nav_order: d 9 | permalink: /numeros-complejos 10 | --- 11 | 12 | # ¿Qué son los números complejos? 13 | 14 | En pocas palabras, los números complejos son aquellos que tienen dos partes: 15 | * Una parte **real**, como por ejemplo `3` o `7.5`. 16 | * Y otra **imaginaria**, como `5j` o `-7j`. 17 | 18 | Como puedes ver, la parte imaginaria viene acompañada de `j`, aunque es también común usar la `i` (de imaginario). Un número complejo tiene la siguiente forma: 19 | 20 | ```python 21 | parte_real + parte_imaginaria*j 22 | ``` 23 | 24 | Veamos unos ejemplos: 25 | 26 | ```python 27 | a = 5 + 5j 28 | b = 1.3 - 7j 29 | ``` 30 | 31 | También podemos tener un número complejo con parte real igual a cero. 32 | 33 | ```python 34 | c = 10.3j 35 | ``` 36 | 37 | Una vez vistos cómo son, vamos a ponerlos en contexto con el resto de conjuntos numéricos: 38 | * Los números **enteros** hacen referencia a los números naturales, es decir, todos aquellos números que no contienen parte decimal: 3, -1, 10. 39 | * Los números **racionales** son el cociente de dos números naturales: 3/10, 7/9. 40 | * Los números **irracionales** son aquellos que no pueden ser expresados como una fracción m/n. Por ejemplo, el número `pi` es irracional. 41 | * Los números **reales** son el conjunto de todos los anteriores, es decir, cualquier número que se te ocurra sería un número real. 42 | * Los números **imaginarios** son números reales acompañados de la constante `i` (a veces la `j` es usada indistintamente), como `4i` o `3.7i`. 43 | * Y por último los números **complejos** son la suma de un número real y otro imaginario, dando lugar a los ya vistos `5+5j`. 44 | 45 | Llegados a este punto, tal vez te preguntes ¿y qué es `i`? Pues bien, `i` es simplemente un nombre que se le da a la raíz cuadrada de `-1`, ya que si recuerdas, los números negativos no tienen raíces cuadradas. Esta notación se la debemos al famoso [Leonhard Euler](https://es.wikipedia.org/wiki/Leonhard_Euler). 46 | 47 | Por lo tanto, la raíz cuadrada de `-5`, podría expresarse como `5i`, y aunque pueda parecer algo poco relevante, se trata de una herramienta muy potente en el mundo de las matemáticas, física e ingeniería. 48 | 49 | 50 | # ¿Para qué sirven los números complejos? 51 | 52 | Los números complejos son muy utilizados en las **telecomunicaciones** y **electrónica**, ya que son muy útiles para describir las ondas electromagnéticas y la corriente eléctrica. 53 | 54 | También son usados en diferentes dominios de las matemáticas y en física, donde destaca su uso en la mecánica cuántica. 55 | 56 | Un número complejo puede representarse en un plano, donde el eje `x` representa la parte real y el eje `y` la imaginaria. Es decir, se puede ver a un número complejo `5+5i` como un punto de coordenadas. 57 | 58 |
59 | 60 | Una vez representado en esta gráfica, cualquier número complejo formará un ángulo con el eje `x`. Todo número complejo tiene también un módulo, que es la distancia que une el punto del origen de coordenadas `0+0i`. 61 | 62 | Ahora sólo tienes que imaginarte a este punto dando vueltas a una determinada frecuencia alrededor del plano, y ya estarías describiendo a una onda sin haberte dado cuenta. 63 | 64 | 65 | # Números complejos en Python 66 | 67 | Los números complejos en Python pueden ser creados sin tener que importar ninguna librería. Basta con hacer lo siguiente: 68 | 69 | ```python 70 | c = 3 + 5j 71 | print(c) #(3+5j) 72 | print(type(c)) # 73 | ``` 74 | 75 | Podemos ver como la [clase](/programacion-orientada-a-objetos-python) que representa a los complejos en Python se llama `complex`. Una vez creado, es posible acceder a la parte real con `real` y a la imaginaria con `imag`. 76 | 77 | 78 | ```python 79 | c = 3 + 5j 80 | print(c.real) #3.0 81 | print(c.imag) #5.0 82 | ``` 83 | 84 | También se puede crear un número complejo haciendo uso de `complex`, pero sin usar la `j`. 85 | 86 | ```python 87 | c = complex(3,5) 88 | print(c) #(3+5j) 89 | ``` 90 | 91 | # Operaciones con números complejos 92 | 93 | Usando variables del tipo `complex`, podemos realizar las operaciones más comunes típicas de los números complejos. 94 | 95 | ## Suma de complejos 96 | 97 | Para sumar números complejos, se suman las partes reales por un lado, y las imaginarias por otro. 98 | 99 | ```python 100 | a = 1 + 3j 101 | b = 4 + 1j 102 | print(a+b) #(5+4j) 103 | ``` 104 | 105 | ## Resta de complejos 106 | 107 | Para restar, se restan las partes reales por un lado y las imaginarias por otro. 108 | 109 | ```python 110 | a = 1 + 3j 111 | b = 4 + 1j 112 | print(a-b) #(-3+2j) 113 | ``` 114 | 115 | ## Multiplicación de complejos 116 | 117 | La multiplicación es algo más compleja. Si multiplicamos `a+bj` por `c+dj`, se puede demostrar fácilmente que el resultado es `(ac-bd)` para la parte real y `(ad+bc)` para la imaginaria. 118 | 119 | ```python 120 | a = 1 + 3j 121 | b = 4 + 1j 122 | print(a*b) #(1+13j) 123 | ``` 124 | 125 | ## División de complejos 126 | 127 | También podemos hacer la división. Si quieres saber cómo se dividen dos números complejos y su demostración matemática, te dejamos [este enlace](https://www.superprof.es/diccionario/matematicas/aritmetica/division-complejos.html). 128 | 129 | ```python 130 | a = 1 + 3j 131 | b = 4 + 1j 132 | print(a/b) #(0.41+0.64j) 133 | ``` 134 | 135 | ## Conjugado de complejos 136 | 137 | Por último, podemos realizar el conjugado de un número complejo en Python con el método `conjugate()`. Calcular el conjugado consiste en negar la parte imaginaria, es decir, cambiar si signo de `+` a `-` y viceversa. 138 | 139 | ```python 140 | a = 1 + 1j 141 | print(a.conjugate()) #(1-1j) 142 | ``` 143 | 144 | # Librería cmath 145 | 146 | Si quieres realizar más operaciones con número complejos, tal vez quieras echar un vistazo a la librería [cmath](https://docs.python.org/3/library/cmath.html), que es mucho más completa, y compleja. 147 | 148 | Algunas de las cosas que puedes hacer son las siguientes: 149 | * Calcular la **fase**, que es el ángulo que forma el vector con el eje `x`, en radianes. 150 | * Calcular la forma **polar**, es decir módulo y ángulo. 151 | 152 | ```python 153 | import cmath 154 | 155 | print(cmath.phase(complex(5, 0))) # 0.0 156 | print(cmath.polar(complex(5, 5))) # (7.0710678118654755, 0.7853981633974483) 157 | ``` 158 | -------------------------------------------------------------------------------- /03_tiposyestructuras/07_estructuras_sets.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Set Python 4 | title_nav: 📙 Set 5 | parent: 📦 03. Tipos y estructuras 6 | description: Los sets en Python son una estructura de datos usada para almacenar elementos de una manera similar a las listas, pero con ciertas diferencias. 7 | order: 32 8 | nav_order: g 9 | permalink: /sets-python 10 | --- 11 | 12 | # Set 13 | 14 | Los sets en Python son una estructura de datos usada para almacenar elementos de una manera similar a las [listas](/listas-en-python/ "listas"), pero con ciertas diferencias. 15 | 16 | ## Crear set Python 17 | 18 | Los `set` en Python son un tipo que permite almacenar varios elementos y acceder a ellos de una forma muy **similar a las listas** pero con ciertas diferencias: 19 | * Los elementos de un set son **único**, lo que significa que no puede haber elementos duplicados. 20 | * Los set son **desordenados**, lo que significa que no mantienen el orden de cuando son declarados. 21 | * Sus elementos deben ser **inmutables**. 22 | 23 | Para entender mejor los `sets`, es necesario entender ciertos conceptos matemáticos como la **teoría de conjuntos**. 24 | 25 | Para **crear** un set en Python se puede hacer con `set()` y pasando como entrada cualquier tipo iterable, como puede ser una lista. Se puede ver como a pesar de pasar elementos duplicados como dos `8` y en un orden determinado, al imprimir el set no conserva ese orden y los duplicados se han eliminado. 26 | 27 | 28 | ```python 29 | s = set([5, 4, 6, 8, 8, 1]) 30 | print(s) #{1, 4, 5, 6, 8} 31 | print(type(s)) # 32 | ``` 33 | 34 | Se puede hacer lo mismo haciendo uso de `{}` y sin usar la palabra `set()` como se muestra a continuación. 35 | 36 | ```python 37 | s = {5, 4, 6, 8, 8, 1} 38 | print(s) #{1, 4, 5, 6, 8} 39 | print(type(s)) # 40 | ``` 41 | 42 | ## Operaciones con sets 43 | 44 | A diferencia de las listas, en los set no podemos modificar un elemento a través de su índice. Si lo intentamos, tendremos un `TypeError`. 45 | 46 | 47 | ```python 48 | s = set([5, 6, 7, 8]) 49 | #s[0] = 3 #Error! TypeError 50 | ``` 51 | 52 | Los elementos de un `set` deben ser **inmutables**, por lo que un elemento de un `set` no puede ser ni un diccionario ni una lista. Si lo intentamos tendremos un `TypeError` 53 | 54 | 55 | ```python 56 | lista = ["Perú", "Argentina"] 57 | #s = set(["México", "España", lista]) #Error! TypeError 58 | ``` 59 | 60 | Los `sets` se pueden iterar de la misma forma que las listas. 61 | 62 | 63 | ```python 64 | s = set([5, 6, 7, 8]) 65 | for ss in s: 66 | print(ss) #8, 5, 6, 7 67 | ``` 68 | 69 | 70 | Con la función `len()` podemos saber la longitud total del `set`. Como ya hemos indicado, los duplicados son eliminados. 71 | 72 | 73 | ```python 74 | s = set([1, 2, 2, 3, 4]) 75 | print(len(s)) #4 76 | ``` 77 | 78 | 79 | También podemos saber si un elemento está presente en un set con el operador `in`. Se el valor existe en el set, se devolverá `True`. 80 | 81 | 82 | ```python 83 | s = set(["Guitarra", "Bajo"]) 84 | print("Guitarra" in s) #True 85 | ``` 86 | 87 | 88 | Los `sets` tienen además diferentes funcionalidades, que se pueden aplicar en forma de operador o de método. Por ejemplo, el operador `|` nos permite realizar la **unión** de dos sets, lo que equivale a juntarlos. El equivalente es el método `union()` que vemos a continuación. 89 | 90 | 91 | ```python 92 | s1 = set([1, 2, 3]) 93 | s2 = set([3, 4, 5]) 94 | print(s1 | s2) #{1, 2, 3, 4, 5} 95 | ``` 96 | 97 | 98 | ## Métodos sets 99 | 100 | ### `s.add()` 101 | 102 | El método `add()` permite añadir un elemento al `set`. 103 | 104 | 105 | ```python 106 | l = set([1, 2]) 107 | l.add(3) 108 | print(l) #{1, 2, 3} 109 | ``` 110 | 111 | 112 | ### `s.remove()` 113 | 114 | El método `remove()` elimina el elemento que se pasa como parámetro. Si no se encuentra, se lanza la excepción `KeyError`. 115 | 116 | 117 | ```python 118 | s = set([1, 2]) 119 | s.remove(2) 120 | print(s) #{1} 121 | ``` 122 | 123 | 124 | ### `s.discard()` 125 | 126 | El método `discard()` es muy parecido al `remove()`, borra el elemento que se pasa como parámetro, y si no se encuentra no hace nada. 127 | 128 | 129 | ```python 130 | s = set([1, 2]) 131 | s.discard(3) 132 | print(s) #{1, 2} 133 | ``` 134 | 135 | 136 | ### `s.pop()` 137 | 138 | El método `pop()` elimina un elemento aleatorio del `set`. 139 | 140 | 141 | ```python 142 | s = set([1, 2]) 143 | s.pop() 144 | print(s) #{2} 145 | ``` 146 | 147 | 148 | ### `s.clear()` 149 | 150 | El método `clear()` elimina todos los elementos de `set`. 151 | 152 | 153 | ```python 154 | s = set([1, 2]) 155 | s.clear() 156 | print(s) #set() 157 | ``` 158 | 159 | 160 | ### Otros 161 | 162 | Los `sets` cuentan con una gran cantidad de métodos que permiten realizar operaciones con dos o más, como la **unión** o la **intersección**. 163 | 164 | Podemos calcular la **unión** entre dos sets usando el método `union()`. Esta operación representa la "mezcla" de ambos sets. Nótese que el método puede ser llamado con más parámetros de entrada, y su resultado será la unión de todos los sets. 165 | 166 | 167 | ```python 168 | s1 = {1, 2, 3} 169 | s2 = {3, 4, 5} 170 | print(s1.union(s2)) #{1, 2, 3, 4, 5} 171 | ``` 172 | 173 | 174 | También podemos calcular la **intersección** entre dos o más set. Su resultado serán aquellos elementos que pertenecen a ambos sets. 175 | 176 | 177 | ```python 178 | s1 = {1, 2, 3} 179 | s2 = {3, 4, 5} 180 | print(s1.intersection(s2)) #{3} 181 | ``` 182 | 183 | 184 | Los `set` en Python tiene gran cantidad de métodos, por lo que lo dejaremos para otro capítulo, pero aquí os dejamos con un listado de ellos: 185 | * `s1.union(s2[, s3 ...])` 186 | * `s1.intersection(s2[, s3 ...])` 187 | * `s1.difference(s2[, s3 ...])` 188 | * `s1.symmetric_difference(s2)` 189 | * `s1.isdisjoint(s2)` 190 | * `s1.issubset(s2)` 191 | * `s1.issuperset(s2)` 192 | * `s1.update(s2[, s3 ...])` 193 | * `s1.intersection_update(s2[, s3 ...])` 194 | * `s1.difference_update(s2[, s3 ...])` 195 | * `s1.symmetric_difference_update(s2)` 196 | -------------------------------------------------------------------------------- /03_tiposyestructuras/09_estructuras_tuplas.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Tuplas en Python 4 | title_nav: 📙 Tupla o tuple 5 | parent: 📦 03. Tipos y estructuras 6 | description: Las tuplas en Python son un tipo o estructura de datos que permite almacenar datos de una manera muy parecida a las listas, con la salvedad de que son inmutables. 7 | order: 33 8 | nav_order: h 9 | permalink: /tuplas-python 10 | --- 11 | 12 | # Tupla (tuple) 13 | 14 | Las tuplas en Python son un tipo o estructura de datos que permite almacenar datos de una manera muy parecida a las [listas](/listas-en-python/ "listas"), con la salvedad de que son [inmutables](/mutabilidad-python). 15 | 16 | ## Crear tupla Python 17 | 18 | Las tuplas en Python o `tuples` son muy similares a las listas, pero con dos diferencias. Son **inmutables**, lo que significa que no pueden ser modificadas una vez declaradas, y en vez de inicializarse con corchetes se hace con `()`. Dependiendo de lo que queramos hacer, **las tuplas pueden ser más rápidas**. 19 | 20 | 21 | ```python 22 | tupla = (1, 2, 3) 23 | print(tupla) #(1, 2, 3) 24 | ``` 25 | 26 | También pueden declararse sin `()`, separando por `,` todos sus elementos. 27 | 28 | 29 | ```python 30 | tupla = 1, 2, 3 31 | print(type(tupla)) # 32 | print(tupla) #(1, 2, 3) 33 | ``` 34 | 35 | 36 | ## Operaciones con tuplas 37 | 38 | Como hemos comentado, las tuplas son tipos **inmutables**, lo que significa que una vez asignado su valor, no puede ser modificado. Si se intenta, tendremos un `TypeError`. 39 | 40 | 41 | ```python 42 | tupla = (1, 2, 3) 43 | #tupla[0] = 5 # Error! TypeError 44 | ``` 45 | 46 | Al igual que las listas, las tuplas también pueden ser anidadas. 47 | 48 | 49 | ```python 50 | tupla = 1, 2, ('a', 'b'), 3 51 | print(tupla) #(1, 2, ('a', 'b'), 3) 52 | print(tupla[2][0]) #a 53 | ``` 54 | 55 | Y también es posible convertir una lista en tupla haciendo uso de al función `tuple()`. 56 | 57 | 58 | ```python 59 | lista = [1, 2, 3] 60 | tupla = tuple(lista) 61 | print(type(tupla)) # 62 | print(tupla) #(1, 2, 3) 63 | ``` 64 | 65 | 66 | Se puede **iterar** una tupla de la misma forma que se hacía con las listas. 67 | 68 | 69 | ```python 70 | tupla = [1, 2, 3] 71 | for t in tupla: 72 | print(t) #1, 2, 3 73 | ``` 74 | 75 | Y se puede también asignar el valor de una tupla con `n` elementos a `n` variables. 76 | 77 | 78 | ```python 79 | l = (1, 2, 3) 80 | x, y, z = l 81 | print(x, y, z) #1 2 3 82 | ``` 83 | 84 | Aunque tal vez no tenga mucho sentido a nivel práctico, es posible crear una tupla de un solo elemento. Para ello debes usar `,` antes del paréntesis, porque de lo contrario `(2)` sería interpretado como `int`. 85 | 86 | 87 | ```python 88 | tupla = (2,) 89 | print(type(tupla)) # 90 | ``` 91 | 92 | 93 | ## Métodos tuplas 94 | 95 | ### `count()` 96 | 97 | El método `count()` cuenta el número de veces que el objeto pasado como parámetro se ha encontrado en la lista. 98 | 99 | 100 | ```python 101 | l = [1, 1, 1, 3, 5] 102 | print(l.count(1)) #3 103 | ``` 104 | 105 | 106 | 107 | ### `index([,index])` 108 | 109 | El método `index()` busca el objeto que se le pasa como parámetro y devuelve el índice en el que se ha encontrado. 110 | 111 | 112 | ```python 113 | l = [7, 7, 7, 3, 5] 114 | print(l.index(5)) #4 115 | ``` 116 | 117 | 118 | En el caso de no encontrarse, se devuelve un `ValueError`. 119 | 120 | 121 | ```python 122 | l = [7, 7, 7, 3, 5] 123 | #print(l.index(35)) #Error! ValueError 124 | ``` 125 | 126 | El método `index()` también acepta un segundo parámetro opcional, que indica a partir de que índice empezar a buscar el objeto. 127 | 128 | 129 | ```python 130 | l = [7, 7, 7, 3, 5] 131 | print(l.index(7, 2)) #2 132 | ``` -------------------------------------------------------------------------------- /03_tiposyestructuras/10_estructuras_diccionarios.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Diccionarios en Python 4 | title_nav: 📙 Diccionario 5 | parent: 📦 03. Tipos y estructuras 6 | description: Los diccionarios en Python son una estructura de datos que permite almacenar su contenido en forma de llave y valor. 7 | order: 34 8 | nav_order: i 9 | permalink: /diccionarios-en-python 10 | --- 11 | 12 | 13 | # Diccionario 14 | 15 | Los diccionarios en Python son una estructura de datos que permite almacenar su contenido en forma de llave y valor. 16 | 17 | ## Crear diccionario Python 18 | 19 | Un diccionario en Python es una colección de elementos, donde cada uno tiene una llave `key` y un valor `value`. Los diccionarios se pueden crear con paréntesis `{}` separando con una coma cada par `key: value`. En el siguiente ejemplo tenemos tres `keys` que son el nombre, la edad y el documento. 20 | 21 | 22 | ```python 23 | d1 = { 24 | "Nombre": "Sara", 25 | "Edad": 27, 26 | "Documento": 1003882 27 | } 28 | print(d1) 29 | #{'Nombre': 'Sara', 'Edad': 27, 'Documento': 1003882} 30 | ``` 31 | 32 | 33 | Otra forma equivalente de crear un diccionario en Python es usando `dict()` e introduciendo los pares `key: value` entre paréntesis. 34 | 35 | 36 | ```python 37 | d2 = dict([ 38 | ('Nombre', 'Sara'), 39 | ('Edad', 27), 40 | ('Documento', 1003882), 41 | ]) 42 | print(d2) 43 | #{'Nombre': 'Sara', 'Edad': '27', 'Documento': '1003882'} 44 | ``` 45 | 46 | También es posible usar el constructor `dict()` para crear un diccionario. 47 | 48 | 49 | ```python 50 | d3 = dict(Nombre='Sara', 51 | Edad=27, 52 | Documento=1003882) 53 | print(d3) 54 | #{'Nombre': 'Sara', 'Edad': 27, 'Documento': 1003882} 55 | ``` 56 | 57 | Algunas propiedades de los diccionario en Python son las siguientes: 58 | * Son **dinámicos**, pueden crecer o decrecer, se pueden añadir o eliminar elementos. 59 | * Son **indexados**, los elementos del diccionario son accesibles a través del `key`. 60 | * Y son **anidados**, un diccionario puede contener a otro diccionario en su campo `value`. 61 | 62 | ## Acceder y modificar elementos 63 | 64 | Se puede acceder a sus elementos con `[]` o también con la función `get()` 65 | 66 | 67 | ```python 68 | print(d1['Nombre']) #Sara 69 | print(d1.get('Nombre')) #Sara 70 | ``` 71 | 72 | 73 | Para modificar un elemento basta con usar `[]` con el nombre del `key` y asignar el valor que queremos. 74 | 75 | 76 | ```python 77 | d1['Nombre'] = "Laura" 78 | print(d1) 79 | #{'Nombre': Laura', 'Edad': 27, 'Documento': 1003882} 80 | ``` 81 | 82 | 83 | Si el `key` al que accedemos no existe, se añade automáticamente. 84 | 85 | 86 | ```python 87 | d1['Direccion'] = "Calle 123" 88 | print(d1) 89 | #{'Nombre': 'Laura', 'Edad': 27, 'Documento': 1003882, 'Direccion': 'Calle 123'} 90 | ``` 91 | 92 | 93 | ## Iterar diccionario 94 | 95 | Los diccionarios se pueden iterar de manera muy similar a las listas u otras estructuras de datos. Para imprimir los `key`. 96 | 97 | 98 | ```python 99 | # Imprime los key del diccionario 100 | for x in d1: 101 | print(x) 102 | #Nombre 103 | #Edad 104 | #Documento 105 | #Direccion 106 | ``` 107 | 108 | 109 | Se puede imprimir también solo el `value`. 110 | 111 | 112 | ```python 113 | # Impre los value del diccionario 114 | for x in d1: 115 | print(d1[x]) 116 | #Laura 117 | #27 118 | #1003882 119 | #Calle 123 120 | ``` 121 | 122 | O si queremos imprimir el `key` y el `value` a la vez. 123 | 124 | ```python 125 | # Imprime los key y value del diccionario 126 | for x, y in d1.items(): 127 | print(x, y) 128 | #Nombre Laura 129 | #Edad 27 130 | #Documento 1003882 131 | #Direccion Calle 123 132 | ``` 133 | 134 | 135 | ## Diccionarios anidados 136 | 137 | Los diccionarios en Python pueden contener uno dentro de otro. Podemos ver como los valores anidado uno y dos del diccionario `d` contienen a su vez otro diccionario. 138 | 139 | 140 | ```python 141 | anidado1 = {"a": 1, "b": 2} 142 | anidado2 = {"a": 1, "b": 2} 143 | d = { 144 | "anidado1" : anidado1, 145 | "anidado2" : anidado2 146 | } 147 | print(d) 148 | #{'anidado1': {'a': 1, 'b': 2}, 'anidado2': {'a': 1, 'b': 2}} 149 | ``` 150 | 151 | 152 | ## Métodos diccionarios Python 153 | 154 | ### `clear()` 155 | 156 | El método `clear()` elimina todo el contenido del diccionario. 157 | 158 | 159 | ```python 160 | d = {'a': 1, 'b': 2} 161 | d.clear() 162 | print(d) #{} 163 | ``` 164 | 165 | 166 | ### `get([,])` 167 | 168 | El método `get()` nos permite consultar el `value` para un `key` determinado. El segundo parámetro es opcional, y en el caso de proporcionarlo es el valor a devolver si no se encuentra la `key`. 169 | 170 | 171 | ```python 172 | d = {'a': 1, 'b': 2} 173 | print(d.get('a')) #1 174 | print(d.get('z', 'No encontrado')) #No encontrado 175 | ``` 176 | 177 | 178 | ### `items()` 179 | 180 | El método `items()` devuelve una lista con los `keys` y `values` del diccionario. Si se convierte en `list` se puede indexar como si de una lista normal se tratase, siendo los primeros elementos las `key` y los segundos los `value`. 181 | 182 | 183 | ```python 184 | d = {'a': 1, 'b': 2} 185 | it = d.items() 186 | print(it) #dict_items([('a', 1), ('b', 2)]) 187 | print(list(it)) #[('a', 1), ('b', 2)] 188 | print(list(it)[0][0]) #a 189 | ``` 190 | 191 | 192 | ### `keys()` 193 | 194 | El método `keys()` devuelve una lista con todas las `keys` del diccionario. 195 | 196 | 197 | ```python 198 | d = {'a': 1, 'b': 2} 199 | k = d.keys() 200 | print(k) #dict_keys(['a', 'b']) 201 | print(list(k)) #['a', 'b'] 202 | ``` 203 | 204 | 205 | ### `values()` 206 | 207 | El método `values()` devuelve una lista con todos los `values` o valores del diccionario. 208 | 209 | 210 | ```python 211 | d = {'a': 1, 'b': 2} 212 | print(list(d.values())) #[1, 2] 213 | ``` 214 | 215 | 216 | ### `pop([,])` 217 | 218 | El método `pop()` busca y elimina la `key` que se pasa como parámetro y devuelve su valor asociado. Daría un error si se intenta eliminar una `key` que no existe. 219 | 220 | 221 | ```python 222 | d = {'a': 1, 'b': 2} 223 | d.pop('a') 224 | print(d) #{'b': 2} 225 | ``` 226 | 227 | 228 | También se puede pasar un segundo parámetro que es el valor a devolver si la `key` no se ha encontrado. En este caso si no se encuentra no habría error. 229 | 230 | 231 | ```python 232 | d = {'a': 1, 'b': 2} 233 | d.pop('c', -1) 234 | print(d) #{'a': 1, 'b': 2} 235 | ``` 236 | 237 | 238 | ### `popitem()` 239 | 240 | El método `popitem()` elimina de manera aleatoria un elemento del diccionario. 241 | 242 | 243 | ```python 244 | d = {'a': 1, 'b': 2} 245 | d.popitem() 246 | print(d) 247 | #{'a': 1} 248 | ``` 249 | 250 | 251 | ### `update()` 252 | 253 | El método `update()` se llama sobre un diccionario y tiene como entrada otro diccionario. Los `value` son actualizados y si alguna `key` del nuevo diccionario no esta, es añadida. 254 | 255 | 256 | ```python 257 | d1 = {'a': 1, 'b': 2} 258 | d2 = {'a': 0, 'd': 400} 259 | d1.update(d2) 260 | print(d1) 261 | #{'a': 0, 'b': 2, 'd': 400} 262 | ``` -------------------------------------------------------------------------------- /03_tiposyestructuras/11_estructuras_frozensets.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Frozenset Python 4 | title_nav: 📙 Frozenset 5 | parent: 📦 03. Tipos y estructuras 6 | description: Los frozenset en Python son una estructura de datos muy similar a los set, con la salvedad de que son inmutables, es decir, no pueden ser modificados una vez declarados. 7 | order: 35 8 | nav_order: j 9 | permalink: /frozenset-en-python 10 | --- 11 | 12 | # Frozenset 13 | 14 | Los frozenset en Python son una estructura de datos muy similar a los [set](/sets-python/ "set"), con la salvedad de que son [inmutables](/mutabilidad-python), es decir, no pueden ser modificados una vez declarados. 15 | 16 | 17 | ## Crear frozenset Python 18 | Los `frozensets` en Python son un tipo muy parecido a los [sets](/sets-python/) con la salvedad de que son inmutables, es decir, están congelados y no pueden ser modificados una vez inicializados. 19 | 20 | 21 | ```python 22 | fs = frozenset([1, 2, 3]) 23 | print(fs) #frozenset({1, 2, 3}) 24 | print(type(fs)) # 25 | ``` 26 | 27 | ## Ejemplos frozenset 28 | 29 | Dado que son inmutables, cualquier intento de modificación con los métodos que ya hemos visto en otros capítulos como `add()` o `clear()` dará un error, ya que los `frozenset` no disponen de esos métodos. 30 | 31 | 32 | ```python 33 | fs = frozenset([1, 2, 3]) 34 | #fs.add(4) #Error! AttributeError 35 | #fs.clear() #Error! AttributeError 36 | ``` 37 | 38 | Los `frozenset` pueden ser útiles cuando queremos usar un `set` pero se requiere que el tipo sea inmutable. Algo no muy común, pero que podría darse, es crear un set de sets. Es decir, un ser que contiene como elementos a otros sets. El siguiente código daría un `TypeError` ya que los elementos de un `set` deben ser por definición inmutables. 39 | 40 | 41 | ```python 42 | s1 = {1, 2} 43 | s2 = {3, 4} 44 | #s3 = {s1, s2} # Error! TypeError 45 | ``` 46 | 47 | Para resolver este problema, podemos crear un set de frozensets. Esto si es posible ya que el `frozenset` es un tipo inmutable. 48 | 49 | 50 | ```python 51 | s1 = frozenset([1, 2]) 52 | s2 = frozenset([3, 4]) 53 | s3 = {s1, s2} 54 | print(s3) #{frozenset({3, 4}), frozenset({1, 2})} 55 | ``` 56 | 57 | 58 | Lo mismo aplica a los [diccionarios](/diccionarios-python/), ya que su `key` debe ser un tipo inmutable. Si intentamos crear un diccionario con `set` como `key`, obtendremos un `TypeError`. 59 | 60 | 61 | ```python 62 | s1 = set([1, 2]) 63 | s2 = set([3, 4]) 64 | #d = {s1: "Texto1", s2: "Texto2"} # Error! TypeError 65 | ``` 66 | 67 | Pero si podemos crear un diccionario donde sus `key` son `frozenset`, ya que son un tipo inmutable. 68 | 69 | ```python 70 | s1 = frozenset([1, 2]) 71 | s2 = frozenset([3, 4]) 72 | d = {s1: "Texto1", s2: "Texto2"} 73 | print(d) #{frozenset({1, 2}): 'Texto1', frozenset({3, 4}): 'Texto2'} 74 | ``` 75 | 76 |

77 | Tal vez te interese leer acerca de otras estructuras de datos similares como los sets o las listas. 78 |

-------------------------------------------------------------------------------- /03_tiposyestructuras/12_castings.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Casting Python 4 | title_nav: 📙 Castings 5 | parent: 📦 03. Tipos y estructuras 6 | description: Hacer un cast significa convertir un tipo de dato a otro. En Python es posible convertir a string con str() o a integer o entero con int(). También se puede convertir a float con float(). 7 | order: 36 8 | nav_order: k 9 | permalink: /casting-python 10 | --- 11 | 12 | # Cast en Python 13 | 14 | Hacer un *cast* o *casting* significa convertir un tipo de dato a otro. Anteriormente hemos visto tipos como los [int](/entero-en-python), [string](/cadenas-python) o [float](/float-python). Pues bien, es posible convertir de un tipo a otro. 15 | 16 | Pero antes de nada, veamos los diferentes tipos de *cast* o conversión de tipos que se pueden hacer. Existen dos: 17 | 18 | * Conversión **implícita**: Es realizada automáticamente por Python. Sucede cuando realizamos ciertas operaciones con dos tipos distintos. 19 | 20 | * Conversión **explícita**: Es realizada expresamente por nosotros, como por ejemplo convertir de `str` a `int` con `str()`. 21 | 22 | ## Conversión implícita 23 | 24 | Esta conversión de tipos es realizada automáticamente por Python, prácticamente sin que nos demos cuenta. Aún así, es importante saber lo que pasa por debajo para evitar problemas futuros. 25 | 26 | El ejemplo más sencillo donde podemos ver este comportamiento es el siguiente: 27 | 28 | ```python 29 | a = 1 # 30 | b = 2.3 # 31 | 32 | a = a + b 33 | print(a) # 3.3 34 | print(type(a)) # 35 | ``` 36 | 37 | * `a` es un [int](/entero-en-python) 38 | * `b` es un [float](/float-python) 39 | 40 | Pero si sumamos `a` y `b` y almacenamos el resultado en `a`, podemos ver como internamente Python ha convertido el [int](/entero-en-python) en [float](/float-python) para poder realizar la operación, y la variable resultante es [float](/float-python) 41 | 42 | Sin embargo hay otros casos donde Python no es tan listo y no es capaz de realizar la conversión. Si intentamos sumar un [int](/entero-en-python) a un [string](/cadenas-python), tendremos un error `TypeError`. 43 | 44 | ```python 45 | a = 1 46 | b = "2.3" 47 | 48 | c = a + b 49 | 50 | # TypeError: unsupported operand type(s) for +: 'int' and 'str' 51 | ``` 52 | 53 | Si te das cuenta, es lógico que esto sea así, ya que en este caso `b` era `"2.3"`, pero ¿y si fuera `"Hola"`? ¿Cómo se podría sumar eso? No tiene sentido. 54 | 55 | ## Conversión explicita 56 | 57 | Por otro lado, podemos hacer conversiones entre tipos o *cast* de manera explícita haciendo uso de diferentes funciones que nos proporciona Python. Las más usadas son las siguientes: 58 | 59 | * float(), str(), int(), list(), set() 60 | * Y algunas otras como hex(), oct() y bin() 61 | 62 | ### Convertir float a int 63 | 64 | Para convertir de [float](/float-python) a [int](/entero-en-python) debemos usar `float()`. Pero mucho cuidado, ya que el tipo entero no puede almacena decimales, por lo que perderemos lo que haya después de la coma. 65 | 66 | ```python 67 | a = 3.5 68 | a = int(a) 69 | print(a) 70 | # Salida: 3 71 | ``` 72 | 73 | ### Convertir float a string 74 | 75 | Podemos convertir un [float](/float-python) a [string](/cadenas-python) con `str()`. Podemos ver en el siguiente código como cambia el tipo de `a` después de hacer el *cast*. 76 | 77 | ```python 78 | a = 3.5 79 | print(type(a)) # 80 | a = str(a) 81 | print(type(a)) # 82 | ``` 83 | 84 | ### Convertir string a float 85 | 86 | Podemos convertir un [string](/cadenas-python) a [float](/float-python) usando `float()`. Es importante notar que se usa el `.` como separador. 87 | 88 | ```python 89 | a = "35.5" 90 | print(float(a)) 91 | # Salida: 35.5 92 | ``` 93 | 94 | El siguiente código daría un error, dado que `,` no se reconoce por defecto como separador decimal. 95 | 96 | ```python 97 | a = "35,5" 98 | print(float(a)) 99 | # Salida: ValueError: could not convert string to float: '35,5' 100 | ``` 101 | 102 | Y por último, resulta obvio pensar que el siguiente código dará un error también. 103 | 104 | ```python 105 | a = "Python" 106 | print(float(a)) 107 | # Salida: ValueError: could not convert string to float: 'Python' 108 | ``` 109 | 110 | ### Convertir string a int 111 | 112 | Al igual que la conversión a [float](/float-python) del caso anterior, podemos convertir de [string](/cadenas-python) a [int](/entero-en-python) usando `int()`. 113 | ```python 114 | a = "3" 115 | print(type(a)) # 116 | a = int(a) 117 | print(type(a)) # 118 | ``` 119 | 120 | Cuidado ya que no es posible convertir a [int](/entero-en-python) cualquier valor. 121 | 122 | ```python 123 | a = "Python" 124 | a = int(a) 125 | # ValueError: invalid literal for int() with base 10: 'Python' 126 | ``` 127 | 128 | ### Convertir int a string 129 | 130 | La conversión de [int](/entero-en-python) a [string](/cadenas-python) se puede realizar con `str()`. 131 | 132 | A diferencia de otras conversiones, esta puede hacerse siempre, ya que cualquier valor entero que se nos ocurra poner en `a`, podrá ser convertido a [string](/cadenas-python). 133 | 134 | ```python 135 | a = 10 136 | print(type(a)) # 137 | a = str(a) 138 | print(type(a)) # 139 | ``` 140 | 141 | 142 | ### Convertir a list 143 | 144 | Es también posible hacer un *cast* a [lista](/listas-en-python), desde por ejemplo un [set](/sets-python). Para ello podemos usar `list()`. 145 | 146 | ```python 147 | a = {1, 2, 3} 148 | b = list(a) 149 | 150 | print(type(a)) # 151 | print(type(b)) # 152 | ``` 153 | 154 | ### Convertir a set 155 | 156 | Y de manera completamente análoga, podemos convertir de [lista](/listas-en-python) a [set](/sets-python) haciendo uso de `set()`. 157 | 158 | ```python 159 | a = ["Python", "Mola"] 160 | b = set(a) 161 | 162 | print(type(a)) # 163 | print(type(b)) # 164 | ``` 165 | 166 | Esperamos que esto haya servido para aclarar el concepto de *casts* o conversiones de tipos en Python. Es muy importante tener bien claro el tipo de datos con el que estamos trabajando en cada momento, ya que Python es un lenguaje con tipado dinámico, algo que puede ser una gran ventaja, pero también causa de muchos quebraderos de cabeza. 167 | 168 | Y recuerda, la función `type()` es tu amiga. 169 | 170 | Si quieres saber más, tal vez te interese el concepto de *duck typing*. 171 | -------------------------------------------------------------------------------- /03_tiposyestructuras/13_colecciones.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Colecciones 4 | parent: 📦 03. Tipos y estructuras 5 | description: xx 6 | order: 37 7 | nav_order: l 8 | permalink: /colecciones-python 9 | --- 10 | 11 | # Colecciones 12 | 13 | Proximamente -------------------------------------------------------------------------------- /04_funciones/00_funciones.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 🕹 04. Funciones 4 | order: 39 5 | has_children: true 6 | nav_order: d 7 | permalink: /funciones-python 8 | --- 9 | 10 | # Funciones en Python 11 | 12 |
13 | -------------------------------------------------------------------------------- /04_funciones/02_paso_valor_referencia.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Paso por valor y referencia 4 | parent: 🕹 04. Funciones 5 | description: Cuando una función es llamada, sus parámetros de entrada pueden ser pasados por valor o por referencia. En el primer caso se actuará sobre una copia de la variable y en el segundo sobre la misma variable. En Python, existen ciertas particularidades. 6 | order: 41 7 | nav_order: b 8 | permalink: /paso-por-valor-y-referencia 9 | --- 10 | 11 | ## Paso por valor y referencia 12 | 13 | En muchos lenguajes de programación existen los conceptos de paso por **valor** y por **referencia** que aplican a la hora de como trata una función a los parámetros que se le pasan como entrada. Su comportamiento es el siguiente: 14 | * Si usamos un parámetro pasado por **valor**, se creará una copia local de la variable, lo que implica que cualquier modificación sobre la misma no tendrá efecto sobre la original. 15 | * Con una variable pasada como **referencia**, se actuará directamente sobre la variable pasada, por lo que las modificaciones afectarán a la variable original. 16 | 17 | En Python las cosas son un poco distintas, y el comportamiento estará definido por el tipo de variable con la que estamos tratando. Veamos un ejemplo de paso por **valor**. 18 | 19 | 20 | ```python 21 | x = 10 22 | def funcion(entrada): 23 | entrada = 0 24 | funcion(x) 25 | 26 | print(x) # 10 27 | ``` 28 | 29 | 30 | Iniciamos la `x` a 10 y se la pasamos a `funcion()`. Dentro de la función hacemos que la variable valga 0. Dado que Python trata a los `int` como pasados por **valor**, dentro de la función se crea una copia local de `x`, por lo que la variable original no es modificada. 31 | 32 | No pasa lo mismo si por ejemplo `x` es una lista como en el siguiente ejemplo. En este caso Python lo trata como si estuviese pasada por **referencia**, lo que hace que se modifique la variable original. La variable original `x` ha sido modificada. 33 | 34 | 35 | ```python 36 | x = [10, 20, 30] 37 | def funcion(entrada): 38 | entrada.append(40) 39 | 40 | funcion(x) 41 | print(x) # [10, 20, 30, 40] 42 | ``` 43 | 44 | 45 | El ejemplo anterior nos podría llevar a pensar que si en vez de añadir un elemento a `x`, hacemos `x=[]`, estaríamos destruyendo la lista original. Sin embargo esto no es cierto. 46 | 47 | 48 | ```python 49 | x = [10, 20, 30] 50 | def funcion(entrada): 51 | entrada = [] 52 | 53 | funcion(x) 54 | print(x) 55 | # [10, 20, 30] 56 | ``` 57 | 58 | 59 | Una forma muy útil de saber lo que pasa por debajo de Python, es haciendo uso de la función `id()`. Esta función nos devuelve un identificador único para cada objeto. Volviendo al primer ejemplo podemos ver como los objetos a los que "apuntan" `x` y `entrada` son distintos. 60 | 61 | 62 | ```python 63 | x = 10 64 | print(id(x)) # 4349704528 65 | def funcion(entrada): 66 | entrada = 0 67 | print(id(entrada)) # 4349704208 68 | 69 | funcion(x) 70 | ``` 71 | 72 | 73 | Sin embargo si hacemos lo mismo cuando la variable de entrada es una lista, podemos ver que en este caso el objeto con el que se trabaja dentro de la función es el mismo que tenemos fuera. 74 | 75 | 76 | ```python 77 | x = [10, 20, 30] 78 | print(id(x)) # 4422423560 79 | def funcion(entrada): 80 | entrada.append(40) 81 | print(id(entrada)) # 4422423560 82 | 83 | funcion(x) 84 | ``` -------------------------------------------------------------------------------- /04_funciones/03_args_kwargs.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Args y Kwargs Python 4 | title_nav: 📙 Uso de args y kwargs 5 | parent: 🕹 04. Funciones 6 | description: Si quieres definir una función en Python con un número variable de argumentos de entrada, esto es posible gracias al uso de args y kwargs. El uso de args nos permite manejar los argumentos como una tupla, y kwargs como un diccionario. 7 | order: 42 8 | nav_order: c 9 | permalink: /args-kwargs-python 10 | --- 11 | 12 | # Args y Kwargs en Python 13 | 14 | Si alguna vez has tenido que definir una [función](/funciones-en-python/) con un número variable de argumentos y no has sabido como hacerlo, a continuación te explicamos cómo gracias a los args y kwargs en Python. 15 | 16 | Vamos a suponer que queremos una función que sume un conjunto de números, pero no sabemos *a priori* la cantidad de números que se quieren sumar. Si por ejemplo tuviéramos tres, la función sería tan sencilla como la siguiente. 17 | 18 | 19 | ```python 20 | def suma(a, b, c): 21 | return a+b+c 22 | 23 | suma(2, 4, 6) 24 | #Salida: 12 25 | ``` 26 | 27 | 28 | 29 | El problema surge si por ejemplo queremos sumar cuatro números. Como es evidente, la siguiente llamada a la función anterior daría un error ya que estamos usando cuatro argumentos mientras que la función sólo soporta tres. 30 | 31 | 32 | ```python 33 | suma(2, 4, 6, 1) 34 | #TypeError: suma() takes 3 positional arguments but 4 were given 35 | ``` 36 | 37 | 38 | Introducida ya la problemática, veamos como podemos resolver este problema con \*args y \*\*kwargs en Python. 39 | 40 | ## Uso de \*args 41 | 42 | Gracias a los \*args en Python, podemos definir funciones cuyo número de argumentos es variable. Es decir, podemos definir funciones genéricas que no aceptan un número determinado de parámetros, sino que se "adaptan" al número de argumentos con los que son llamados. 43 | 44 | De hecho, el *args* viene de *arguments* en Inglés, o argumentos. Haciendo uso de \*args en la declaración de la función podemos hacer que el número de parámetros que acepte sea variable. 45 | 46 | 47 | ```python 48 | def suma(*args): 49 | s = 0 50 | for arg in args: 51 | s += arg 52 | return s 53 | 54 | suma(1, 3, 4, 2) 55 | #Salida 10 56 | 57 | suma(1, 1) 58 | #Salida 2 59 | ``` 60 | 61 | 62 | Antes de nada, el uso del nombre `args` es totalmente arbitrario, por lo que podrías haberlo llamado como quisieras. Es una mera convención entre los usuarios de Python y resulta frecuente darle ese nombre. Lo que si es un requisito, es usar `*` junto al nombre. 63 | 64 | En el ejemplo anterior hemos visto como `*args` puede ser iterado, ya que en realidad es una [tupla](/tuplas-python/). Por lo tanto iterando la tupla podemos acceder a todos los argumentos de entrada, y en nuestro caso sumarlos y devolverlos. 65 | 66 | Nótese que es un mero ejemplo didáctico. En realidad podríamos hacer algo como lo siguiente, lo que sería mucho más sencillo. 67 | 68 | 69 | ```python 70 | def suma(*args): 71 | return sum(args) 72 | 73 | suma(5, 5, 3) 74 | #Salida 13 75 | ``` 76 | 77 | 78 | 79 | Con esto resolvemos nuestro problema inicial, en el que necesitábamos un número variable de argumentos. Sin embargo, hay otra forma que nos proporciona además un nombre asociado al argumento, con el uso de \*\*kwargs. La explicamos a continuación. 80 | 81 | ## Uso de \*\*kwargs 82 | 83 | Al igual que en \*args, en \*\*kwargs el nombre es una mera convención entre los usuarios de Python. Puedes usar cualquier otro nombre siempre y cuando respetes el `**`. 84 | 85 | En este caso, en vez de tener una tupla tenemos un [diccionario](/diccionarios-en-python/). Puedes verificarlo de la siguiente forma con `type()`. 86 | 87 | 88 | ```python 89 | def suma(**kwargs): 90 | print(type(kwargs)) 91 | 92 | suma(x=3) 93 | # 94 | ``` 95 | 96 | Pero veamos un ejemplo más completo. A diferencia de \*args, los \*\*kwargs nos permiten dar un nombre a cada argumento de entrada, pudiendo acceder a ellos dentro de la función a través de un diccionario. 97 | 98 | 99 | ```python 100 | def suma(**kwargs): 101 | s = 0 102 | for key, value in kwargs.items(): 103 | print(key, "=", value) 104 | s += value 105 | return s 106 | 107 | suma(a=3, b=10, c=3) 108 | #Salida 109 | #a = 3 110 | #b = 10 111 | #c = 3 112 | #16 113 | ``` 114 | 115 | Como podemos ver, es posible iterar los argumentos de entrada con `items()`, y podemos acceder a la clave `key` (o nombre) y el valor o `value` de cada argumento. 116 | 117 | El uso de los \*\*kwargs es muy útil si además de querer acceder al valor de las variables dentro de la función, quieres darles un nombre que de una información extra. 118 | 119 | ## Mezclando \*args y \*\*kwargs 120 | 121 | Una vez entendemos el uso de \*args y \*\*kwargs, podemos complicar las cosas un poco más. Es posible mezclar argumentos normales con \*args y \*\*kwargs dentro de la misma función. Lo único que necesitas saber es que debes definir la función en el siguiente orden: 122 | * Primero argumentos normales. 123 | * Después los \*args. 124 | * Y por último los \*\*kwargs. 125 | 126 | Veamos un ejemplo. 127 | 128 | 129 | ```python 130 | def funcion(a, b, *args, **kwargs): 131 | print("a =", a) 132 | print("b =", b) 133 | for arg in args: 134 | print("args =", arg) 135 | for key, value in kwargs.items(): 136 | print(key, "=", value) 137 | 138 | funcion(10, 20, 1, 2, 3, 4, x="Hola", y="Que", z="Tal") 139 | #Salida 140 | #a = 10 141 | #b = 20 142 | #args = 1 143 | #args = 2 144 | #args = 3 145 | #args = 4 146 | #x = Hola 147 | #y = Que 148 | #z = Tal 149 | ``` 150 | 151 | 152 | Y por último un truco que no podemos dejar sin mencionar es lo que se conoce como *tuple unpacking*. Haciendo uso de `*`, podemos extraer los valores de una lista o tupla, y que sean pasados como argumentos a la función. 153 | 154 | 155 | ```python 156 | def funcion(a, b, *args, **kwargs): 157 | print("a =", a) 158 | print("b =", b) 159 | for arg in args: 160 | print("args =", arg) 161 | for key, value in kwargs.items(): 162 | print(key, "=", value) 163 | 164 | args = [1, 2, 3, 4] 165 | kwargs = {'x':"Hola", 'y':"Que", 'z':"Tal"} 166 | 167 | funcion(10, 20, *args, **kwargs) 168 | #Salida 169 | #a = 10 170 | #b = 20 171 | #args = 1 172 | #args = 2 173 | #args = 3 174 | #args = 4 175 | #x = Hola 176 | #y = Que 177 | #z = Tal 178 | ``` 179 | -------------------------------------------------------------------------------- /04_funciones/05_funciones_lambda.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Funciones Lambda 4 | parent: 🕹 04. Funciones 5 | description: Las funciones lambda en Python o expresiones lambda, son un tipo de función anónima que puede ser definida en una sola línea de código. También pueden ser asignadas a una variable para ser usadas después. 6 | order: 44 7 | nav_order: e 8 | permalink: /lambda-python 9 | --- 10 | 11 | # Funciones lambda 12 | 13 | Las funciones `lambda` o anónimas son un tipo de funciones en Python que típicamente se definen en una línea y cuyo código a ejecutar suele ser pequeño. Resulta complicado explicar las diferencias, y para que te hagas una idea de ello te dejamos con la siguiente cita sacada de [la documentación oficial](https://docs.python.org/3/faq/design.html#why-can-t-lambda-expressions-contain-statements). 14 | 15 | 16 | > "Python lambdas are only a shorthand notation if you’re too lazy to define a function." 17 | 18 | Lo que significa algo así como, "las funciones lambda son simplemente una versión acortada, que puedes usar si te da pereza escribir una función" 19 | 20 | Lo que sería una función que suma dos números como la siguiente. 21 | 22 | 23 | ```python 24 | def suma(a, b): 25 | return a+b 26 | ``` 27 | 28 | Se podría expresar en forma de una función `lambda` de la siguiente manera. 29 | 30 | 31 | ```python 32 | lambda a, b : a + b 33 | ``` 34 | 35 | 36 | 37 | La primera diferencia es que una función `lambda` no tiene un nombre, y por lo tanto salvo que sea asignada a una variable, es totalmente inútil. Para ello debemos. 38 | 39 | 40 | ```python 41 | suma = lambda a, b: a + b 42 | ``` 43 | 44 | Una vez tenemos la función, es posible llamarla como si de una función normal se tratase. 45 | 46 | 47 | ```python 48 | suma(2, 4) 49 | ``` 50 | 51 | 52 | 53 | 54 | Si es una función que solo queremos usar una vez, tal vez no tenga sentido almacenarla en una variable. Es posible declarar la función y llamarla en la misma línea. 55 | 56 | 57 | ```python 58 | (lambda a, b: a + b)(2, 4) 59 | ``` 60 | 61 | 62 | ## Ejemplos 63 | 64 | Una función `lambda` puede ser la entrada a una función normal. 65 | 66 | 67 | ```python 68 | def mi_funcion(lambda_func): 69 | return lambda_func(2,4) 70 | 71 | mi_funcion(lambda a, b: a + b) 72 | ``` 73 | 74 | 75 | 76 | Y una función normal también puede ser la entrada de una función `lambda`. Nótese que son ejemplo didácticos y sin demasiada utilidad práctica per se. 77 | 78 | 79 | ```python 80 | def mi_otra_funcion(a, b): 81 | return a + b 82 | 83 | (lambda a, b: mi_otra_funcion(a, b))(2, 4) 84 | ``` 85 | 86 | 87 | 88 | A pesar de que las funciones `lambda` tienen muchas limitaciones frente a las funciones normales, comparten gran cantidad de funcionalidades. Es posible tener argumentos con valor asignado por defecto. 89 | 90 | 91 | ```python 92 | (lambda a, b, c=3: a + b + c)(1, 2) # 6 93 | ``` 94 | 95 | 96 | 97 | También se pueden pasar los parámetros indicando su nombre. 98 | 99 | 100 | ```python 101 | (lambda a, b, c: a + b + c)(a=1, b=2, c=3) # 6 102 | ``` 103 | 104 | 105 | 106 | Al igual que en las funciones se puede tener un número variable de argumentos haciendo uso de `*`, lo conocido como **tuple unpacking**. 107 | 108 | 109 | ```python 110 | (lambda *args: sum(args))(1, 2, 3) # 6 111 | ``` 112 | 113 | 114 | Y si tenemos los parámetros de entrada almacenados en forma de `key` y `value` como si fuera un diccionario, también es posible llamar a la función. 115 | 116 | 117 | ```python 118 | (lambda **kwargs: sum(kwargs.values()))(a=1, b=2, c=3) # 6 119 | ``` 120 | 121 | Por último, es posible devolver más de un valor. 122 | 123 | ```python 124 | x = lambda a, b: (b, a) 125 | print(x(3, 9)) 126 | # Salida (9,3) 127 | ``` -------------------------------------------------------------------------------- /04_funciones/06_recursividad.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Recursividad 4 | parent: 🕹 04. Funciones 5 | description: Podemos hacer uso de las funciones recursivas cuando es posible expresar un problema en subproblemas cuya naturaleza es la misma que la de el problema inicial. Dicho de otra manera, una función recursiva es aquella que se define en función de si misma. 6 | order: 45 7 | nav_order: f 8 | permalink: /recursividad 9 | --- 10 | 11 | # Recursividad 12 | 13 | > ¿En qué trabajas? Estoy intentando arreglar los problemas que creé cuando intentaba arreglar los problemas que creé cuando intentaba arreglar los problemas que creé. Y así nació la recursividad. 14 | 15 | La **recursividad** o recursión es un concepto que proviene de las matemáticas, y que aplicado al mundo de la programación nos permite resolver problemas o tareas donde las mismas pueden ser divididas en subtareas cuya funcionalidad es la misma. Dado que los subproblemas a resolver son de la misma naturaleza, se puede usar la misma función para resolverlos. Dicho de otra manera, una función recursiva es aquella que está definida en función de sí misma, por lo que se llama repetidamente a sí misma hasta llegar a un punto de salida. 16 | 17 | Cualquier función recursiva tiene dos secciones de código claramente divididas: 18 | * Por un lado tenemos la sección en la que la función se llama a sí misma. 19 | * Por otro lado, tiene que existir siempre una condición en la que la función retorna sin volver a llamarse. Es muy importante porque de lo contrario, la función se llamaría de manera indefinida. 20 | 21 | Veamos unos ejemplos con el **factorial** y la **serie de fibonacci**. 22 | 23 | ## Calcular factorial 24 | 25 | Uno de los ejemplos mas usados para entender la recursividad, es el cálculo del factorial de un número `n!`. El factorial de un número `n` se define como la multiplicación de todos sus números predecesores hasta llegar a uno. Por lo tanto `5!`, leído como cinco factorial, sería `5*4*3*2*1`. 26 | 27 | Utilizando un enfoque tradicional no recursivo, podríamos calcular el factorial de la siguiente manera. 28 | 29 | 30 | ```python 31 | def factorial_normal(n): 32 | r = 1 33 | i = 2 34 | while i <= n: 35 | r *= i 36 | i += 1 37 | return r 38 | 39 | factorial_normal(5) # 120 40 | ``` 41 | 42 | 43 | 44 | Dado que el factorial es una tarea que puede ser dividida en subtareas del mismo tipo (multiplicaciones), podemos darle un enfoque recursivo y escribir la función de otra manera. 45 | 46 | 47 | ```python 48 | def factorial_recursivo(n): 49 | if n == 1: 50 | return 1 51 | else: 52 | return n * factorial_recursivo(n-1) 53 | 54 | factorial_recursivo(5) # 120 55 | ``` 56 | 57 | 58 | 59 | Lo que realmente hacemos con el código anterior es llamar a la función `factorial_recursivo()` múltiples veces. Es decir, `5!` es igual a `5 * 4!` y `4!` es igual a `4 * 3!` y así sucesivamente hasta llegar a 1. 60 | 61 | Algo muy importante a tener en cuenta es que si realizamos demasiadas llamadas a la función, podríamos llegar a tener un error del tipo `RecursionError`. Esto se debe a que todas las llamadas van apilándose y creando un contexto de ejecución, algo que podría llegar a causar un `stack overflow`. Es por eso por lo que Python lanza ese error, para protegernos de llegar a ese punto. 62 | 63 | ## Serie de Fibonacci 64 | 65 | Otro ejemplo muy usado para ilustrar las posibilidades de la recursividad, es calcular la serie de fibonacci. Dicha serie calcula el elemento `n` sumando los dos anteriores `n-1` + `n-2`. Se asume que los dos primeros elementos son 0 y 1. 66 | 67 | 68 | ```python 69 | def fibonacci_recursivo(n): 70 | if n == 0: 71 | return 0 72 | elif n == 1: 73 | return 1 74 | else: 75 | return fibonacci_recursivo(n-1) + fibonacci_recursivo(n-2) 76 | ``` 77 | 78 | Podemos ver que siempre que la `n` sea distinta de cero o uno, se llamará recursivamente a la función, buscando los dos elementos anteriores. Cuando la `n` valga cero o uno se dejará de llamar a la función y se devolverá un valor concreto. Podemos calcular el elemento 7, que será 0,1,1,2,3,5,8,13, es decir, 13. 79 | 80 | 81 | ```python 82 | fibonacci_recursivo(7) 83 | # 13 84 | ``` -------------------------------------------------------------------------------- /04_funciones/10_corrutinas.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📕 Corrutinas 4 | parent: 🕹 04. Funciones 5 | description: xx 6 | order: 48 7 | nav_order: i 8 | permalink: /corrutinas-python 9 | --- 10 | 11 | -------------------------------------------------------------------------------- /04_funciones/11_caching_funciones.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📕 Caching Funciones 4 | parent: 🕹 04. Funciones 5 | description: Python nos permite realizar caching de funciones, lo que permite ahorrar en tiempo de ejecución cuando el valor con el que se llama a la función ya ha sido calculado anteriormente. El caching puede ser implementado por nosotros mismos usando un diccionario, o también podemos usar el lru_cache del paquete functools. 6 | order: 49 7 | nav_order: j 8 | permalink: /caching-python 9 | --- 10 | 11 | # Caching de Funciones 12 | 13 | El [caché](https://es.wikipedia.org/wiki/Cach%C3%A9_(inform%C3%A1tica)) es un término muy usado en informática, y hace referencia al almacenamiento de resultados previos para su posterior reutilización, lo que permite reducir el tiempo de respuesta. Por ejemplo, si llamamos a una función con un determinado parámetro y acto seguido realizamos la misma llamada, sería interesante **reutilizar el primer resultado** para no tener que calcularlo otra vez. Existen por lo tanto dos posibilidades: 14 | * Si ejecutamos la función y el resultado no ha sido calculado con anterioridad, se calcula y se almacena por si fuera útil en el futuro. Esto se conoce como **cache miss**. 15 | * Si ejecutamos la función y el caché tiene almacenado el resultado para esa operación, en vez de calcular otra vez la salida la podemos reutilizar, lo que se conoce como **cache hit**. Dado que estamos reutilizando un valor ya calculado, generalmente el tiempo de respuesta será menor. 16 | 17 | 18 | Por suerte, Python nos permite añadir *caching* a nuestras [funciones](/funciones-en-python), pero antes de implementarlo es conveniente hacer un análisis sobre nuestro programa y determinar si merece la pena. Algunas cosas a tener en cuenta: 19 | * El *caching* es especialmente útil cuando trabajamos con **funciones muy intensivas en cálculo**, lo que hace que reutilizar el valor del caché reduzca notablemente el tiempo de respuesta. 20 | * Es necesario conocer (a nivel estadístico) **la distribución de los argumentos con los que se llama la función**. Si la función bajo estudio se llama con valores muy dispares y apenas repetidos, el *caching* poco ayudará, ya que apenas tendremos un *cache hit*. 21 | * El uso de un caché puede mejorar el tiempo de respuesta, pero frecuentemente **se paga en un incremento del uso de memoria**. También es necesario decidir el número de valores a almacenar. 22 | 23 | A continuación veremos como implementar *caching* en Python, pudiendo hacerlo con [diccionarios](/diccionarios-en-python) o utilizando la librería `functools`. Para ejemplificarlo, veremos como implementar un caché en nuestro código de [números primos](/numeros-primos-python) visto anteriormente, empleando ambas formas. 24 | 25 | ```python 26 | def es_primo(num): 27 | for n in range(2, num): 28 | if num % n == 0: 29 | return False 30 | return True 31 | ``` 32 | 33 | # Caching con Diccionarios 34 | 35 | La primera forma de realizarlo es usando un [diccionario](/diccionarios-en-python) como caché. Nótese que este es un ejemplo didáctico, y que obvia algunos factores. Como puedes ver tenemos claramente diferenciado el *cache hit* y el *cache miss*. Si el valor no está en el caché se calcula y se devuelve. 36 | 37 | ```python 38 | def es_primo_concache(num, _cache={}): 39 | if num not in _cache: 40 | _cache[num] = True 41 | for n in range(2, num): 42 | if num % n == 0: 43 | _cache[num] = False 44 | break 45 | return _cache[num] 46 | ``` 47 | 48 | Dado que `es_primo` es bastante intensivo en cálculo, cuando usamos números grandes el ahorro puede ser muy significativo. En el siguiente código podemos ver como la primera vez que ejecutamos la función, se tardan 3.5 segundos, ya que el resultado tiene que ser calculado. Sin embargo la segunda vez que la llamamos con la misma entrada, tenemos un *cache hit*, por lo que el valor ya no es calculado sino recuperado del caché, tardando microsegundos. 49 | 50 | ```python 51 | 52 | import time 53 | tic = time.time() 54 | es_primo_concache(25565479) 55 | print(time.time() - tic) 56 | 57 | tic = time.time() 58 | es_primo_concache(25565479) 59 | print(time.time() - tic) 60 | 61 | # 3.5551438331604004 62 | # 4.0531158447265625e-06 63 | ``` 64 | 65 | # Caching con functools y lru_cache 66 | 67 | La segunda forma de realizarlo, y un poco más sofisticada es usando `lru_cache`, un [decorador](/decoradores-python) que viene con la librería estándar [functools](https://docs.python.org/3/library/functools.html). La mayor ventaja es que no necesitamos modificar la función. Nótese que `maxsize` nos permite indicar el número máximo de valores que queremos almacenar en el caché. 68 | 69 | ```python 70 | from functools import lru_cache 71 | 72 | @lru_cache(maxsize=32) 73 | def es_primo_concache(num): 74 | for n in range(2, num): 75 | if num % n == 0: 76 | return False 77 | return True 78 | ``` 79 | 80 | Por lo tanto si ahora llamamos a nuestra función con los mismos valores, podemos ver como la primera vez tarda 3.9 segundos, pero la segunda apenas tarda unos microsegundos. 81 | 82 | ```python 83 | import time 84 | tic = time.time() 85 | es_primo_concache(25565479) 86 | print(time.time() - tic) 87 | 88 | tic = time.time() 89 | es_primo_concache(25565479) 90 | print(time.time() - tic) 91 | 92 | # 3.9316678047180176 93 | # 3.0994415283203125e-06 94 | ``` 95 | 96 | En el caso de que queramos limpiar el caché de nuestra función, podemos realizar lo siguiente. 97 | 98 | ```python 99 | es_primo_concache.cache_clear() 100 | ``` -------------------------------------------------------------------------------- /05_operadores/00_operadores.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: ➗ 05. Operadores 4 | order: 51 5 | has_children: true 6 | nav_order: e 7 | permalink: /operadores-python 8 | --- 9 | -------------------------------------------------------------------------------- /05_operadores/03_operadores_relacionales.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Operadores Relacionales 4 | parent: ➗ 05. Operadores 5 | description: Los operadores relacionales permiten saber la relación existente entre dos variables, devolviendo True o False en función de si la relación indicada se cumple o no. 6 | order: 54 7 | nav_order: c 8 | permalink: /operadores-relacionales 9 | --- 10 | 11 | # Operadores relacionales 12 | Los operadores relacionales, o también llamados *comparison operators* nos permiten saber la **relación existente entre dos variables**. Se usan para saber si por ejemplo un número es mayor o menor que otro. Dado que estos operadores indican si se cumple o no una operación, el valor que devuelven es `True` o `False`. Veamos un ejemplo con `x=2` e `y=3` 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
OperadorNombreEjemplo
==Igualx == y = False
!=Distintox != y = True
>Mayorx > y = False
<Menorx < y = True
>=Mayor o igualx >= y = False
<=Menor o igualx < y = True
55 | 56 | 57 | ```python 58 | x=2; y=3 59 | print("Operadores Relacionales") 60 | print("x==y =", x==y) # False 61 | print("x!=y =", x!=y) # True 62 | print("x>y =", x>y) # False 63 | print("x=y =", x>=y) # False 65 | print("x<=y =", x<=y) # True 66 | ``` 67 | 68 | 69 | ## Operador == 70 | 71 | El operador `==` permite comparar si las variables introducidas a su izquierda y derecha son iguales. Muy importante no confundir con `=`, que es el operador de asignación. 72 | 73 | 74 | ```python 75 | print(4==4) # True 76 | print(4==5) # False 77 | print(4==4.0) # True 78 | print(0==False) # True 79 | print("asd"=="asd") # True 80 | print("asd"=="asdf") # False 81 | print(2=="2") # False 82 | print([1, 2, 3] == [1, 2, 3]) # True 83 | ``` 84 | 85 | 86 | 87 | ## Operador != 88 | El operador `!=` devuelve `True` si los elementos a comparar son iguales y `False` si estos son distintos. De hecho, una vez definido el operador `==`, no sería necesario ni explicar `!=` ya que hace exactamente lo contrario. Definido primero, definido el segundo. Es decir, si probamos con los mismos ejemplo que el apartado anterior, veremos como el resultado es el contrario, es decir `False` donde era `True` y viceversa. 89 | 90 | 91 | ```python 92 | print(4!=4) # False 93 | print(4!=5) # True 94 | print(4!=4.0) # False 95 | print(0!=False) # False 96 | print("asd"!="asd") # False 97 | print("asd"!="asdf") # True 98 | print(2!="2") # True 99 | print([1, 2, 3] != [1, 2, 3]) # False 100 | ``` 101 | 102 | 103 | 104 | ## Operador > 105 | El operador `>` devuelve `True` si el primer valor es mayor que el segundo y `False` de lo contrario. 106 | 107 | 108 | ```python 109 | print(5>3) # True 110 | print(5>5) # False 111 | ``` 112 | 113 | 114 | 115 | Algo bastante curioso, es como Python trata al tipo booleano. Por ejemplo, podemos ver como `True` es igual a `1`, por lo que podemos comprar el tipo `True` como si de un número se tratase. 116 | 117 | 118 | ```python 119 | print(True==1) # True 120 | print(True>0.999) # True 121 | ``` 122 | 123 | 124 | 125 |

126 | Para saber más: De hecho, el tipo bool en Python hereda de la clase int. Si quieres saber más acerca del tipo bool en Python puedes leer la PEP285 127 |

128 | 129 | También se pueden comparar listas. Si los elementos de la lista son numéricos, se comparará elemento a elemento. 130 | 131 | 132 | ```python 133 | print([1, 2] > [10, 10]) # False 134 | ``` 135 | 136 | 137 | 138 | ## Operador < 139 | El operador `<` devuelve `True` si el primer elemento es mayor que el segundo. Es totalmente válido aplicar operadores relacionales como `<` sobre cadenas de texto, pero el comportamiento es un tanto difícil de ver a simple vista. Por ejemplo `abc` es menor que `abd` y `A` es menor que `a` 140 | 141 | 142 | ```python 143 | print("abc" < "abd") # True 144 | print("A"<"a") # True 145 | ``` 146 | 147 | 148 | 149 | Para el caso de `A` y `a` la explicación es muy sencilla, ya que `Python` lo que en realidad está comparando es el valor entero `Unicode` que representa tal caracter. La función `ord()` nos da ese valor. Por lo tanto cuando hacemos `"A"<"a"` lo que en realidad hacemos es comprar dos números. 150 | 151 | 152 | ```python 153 | print(ord('A')) # 65 154 | print(ord('a')) # 97 155 | ``` 156 | 157 | 158 | 159 |

160 | Para saber más: En el siguiente enlace tienes más información de como Python compara variables que no son números. 161 |

162 | 163 | 164 | ## Operador >= 165 | 166 | Similar a los anteriores, `>=` permite comparar si el primer elemento es mayor o igual que es segundo, devolviendo `True` en el caso de ser cierto. 167 | 168 | 169 | ```python 170 | print(3>=3) # True 171 | print([3,4] >= [3,5]) # False 172 | ``` 173 | 174 | 175 | 176 | ## Operdor <= 177 | 178 | De la misma manera, `<=` devuelve `True` si el primer elemento es menor o igual que el segundo. Nos podemos encontrar con cosas interesantes debido a la precisión numérica existente al representar valores, como el siguiente ejemplo. 179 | 180 | 181 | ```python 182 | print(3<=2.99999999999999999) 183 | ``` 184 | -------------------------------------------------------------------------------- /05_operadores/06_operadores_identidad.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Operadores Identidad 4 | parent: ➗ 05. Operadores 5 | description: El operador de identidad o identity operator "is" nos indica si dos variables hacen referencia al mismo objeto, devolviendo True en el caso de ser cierto. 6 | order: 57 7 | nav_order: f 8 | permalink: /operadores-identidad 9 | --- 10 | 11 | # Operadores de Identidad 12 | 13 | El operador de identidad o *identity operator* `is` nos indica si dos variables hacen referencia al mismo objeto. Esto implica que si dos variables distintas tienen el mismo `id()`, el resultado de aplicar el operador `is` sobre ellas será `True`. 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
OperadorNombre
isDevuelve True si hacen referencia a el mismo objeto
is notDevuelve False si no hacen referencia a el mismo objeto
33 | 34 | ## Operador `is` 35 | 36 | El operador `is` comprueba si dos variables hacen referencia a el mismo objeto. En el siguiente ejemplo podemos ver como al aplicarse sobre `a` y `b` el resultado es `True`. 37 | 38 | 39 | ```python 40 | a = 10 41 | b = 10 42 | 43 | print(a is b) # True 44 | ``` 45 | 46 | Esto es debido a que Python reutiliza el mismo objeto que almacena el valor 10 para ambas variables. De hecho, si usamos la función `id()`, podemos ver que el objeto es el mismo. 47 | 48 | ```python 49 | print(id(a)) # 4397849536 50 | print(id(b)) # 4397849536 51 | ``` 52 | 53 | Podemos ver como también, ambos valores son iguales con el [operador relacional](/operadores-relacionales) `==`, pero esto es una mera casualidad como veremos a continuación. Que dos variables tengan el mismo contenido, no implica necesariamente que hagan referencia a el mismo objeto. 54 | 55 | ```python 56 | print(a == b) # True 57 | ``` 58 | 59 | En el siguiente ejemplo, podemos ver como `a` y `b` almacenan el mismo valor, por lo que `==` nos indica `True`. 60 | 61 | ```python 62 | a = [1, 2, 3] 63 | b = [1, 2, 3] 64 | 65 | print(a == b) # True 66 | print(a is b) # False 67 | ``` 68 | 69 | Sin embargo, por como Python funciona por debajo, almacena el contenido en dos objetos diferentes. Al tratarse de objetos diferentes, esto hace que el operador `is` devuelva `False`. 70 | 71 | A diferencia de antes, podemos ver como la función `id()` en este caso nos devuelve un valor diferente. 72 | 73 | ```python 74 | print(id(a)) # 4496626880 75 | print(id(b)) # 4496626816 76 | ``` 77 | 78 | Esta diferencia puede resultar algo liosa, por lo que te recomendamos que leas más acerca de la [mutabilidad](/mutabilidad-python) en Python. 79 | 80 | 81 |

82 | Para saber más: Si quieres saber más acerca del operador id() te dejamos este enlace a la documentación oficial. 83 |

84 | 85 | 86 | ## Operador `is not` 87 | 88 | Una vez definido `is`, es trivial definir `is not` porque es exactamente lo contrario. Devuelve `True` cuando ambas variables no hacen referencia al mismo objeto. 89 | 90 | ```python 91 | # Python crea dos objetos diferentes, uno 92 | # para cada lista. Las listas son mutables. 93 | a = [1, 2, 3] 94 | b = [1, 2, 3] 95 | 96 | print(a is not b) # True 97 | ``` 98 | 99 | ```python 100 | # Python reutiliza el objeto que almacena 5 101 | # por lo que ambas variables apuntan a el mismo 102 | a = 5 103 | b = 5 104 | 105 | print(a is not b) # False 106 | ``` 107 | 108 |

109 | Para saber más: Te dejamos un enlace muy interesante con más información sobre el is y is not. 110 |

111 | 112 | -------------------------------------------------------------------------------- /05_operadores/07_operadores_membresia.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Operadores Membresía 4 | parent: ➗ 05. Operadores 5 | description: Los operadores de membresía en Python permiten saber si un elemento esta contenido en una secuencia, y son el in y not in. 6 | order: 58 7 | nav_order: g 8 | permalink: /operadores-membresia 9 | --- 10 | 11 | # Operadores de membresía 12 | 13 | Los operadores de membresía o *membership operators* son operadores que nos **permiten saber si un elemento esta contenido en una secuencia**. Por ejemplo si un número está contenido en una lista de números. 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
OperadorNombreEjemplo
inTrue si el elemento esta contenidoxxx
not inFalse si el elemento no esta contenidoxxx
36 | 37 | ## Operador `in` 38 | 39 | El operador `in` nos permite ver si un elemento esta contenido dentro de una secuencia, como podría ser una lista. En el siguiente ejemplo se ve un caso sencillo donde se verifica si `3` esta contenido en la lista `[1, 2, 3]`. Como efectivamente lo está, el resultado es `True`. 40 | 41 | 42 | ```python 43 | print(3 in [1, 2, 3]) 44 | # True 45 | ``` 46 | 47 | 48 | 49 | Vamos a complicar las cosas un poco y explorar los límites del operador. Que pasaría si intentásemos hacer algo como lo que se ve en el siguiente ejemplo. Podría ser lógico pensar que `3 in 3` sería `True`, porque realmente si que parece que el 3 esta contenido en el segundo 3. Pues no, el siguiente código daría un error, diciendo que la clase `int` no es iterable. En otros capítulos exploraremos más acerca de esto. Por ahora nos basta con decir que el elemento a la derecha del `in` debe ser un objeto tipo lista 50 | 51 | 52 | ```python 53 | #print(3 in 3) # Error! TypeError 54 | ``` 55 | 56 | Vamos a darle una última vuelta de tuerca. Podríamos también ver si una lista está contenida en otra lista. En este caso, la lista de la derecha del `in` es una lista embebida dentro de otra lista. Como `[1, 2]` está dentro de la segunda lista, el resultado es `True` 57 | 58 | 59 | ```python 60 | print([1, 2] in [4, [1, 2], 7]) 61 | # True 62 | ``` 63 | 64 | 65 | ## Operador `not in` 66 | 67 | Por último, el operador `not in` realiza lo contrario al operador `in`. Verifica que un elemento no está contenido en otra secuencia. En el siguiente ejemplo se puede ver como `3` no es parte de la secuencia, por lo que el resultado es `False` 68 | 69 | 70 | ```python 71 | print(3 not in [1, 2, 4, 5]) 72 | # True 73 | ``` 74 | 75 | 76 | 77 | La verdad que ambos operadores `in` y `not in` son muy útiles y nos ahorran mucho trabajo. Es importante tenerlo en cuenta, porque no otros lenguajes de programación no existen tales operadores, y debemos escribir código extra para obtener tal funcionalidad. Una forma de implementar nuestro operador `in` y `is not` con una función sería la siguiente. Simplemente iteramos la lista y si encontramos el elemento que estábamos buscando devolvemos `True`, de lo contrario `False`. 78 | 79 | 80 | ```python 81 | a=3 82 | lista=[1, 2, 3, 4, 5] 83 | 84 | # Función que implementa "is" y "is not" 85 | def estaContenido(a, lista): 86 | for l in lista: 87 | if a==l: 88 | return True 89 | return False 90 | 91 | print(estaContenido(a, lista)) 92 | ``` 93 | 94 | 95 | 96 |

97 | Para saber más: Te dejamos un enlace a la documentación oficial acerca de los operadores de membresía. 98 |

99 | 100 | -------------------------------------------------------------------------------- /05_operadores/08_operador_walrus.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Walrus Python 4 | title_nav: 📙 Operador walrus 5 | parent: ➗ 05. Operadores 6 | description: El operador walrus fue introducido en Python 3.8 y se representa por dos puntos seguidos de un igual (:=). Su uso permite asignar y devolver una variable en la misma sentencia, y su nombre se debe a que el operador se parece a una morsa con sus colmillos (walrus significa morsa en Inglés). 7 | order: 59 8 | nav_order: h 9 | permalink: /operador-walrus 10 | --- 11 | 12 | # Operador Walrus 13 | 14 | ## Introducción 15 | 16 | El operador walrus o *walrus operator* se introdujo con la [PEP572](https://www.python.org/dev/peps/pep-0572) en Python 3.8, y se trata de un operador de [asignación](/operadores-asignacion) con una funcionalidad extra que veremos a continuación. El operador se representa con dos puntos seguidos de un igual `:=`, lo que tiene cierto parecido a una morsa, siendo `:` los ojos y `=` los colmillos, por lo que de ahí viene su nombre (*walrus* significa morsa en Inglés). 17 | 18 | El problema que dicho operador intenta resolver es el siguiente. Podemos simplificar las siguientes tres líneas de código. 19 | 20 | ```python 21 | x = "Python" 22 | print(x) 23 | print(type(x)) 24 | 25 | # Salida: 26 | # Python 27 | # 28 | ``` 29 | 30 | En sólo dos líneas. Como podemos ver, el uso de `:=` **asigna y devuelve** el contenido de la variable. 31 | ```python 32 | print(x := "Python") 33 | print(type(x)) 34 | # Python 35 | # 36 | ``` 37 | 38 | Para gente que venga de otros lenguajes de programación como `C`, tal vez le resulte raro este operador, ya que C permite realizar lo siguiente. Podemos ver como `(x = 5)` puede ser comparado con `== 5`. Sin embargo **esta sintaxis no es válida en Python**, y el operador wallrus es precisamente lo que intenta resolver. 39 | 40 | ```c 41 | #include 42 | int main(){ 43 | int x; 44 | if ((x = 5) == 5) { 45 | printf("Hola"); 46 | } 47 | return 0; 48 | } 49 | ``` 50 | 51 | ## Ejemplos Operador Walrus 52 | 53 | En el siguiente programa pedimos al usuario que introduzca un texto y lo vamos añadiendo a una lista hasta que el texto introducido sea "terminar". Sin el operador walrus se podría escribir de la siguiente forma. 54 | ```python 55 | lista = [] 56 | entrada = input("Escribe algo: ") 57 | while entrada != "terminar": 58 | lista.append(entrada) 59 | entrada = input("Escribe algo: ") 60 | 61 | print(lista) 62 | ``` 63 | 64 | Sin embargo aprovechando que el operador walrus `:=` devuelve el contenido que es asignado, podemos usar `entrada` para comparar con `!= "terminar"`. Es decir, podemos unir la asignación `=` y la comparación en la misma línea. Esto nos permite ahorrar alguna línea de código. 65 | 66 | ```python 67 | lista = [] 68 | while (entrada := input("Escribe algo: ")) != "terminar": 69 | lista.append(entrada) 70 | 71 | print(lista) 72 | ``` 73 | 74 | También nos podemos ahorrar una línea de código si queremos primero asignar a una variable y luego emplear dicha variable en, por ejemplo, un [if](/if-python). 75 | ```python 76 | a = 20*[1] 77 | if (n := len(a)) > 10: 78 | print(f"La lista tiene {n} elementos (>10)") 79 | ``` 80 | 81 | Por otro lado puede ser útil cuando usemos [list comprehensions](/list-comprehension-python) donde el valor que usamos para filtrar es modificado y se necesita también en el cuerpo del bucle. 82 | ```python 83 | resultado = [(x, y, x/y) for x in datos if (y := f(x)) > 0] 84 | ``` 85 | 86 | De manera similar, podemos reutilizar el resultado de una expresión evitando tener que volver a computarla. 87 | 88 | ```python 89 | lista = [[y := f(x), x/y] for x in range(5)] 90 | ``` 91 | 92 | Puedes encontrar otros ejemplos en los siguientes enlaces: 93 | * En la [documentación oficial](https://docs.python.org/3/whatsnew/3.8.html#assignment-expressions) de Python 94 | * En la propia sección de ejemplos de la [PEP572](https://www.python.org/dev/peps/pep-0572/#examples). 95 | * Y en este [pull request](https://github.com/python/cpython/pull/8122/files). 96 | 97 | 98 | ## Críticas Al Operador Walrus 99 | 100 | El operador walrus ha recibido muchas críticas entre los desarrolladores de Python, principalmente porque aunque sea cierto que su uso puede ahorrarnos alguna línea de código, a veces empeora la legibilidad. No siempre menos código implica una mejora, sobre todo si se hace más complicado de leer. 101 | 102 | Por otro lado, aunque el operador walrus sea muy similar al operador de asignación `=`, su uso no es consistente. Por ejemplo, el siguiente código no es correcto usando `:=`. 103 | 104 | ```python 105 | # Incorrecto 106 | class Example: 107 | [(j := i) for i in range(5)] 108 | # SyntaxError: assignment expression within a comprehension cannot be used in a class body 109 | ``` 110 | 111 | De hecho, el propio creador *Christoph Groth* sugirió en el siguiente [hilo](https://mail.python.org/pipermail/python-ideas/2018-March/049409.html) que tal vez sería conveniente implementar el uso de `=` como en C/C++ (recuerda el ejemplo que hemos visto anteriormente). Si esto se llega a producir, no sabemos que podría pasar con el operador walrus, pero tal vez ya no sería necesario. -------------------------------------------------------------------------------- /06_programacionorientadaobjetos/00_programacionorientaaobjetos.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 🏄‍♂️ 06. Programación orientada a objetos 4 | order: 60 5 | has_children: true 6 | nav_order: f 7 | permalink: /programacion-orientada-a-objetos 8 | --- 9 | -------------------------------------------------------------------------------- /06_programacionorientadaobjetos/02_poo_tipos_metodos.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Tipos de métodos 4 | parent: 🏄‍♂️ 06. Programación orientada a objetos 5 | description: Existen varios tipos de métodos y pueden ser clasificados en tres. Los de instancia son los "normales" que pueden acceder al contenido de la clase y el objeto. Los classmethod solamente pueden acceder al contenido de la clase. Por último, los staticmethod no pueden ni a la clase ni al objeto. 6 | order: 62 7 | nav_order: b 8 | permalink: /metodos-estaticos-clase-python 9 | --- 10 | 11 | 12 | # Métodos en Python: instancia, clase y estáticos 13 | En otros posts hemos visto como se pueden crear métodos con `def` dentro de una clase, pudiendo recibir parámetros como entrada y modificar el estado (como los atributos) de la instancia. Pues bien, haciendo uso de los decoradores, es posible crear diferentes tipos de métodos: 14 | * Lo métodos de instancia "normales" que ya hemos visto como `metodo()` 15 | * Métodos de clase usando el decorador `@classmethod` 16 | * Y métodos estáticos usando el decorador `@staticmethod` 17 | 18 | En la siguiente clase tenemos un ejemplo donde definimos los tres tipos de métodos. 19 | 20 | 21 | ```python 22 | class Clase: 23 | def metodo(self): 24 | return 'Método normal', self 25 | 26 | @classmethod 27 | def metododeclase(cls): 28 | return 'Método de clase', cls 29 | 30 | @staticmethod 31 | def metodoestatico(): 32 | return "Método estático" 33 | ``` 34 | 35 | Veamos su comportamiento en detalle uno por uno. 36 | 37 | ## Métodos de instancia 38 | 39 | Los **métodos de instancia** son los métodos normales, de toda la vida, que hemos visto anteriormente. Reciben como parámetro de entrada `self` que hace referencia a la instancia que llama al método. También pueden recibir otros argumentos como entrada. 40 | 41 |

42 | Para saber más: El uso de "self" es totalmente arbitrario. Se trata de una convención acordada por los usuarios de Python, usada para referirse a la instancia que llama al método, pero podría ser cualquier otro nombre. Lo mismo ocurre con "cls", que veremos a continuación. 43 |

44 | 45 | 46 | 47 | ```python 48 | class Clase: 49 | def metodo(self, arg1, arg2): 50 | return 'Método normal', self 51 | ``` 52 | 53 | Y como ya sabemos, una vez creado un objeto pueden ser llamados. 54 | 55 | 56 | ```python 57 | mi_clase = Clase() 58 | mi_clase.metodo("a", "b") 59 | # ('Método normal', <__main__.Clase at 0x10b9daa90>) 60 | ``` 61 | 62 | 63 | En vista a esto, los **métodos de instancia**: 64 | * Pueden **acceder y modificar los atributos del objeto**. 65 | * Pueden **acceder a otros métodos**. 66 | * Dado que desde el objeto `self` se puede acceder a la clase con ` self.__class__`, también **pueden modificar el estado de la clase** 67 | 68 | ## Métodos de clase (classmethod) 69 | 70 | A diferencia de los métodos de instancia, los métodos de clase reciben como argumento `cls`, que hace referencia a la clase. Por lo tanto, pueden acceder a la clase pero no a la instancia. 71 | 72 | 73 | ```python 74 | class Clase: 75 | @classmethod 76 | def metododeclase(cls): 77 | return 'Método de clase', cls 78 | ``` 79 | 80 | Se pueden llamar sobre la clase. 81 | 82 | 83 | ```python 84 | Clase.metododeclase() 85 | # ('Método de clase', __main__.Clase) 86 | ``` 87 | 88 | 89 | 90 | 91 | Pero también se pueden llamar sobre el objeto. 92 | 93 | 94 | ```python 95 | mi_clase.metododeclase() 96 | # ('Método de clase', __main__.Clase) 97 | ``` 98 | 99 | 100 | 101 | 102 | 103 | Por lo tanto, los **métodos de clase**: 104 | * **No** pueden acceder a los atributos de la instancia. 105 | * Pero **si pueden** modificar los atributos de la clase. 106 | 107 | 108 | ## Métodos estáticos (staticmethod) 109 | 110 | Por último, los métodos estáticos se pueden definir con el decorador `@staticmethod` y no aceptan como parámetro ni la instancia ni la clase. Es por ello por lo que **no pueden modificar el estado ni de la clase ni de la instancia**. Pero por supuesto pueden aceptar parámetros de entrada. 111 | 112 | 113 | ```python 114 | class Clase: 115 | @staticmethod 116 | def metodoestatico(): 117 | return "Método estático" 118 | ``` 119 | 120 | 121 | ```python 122 | mi_clase = Clase() 123 | Clase.metodoestatico() 124 | mi_clase.metodoestatico() 125 | 126 | # 'Método estático' 127 | # 'Método estático' 128 | ``` 129 | 130 | 131 | Por lo tanto el uso de los **métodos estáticos** pueden resultar útil para indicar que un método no modificará el estado de la instancia ni de la clase. Es cierto que se podría hacer lo mismo con un método de instancia por ejemplo, pero a veces resulta importante indicar de alguna manera estas peculiaridades, evitando así futuros problemas y malentendidos. 132 | 133 | En otras palabras, los métodos estáticos se podrían ver como funciones normales, con la salvedad de que van ligadas a una clase concreta. -------------------------------------------------------------------------------- /06_programacionorientadaobjetos/04_poo_property.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Decorador Property 4 | parent: 🏄‍♂️ 06. Programación orientada a objetos 5 | description: El decorador property en Python nos permite modificar el comportamiento de un método, haciendo que este se comporte como una propiedad y no como un método. Existen variable del mismo como el getter y el setter. 6 | order: 64 7 | nav_order: d 8 | permalink: /decorador-property-python 9 | --- 10 | 11 | 12 | # Decorador Property 13 | 14 | En otros tutoriales hemos visto como se crean y usan los decoradores en Python. A continuación veremos el decorador `@property`, que viene por defecto con Python, y puede ser usado para modificar un método para que sea un atributo o propiedad. Es importante que conozcan antes la programación orientada a objetos. 15 | 16 | El decorador puede ser usado sobre un método, que hará que actúe como si fuera un atributo. 17 | 18 | 19 | ```python 20 | class Clase: 21 | def __init__(self, mi_atributo): 22 | self.__mi_atributo = mi_atributo 23 | 24 | @property 25 | def mi_atributo(self): 26 | return self.__mi_atributo 27 | ``` 28 | 29 | Como si de un atributo normal se tratase, podemos acceder a el con el objeto `.` y nombre. 30 | 31 | 32 | ```python 33 | mi_clase = Clase("valor_atributo") 34 | mi_clase.mi_atributo 35 | # 'valor_atributo' 36 | ``` 37 | 38 | 39 | 40 | 41 | Muy importante notar que aunque `mi_atributo` pueda parecer un método, en realidad no lo es, por lo que no puede ser llamado con `()`. 42 | 43 | 44 | ```python 45 | # mi_clase.mi_atributo() # Error! Es un atributo, no un método 46 | ``` 47 | 48 | Tal vez te preguntes para que sirve esto, ya que el siguiente código hace exactamente lo mismo sin hacer uso de decoradores. 49 | 50 | 51 | ```python 52 | class Clase: 53 | def __init__(self, mi_atributo): 54 | self.mi_atributo = mi_atributo 55 | 56 | mi_clase = Clase("valor_atributo") 57 | mi_clase.mi_atributo 58 | # 'valor_atributo' 59 | ``` 60 | 61 | 62 | 63 | Bien, la explicación no es sencilla, pero está relacionada con el concepto de **encapsulación** de la programación orientada a objetos. Este concepto nos indica que en determinadas ocasiones es importante ocultar el estado interno de los objetos al exterior, para evitar que sean modificados de manera incorrecta. Para la gente que venga del mundo de Java, esto no será nada nuevo, y está muy relacionado con los métodos `set()`y `get()` que veremos a continuación. 64 | 65 | La primera diferencia que vemos entre los códigos anteriores es el uso de `__` antes de `mi_atributo`. Cuando nombramos una variable de esta manera, es una forma de decirle a Python que queremos que se "oculte" y que no pueda ser accedida como el resto de atributos. 66 | 67 | 68 | ```python 69 | class Clase: 70 | def __init__(self, mi_atributo): 71 | self.__mi_atributo = mi_atributo 72 | 73 | mi_clase = Clase("valor_atributo") 74 | 75 | # mi_clase.__mi_atributo # Error! 76 | ``` 77 | 78 | Esto puede ser importante con ciertas variables que no queremos que sean accesibles desde el exterior de una manera no controlada. Al definir la propiedad con `@property` el acceso a ese atributo se realiza a través de una función, siendo por lo tanto un acceso controlado. 79 | 80 | 81 | ```python 82 | class Clase: 83 | def __init__(self, mi_atributo): 84 | self.__mi_atributo = mi_atributo 85 | 86 | @property 87 | def mi_atributo(self): 88 | # El acceso se realiza a través de este "método" y 89 | # podría contener código extra y no un simple retorno 90 | return self.__mi_atributo 91 | ``` 92 | 93 | Otra utilidad podría ser la consulta de un parámetro que requiera de muchos cálculos. Se podría tener un atributo que no estuviera directamente almacenado en la clase, sino que precisara de realizar ciertos cálculos. Para optimizar esto, se podrían hacer los cálculos sólo cuando el atributo es consultado. 94 | 95 | Por último, existen varios añadidos al decorador `@property` como pueden ser el `setter`. Se trata de otro decorador que permite definir un "método" que modifica el contenido del atributo que se esté usando. 96 | 97 | 98 | ```python 99 | class Clase: 100 | def __init__(self, mi_atributo): 101 | self.__mi_atributo = mi_atributo 102 | 103 | @property 104 | def mi_atributo(self): 105 | return self.__mi_atributo 106 | 107 | @mi_atributo.setter 108 | def mi_atributo(self, valor): 109 | if valor != "": 110 | print("Modificando el valor") 111 | self.__mi_atributo = valor 112 | else: 113 | print("Error está vacío") 114 | ``` 115 | 116 | De esta forma podemos añadir código al `setter`, haciendo que por ejemplo realice comprobaciones antes de modificar el valor. Esto es una cosa que de usar un atributo normal no podríamos hacer, y es muy útil de cara a la encapsulación. 117 | 118 | 119 | ```python 120 | mi_clase = Clase("valor_atributo") 121 | mi_clase.mi_atributo 122 | # 'valor_atributo' 123 | 124 | mi_clase.mi_atributo = "nuevo_valor" 125 | mi_clase.mi_atributo 126 | # 'nuevo_valor' 127 | 128 | mi_clase.mi_atributo = "" 129 | # Error está vacío 130 | ``` 131 | 132 | 133 | Resulta lógico pensar que si un determinado atributo pertenece a una clase, si queremos modificarlo debería de tener la "aprobación" de la clase, para asegurarse que ninguna entidad externa está "haciendo cosas raras". -------------------------------------------------------------------------------- /06_programacionorientadaobjetos/05_todo_dunder_methods.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Métodos dunder o mágicos 4 | parent: 🏄‍♂️ 06. Programación orientada a objetos 5 | description: xx 6 | order: 65 7 | nav_order: e 8 | permalink: /metodos-magicos-python 9 | --- 10 | 11 | # Métodos dunder o mágicos 12 | 13 | Proximamente 14 | -------------------------------------------------------------------------------- /06_programacionorientadaobjetos/06_todo_op_overloadig.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📕 Sobreescribiendo métodos mágicos 4 | parent: 🏄‍♂️ 06. Programación orientada a objetos 5 | description: xx 6 | order: 66 7 | nav_order: f 8 | permalink: /sobreescribir-metodo-python 9 | --- 10 | 11 | # Sobreescribiendo métodos mágicos 12 | Proximamente -------------------------------------------------------------------------------- /06_programacionorientadaobjetos/exclude_abstraccion.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Abstracción 4 | parent: 🏄‍♂️ 06. Programación orientada a objetos 5 | description: La abstracción en programación es un termino que hace referencia a la ocultación de la complejidad intrínseca de una aplicación al exterior, centrándose sólo en como puede ser usada, lo que se conoce como el interfaz. 6 | order: 68 7 | nav_order: h 8 | nav_exclude: true 9 | permalink: /abstraccion-en-programacion 10 | --- 11 | 12 | 13 | # Abstracción en programación 14 | 15 | La abstracción es un termino que hace referencia a la ocultación de la complejidad intrínseca de una aplicación al exterior, centrándose sólo en como puede ser usada, lo que se conoce como **interfaz**. Si has oído hablar del enfoque **caja negra**, es un concepto muy relacionado. Dicho en otras palabras, la abstracción consiste en ocultar toda la complejidad que algo puede tener por dentro, ofreciéndonos unas funciones de alto nivel, por lo general sencillas de usar, que pueden ser usadas para interactuar con la aplicación sin tener conocimiento de lo que hay dentro. 16 | 17 | Una analogía del mundo real podría ser la televisión. Se trata de un dispositivo muy complejo donde han trabajado gran cantidad de ingenieros de diversas disciplinas como telecomunicaciones o electrónica. ¿Os imagináis que para cambiar de canal tuviéramos que saber todos los entresijos del aparato?. Pues bien, se nos ofrece una abstracción de la televisión, un **mando a distancia**. El mando nos abstrae por completo de la complejidad de los circuitos y señales, y nos da una interfaz sencilla que con unos pocos botones podemos usar. 18 | 19 | Algo muy parecido sucede en la programación orientada a objetos. Si tuviéramos una clase `Televisor`, en su interior podría haber líneas y líneas de código super complejas, pero una buena abstracción sería la que simplemente ofreciera los métodos `encender()`, `apagar()` y `cambiar_canal()` al exterior. 20 | 21 | Un concepto relacionado con la abstracción, serían las **clases abstractas** o más bien los **métodos abstractos**. Se define como clase abstracta la que contiene métodos abstractos, y se define como método abstracto a un método que ha sido declarado pero no implementado. Es decir, que no tiene código. 22 | 23 | Es posible crear métodos abstractos en Python con decoradores como `@absttractmethod`, pero esto lo dejamos para otro post. 24 | 25 | Existen otros conceptos muy importantes y relacionados con la [programación orientada a objetos](/programacion-orientada-a-objetos/ "programación orientada a objetos"). Aquí te los dejamos: 26 | * [Herencia](/herencia-en-python/ "Herencia") 27 | * [Cohesión](/cohesion-en-programacion/ "Cohesión") 28 | * [Abstracción](/abstraccion-en-programacion/ "Abstracción") 29 | * [Polimorfismo](/polimorfismo-en-programacion/ "Polimorfismo") 30 | * [Acoplamiento](/acoplamiento-poo/ "Acoplamiento") 31 | * [Encapsulamiento](/encapsulamiento-poo/ "Encapsulamiento") -------------------------------------------------------------------------------- /06_programacionorientadaobjetos/exclude_acoplamiento.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Acoplamiento 4 | parent: 🏄‍♂️ 06. Programación orientada a objetos 5 | description: El encapsulamiento o encapsulación en programación es un concepto relacionado con la programación orientada a objetos, y hace referencia al ocultamiento de los estado internos de una clase al exterior. Dicho de otra manera, encapsular consiste en hacer que los atributos o métodos internos a una clase no se puedan acceder ni modificar desde fuera, sino que tan solo el propio objeto pueda acceder a ellos. 6 | order: 69 7 | nav_order: i 8 | nav_exclude: true 9 | permalink: /acoplamiento-poo 10 | --- 11 | 12 | # Acoplamiento en programación 13 | 14 | El acoplamiento en programación (denominado *coupling* en Inglés) es un concepto que mide la dependencia entre dos módulos distintos de software, como pueden ser por ejemplo las clases. El acoplamiento puede ser de dos tipos: 15 | * Acoplamiento **débil**, que indica que no existe dependencia de un módulo con otros. Esto debería ser la meta de nuestro software. 16 | * Acoplamiento **fuerte**, que por lo contrario indica que un módulo tiene dependencias internas con otros. 17 | 18 | El término acoplamiento está muy relacionado con la [cohesión](/cohesion-en-programacion/ "cohesión"), ya que acoplamiento débil suele ir ligado a cohesión fuerte. En general lo que buscamos en nuestro código es que tenga acoplamiento débil y cohesión fuerte, es decir, que no tenga dependencias con otros módulos y que las tareas que realiza estén relacionadas entre sí. Un código así es fácil de leer, de reusar, mantener y tiene que ser nuestra meta. Nótese que se suele emplear alta y baja para designar fuerza y débil respectivamente. 19 | 20 | Si aún no te hemos convencido de porque buscamos código débilmente acoplado, veamos lo que pasaría con un código fuertemente acoplado: 21 | * Debido a las dependencias con otros módulo, un cambio en un modulo ajeno al nuestro podría tener un "efecto mariposa" en nuestro código, aún sin haber modificado directamente nuestro módulo. 22 | * Si un módulo tiene dependencias con otros, reduce la reusabilidad, ya que para reusarlo deberíamos copiar también las dependencias. 23 | 24 | Veamos un ejemplo usando clases y objetos en Python. Tenemos una `Clase1` que define un atributo de clase `x`. Por otro lado la `Clase2` basa el comportamiento del método `mi_metodo()` en el valor de `x` de la `Clase1`. En este ejemplo existe acoplamiento fuerte, ya que existe una dependencia con una variable de otro módulo. 25 | 26 | 27 | ```python 28 | class Clase1: 29 | x = True 30 | pass 31 | 32 | class Clase2: 33 | def mi_metodo(self, valor): 34 | if Clase1.x: 35 | self.valor = valor 36 | 37 | mi_clase = Clase2() 38 | mi_clase.mi_metodo("Hola") 39 | mi_clase.valor 40 | ``` 41 | 42 | 43 | 44 | Puede parecer un ejemplo trivial, pero cuando el software se va complicando, no es nada raro acabar haciendo cosas de este tipo casi sin darnos cuenta. Hay veces que dependencias externas pueden estar justificadas, pero hay que estar muy seguro de lo que se hace. 45 | 46 | Este tipo de dependencias también puede hacer el código muy difícil de depurar. Imaginemos que nuestro código de la `Clase2` funciona perfectamente, pero de repente alguien hace un cambio en la `Clase1`. Un cambio tan sencillo como el siguiente. 47 | 48 | 49 | ```python 50 | Clase1.x = False 51 | ``` 52 | 53 | Este cambio estaría modificando el comportamiento de nuestra clase y nos preguntaríamos ¿porqué ha dejado de funcionar mi código si no he tocado nada? A veces atribuimos estos comportamientos a la magia o radiación cósmica, pero simplemente tenemos código con acoplamiento fuerte. 54 | 55 | Existen otros conceptos muy importantes y relacionados con la [programación orientada a objetos](/programacion-orientada-a-objetos/ "programación orientada a objetos"). Aquí te los dejamos: 56 | * [Herencia](/herencia-en-python/ "Herencia") 57 | * [Cohesión](/cohesion-en-programacion/ "Cohesión") 58 | * [Abstracción](/abstraccion-en-programacion/ "Abstracción") 59 | * [Polimorfismo](/polimorfismo-en-programacion/ "Polimorfismo") 60 | * [Acoplamiento](/acoplamiento-poo/ "Acoplamiento") 61 | * [Encapsulamiento](/encapsulamiento-poo/ "Encapsulamiento") -------------------------------------------------------------------------------- /06_programacionorientadaobjetos/exclude_crear_clase.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Crear clase 4 | parent: 🏄‍♂️ 06. Programación orientada a objetos 5 | description: Una clase en Python puede ser creada usando la palabra class seguida de dos puntos. Una vez la clase está definida, podemos crear dentro de ella diferentes métodos y atributos. 6 | order: 70 7 | nav_order: j 8 | nav_exclude: true 9 | permalink: /crear-clase-python 10 | --- 11 | 12 | # Crear clase Python 13 | 14 | Crear una [clase en Python](/programacion-orientada-a-objetos/ "clase en Python") se puede hacer un tan sólo dos líneas de código haciendo uso de la palabra `class`. 15 | 16 | ```python 17 | class MiClase: 18 | pass 19 | ``` 20 | 21 | ## Añadir atributos a la clase 22 | Podemos añadir algún atributo de clase. En este caso tenemos atributos generales que pertenecen a la clase y no a la instancia. 23 | ```python 24 | class MiClase: 25 | atributo1 = "valor1" 26 | atributo2 = "valor2" 27 | ``` 28 | 29 | ## Añadir constructor a la clase 30 | Se podría decir que toda clase tiene un constructor, que recibe unos parámetros de entrada cuando el objeto es creado. Creamos por lo tanto el constructor `__init__`. Nótese la diferencia entre `atributo1` y `argumento1` (pista, atributo de clase vs instancia). 31 | ```python 32 | class MiClase: 33 | atributo1 = "valor1" 34 | atributo2 = "valor2" 35 | def __init__(self, argumento1): 36 | self.argumento1 = argumento1 37 | ``` 38 | 39 | Ya vamos teniendo una clase mucho más completa, pero sigamos. 40 | 41 | ## Añadir métodos a la clase 42 | A parte de atributos y el constructor, toda clase tiene un conjunto de funciones o métodos que realizan diferentes funcionalidades. Creamos la `funcion1()`. 43 | ```python 44 | class MiClase: 45 | atributo1 = "valor1" 46 | atributo2 = "valor2" 47 | def __init__(self, argumento1): 48 | self.argumento1 = argumento1 49 | def funcion1(self): 50 | print("Esta es la función 1") 51 | ``` 52 | 53 | ## Crear objeto 54 | A diferencia de la clase, un objeto define una clase particular, con unos atributos particulares para ese objeto. Es decir, el objeto es la instancia de la clase. Se puede crear usando `()` sobre la clase y pasando los argumentos de entrada separados por `,`. 55 | ```python 56 | mi_clase = MiClase("Hola") 57 | ``` 58 | 59 | ## Acceder a métodos y atributos 60 | Una vez tenemos el objeto `mi_clase`, podemos acceder a todo su contenido, tanto métodos como atributos de clase o de instancia. Simplemente hay que usar el objeto `.` y el método o atributo. 61 | 62 | ```python 63 | mi_clase.atributo1 64 | mi_clase.atributo2 65 | mi_clase.argumento1 66 | mi_clase.funcion1() 67 | ``` 68 | 69 | Para más detalle, te dejamos otros posts por si te pudieran interesar: 70 | * [Programación orientada a objetos, una introducción a clases, objetos, métodos y atributos](/programacion-orientada-a-objetos/ "Programación orientada a objetos, una introducción a clases, objetos, métodos y atributos") 71 | * [Métodos «normales», estáticos y de clase en Python. Diferencias y usos](/metodos-estaticos-clase-python/ "Métodos «normales», estáticos y de clase en Python. Diferencias y usos") 72 | * [Dando un toque de elegancia con el decorador @property en nuestras clases](/decorador-property-python/ "Dando un toque de elegancia con el decorador @property en nuestras clases") -------------------------------------------------------------------------------- /06_programacionorientadaobjetos/exclude_encapsulamiento.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Encapsulamiento 4 | parent: 🏄‍♂️ 06. Programación orientada a objetos 5 | description: El encapsulamiento o encapsulación en programación es un concepto relacionado con la programación orientada a objetos, y hace referencia al ocultamiento de los estado internos de una clase al exterior. Dicho de otra manera, encapsular consiste en hacer que los atributos o métodos internos a una clase no se puedan acceder ni modificar desde fuera, sino que tan solo el propio objeto pueda acceder a ellos. 6 | order: 71 7 | nav_order: k 8 | nav_exclude: true 9 | permalink: /encapsulamiento-poo 10 | --- 11 | 12 | # Encapsulamiento en programación 13 | 14 | El encapsulamiento o encapsulación en programación es un concepto relacionado con la programación orientada a objetos, y hace referencia al ocultamiento de los estado internos de una clase al exterior. Dicho de otra manera, encapsular consiste en hacer que los atributos o métodos internos a una clase no se puedan acceder ni modificar desde fuera, sino que tan solo el propio objeto pueda acceder a ellos. 15 | 16 | Para la gente que conozca Java, le resultará un termino muy familiar, pero en Python es algo distinto. Digamos que Python por defecto no oculta los atributos y métodos de una clase al exterior. Veamos un ejemplo con el lenguaje Python. 17 | 18 | 19 | ```python 20 | class Clase: 21 | atributo_clase = "Hola" 22 | def __init__(self, atributo_instancia): 23 | self.atributo_instancia = atributo_instancia 24 | 25 | mi_clase = Clase("Que tal") 26 | mi_clase.atributo_clase 27 | mi_clase.atributo_instancia 28 | 29 | # 'Hola' 30 | # 'Que tal' 31 | ``` 32 | 33 | 34 | 35 | Ambos atributos son perfectamente accesibles desde el exterior. Sin embargo esto es algo que tal vez no queramos. Hay ciertos métodos o atributos que queremos que pertenezcan sólo a la clase o al objeto, y que sólo puedan ser accedidos por los mismos. Para ello podemos usar la doble `__` para nombrar a un atributo o método. Esto hará que Python los interprete como "privados", de manera que no podrán ser accedidos desde el exterior. 36 | 37 | 38 | ```python 39 | class Clase: 40 | atributo_clase = "Hola" # Accesible desde el exterior 41 | __atributo_clase = "Hola" # No accesible 42 | 43 | # No accesible desde el exterior 44 | def __mi_metodo(self): 45 | print("Haz algo") 46 | self.__variable = 0 47 | 48 | # Accesible desde el exterior 49 | def metodo_normal(self): 50 | # El método si es accesible desde el interior 51 | self.__mi_metodo() 52 | 53 | mi_clase = Clase() 54 | #mi_clase.__atributo_clase # Error! El atributo no es accesible 55 | #mi_clase.__mi_metodo() # Error! El método no es accesible 56 | mi_clase.atributo_clase # Ok! 57 | mi_clase.metodo_normal() # Ok! 58 | ``` 59 | 60 | 61 | 62 | Y como curiosidad, podemos hacer uso de `dir` para ver el listado de métodos y atributos de nuestra clase. Podemos ver claramente como tenemos el `metodo_normal` y el `atributo` de clase, pero no podemos encontrar `__mi_metodo` ni `__atributo_clase`. 63 | 64 | 65 | ```python 66 | print(dir(mi_clase)) 67 | #['_Clase__atributo_clase', '_Clase__mi_metodo', '_Clase__variable', 68 | #'__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', 69 | #'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', 70 | #'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', 71 | #'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', 72 | #'__str__', '__subclasshook__', '__weakref__', 'atributo_clase', 'metodo_normal'] 73 | ``` 74 | 75 | 76 | Pues bien, en realidad si que podríamos acceder a `__atributo_clase` y a `__mi_metodo` haciendo un poco de trampa. Aunque no se vea a simple vista, si que están pero con un nombre distinto, para de alguna manera ocultarlos y evitar su uso. Pero podemos llamarlos de la siguiente manera, pero por lo general **no es una buena idea**. 77 | 78 | 79 | ```python 80 | mi_clase._Clase__atributo_clase 81 | # 'Hola' 82 | mi_clase._Clase__mi_metodo() 83 | # 'Haz algo' 84 | ``` 85 | 86 | Existen otros conceptos muy importantes y relacionados con la [programación orientada a objetos](/programacion-orientada-a-objetos/ "programación orientada a objetos"). Aquí te los dejamos: 87 | * [Herencia](/herencia-en-python/ "Herencia") 88 | * [Cohesión](/cohesion-en-programacion/ "Cohesión") 89 | * [Abstracción](/abstraccion-en-programacion/ "Abstracción") 90 | * [Polimorfismo](/polimorfismo-en-programacion/ "Polimorfismo") 91 | * [Acoplamiento](/acoplamiento-poo/ "Acoplamiento") 92 | * [Encapsulamiento](/encapsulamiento-poo/ "Encapsulamiento") -------------------------------------------------------------------------------- /06_programacionorientadaobjetos/exclude_polimorfismo.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Polimorfismo 4 | parent: 🏄‍♂️ 06. Programación orientada a objetos 5 | description: El polimorfismo en programación es un concepto muy importante en la programación orientada a objetos, y hace referencia a la capacidad que puede tener un objeto para ofrecer una respuesta distinta en función de su implementación. 6 | order: 72 7 | nav_order: l 8 | nav_exclude: true 9 | permalink: /polimorfismo-en-programacion 10 | --- 11 | 12 | 13 | # Polimorfismo en programación 14 | 15 | El **polimorfismo** es uno de los pilares básicos en la [programación orientada a objetos](/programacion-orientada-a-objetos-python), por lo que para entenderlo es importante tener las bases de la POO y la [herencia](/herencia-en-python) bien asentadas. 16 | 17 | El término polimorfismo tiene origen en las palabras *poly* (muchos) y *morfo* (formas), y aplicado a la programación hace referencia a que los objetos pueden tomar diferentes formas. ¿Pero qué significa esto? 18 | 19 | Pues bien, significa que objetos de diferentes clases pueden ser accedidos utilizando el mismo interfaz, mostrando un comportamiento distinto (tomando diferentes formas) según cómo sean accedidos. 20 | 21 | En lenguajes de programación como Python, que tiene tipado dinámico, el polimorfismo va muy relacionado con el [duck typing](/duck-typing-python). 22 | 23 | Sin embargo, para entender bien este concepto, es conveniente explicarlo desde el punto de vista de un lenguaje de programación con tipado estático como Java. Vamos a por ello. 24 | 25 | ## Polimorfismo en Java 26 | 27 | Vamos a comenzar definiendo una clase `Animal`. 28 | 29 | ```java 30 | // Código Java 31 | class Animal{ 32 | public Animal() {} 33 | } 34 | ``` 35 | 36 | Y dos clases `Perro` y `Gato` que heredan de la anterior. 37 | 38 | ```java 39 | // Código Java 40 | class Perro extends Animal { 41 | public Perro() {} 42 | } 43 | 44 | class Gato extends Animal { 45 | public Gato() {} 46 | } 47 | ``` 48 | 49 | El polimorfismo es precisamente lo que nos permite hacer lo siguiente: 50 | 51 | ```java 52 | // Código Java 53 | Animal a = new Perro(); 54 | ``` 55 | 56 | Recuerda que Java es un lenguaje con tipado estático, lo que significa que el tipo tiene que ser definido al crear la variable. 57 | 58 | Sin embargo estamos asignando a una variable `Animal` un objeto de la clase `Perro`. ¿Cómo es esto posible? 59 | 60 | Pues ahí lo tienes, **el polimorfismo es lo que nos permite usar ambas clases de forma indistinta**, ya que soportan el mismo "interfaz" (no confundir con el `interface` de Java). 61 | 62 | El siguiente código es también correcto. Tenemos un array de `Animal` donde cada elemento toma la forma de `Perro` o de `Gato`. 63 | 64 | ```java 65 | // Código Java 66 | Animal[] animales = new Animal[10]; 67 | animales[0] = new Perro(); 68 | animales[1] = new Gato(); 69 | ``` 70 | 71 | Sin embargo, no es posible realizar lo siguiente, ya que `OtraClase` no comparte interfaz con `Animal`. Tendremos un error `error: incompatible types`. 72 | 73 | ```java 74 | // Código Java 75 | class OtraClase { 76 | public OtraClase() {} 77 | } 78 | Animal a = new OtraClase(); 79 | animales[0] = new OtraClase(); 80 | ``` 81 | 82 | ## Polimorfismo en Python 83 | 84 | El término polimorfismo visto desde el punto de vista de Python es complicado de explicar sin hablar del [duck typing](/duck-typing-python), por lo que te recomendamos la lectura. 85 | 86 | Al ser un lenguaje con tipado dinámico y permitir duck typing, en Python no es necesario que los objetos compartan un interfaz, simplemente basta con que tengan los métodos que se quieren llamar. 87 | 88 | Podemos recrear el ejemplo de Java de la siguiente manera. Supongamos que tenemos un clase `Animal` con un método `hablar()`. 89 | 90 | ```python 91 | class Animal: 92 | def hablar(self): 93 | pass 94 | ``` 95 | 96 | Por otro lado tenemos otras dos clases, `Perro`, `Gato` que heredan de la anterior. Además, implementan el método `hablar()` de una forma distinta. 97 | 98 | ```python 99 | class Perro(Animal): 100 | def hablar(self): 101 | print("Guau!") 102 | 103 | class Gato(Animal): 104 | def hablar(self): 105 | print("Miau!") 106 | ``` 107 | 108 | A continuación creamos un objeto de cada clase y llamamos al método `hablar()`. Podemos observar que cada animal se comporta de manera distinta al usar `hablar()`. 109 | 110 | ```python 111 | for animal in Perro(), Gato(): 112 | animal.hablar() 113 | 114 | # Guau! 115 | # Miau! 116 | ``` 117 | 118 | En el caso anterior, la variable `animal` ha ido "tomando las formas" de `Perro` y `Gato`. Sin embargo, nótese que al tener tipado dinámico este ejemplo hubiera funcionado igual sin que existiera herencia entre `Perro` y `Gato`, pero esta explicación la dejamos para el capítulo de [duck typing](/duck-typing-python) 119 | 120 | Existen otros conceptos muy importantes y relacionados con la [programación orientada a objetos](/programacion-orientada-a-objetos/ "programación orientada a objetos"). Aquí te los dejamos: 121 | * [Herencia](/herencia-en-python/ "Herencia") 122 | * [Cohesión](/cohesion-en-programacion/ "Cohesión") 123 | * [Abstracción](/abstraccion-en-programacion/ "Abstracción") 124 | * [Polimorfismo](/polimorfismo-en-programacion/ "Polimorfismo") 125 | * [Acoplamiento](/acoplamiento-poo/ "Acoplamiento") 126 | * [Encapsulamiento](/encapsulamiento-poo/ "Encapsulamiento") -------------------------------------------------------------------------------- /06_programacionorientadaobjetos/exclude_poo_cohesion.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Cohesión 4 | parent: 🏄‍♂️ 06. Programación orientada a objetos 5 | description: La cohesión hace referencia al grado de relación entre los elementos de un módulo. En el diseño de una función, es importante pensar bien la tarea que va a realizar, intentando que sea única y bien definida. Cuantas más cosas diferentes haga la función sin relación entre sí, más complicado será el código de entender y reutilizar. 6 | order: 73 7 | nav_order: m 8 | nav_exclude: true 9 | permalink: /cohesion-en-programacion 10 | --- 11 | 12 | 13 | # Cohesión en Programación 14 | 15 | La **cohesión** hace referencia al grado de **relación entre los elementos de un módulo**. En el diseño de una función, es importante pensar bien la tarea que va a realizar, intentando que sea única y bien definida. Cuantas más cosas diferentes haga una función sin relación entre sí, más complicado será el código de entender. Existen por lo tanto dos tipos de cohesión: 16 | * Por un lado tenemos la cohesión **débil** que indica que la relación entre los elementos es baja. Es decir, no pertenecen a una única funcionalidad. 17 | * Por otro la cohesión **fuerte**, que debe ser nuestro objetivo al diseñar programas. La cohesión fuerte indica que existe una alta relación entre los elementos existentes dentro del módulo. 18 | 19 | Existe también otro concepto llamado acoplamiento que explicamos en [otro post](/acoplamiento-poo/ "otro post"). Normalmente acoplamiento débil se relaciona con cohesión fuerte o alta. 20 | 21 | Veámoslo con un ejemplo. Tenemos una función `suma()` que suma dos números. El problema es que además de sumar dos números, los convierte a `float()` y además pide al usuario que introduzca por pantalla el número. Podría parecer que esas otras dos funcionalidades no son para tanto, pero si por ejemplo una persona quiere usar nuestra función `suma()` pero ya tiene los números y no quiere pedirlos por pantalla, no le serviría nuestra función. 22 | 23 | 24 | ```python 25 | # Mal. Cohesión débil 26 | def suma(): 27 | num1 = float(input("Dame primer número")) 28 | num2 = float(input("Dame segundo número")) 29 | suma = num1 + num2 30 | print(suma) 31 | 32 | suma() 33 | ``` 34 | 35 | Para que la función tuviese una cohesión fuerte, sería conveniente que la `suma` realizara una única tarea bien definida, que es sumar. 36 | 37 | 38 | ```python 39 | # Bien. Cohesión fuerte 40 | def suma(numeros): 41 | total = 0 42 | for i in numeros: 43 | total = total + i 44 | return total 45 | ``` 46 | 47 | Evidentemente un ejemplo tan sencillo como el explicado no tiene implicaciones demasiado graves, pero es importante buscar que las funciones realicen una única tarea (o conjunto) pero relacionadas entre sí. Diseñar código con cohesión fuerte nos permite: 48 | * Reducir la complejidad del módulo, ya que tendrá un menor número de operaciones. 49 | * Se podrá reutilizar los módulos más fácilmente 50 | * El sistema será más fácilmente mantenible. 51 | 52 | 53 | Existen otros conceptos muy importantes y relacionados con la [programación orientada a objetos](/programacion-orientada-a-objetos/ "programación orientada a objetos"). Aquí te los dejamos: 54 | * [Herencia](/herencia-en-python/ "Herencia") 55 | * [Cohesión](/cohesion-en-programacion/ "Cohesión") 56 | * [Abstracción](/abstraccion-en-programacion/ "Abstracción") 57 | * [Polimorfismo](/polimorfismo-en-programacion/ "Polimorfismo") 58 | * [Acoplamiento](/acoplamiento-poo/ "Acoplamiento") 59 | * [Encapsulamiento](/encapsulamiento-poo/ "Encapsulamiento") -------------------------------------------------------------------------------- /07_excepciones/00_excepciones.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 🛠 07. Excepciones 4 | order: 74 5 | has_children: true 6 | nav_order: g 7 | permalink: /excepciones-python 8 | --- 9 | -------------------------------------------------------------------------------- /07_excepciones/02_excepciones_assert.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Uso del assert() 4 | parent: 🛠 07. Excepciones 5 | description: El uso del assert en Python nos permite verificar que una determinada condición sea True, y de no serlo, se lanzará una excepción. 6 | order: 76 7 | nav_order: b 8 | permalink: /assert-python 9 | --- 10 | 11 | 12 | # Assert en Python 13 | 14 | 15 | El uso de `assert` en Python nos **permite realizar comprobaciones**. Si la expresión contenida dentro del mismo es `False`, se lanzará una [excepción](/excepciones-try-except-finally), concretamente `AssertionError`. Veamos un ejemplo: 16 | 17 | 18 | ```python 19 | assert(1==2) 20 | # AssertionError 21 | ``` 22 | 23 | 24 | Es decir, si el contenido existente dentro del `assert` es igual a `False`, se lanzará la excepción. Se podría conseguir el mismo resultado haciendo lo siguiente, pero el uso de `assert()` resulta más cómodo. 25 | 26 | 27 | ```python 28 | if condicion: 29 | raise AssertionError() 30 | ``` 31 | 32 | Podemos también añadir un texto con información relevante acerca del `assert()`. 33 | 34 | 35 | ```python 36 | assert False, "El assert falló" 37 | ``` 38 | 39 | Aunque mucho cuidado, ya que la expresión anterior no es equivalente a la siguiente, siendo la misma errónea. Esto se debe a que en realidad se está evaluando `bool((False, "El assert falló"))`, lo que resulta ser siempre `True`. De hecho el siguiente código no lanzaría una excepción, cuando realmente se esperaría que lo hiciera. 40 | 41 | ```python 42 | # INCORRECTO 43 | assert(False, "El assert falló") 44 | ``` 45 | 46 | 47 | Por otro lado, también se puede hacer uso del `assert()` sin usar paréntesis como se muestra a continuación. 48 | 49 | 50 | ```python 51 | x = "ElLibroDePython" 52 | assert x == "ElLibroDePython" 53 | ``` 54 | 55 | ## assert() en testing 56 | La función `assert()` puede ser también muy útil para realizar [testing](/python-testing) de nuestro código, especialmente para test unitarios o *unit tests*. Imagínate que tenemos una función `calcula_media()` que como su nombre indica calcula la media de un conjunto de números. 57 | 58 | ```python 59 | def calcula_media(lista): 60 | return sum(lista)/len(lista) 61 | ``` 62 | 63 | En el mundo de la programación es muy importante probar o *testear* el software, para asegurarse de que está libre de errores. Gracias al uso de `assert()` podemos realizar estas comprobaciones de manera automática. 64 | 65 | ```python 66 | assert(calcula_media([5, 10, 7.5]) == 7.5) 67 | assert(calcula_media([4, 8]) == 6) 68 | ``` 69 | 70 | Por lo que si hacemos que estas comprobaciones sean parte de nuestro código, podríamos proteger nuestra función, asegurándonos de que nadie la "rompa". 71 | 72 | 73 | ## assert() en funciones 74 | 75 | Puede resultar útil usar `assert()` cuando queremos realizar alguna comprobación, como podría ser **dentro de una función**. En el siguiente ejemplo tenemos una función `suma()` que sólo suma las variables si son números enteros. 76 | 77 | 78 | ```python 79 | # Funcion suma de variables enteras 80 | def suma(a, b): 81 | assert(type(a) == int) 82 | assert(type(b) == int) 83 | return a+b 84 | 85 | # Error, ya que las variables no son int 86 | suma(3.0, 5.0) 87 | 88 | # Ok, los argumentos son int 89 | suma(3, 5) 90 | ``` 91 | 92 | ## assert() con clases 93 | 94 | Otro ejemplo podría verificar que un objeto **pertenece a una clase determinada**. 95 | 96 | 97 | ```python 98 | class MiClase(): 99 | pass 100 | 101 | class MiOtraClase(): 102 | pass 103 | 104 | mi_objeto = MiClase() 105 | mi_otro_objeto = MiOtraClase() 106 | 107 | # Ok 108 | assert(isinstance(mi_objeto, MiClase)) 109 | 110 | # Ok 111 | assert(isinstance(mi_otro_objeto, MiOtraClase)) 112 | 113 | # Error, mi_objeto no pertenece a MiOtraClase 114 | assert(isinstance(mi_objeto, MiOtraClase)) 115 | 116 | # Error, mi_otro_objeto no pertenece a MiClase 117 | assert(isinstance(mi_otro_objeto, MiClase)) 118 | ``` 119 | 120 | ## Deshabilitar assert 121 | 122 | A modo de curiosidad, es posible ejecutar un script de Python deshabilitando el `assert`. Supongamos que tenemos el siguiente código. 123 | 124 | ```python 125 | # ejemplo.py 126 | assert(1==2) 127 | ``` 128 | 129 | Si ejecutamos nuestro script de la siguiente manera, los `assert` se eliminarán, por lo que no se producirá ninguna excepción. 130 | 131 | 132 | ```console 133 | $ python -O ejemplo.py 134 | ``` -------------------------------------------------------------------------------- /07_excepciones/03_excepciones_definiendo.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Definiendo Excepciones 4 | parent: 🛠 07. Excepciones 5 | description: Al igual que otros lenguajes de programación en Python se puede definir una excepción propia heredando de la clase genérica Exception(). Te lo explicamos de manera sencilla y con ejemplos. 6 | order: 77 7 | nav_order: c 8 | permalink: /definir-excepcion 9 | --- 10 | 11 | # Definiendo Excepciones 12 | 13 | Antes de ver como se define una excepción, te recomendamos otros de nuestros posts que te ayudarán a entenderlo mejor: 14 | * [Programación Orientada a Objetos](/programacion-orientada-a-objetos/ "Programación Orientada a Objetos") 15 | * [Herencia en Python](/herencia-en-python/ "Herencia en Python") 16 | * [Excepciones en Python](/excepciones-try-except-finally/ "Excepciones en Python") 17 | 18 | 19 | En los posts anteriores verás como **lanzar y capturar** las excepciones. A continuación explicamos como **definirlas**. 20 | 21 | A pesar de que Python define un [conjunto de excepciones por defecto](https://docs.python.org/3/library/exceptions.html "excepciones por defecto de Python"), podrían no ser suficientes para nuestro programa. En ese caso, deberíamos **definir nuestra propia excepción**. 22 | 23 | Si queremos crear una excepción, solamente tenemos que crear una clase que herede de la clase `Exception`. Es tan sencillo como el siguiente ejemplo. 24 | 25 | 26 | ```python 27 | # Creamos una excepción personalizada 28 | class MiExcepcionPersonalizada(Exception): 29 | pass 30 | ``` 31 | 32 | Y ya podríamos lanzarla con `raise` cuando quisiéramos. 33 | 34 | 35 | ```python 36 | raise MiExcepcionPersonalizada() 37 | ``` 38 | 39 | 40 | También se pueden pasar parámetros de entrada al lanzarla. Esto es muy útil para dar información adicional a la excepción. En el siguiente ejemplo se pasan dos parámetros. Para ello tenemos que modificar la función `__init__()` de la siguiente manera. 41 | 42 | 43 | ```python 44 | # Creamos nuestra propia excepción heredando 45 | # de la clase Exception 46 | class MiExcepcionPersonalizada(Exception): 47 | def __init__(self, parametro1, parametro2): 48 | self.parametro1 = parametro1 49 | self.parametro2 = parametro2 50 | ``` 51 | 52 | Y una vez hemos creado nuestra excepción, podemos lanzarla con `raise` como ya hemos visto. También es posible acceder a los parámetros pasados como argumentos al lanzar la excepción. 53 | 54 | 55 | ```python 56 | # Lanzamos con raise la excepción que hemos creado 57 | try: 58 | raise MiExcepcionPersonalizada("ValorPar1", "ValorPar2") 59 | except MiExcepcionPersonalizada as ex: 60 | p1, p2 = ex.args 61 | print(type(ex)) 62 | print("parametro1 =", p1) 63 | print("parametro2 =", p2) 64 | 65 | # 66 | #parametro1 = ValorPar1 67 | #parametro2 = ValorPar2 68 | ``` 69 | 70 | ## Ejemplos 71 | Hay un truco muy interesante que nos permite pasar a la excepción un argumento en forma de [diccionario](/diccionarios-en-python/ "diccionario") como se muestra a continuación. 72 | ```python 73 | # Se define una excepción 74 | class MiExcepcion(Exception): 75 | pass 76 | 77 | # Se lanza 78 | try: 79 | raise MiExcepcion({"mensaje":"Mi Mensaje", "informacion":"Mi Informacion"}) 80 | 81 | # Se captura 82 | except MiExcepcion as e: 83 | detalles = e.args[0] 84 | print(detalles) 85 | print(detalles["mensaje"]) 86 | print(detalles["informacion"]) 87 | 88 | #{'mensaje': 'Este es el mensaje', 'informacion': 'Esto es la informacion'} 89 | # Mi Mensaje 90 | # Mi Informacion 91 | ``` 92 | 93 | Como se puede ver, los parámetros son accesibles con `[]` ya que se trata de un diccionario. 94 | 95 | Una forma un poco más larga de hacer lo mismo sería se la siguiente manera. En este caso los parámetros que se pasan no son accesibles como si fueran un diccionario sino como se de un objeto normal se tratase con `.mensaje` y `.informacion` 96 | 97 | 98 | ```python 99 | class MiExcepcion(Exception): 100 | def __init__(self, mensaje, informacion): 101 | self.mensaje = mensaje 102 | self.informacion = informacion 103 | 104 | try: 105 | raise MiExcepcion("Mi Mensaje", "Mi Informacion") 106 | except MiExcepcion as e: 107 | print(e.mensaje) 108 | print(e.informacion) 109 | ``` 110 | 111 | Nótese que para los ejemplos hemos usado *mensaje* en *informacion*, pero estas variables pueden ser las que se te ocurran, y por supuesto tampoco tienen porque ser dos, podrían ser mas. -------------------------------------------------------------------------------- /08_ficheros/00_ficheros.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📥 08. Ficheros 4 | order: 79 5 | has_children: true 6 | nav_order: h 7 | permalink: /ficheros-python 8 | --- 9 | -------------------------------------------------------------------------------- /08_ficheros/02_ficheros_escribir.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Escribir archivos 4 | parent: 📥 08. Ficheros 5 | description: Te explicamos como escribir archivos en Python. En Python al igual que en otros lenguajes de programación es posible escribir el contenido de diferentes variables en un archivo, como podría ser un txt o un csv. Para ello es necesario hacer uso de la función open() con el argumento "w" o "a". 6 | order: 81 7 | nav_order: b 8 | permalink: /escribir-archivos-python 9 | --- 10 | 11 | # Escribir archivos en Python 12 | 13 | A continuación te explicamos **como escribir datos en un fichero usando Python**. Imagínate que tienes unos datos que te gustaría guardar en un fichero para su posterior análisis. Te explicamos como guardarlos en un fichero, por ejemplo, `.txt`. Si también quieres aprender como leer un fichero en Python [te lo explicamos en este otro post](/leer-archivos-python/ "te lo explicamos en este otro post"). 14 | 15 | Lo primero que debemos de hacer es crear un objeto para el fichero, con el nombre que queramos. Al igual que vimos en el post de leer ficheros, además del nombre se puede pasar un segundo parámetro que indica el modo en el que se tratará el fichero. Los más relevantes en este caso son los siguientes. Para más información consulta [la documentación oficial](https://docs.python.org/3/library/functions.html#open "la documentación oficial"). 16 | * 'w': Borra el fichero si ya existiese y crea uno nuevo con el nombre indicado. 17 | * 'a': Añadirá el contenido al final del fichero si ya existiese (*append* end Inglés) 18 | * 'x': Si ya existe el fichero se devuelve un error. 19 | 20 | Por lo tanto con la siguiente línea estamos creando un fichero con el nombre *datos_guardados.txt*. 21 | 22 | 23 | ```python 24 | # Abre un nuevo fichero 25 | fichero = open("datos_guardados.txt", 'w') 26 | ``` 27 | 28 | Si por lo contrario queremos añadir el contenido al ya existente en un fichero de antes, podemos hacerlo en el modo *append* como hemos indicado. 29 | 30 | 31 | ```python 32 | # Abre un nuevo y añade el contenido al final 33 | fichero = open("datos_guardados.txt", 'a') 34 | ``` 35 | 36 | ## Método `write()` 37 | 38 | Ya hemos visto como crear el fichero. Veamos ahora como podemos añadir contenido. Empecemos escribiendo un texto. 39 | 40 | 41 | ```python 42 | fichero = open("datos_guardados.txt", 'w') 43 | fichero.write("Contenido a escribir") 44 | fichero.close() 45 | ``` 46 | 47 | Por lo tanto si ahora abrimos el fichero `datos_guardados.txt`, veremos como efectivamente contiene una línea con *Contenido a escribir*. ¿A que es fácil? 48 | 49 | Es **muy importante** el uso de `close()` ya que si dejamos el fichero abierto, podríamos llegar a tener un comportamiento inesperado que queremos evitar. Por lo tanto, siempre que se abre un fichero **es necesario cerrarlo** cuando hayamos acabado. 50 | 51 | Compliquemos un poco más las cosas. Ahora vamos a guardar una lista de elementos en el fichero, donde cada elemento de la lista se almacenará en una línea distinta. 52 | 53 | 54 | ```python 55 | # Abrimos el fichero 56 | fichero = open("datos_guardados.txt", 'w') 57 | 58 | # Tenemos unos datos que queremos guardar 59 | lista = ["Manzana", "Pera", "Plátano"] 60 | 61 | # Guardamos la lista en el fichero 62 | for linea in lista: 63 | fichero.write(linea + "\n") 64 | 65 | # Cerramos el fichero 66 | fichero.close() 67 | ``` 68 | 69 | Si te fijas, estamos almacenando la línea mas `\n`. Es importante añadir el salto de línea porque por defecto no se añade, y si queremos que cada elemento de la lista se almacena en una línea distinta, será necesario su uso. 70 | 71 | ## Método `writelines()` 72 | 73 | También podemos usar el método `writelines()` y pasarle una lista. Dicho método se encargará de guardar todos los elementos de la lista en el fichero. 74 | 75 | 76 | ```python 77 | fichero = open("datos_guardados.txt", 'w') 78 | lista = ["Manzana", "Pera", "Plátano"] 79 | 80 | fichero.writelines(lista) 81 | fichero.close() 82 | 83 | # Se guarda 84 | # ManzanaPeraPlátano 85 | ``` 86 | 87 | Tal vez te hayas dado cuenta de que en realidad lo que se guarda es *ManzanaPeraPlátano*, todo junto. Si queremos que cada elemento se almacene en una línea distinta, deberíamos añadir el salto de línea en cada elemento de la lista como se muestra a continuación. 88 | 89 | 90 | ```python 91 | fichero = open("datos_guardados.txt", 'w') 92 | lista = ["Manzana\n", "Pera\n", "Plátano\n"] 93 | 94 | fichero.writelines(lista) 95 | fichero.close() 96 | 97 | # Se guarda 98 | # Manzana 99 | # Pera 100 | # Plátano 101 | ``` 102 | 103 | ## Uso del `with` 104 | 105 | Podemos ahorrar una línea de código si hacemos uso de lo siguiente. En este caso nos podemos ahorrar la llamada al `close()` ya que se realiza automáticamente. El código anterior se podría reescribir de la siguiente manera. 106 | 107 | 108 | ```python 109 | lista = ["Manzana\n", "Pera\n", "Plátano\n"] 110 | with open("datos_guardados.txt", 'w') as fichero: 111 | fichero.writelines(lista) 112 | ``` 113 | ## Ejemplos escribir ficheros en Python 114 | El uso de 'x' hace que **si el fichero ya existe se devuelve un error**. En el siguiente código creamos un fichero e inmediatamente después intentamos crear un fichero con el mismo nombre con la opción 'x'. Por lo tanto se devolverá un error. 115 | 116 | 117 | ```python 118 | f = open("mi_fichero.txt", "w") 119 | # f = open("mi_fichero.txt", "x") 120 | # Error! Ya existe 121 | ``` 122 | 123 | En este otro ejemplo vamos a usar un fichero para establecer una comunicación entre dos funciones. A efectos prácticos puede no resultar muy útil, pero es un buen ejemplo para mostrar la lectura y escritura de ficheros. 124 | 125 | Tenemos por lo tanto una función `escribe_fichero()` que recibe un mensaje y lo escribe en un fichero determinado. Y por otro lado tenemos una función `lee_fichero()` que devuelve el mensaje que está escrito en el fichero. 126 | 127 | Date cuenta lo interesante del ejemplo, ya que podríamos tener estos dos códigos ejecutándose en diferentes maquinas o procesos, y **podrían comunicarse a través del fichero**. Por un lado se escribe y por el otro se lee. 128 | 129 | 130 | ```python 131 | # Escribe un mensaje en un fichero 132 | def escribe_fichero(mensaje): 133 | with open('fichero_comunicacion.txt', 'w') as fichero: 134 | fichero.write(mensaje) 135 | 136 | # Leer el mensaje del fichero 137 | def lee_fichero(): 138 | mensaje = "" 139 | with open('fichero_comunicacion.txt', 'r') as fichero: 140 | mensaje = fichero.read() 141 | # Borra el contenido del fichero para dejarlo vacío 142 | f = open('fichero_comunicacion.txt', 'w') 143 | f.close() 144 | return mensaje 145 | 146 | escribe_fichero("Esto es un mensaje") 147 | print(lee_fichero()) 148 | ``` 149 | -------------------------------------------------------------------------------- /09_testing_doc/00_testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 🚀 09. Test y Documentación 4 | order: 84 5 | has_children: true 6 | nav_order: j 7 | permalink: /test-python 8 | --- 9 | -------------------------------------------------------------------------------- /09_testing_doc/02_nombrando_variables.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Nombrar Variables en Python 4 | title_nav: 📙 Nombrando Variables 5 | parent: 🚀 09. Test y Documentación 6 | description: El uso del guión o barra baja en Python para nombrar variables, funciones o métodos modifica en ciertas ocasiones el comportamiento de los mismos. Existen varios tipos, como los métodos mágicos. 7 | order: 86 8 | nav_order: b 9 | permalink: /guion-bajo-python 10 | --- 11 | 12 | # Nombrando Variables en Python 13 | 14 | Estoy seguro de que alguna vez has visto métodos, atributos o funciones que tienen algún tipo de guión o barra baja `_` en su nombre. La verdad que al principio puede resultar un poco confuso, ya algunos pueden tenerlo al principio otros pueden tener incluso doble barra baja y otros pueden tenerlo al principio y al final. Todo un lío. Veamos las posibilidades: 15 | * Barra baja al principio `_nombre` 16 | * Barra baja al final `nombre_` 17 | * Doble barra baja al principio `__nombre` 18 | * Doble barra baja al principio y final `__nombre__` 19 | * Barra baja sin contenido `_` 20 | 21 | Es importante notar que el uso de `_` puede significar dos cosas. Por un lado puede significar una **mera convención** que no alterará el comportamiento de nuestro código. Por otro lado, en algunos casos su uso **si modifica** el comportamiento y su uso es relevante. Lo vemos a continuación. 22 | 23 | ## Al inicio: _nombre 24 | 25 | El uso de `_` antes del nombre de una variable, como podría ser `_mi_variable` no modifica el comportamiento del código. Su uso es una mera convención que ha sido acordada por los usuarios de Python, y que indica que esa variable **no debería ser accedida desde fuera de la clase**, pero puede serlo. 26 | 27 | 28 | ```python 29 | class Clase: 30 | def __init__(self): 31 | self._variable = 10 32 | 33 | mi_clase = Clase() 34 | 35 | # No se debería hacer, pero se puede 36 | mi_clase._variable # 10 37 | ``` 38 | 39 | 40 | 41 | 42 | Su uso si que puede modificar el comportamiento del código usado con funciones. Una función es definida con `_` no es importada por defecto si usamos `from x import *`. 43 | 44 | ## Al Final: nombre_ 45 | 46 | Para entender el uso de la barra baja al final de una variable o función, es importante saber que Python tiene un determinado conjunto de palabras reservadas o **keywords**. Estas palabras no pueden ser usadas, porque de serlo Python se confundiría y no sabría como interpretarlas. Si por el motivo que sea, queremos llamar a una variable con el mismo nombre que una palabra reservada, podemos hacer algo así como `class_`. El siguiente código muestra que pasa si intentamos usar una palabra reservada para llamar a una variable. 47 | 48 | 49 | ```python 50 | #class = 5 # Error! class es una palabra reservada 51 | ``` 52 | 53 | Podemos usar `_` para solucionarlo. Nótese que a diferencia de otras formas de usar `_` en este caso **no modifica comportamiento**, por lo que su uso es arbitrario. 54 | 55 | 56 | ```python 57 | class_ = 5 58 | def_ = 10 59 | ``` 60 | 61 | ## Doble al Principio: __nombre 62 | 63 | A diferencia del uso de una sola barra baja, el uso de la doble `__` en un atributo o método hace que Python lo oculte al exterior. Existe un concepto muy interesante y particular de Python llamado **name mangling**, del que te recomendamos leer más. 64 | 65 | Por lo tanto, en el siguiente ejemplo `__nombre` no será accesible desde el exterior de la clase. Por supuesto si que podría accederse desde la propia clase. 66 | 67 | 68 | ```python 69 | class Clase: 70 | def __init__(self): 71 | self.__variable = 10 72 | 73 | mi_clase = Clase() 74 | #mi_clase.__variable # Error! No es accesible 75 | ``` 76 | 77 | ## Doble al Principio y Final: __nombre__ 78 | 79 | Por último, el uso de `__` al principio y al final de un nombre tiene especial relevancia cuando es **aplicado a métodos**. De hecho, a lo largo de este post ya has visto el uso de `__init__` varias veces. Se trata de una forma que usa Python para designar a los conocidos como [métodos mágicos](/metodos-magicos-python) como pueden ser también el `__call__` o el `__le__`. Por norma general, es mejor no definir métodos propios con estos nombres, y limitarse a utilizar los que Python ya ofrece. 80 | 81 | 82 | ```python 83 | class Clase: 84 | def __init__(self): 85 | print("Init") 86 | ``` 87 | 88 | 89 | ## Descartando Variables con Barra Baja 90 | 91 | Su uso suele ser común cuando queremos descartar una variable que por ejemplo pueda devolver una función. Veamos un ejemplo de una función que devuelve dos parámetros, la suma y la resta de dos variables. 92 | 93 | 94 | ```python 95 | def sumayresta(a, b): 96 | return (a+b), (a-b) 97 | ``` 98 | 99 | Tal vez queramos sólo el valor de la suma y no nos interese el valor de la resta. Para ello podríamos hacer lo siguiente. 100 | 101 | 102 | ```python 103 | suma, _ = sumayresta(5, 5) 104 | # 10 105 | ``` 106 | 107 | Es una forma de decir "no me interesa esta variable", pero como no se puede dejar el hueco vacío, se usa `_` para rellenarlo. -------------------------------------------------------------------------------- /09_testing_doc/04_errores_comunes.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Errores Comunes 4 | parent: 🚀 09. Test y Documentación 5 | description: xx 6 | order: 88 7 | nav_order: d 8 | permalink: /errores-comunes-python 9 | --- 10 | 11 | # Errores Comunes en Python -------------------------------------------------------------------------------- /09_testing_doc/05_codigo_pythonico.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Escribir Código Pythonico 4 | title_nav: 📙 Código Pythonico 5 | parent: 🚀 09. Test y Documentación 6 | description: xx 7 | order: 89 8 | nav_order: e 9 | permalink: /codigo-pythonico 10 | --- 11 | 12 | # Código Pythonico 13 | 14 | Próximamente -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # El Libro De Python 2 | 3 | Este repositorio contiene el código de los posts de [www.ellibrodepython.com](http://www.ellibrodepython.com). Todo el contenido está licenciado con [CC BY-NC-SA 4.0](https://github.com/ellibrodepython/blog/blob/main/LICENSE), lo que permite que sea compartido, adaptado y modificado siempre y cuando se atribuya la autoría, no tenga fines comerciales y las obras derivadas mantengan la misma licencia. 4 | 5 | Estamos abiertos a contribuciones externas a través de *Pull Requests* a este reposotorio, tanto para corregir contenido existente como para ampliar o añadir nuevos posts. Una vez *merged*, los cambios se verán reflejados en la web. Todo nuevo contenido deberá ceder los derechos a *El Libro De Python* y serán licenciados con la licencia anteriormente mencionada. Si estás interesado en contribuir, te recomendamos que eches un ojo a los siguientes consejos para escribir posts. 6 | 7 | ## Contribuir 8 | 9 | Algunos consejos: 10 | * Usar un lenguaje directo y conciso, con frases preferiblemente cortas. La intención debe ser siempre clara, qué se está explicando y qué problema vamos a resolver. 11 | * Cada post debe explicar un concepto único, sin mezclar temas no relacionados. Ejemplo: listas, funciones, iteradores. 12 | * Usar fragmentos de código. Se dice que una imagen vale más que mil palabras. Un fragmento de código también vale más que mil palabras. 13 | * Usar ejemplos cortos y concisos, que no requieran conocimiento específico de otras áreas. 14 | * Todo contenido debe ser único y revisado. No se aceptará contenido plagiado. 15 | * En la medida de lo posible, únicamente referenciar la documentación oficial de Python. Referenciar otros blogs tiene el riesgo de que en unos años, los enlaces estén rotos. Buscamos contenido único y que la web sea autocontenida. 16 | * El Libro de Python no es un manual, aburrido de leer y con tecnicismos innecesarios. Es un libro didáctico, fácil de leer, con un lenguaje cercano, ejemplos y enfocado en lo práctico. 17 | * Catalogar los contenidos por nivel siendo 📗 básico, 📙 intermedio y 📕 avanzado usando (verde, amarillo, rojo) 18 | * Todo contenido relacionado con Python es bienvenido, desde conceptos del propio lenguaje a librerías externas. 19 | 20 | *Made with ❤️ by [www.ellibrodepython.com](http://www.ellibrodepython.com)* 21 | -------------------------------------------------------------------------------- /img/complejo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/complejo1.png -------------------------------------------------------------------------------- /img/el-libro-de-python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/el-libro-de-python.png -------------------------------------------------------------------------------- /img/hash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/hash.png -------------------------------------------------------------------------------- /img/install-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/install-config.png -------------------------------------------------------------------------------- /img/install-python.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/install-python.gif -------------------------------------------------------------------------------- /img/jup-notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/jup-notebook.png -------------------------------------------------------------------------------- /img/jupyter-gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/jupyter-gui.png -------------------------------------------------------------------------------- /img/jupyter-helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/jupyter-helloworld.png -------------------------------------------------------------------------------- /img/naive_vs_horner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/naive_vs_horner.png -------------------------------------------------------------------------------- /img/pycharm-helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/pycharm-helloworld.png -------------------------------------------------------------------------------- /img/pycharm-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/pycharm-init.png -------------------------------------------------------------------------------- /img/pycharm-newfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/pycharm-newfile.png -------------------------------------------------------------------------------- /img/pycharm-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/pycharm-run.png -------------------------------------------------------------------------------- /img/pycharm-venv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/pycharm-venv.png -------------------------------------------------------------------------------- /img/python-conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/python-conf.png -------------------------------------------------------------------------------- /img/python-evolution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellibrodepython/blog/3f23e64f87ab21943dcebff73c20b2eb2e3c88f4/img/python-evolution.png -------------------------------------------------------------------------------- /order_files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import string 4 | import os 5 | import collections 6 | 7 | result = [os.path.join(dp, f) for dp, dn, filenames in os.walk(".") for f in filenames if os.path.splitext(f)[1] == '.md'] 8 | 9 | # TODO: Wont work with 2 nested levels of posts 10 | 11 | def check_duplicated_link(all_files): 12 | all_links = [] 13 | for infile in all_files: 14 | a_file = open(infile, 'r') 15 | lines = a_file.readlines() 16 | 17 | order_line = -1 18 | for idx, line in enumerate(lines): 19 | if "permalink:" in line: 20 | link = lines[idx].split("permalink:")[1].split()[0] 21 | #print("Post name", link) 22 | all_links.append(link) 23 | break 24 | 25 | duplicates = [item for item, count in collections.Counter(all_links).items() if count > 1] 26 | print("Found the following duplicates", duplicates) 27 | if duplicates != []: 28 | raise Exception("There are duplicates posts", duplicates) 29 | else: 30 | print("Duplicates check. ok") 31 | 32 | def order_posts(all_files): 33 | counter = 1 34 | letter_counter0 = 0 35 | letter_counter1 = 0 36 | 37 | alphabet_string = string.ascii_lowercase 38 | alphabet = list(alphabet_string) 39 | for infile in sorted(all_files): 40 | if infile not in ["./index.md", "./template.md", "./README.md"]: 41 | a_file = open(infile, 'r') 42 | lines = a_file.readlines() 43 | 44 | order_line = -1 45 | for idx, line in enumerate(lines): 46 | if "order:" in line and "nav_order" not in line: 47 | order_line = idx 48 | break 49 | 50 | nav_order_line = -1 51 | for idx, line in enumerate(lines): 52 | if "nav_order" in line: 53 | nav_order_line = idx 54 | break 55 | 56 | if order_line == -1 or nav_order_line == -1: 57 | raise Exception("error") 58 | 59 | if "00_" in infile: 60 | lines[nav_order_line] = f"nav_order: {alphabet[letter_counter0]}\n" 61 | print(infile, alphabet[letter_counter0]) 62 | letter_counter0 += 1 63 | letter_counter1 = 0 64 | else: 65 | lines[nav_order_line] = f"nav_order: {alphabet[letter_counter1]}\n" 66 | print(infile, alphabet[letter_counter1]) 67 | letter_counter1 += 1 68 | 69 | lines[order_line] = f"order: {str(counter)}\n" 70 | print(infile, counter) 71 | 72 | a_file = open(infile, "w") 73 | a_file.writelines(lines) 74 | a_file.close() 75 | counter += 1 76 | 77 | order_posts(result) 78 | check_duplicated_link(result) -------------------------------------------------------------------------------- /xx_ejemplosyejercicios/01_numeros_aleatorios.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📗 Números aleatorios 4 | description: Generar números aleatorios en Python es por suerte una tarea muy fácil usando la librería random. Existen diferentes funciones que nos permiten generar números enteros o decimales aleatorios como la función random(), la uniform() o randint(). 5 | order: 93 6 | nav_order: a 7 | permalink: /numeros-aleatorios-python 8 | nav_exclude: true 9 | --- 10 | 11 | # Números aleatorios Python 12 | Generar números aleatorios en Python es por suerte una tarea muy fácil usando la librería `random`. 13 | 14 | Vamos a ver diferentes funciones: 15 | * randint 16 | * uniform 17 | * random 18 | * choice 19 | 20 | ## Función randint 21 | 22 | La función `randint` genera un número aleatorio **entero** entre 0 y n, siendo el segundo valor 30 en este caso. 23 | ```python 24 | from random import * 25 | print(randint(0, 30)) 26 | # 10 27 | ``` 28 | 29 | ## Función uniform 30 | 31 | Si queremos generar valores aleatorios que sean **decimales** podemos hacer uso de `uniform`. 32 | ```python 33 | from random import * 34 | print(uniform(0,10)) 35 | # 6.68079620859125 36 | ``` 37 | 38 | ## Función random 39 | 40 | Por otro lado tenemos la función `random` que no acepta parámetros y genera números aleatorios **decimales** de entre 0 y 1. 41 | 42 | ```python 43 | from random import * 44 | print(random()) 45 | # 0.00402817235037356 46 | ``` 47 | 48 | 49 | ## Función choice 50 | 51 | Por último, la función `choice` nos permite elegir un elemento aleatorio de una lista. 52 | 53 | ```python 54 | from random import * 55 | print(choice(["A", "B", "C"])) 56 | # C 57 | ``` 58 | -------------------------------------------------------------------------------- /xx_ejemplosyejercicios/02_numeros_primos.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Números primos 4 | description: Los números primos son aquellos que sólo son divisibles por uno y por sí mismos. Por lo tanto los números 2, 3, 5, 7, 11 son primos. Puede implementarse de varias formas en Python, con bucles o de forma recursiva. 5 | order: 94 6 | nav_order: b 7 | permalink: /numeros-primos-python 8 | nav_exclude: true 9 | --- 10 | 11 | # Números primos Python 12 | 13 | Los números primos son aquellos que sólo son divisibles por uno y por sí mismos. Se entiende por divisible a que el resultado sea un número entero, es decir, no decimal. 14 | 15 | Por lo tanto los números 2, 3, 5, 7, 11 **son primos**, ya que no hay ningún número que los pueda dividir de manera entera (excluyendo al 1 y al propio número) 16 | 17 | Sin embargo, el número 10 **no es primo**, ya que se puede dividir por 2 y por 5. 18 | 19 | Los números primos son un concepto un tanto enigmático y muy estudiado. Algunas teorías como la [Conjetura de Goldbach](https://es.wikipedia.org/wiki/Conjetura_de_Goldbach "Conjetura de Goldbach") son de lo más interesantes. 20 | 21 | > Todo número entero mayor que 5 se puede escribir como suma de tres números primos. 22 | 23 | Sabida ya la teoría, veamos como se puede implementar en Python una [función](/funciones-en-python) que calcule si un número es primo o no. Veremos dos formas, una usando bucles [for](/for-python) y otra empleando [recursividad](/recursividad). 24 | 25 | ## Números primos en Python (bucles) 26 | 27 | Para implementar una calculadora de números primos en Python, lo primero es saber si dos números son divisibles. Usamos el operador módulo `%`. 28 | 29 | ```python 30 | if D%d != 0: 31 | print("No es divisor") 32 | ``` 33 | 34 | El operador [módulo](/operadores-aritmeticos) nos devuelve el resto de la división entre dos números. Por lo tanto `5%3` es `2`. Es decir, si el resto es distinto de cero, decimos que `d` no es divisor de `D`. 35 | 36 | Para determinar si un número es primo, iteramos todos los números desde `2` hasta nuestro número, comprobando si ese número `n` puede dividir al nuestro. En el momento en el que encontramos a un divisor, ya sabemos que no es primo y devolvemos `False`. 37 | 38 | Si por lo contrario ningún número consigue dividir al nuestro, devolvemos `True` indicando que el número es primo. 39 | 40 | ```python 41 | def es_primo(num): 42 | for n in range(2, num): 43 | if num % n == 0: 44 | print("No es primo", n, "es divisor") 45 | return False 46 | print("Es primo") 47 | return True 48 | ``` 49 | 50 | Veamos un ejemplo de cómo usar nuestra función. 51 | 52 | ```python 53 | es_primo(13) # Es primo 54 | es_primo(14) # No es primo 2 es divisor 55 | es_primo(887) # Es primo 56 | es_primo(1001) # No es primo 7 es divisor 57 | ``` 58 | 59 | 60 | ## Número primos en Python (recursividad) 61 | 62 | Ciertos algoritmos que usan bucles, pueden ser implementados con [recursividad](/recursividad), es decir, haciendo uso de una función que se llama repetidas veces a sí misma. 63 | 64 | La siguiente función se llama a sí misma comprueba si un número es divisible por otro, empezando por `n=2` hasta llegar al número en cuestión. 65 | 66 | * Si `n` es mayor que nuestro número, significa que ya hemos probado a dividir nuestro número con todos los anteriores, por lo que podemos considerar que **es primo**. 67 | * Si el número no es divisible entre `n`, continuamos llamando a `es_primo` incrementando `n`. 68 | * Si detectamos que el número es divisible por `n`, salimos de la función indicando que no es primo. 69 | 70 | ```python 71 | def es_primo(num, n=2): 72 | if n >= num: 73 | print("Es primo") 74 | return True 75 | elif num % n != 0: 76 | return es_primo(num, n + 1) 77 | else: 78 | print("No es primo", n, "es divisor") 79 | return False 80 | ``` 81 | 82 | Fíjate en lo siguiente: 83 | * **Resulta fácil comprobar que un número no es primo**, ya que basta con encontrar un número que lo divida. Se puede ver a simple vista que `10029438` no es primo, ya que al ser par es divisible por 2. 84 | * **Pero es difícil demostrar que un número es primo**, ya que es necesario comprobar que no es divisible por ninguno, y esto requiere de más esfuerzo. 85 | 86 | Veamos como usar la función. 87 | 88 | ```python 89 | es_primo(33) # No es primo 3 es divisor 90 | es_primo(10000000175489) # No es primo 599 es divisor 91 | ``` 92 | 93 | Por último, es importante tener en cuenta que Python pone un cierto límite a las veces que una función puede ser llamada recursivamente. En algunos casos podrías encontrarte con un `RecursionError`, indicando que has superado el límite. 94 | 95 | ```python 96 | es_primo(1009) 97 | # RecursionError: maximum recursion depth exceeded in comparison 98 | ``` -------------------------------------------------------------------------------- /xx_ejemplosyejercicios/03_medir_tiempo_exec.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Medir tiempo ejecución 4 | description: Puedes medir el tiempo de ejecución de tus programas en Python de diferentes maneras. Para fragmentos de código pequeños puedes usar timeit y para más largos time. Ambos paquetes vienen por defecto con Python. 5 | order: 95 6 | nav_order: c 7 | permalink: /tiempo-ejecucion-python 8 | nav_exclude: true 9 | --- 10 | 11 | # Medir tiempo de ejecución 12 | 13 | Medir el tiempo de ejecución de un programa es una tarea muy importante, ya que programar no sólo consiste en crear código que funcione, sino código que pueda ser ejecutado en un tiempo razonable. ¿Te imaginas que consultar tu saldo en la web del banco tardase 5 horas? 14 | 15 | Imagina que tienes un determinado algoritmo que tarda en ejecutarse 1 segundo. *A priori* no parece demasiado, pero ¿y si tuviéramos a 1000 usuarios ejecutando ese algoritmo periódicamente? En este caso, podría ser interesante optimizarlo, ya que una simple reducción de 0.1 segundos, podría aliviar la carga de nuestro sistema notablemente. 16 | 17 | Por lo contrario, si tenemos un determinado algoritmo o proceso que tarda 1 segundo, pero es usado muy de vez en cuanto, tal vez no sea interesante invertir recursos en optimizarlo. 18 | 19 | Afortunadamente, Python nos proporciona diferentes formas de medir el tiempo de ejecución de un programa. 20 | 21 | ## Tiempo de ejecución con time 22 | 23 | La librería [time](https://docs.python.org/3/library/time.html) es bastante completa, pero lo único que nos interesa para medir el tiempo de ejecución es el método `time()`. 24 | 25 | Este método nos devuelve el número de segundos, con precisión de microsegundos, que han pasado desde el 1 de Enero de 1970. 26 | 27 | ```python 28 | import time 29 | print(time.time()) # 1609954317.943479 30 | ``` 31 | 32 | Para medir el tiempo de ejecución de nuestros programas, **basta con saber el tiempo que ha pasado antes y después** de ejecutar nuestro programa, y la diferencia será el tiempo que ha tardado nuestro código. 33 | 34 | ```python 35 | import time 36 | inicio = time.time() 37 | 38 | # Código a medir 39 | time.sleep(1) 40 | # ------------- 41 | 42 | fin = time.time() 43 | print(fin-inicio) # 1.0005340576171875 44 | ``` 45 | 46 | En este caso podemos ver como el tiempo transcurrido es prácticamente `1`, ya que `time.sleep(1)` tarda 1 segundo en ejecutarse. 47 | 48 | Podemos ver también el tiempo que tardamos en calcular los primeros `10000000` números pares. 49 | 50 | ```python 51 | import time 52 | inicio = time.time() 53 | 54 | # Código a medir 55 | lista = [i for i in range(10000000) if i%2==0] 56 | # ------------- 57 | 58 | fin = time.time() 59 | print(fin-inicio) # 1.5099220275878906 60 | ``` 61 | 62 | Por otro lado, también podemos crear un [decorador](/decoradores-python) que use `time`, lo que nos permitirá medir el tiempo de ejecución de nuestras funciones sin repetir tanto código. Creamos el decorador de la siguiente manera: 63 | 64 | ```python 65 | def mide_tiempo(funcion): 66 | def funcion_medida(*args, **kwargs): 67 | inicio = time.time() 68 | c = funcion(*args, **kwargs) 69 | print(time.time() - inicio) 70 | return c 71 | return funcion_medida 72 | ``` 73 | 74 | Ahora podemos usar el decorador `mide_tiempo` con `@` antes de nuestra función, y cada vez que la llamemos se imprimirá por pantalla el tiempo que tardó en ejecutarse. 75 | 76 | ```python 77 | @mide_tiempo 78 | def calcula_pares(n): 79 | return [i for i in range(n) if i%2 == 0] 80 | 81 | calcula_pares(10000000) 82 | # 1.4356 83 | ``` 84 | 85 | ## Tiempo de ejecución con timeit 86 | 87 | Python nos ofrece también otra librería denominada [timeit](https://docs.python.org/3/library/timeit.html), y está pensada para medir tiempos de ejecución de fragmentos pequeños de código. Puede ser usada por línea de comandos o como una función dentro de Python. 88 | 89 | El siguiente fragmento de código nos permite medir el tiempo de ejecución de la sentencia `x=5`. El argumento `number` es el número de veces que se ejecuta el fragmento de código. 90 | 91 | ```python 92 | import timeit 93 | tiempo = timeit.timeit('x = 5', number=100000000) 94 | print(tiempo) # 2.173444958 95 | ``` 96 | 97 | Es importante notar que el `tiempo` que se nos devuelve, es la suma de todas las ejecuciones. Es decir, el siguiente ejemplo tarda `2.17` segundos en ejecutar `x=5` un total de `100000000` veces. 98 | 99 | En el siguiente ejemplo vemos como la siguiente [list comprehension](/list-comprehension-python) tarda en ejecutarse una media de `0.18` segundos. 100 | 101 | ```python 102 | import timeit 103 | tiempo = timeit.timeit('lista = [i for i in range(1000000) if i%2==0]', number=5) 104 | # Calculamos el tiempo medio 105 | print(tiempo/5) # 0.18671 106 | ``` 107 | 108 | # Consejos y conclusiones 109 | 110 | * Para medir el tiempo de ejecución de tus programas, haz la media de varias ejecuciones. Debido a diferentes factores, nunca obtendrás el mismo resultado, por eso es importante realizar la media. Observa los diferentes valores que obtienes, y si son muy dispares, plantéate las cosas. 111 | * Si mides fragmentos de código que tardan muy poco en ejecutarse, deberás promediar más valores. 112 | * Ten en cuenta que dependiendo de tu sistema operativo, la precisión que obtendrás de las diferentes librerías puede variar. 113 | * No pierdas tiempo en optimizar códigos que apenas son usados. Dedica tiempo a analizar el código que requiere de optimización y centra ahí tus esfuerzos. 114 | 115 | -------------------------------------------------------------------------------- /xx_ejemplosyejercicios/05_bubble_sort.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Bubble Sort en Python 4 | description: Bubble Sort en Python es un algoritmo de ordenación que permite ordenar listas de manera sencilla y sin requerir un extra de memoria. No es el algoritmo más eficiente pero si de los más sencillos, con una complejidad O(n^2) y O(n) en el mejor de los casos. 5 | order: 96 6 | nav_order: d 7 | permalink: /bubble-sort 8 | nav_exclude: true 9 | --- 10 | 11 | # Bubble Sort en Python 12 | 13 | 14 | Imaginemos que tenemos una lista `x` con un determinado número de elementos y la **queremos ordenar de menor a mayor**. Para ello existen diferentes algoritmos de ordenación o *sorting*, cada uno con sus ventajas e inconvenientes en por ejemplo complejidad en *time* y *space*. Es decir, el tiempo que tardan en ejecutarse y la memoria que consumen. 15 | 16 | Vamos a explicar el **bubble sort** en Python, y cómo implementarlo. No se trata del algoritmo más eficiente, pero si del más sencillo y podemos expresarlo en tan sólo 8 líneas de código Python como se muestra a continuación: 17 | 18 | ```python 19 | def bubble_sort(arr): 20 | n = len(arr) 21 | for i in range(n): 22 | swapped = False 23 | for j in range(0, n-i-1): 24 | if arr[j] > arr[j+1]: 25 | arr[j], arr[j+1] = arr[j+1], arr[j] 26 | swapped = True 27 | if not swapped: break 28 | 29 | # Dada una lista desordenada 30 | x = [9, 8, 7, 5, 4, 3, 0] 31 | 32 | # La ordenamos utilizando bubble sort 33 | bubble_sort(x) 34 | print(x) 35 | # [0, 3, 4, 5, 7, 8, 9] 36 | ``` 37 | 38 | Como podemos ver, el algoritmo es de lo más simple. Itera la lista entera múltiples veces, tomando valores de dos en dos. En cada iteración compara cada par adyacente, y pone el más alto a la derecha. Hace esto a lo largo de toda la lista, por lo que acaba moviendo al número más alto al final de la misma. Repitiendo el mismo proceso varias pasadas, acabamos teniendo la lista ordenada. 39 | 40 | Veamos lo que el algoritmo hace en cada iteración. Fíjate en la `↓` y como en cada iteración, ese número se va moviendo hacia el final, quedando ordenado. Podemos ver que en cada iteración, recorremos un elemento menos, ya que al quedar ordenado por la derecha, nos podemos ahorrar volver a comprar hasta el final. Por ejemplo, una vez hemos pasado el `9` desde el principio hasta el final, no es necesario volver a compararlo, ya que sabemos que es el más alto y ya está al final. 41 | 42 | ``` 43 | # Empezamos 44 | # [9, 8, 7, 5, 4, 3, 0] 45 | 46 | #  ↓ 47 | # [8, 9, 7, 5, 4, 3, 0] 48 | # [8, 7, 9, 5, 4, 3, 0] 49 | # [8, 7, 5, 9, 4, 3, 0] 50 | # [8, 7, 5, 4, 9, 3, 0] 51 | # [8, 7, 5, 4, 3, 9, 0] 52 | # [8, 7, 5, 4, 3, 0, 9] 53 | 54 | # ↓ 55 | # [7, 8, 5, 4, 3, 0, 9] 56 | # [7, 5, 8, 4, 3, 0, 9] 57 | # [7, 5, 4, 8, 3, 0, 9] 58 | # [7, 5, 4, 3, 8, 0, 9] 59 | # [7, 5, 4, 3, 0, 8, 9] 60 | 61 | #  ↓ 62 | # [5, 7, 4, 3, 0, 8, 9] 63 | # [5, 4, 7, 3, 0, 8, 9] 64 | # [5, 4, 3, 7, 0, 8, 9] 65 | # [5, 4, 3, 0, 7, 8, 9] 66 | 67 | #  ↓ 68 | # [4, 5, 3, 0, 7, 8, 9] 69 | # [4, 3, 5, 0, 7, 8, 9] 70 | # [4, 3, 0, 5, 7, 8, 9] 71 | 72 | #  ↓ 73 | # [3, 4, 0, 5, 7, 8, 9] 74 | # [3, 0, 4, 5, 7, 8, 9] 75 | 76 | # [0, 3, 4, 5, 7, 8, 9] 77 | ``` 78 | 79 | Ahora vamos a desgranar el código. Para intercambiar o hacer un *swap* de dos elementos de la lista hacemos uso del [tuple unpacking](https://ellibrodepython.com/unpacking-python). Es decir, imaginemos que queremos mover el elemento `[0]` a la posición `[1]` y el `[1]` a la posición `[0]`. Se podría hacer de muchas formas pero Python nos ofrece el que permite realizar el cambio en una sola línea: 80 | 81 | ```python 82 | x = [1, 2] 83 | x[1], x[0] = x[0], x[1] 84 | 85 | print(x) 86 | # [2, 1] 87 | ``` 88 | 89 | Por otro lado, el uso de `for j in range(0, n-i-1):` hace que en cada iteración recorramos un elemento menos, en vez de hasta el final. Dado que los últimos elementos ya están ordenados, esto nos ahorra comparaciones innecesarias. Es importante también el `-1` ya que al tomar elementos de dos en dos, sin él nos iríamos fuera de la lista. 90 | 91 | Por último el uso de la variable `swapped` no ofrece una optimización muy interesante que en ciertas ocasiones puede mejorar la eficiencia de nuestro *bubble sort*. Cuando tenemos listas ya (o casi) ordenadas, esto reduce la cantidad de iteraciones necesarias. Es decir, imaginemos que tenemos la lista ya ordenada. Si hacemos una pasada y `swapped` es `False`, significa que no se ha realizado ningún intercambio, y por lo tanto podremos considerar nuestro trabajo completado, ya que la lista ya está ordenada. Con esta optimización reduciríamos de complejidad `O(N^2)` a `O(n)`. 92 | 93 | Algunas características del *bubble sort*: 94 | * Sencillo de implementar. 95 | * No requiere de memoria extra. 96 | * Complejidad de `O(n^2)`. 97 | * Complejidad en el mejor de los casos de `O(n)`. 98 | * Requiere `n*(n-1)/2` comparaciones. 99 | 100 | -------------------------------------------------------------------------------- /xx_ejemplosyejercicios/08_calcular_letra_dni.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Calcular letra DNI 4 | description: Calcular la letra del DNI se puede hacer obteniendo el módulo 23 del DNI. 5 | order: 98 6 | nav_order: f 7 | permalink: /calcular-letra-dni 8 | nav_exclude: true 9 | --- 10 | 11 | # Letra DNI 12 | 13 |
14 | 15 | 16 | 17 |

18 |
19 | 20 | 37 | 38 | # Calcular letra DNI en Python 39 | 40 | En España todo Documento Nacional de Identidad (DNI) está compuesto por 8 dígitos y una letra al final. 41 | Esta letra actúa a modo de *checksum*. Es decir, la letra se calcula a partir de los dígitos del DNI. 42 | 43 | Podemos escribir una función en Python que dado un DNI nos devuelva el DNI con la letra de la siguiente manera. 44 | 45 | ```python 46 | def letra_dni(dni): 47 | if len(str(dni)) != 8: 48 | raise Exception("El DNI debe tener 8 dígitos") 49 | LETRAS_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE' 50 | return f"{dni}{LETRAS_DNI[int(dni) % 23]}" 51 | 52 | print(letra_dni('00000000')) 53 | # 00000000T 54 | ``` 55 | 56 | Como puedes ver: 57 | * Se comprueba que el DNI tiene 8 dígitos. 58 | * La letra se calcula usando el módulo 23 del DNI. 59 | * Se elige la letra de la secuencia. 60 | * Si el resultado es 0, se toma la `T`, si es 1 la `R`, etc. 61 | -------------------------------------------------------------------------------- /xx_modulosypaquetes/01_publicar_paquete_pip.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 Publicar paquete Python 4 | description: Publicar paquete Python 5 | order: 83 6 | nav_order: a 7 | permalink: /crear-paquete-python 8 | nav_exclude: true 9 | --- -------------------------------------------------------------------------------- /xx_usandopaquetesexternos/01_moviepy.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 📙 moviepy, edición de vídeo 4 | description: Edita, corta y pega vídeos usando Python y la libería moviepy 5 | order: 98 6 | nav_order: a 7 | permalink: /moviepy-python 8 | nav_exclude: true 9 | --- 10 | 11 | # moviepy: Editar vídeo con Python 12 | 13 | Seguramente alguna vez habrás editado un vídeo usando una aplicación como Vegas, After Effects o Premiere, pero ¿y si te digo que también se puede editar vídeo con Python?. La librería moviepy nos permite cortar, unir, rotar y mucho más. Se trata de un paquete open source para Python, y que por supuesto es compatible con Linux, macOS y Windows. A continuación te enseñamos sus posibilidades. 14 | 15 | Lo primero de todo, deberás instalar el paquete. Si ya tienes pip instalado, llama el siguiente comando en el terminal. 16 | 17 | ```python 18 | pip install moviepy 19 | ``` 20 | 21 | Vale, ya tenemos el paquete instalado, empecemos a usarlo. La mayoría de funcionalidades de moviepy, giran en torno a la clase VideoFileClip y AudioFileClip. La primera permite trabajar con vídeo y la segunda con audio. 22 | 23 | Importa el paquete 24 | 25 | ```python 26 | from moviepy.editor import * 27 | ``` 28 | 29 | Abre un video que tengas 30 | 31 | ```python 32 | video = VideoFileClip("video_ejemplo.mp4") 33 | ``` 34 | 35 | Una vez hemos abierto el vídeo, ya lo tenemos en video almacenado y podemos empezar a hacer cosas con el. A continuación te enseñamos. 36 | 37 | ## Rotar vídeo 38 | 39 | Si llamamos al método rotate() y le pasamos un número como entrada con los grados que queremos rotar el vídeo, nos devolverá un VideoFileClip rotado. Si queremos guardar el vídeo tan solo hay que llamar a write_videofile() y decirle el nombre que queremos. 40 | 41 | ```python 42 | from moviepy.editor import * 43 | video = VideoFileClip("video_ejemplo.mp4").rotate(180) 44 | video.write_videofile("rotado.mp4") 45 | ``` 46 | 47 | ## Acelerar 48 | 49 | Podemos también acelerar o ralentizar el vídeo haciendo uso de los efectos fx. Te dejamos el siguiente código para que pruebes, donde 2 indica que queremos el vídeo dos vez más rápido. Valores menores a uno significará más lento. 50 | 51 | ```python 52 | from moviepy.editor import * 53 | video = VideoFileClip("video_ejemplo.mp4").fx(vfx.speedx, 2) 54 | video.write_videofile("rapido.mp4") 55 | ``` 56 | 57 | ## Cortar vídeo 58 | 59 | Otra funcionalidad muy útil es cortar el vídeo, haciendo uso de subclip(). Acepta dos parámetros, el inicio y final en segundos. Por lo tanto (1, 2) nos devolverá un vídeo que dura un segundo desde el segundo uno. 60 | 61 | ```python 62 | from moviepy.editor import * 63 | video = VideoFileClip("video_ejemplo.mp4") 64 | cortado = video.subclip(1, 2) 65 | cortado.write_videofile("cortado.mp4") 66 | ``` 67 | 68 | ## Cambiar formato 69 | 70 | Se pueden realizar ciertos cambios de formato. Por ejemplo, si tenemos el vídeo en mp4, podemos pasarlo a webm simplemente guardándolo y cambiando la extensión. 71 | 72 | ```python 73 | from moviepy.editor import * 74 | video = VideoFileClip("video_ejemplo.mp4") 75 | video.write_videofile("otro_formato.webm") 76 | ``` 77 | 78 | ## Unir vídeos 79 | 80 | Una de las herramientas más potentes de moviepy, es que se pueden unir diferentes vídeos, y guardar su resultado en un nuevo vídeo. Si tenemos 3 vídeos, simplemente tenemos que leerlos y luego unirlos con concatenate_videoclips. 81 | 82 | ```python 83 | from moviepy.editor import * 84 | clip1 = VideoFileClip("video1.mp4") 85 | clip2 = VideoFileClip("video2.mp4").subclip(5,20) 86 | clip3 = VideoFileClip("video3.mp4") 87 | final_clip = concatenate_videoclips([clip1,clip2,clip3]) 88 | final_clip.write_videofile("resultado.mp4") 89 | ``` 90 | 91 | Además de unir diferentes vídeos, también podemos aplicar las transformaciones que queramos sobre cada vídeo, como crear un subclip(), cambiar la velocidad o lo que se nos ocurra. 92 | 93 | ## Redimensionar 94 | 95 | Tal vez tengamos un vídeo con una resolución muy grande y queramos generar una versión más pequeña. En una línea de código se puede realizar el redimensionado, en este caso para un ancho de 480. 96 | 97 | ```python 98 | from moviepy.editor import * 99 | video = VideoFileClip("video_ejemplo.mp4") 100 | reducido = video.resize(width=480).write_videofile("reducido.mp4") 101 | reducido.write_videofile("resultado.mp4") 102 | ``` 103 | 104 | ## Extraer audio 105 | 106 | También podemos extraer el audio de un vídeo, y almacenarlo en mp3 u otro formato que queramos. 107 | 108 | ```python 109 | from moviepy.editor import * 110 | video = VideoFileClip("video_ejemplo.mp4") 111 | audio = video.audio 112 | audio.write_audiofile("audio.mp3") 113 | ``` 114 | 115 | Y para que veas toda la potencia que tiene moviepy, te dejamos con el clásico texto de apertura de Start Wars, hecho con este paquete. Para más información consulta la documentación oficial. --------------------------------------------------------------------------------