From e81137474f102d71fe0b25307bcf88810c1b2d43 Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga Date: Wed, 4 Mar 2020 13:41:02 +0100 Subject: New arrangement for multilanguage and metadata support --- es/01_intro.md | 207 +++++++++ es/02_datos.md | 662 ++++++++++++++++++++++++++++ es/03_estructura.md | 550 ++++++++++++++++++++++++ es/04_funciones.md | 654 ++++++++++++++++++++++++++++ es/05_oop.md | 1093 +++++++++++++++++++++++++++++++++++++++++++++++ es/06_ejec_mod.md | 271 ++++++++++++ es/07_install.md | 243 +++++++++++ es/08_stdlib.md | 372 ++++++++++++++++ es/09_extralib.md | 162 +++++++ es/10_closing_words.md | 123 ++++++ es/A_devtools.md | 90 ++++ es/Metadata.yaml | 10 + es/Z_license.md | 337 +++++++++++++++ src/00_meta.md | 12 - src/01_intro.md | 207 --------- src/02_datos.md | 662 ---------------------------- src/03_estructura.md | 550 ------------------------ src/04_funciones.md | 654 ---------------------------- src/05_oop.md | 1093 ----------------------------------------------- src/06_ejec_mod.md | 271 ------------ src/07_install.md | 243 ----------- src/08_stdlib.md | 372 ---------------- src/09_extralib.md | 162 ------- src/10_closing_words.md | 123 ------ src/A_devtools.md | 90 ---- src/Z_license.md | 337 --------------- 26 files changed, 4774 insertions(+), 4776 deletions(-) create mode 100644 es/01_intro.md create mode 100644 es/02_datos.md create mode 100644 es/03_estructura.md create mode 100644 es/04_funciones.md create mode 100644 es/05_oop.md create mode 100644 es/06_ejec_mod.md create mode 100644 es/07_install.md create mode 100644 es/08_stdlib.md create mode 100644 es/09_extralib.md create mode 100644 es/10_closing_words.md create mode 100644 es/A_devtools.md create mode 100644 es/Metadata.yaml create mode 100644 es/Z_license.md delete mode 100644 src/00_meta.md delete mode 100644 src/01_intro.md delete mode 100644 src/02_datos.md delete mode 100644 src/03_estructura.md delete mode 100644 src/04_funciones.md delete mode 100644 src/05_oop.md delete mode 100644 src/06_ejec_mod.md delete mode 100644 src/07_install.md delete mode 100644 src/08_stdlib.md delete mode 100644 src/09_extralib.md delete mode 100644 src/10_closing_words.md delete mode 100644 src/A_devtools.md delete mode 100644 src/Z_license.md diff --git a/es/01_intro.md b/es/01_intro.md new file mode 100644 index 0000000..b2a1752 --- /dev/null +++ b/es/01_intro.md @@ -0,0 +1,207 @@ +# Introducción + +Python es un lenguaje de programación de alto nivel orientado al uso general. +Fue creado por Guido Van Rossum y publicado en 1991. La filosofía de python +hace hincapié en la limpieza y la legibilidad del código fuente con una +sintaxis que facilita expresar conceptos en menos líneas de código que en otros +lenguajes. + +Python es un lenguaje de tipado dinámico y gestión de memoria automática. +Soporta múltiples paradigmas de programación, incluyendo la programación +orientada a objetos, imperativa, funcional y procedural e incluye una extensa +librería estándar. + +Pronto entenderás lo que todo esto significa, pero antes hay que instalar las +herramientas necesarias y trastear con ellas. + +## Instalación + +Para trabajar con python se necesita: + +- python3: el intérprete de python, en su versión 3. Verás que hay muchas + subversiones. Este documento cubre cualquiera de ellas. + +- pip: el gestor de paquetería de python. También se conoce como pip3 para + diferenciarlo del pip de python2. + +Nosotros añadiremos un par de amigos a la lista: + +- idle3: un editor de código python muy sencillo. Usaremos este porque + representa el ecosistema de forma muy simple. En el futuro, te recomiendo + usar algún otro editor más avanzado. + +- pipenv: el estándar de facto para gestionar entornos virtuales en python3. + Luego entenderás qué es eso. + + +### Instalación en distribuciones de Linux + +La instalación puede realizarse desde el gestor de paquetes habitual, ya que +python suele distribuirse en todos los repositorios de paquetes. + +En las distribuciones que usan el sistema de paquetes de Debian, puede +instalarse desde la terminal con el siguiente comando: + +``` bash +sudo apt-get install python3 python3-pip idle3 +``` + +### Instalación en otros sistemas + +Como siempre instalar en otros sistemas es más farragoso. Pero no es demasiado +difícil en este caso. La instalación puede realizarse con una descarga desde la +página web oficial de python: + + + +Una vez ahí seleccionar la versión necesaria, descargar el instalador y seguir +las instrucciones de éste. Recuerda seleccionar **instalar pip** entre las +opciones y activar la casilla de **añadir python al PATH**, que permitirá que +que ejecutes programas de python sin problemas. También puedes añadir +**IDLE**, el programa que sirve para editar el código, pero te recuerdo que +es un programa muy sencillo, que nos servirá para entender lo básico del +entorno sin ocultarnos el proceso, pero que más adelante podrás utilizar otros +editores que simplifiquen tareas. + + +## Admira el paisaje + +Una vez que has instalado python, es interesante ver lo que eso significa. +Python es un intérprete de código fuente del lenguaje del mismo nombre. +Concretamente, la que has instalado es una de las posibles implementaciones (la +implementación de referencia, en este caso) de este intérprete, conocida como +CPython, en su versión 3. Existen otras implementaciones, cada una con sus +peculiaridades, pero ésta es la principal y la más usada. + +Como intérprete que es, python es capaz de leer un archivo escrito en su +lenguaje y ejecutar sus órdenes en tu computadora. Ésta es principalmente su +labor. Sin embargo, también es capaz de realizar esta operación de forma +interactiva recibiendo las órdenes una por una y devolviendo el resultado de su +ejecución como respuesta. Este proceso se conoce como REPL, acrónimo de +read-eval-print-loop (lee-evalúa-imprime-repite), aunque en otros lugares se le +conoce como la shell de python. + +> NOTA: La shell de python (o REPL) y la shell del sistema son cosas +> diferentes. La shell de sistema también es un intérprete pero del lenguaje +> que el sistema ha definido (Bash, PowerShell...) y no suele ser capaz de +> entender python. + +Para acostumbrarte a la shell te propongo que abras IDLE. Lo primero que verás +será parecido a esto: + +``` python +Python 3.6.8 (default, Oct 7 2019, 12:59:55) +[GCC 8.3.0] on linux +Type "help", "copyright", "credits" or "license()" for more information. +>>> +``` + +Todo lo que escribas tras el símbolo `>>>` será interpretado como una orden y +cuando la termines pulsando la tecla `ENTER` de tu teclado, recibirás el +resultado de la ejecución de la orden insertada. El acrónimo REPL define el +comportamiento de este ciclo a las mil maravillas: + +> NOTA: En este documento, siempre que veas el símbolo `>>>` significa que se +> trata de un ejemplo ejecutado en la REPL. Si no lo ves, se tratará del +> contenido de un archivo de código python ejecutado de forma independiente. + +1. Lee lo que introduces. +2. Lo evalúa, obteniendo así un valor como resultado. +3. Lo imprime. +4. Repite el ciclo volviendo a leer. + +Por tanto, si introduces un valor directamente será devuelto: + +``` python +>>> 1 +1 +``` + +Y si lo alteras, por ejemplo, con una operación matemática sencilla, devuelve +el resultado correspondiente: + +``` python +>>> 2+2 +4 +``` + +Como ejercicio te propongo lo siguiente: + +1. Abre la shell de python (puedes hacerlo en IDLE o desde la shell de sistema + ejecutando `python` o `python3`). +2. Entra en la ayuda interactiva. PISTA: el mensaje que aparece al abrir la + REPL te dice cómo. +3. Sal de la ayuda (descubre tú mismo cómo se hace). +4. Ejecuta `import this` y lee el resultado. + + +### Tu primer archivo de código fuente + +La REPL es interesante para probar y depurar tus programas (o para usarla como +calculadora), pero es necesario grabar tus programas en ficheros si quieres +poder volver a ejecutarlos más adelante o compartirlos. + +En IDLE puedes abrir un nuevo documento de código en el menú de archivo. Una +vez lo tengas, como aún no sabes python puedes introducir lo siguiente: + +``` python +nombre = "Guido" +print("Hola, " + nombre) +``` + +Si guardas el fichero y pulsas `F5` (Ejecutar módulo), verás que en la pantalla +de la REPL aparece el resultado `Hola, Guido`. + +Como ves, el resultado de ejecutar los ficheros de código fuente aparece en la +shell, pero únicamente aparece lo que explícitamente le has pedido que imprima +con la orden `print`. + +Para entender el valor de la REPL, te sugiero que vayas a su ventana, y justo +después del resultado de la ejecución hagas lo siguiente: + +``` python +Hola, Guido +>>> nombre +'Guido' +>>> +``` + +La REPL conoce el valor `nombre` a pesar de que tu programa ha terminado de +ejecutarse. Esto es interesante a la hora de probar y analizar el programa. + + +### La vida real + +En realidad, los programas de producción no se ejecutan en una shell como la +que IDLE nos brinda. IDLE sólo está facilitando nuestro trabajo como +desarrolladores, como otros entornos de desarrollo hacen, cada uno a su manera. +En producción el código se levantará ejecutando el intérprete de python +directamente con nuestro programa como input. Por ejemplo en la shell **de +sistema** usando el siguiente comando. + +``` bash +python ejemplo.py +``` + +También es posible ejecutar los programas de python desde la interfaz gráfica, +pero internamente el resultado será el mismo. Siempre que todo esté bien +instalado y configurado, el sistema operativo despertará un intérprete de +python que ejecute las órdenes del fichero. + +Es importante ser consciente de lo que ocurre bajo la alfombra, para así ser +capaces de intervenir si encontramos errores. + +Más adelante, en la sección sobre módulos e importación volveremos aquí y +estudiaremos cómo se cargan y se interpretan los programas. + + +## Lo que has aprendido + +Has instalado python y te has acostumbrado a la herramienta (IDLE) que usarás +durante tu aprendizaje. Has ejecutado tu primer fichero y encontrado la +potencia de la REPL. + +Además, has abierto la ayuda y te has leído el Zen de Python, que pronto iremos +desgranando juntos. + +Para ser una introducción no está nada mal. diff --git a/es/02_datos.md b/es/02_datos.md new file mode 100644 index 0000000..1eb2f1a --- /dev/null +++ b/es/02_datos.md @@ -0,0 +1,662 @@ +# Trabajando con datos + +Programar es, principalmente, tratar con datos y los datos no son más que +piezas de información que se estructura de una forma concreta. + +## Nombrando datos + +Los datos se almacenan en la memoria principal de la computadora. Puedes +imaginarte la memoria como un conjunto de cajas identificadas con direcciones, +como si fuesen los buzones de un portal con muchos pisos. Para utilizar datos, +éstos se guardan en los diferentes cajones y se van extrayendo y actualizando. +La importancia de nombrar las cosas es evidente, si no guardamos un listado de +dónde hemos guardado qué no podemos recuperar los datos que hemos creado. + +Al igual que en las matemáticas se utilizan los símbolos para representar +posibles valores y simplificar los procesos, en los lenguajes de programación +se utilizan símbolos para definir referencias a los datos y así poder referirse +a los mismos datos por su nombre sin tener que introducir su contenido +constantemente. Los lenguajes de programación aportan al programador la +facilidad de poder crear sus propios nombres para no tener que usar las +direcciones propias de la memoria, que normalmente son número sin más +significado que su posición, la mayor parte de las veces permitiendo al +programador abstraerse de estos detalles internos. + +En python existe un operador para declarar símbolos que hacen referencia a un +valor: el operador `=`. Este operador enlaza el valor de la derecha con el +nombre de la izquierda. Con un ejemplo es más fácil de comprender. Probando en +la REPL: + +``` python +>>> a = 10 +>>> a +10 +>>> a + 1 +11 +``` + +Como ves, en el primer paso se asocia el valor `10` al identificador `a` y más +adelante se puede utilizar `a` para hacer referencia al valor enlazado. + +Las referencias han de ser declaradas antes de usarse, si no, el intérprete no +las conoce y lanza una excepción: + +``` python +>>> b +Traceback (most recent call last): + File "", line 1, in + b +NameError: name 'b' is not defined +``` + +> NOTA: Los nombres para poder ser interpretados correctamente por python deben +> cumplir unas normas estrictas: +> +> - No pueden tener espacios. +> - Sólo pueden estar formados por combinaciones de letras, números y el +> símbolo `_`. +> - No pueden empezar por un dígito. + +### Todo es una referencia + +El comportamiento de estas referencias puede no ser intuitivo si vienes de +otros lenguajes. El operador `=` enlaza, como se dice anteriormente, un nombre +a un valor concreto, pero eso no significa que copie su contenido. En python +los valores y los nombres son conceptos independientes. Los valores ocupan su +lugar en la memoria y los nombres hacen referencia a dónde se encuentran estos +valores. Es muy común tener muchas referencias a la misma estructura de datos y +transformar su contenido desde todas las referencias. + +A nivel técnico, lo que en python se conoce como variable y en este documento +hemos hecho el esfuerzo de llamar referencia es lo que en otros lenguajes se +conoce como un puntero y la labor del operador `=` es la de asignar el puntero +a la dirección donde se encuentran los datos a los que debe apuntar. + +Volviendo a la metáfora de los buzones, en lenguajes de más bajo nivel como C +estás obligado a seleccionar qué buzón vas a utilizar para introducir cada +dato. Por lo que si cambia el tipo de dato a gestionar, puede que el buzón se +quede pequeño o que los datos se interpreten de forma incorrecta. Sin embargo, +python guarda las referencias de forma independiente a los valores y adecua el +número de buzones en uso al tamaño de los datos de los que dispones +reordenando, si es necesario, la estructura completa de valores y referencias. +A este concepto se le conoce como gestión automática de memoria (*automatic +memory management*). + +En resumen, las referencias en python son únicamente un recordatorio que sirve +para poder acceder a un valor. Esto cobra importancia más adelante, y no vamos +a rehuir la responsabilidad de enfangarnos en ello. + +## Tipos + +Tal y como aclaraba el texto de la introducción, python tiene un sistema de +tipos dinámico (*dynamic type system*). Lo que significa que gestiona los tipos +de forma automática, permitiendo a los nombres hacer referencia a diferentes +tipos de valor durante la ejecución del programa a diferencia de otros +lenguajes como, por ejemplo, C, donde el tipo de las variables debe ser +declarado de antemano y no puede cambiarse. + +Esto es posible debido al fenómeno explicado en el apartado anterior por un +lado, y, por el otro, a que los datos de python son un poco más complejos que +en otros lenguajes y guardan una pequeña nota que indica cómo deben ser +interpretados. + +Si en algún momento se le pide a python que asigne un valor de un tipo distinto +al que una referencia tenía no habrá problemas porque es el propio dato quien +guarda la información suficiente para saber cómo entenderlo. Las referencias +sólo almacenan dónde se guarda este dato. + +> NOTA: Seguramente te habrás dado cuenta de que el funcionamiento de python es +> más ineficiente que el de C o lenguajes similares pero mucho más flexible. Y +> así es. A la hora de elegir el lenguaje debemos valorar cuál nos interesa +> más para la labor que vamos a realizar. + +### Tipos simples + +Hemos denominado tipos simples a los que no empaquetan más de un valor +internamente. En otros lenguajes o contextos se les conoce como escalares. + + +#### La nada + +La nada en python se representa con el valor `None` y es útil en innumerables +ocasiones. + +#### Boolean + +Los valores booleanos expresan *verdad* (`True`) o *mentira* (`False`) y sirven +para gestionar lógica desde esos términos. Mas adelante los veremos en acción. + +#### Integer + +Los Integer, números enteros en inglés, ya han aparecido anteriormente. Para +usar un número entero puedes introducirlo tal cual. Recuerda que hay enteros +positivos y negativos. El símbolo para marcar números negativos en python es el +`-`, que coincide con el operador de la resta. + +``` python +>>> 14 +14 +>>> -900 +-900 +>>> +``` + +Los números enteros también pueden expresarse en otras bases, como en +hexadecimal. Dependiendo de la aplicación en la que te encuentres igual +necesitas mirarlo más en detalle: + +``` python +>>> 0x10 +16 +``` + +#### Float + +Los números Float, o de coma flotante, son números no-enteros. Ten cuidado +con ellos porque la coma flotante es peculiar. + +El nombre coma flotante viene de que la coma no siempre se mantiene con la +misma precisión. En realidad estos números se guardan como los números en +notación científica (2,997E8 m/s para la velocidad de la luz, por ejemplo). +La notación científica siempre implica tener una coma, pero cuya posición se +varía con el exponente posterior. + +El modo de almacenamiento de los números de coma flotante es muy similar a la +notación científica: se almacenan dos valores de tamaño fijo, la *mantisa* y el +*exponente* para, de este modo, poder ajustar la precisión en función del +tamaño del número almacenado. No es lo mismo expresar la velocidad de la luz, +2,997E8 m/s, que la longitud de onda del color rojo, 6,250E-7 m, ambos +valores tienen un tamaño muy distinto, pero usando dos *mantisas* de tamaño +similar, cuatro dígitos (2997 y 6250), y dos exponentes de tamaño similar, un +dígito (8 y -7), hemos expresado valores muy diferentes, ambos con la misma +precisión relativa. + +El problema viene cuando nos apetece mezclarlos, por ejemplo, sumándolos. +Imagina que tienes dos valores de esas dimensiones, uno de millones y otro de +millonésimas partes de la unidad, y los quieres sumar entre ellos. Si sólo +tienes una mantisa limitada para representarlos, la suma resultará en el +redondeo de ambos a la precisión que tienes disponible. Es decir: el resultado +será el valor grande y el pequeño se perderá en el redondeo. + +Aunque en este caso la suma es precisa, si tratas con un billón de valores +pequeños y uno grande y quieres obtener la suma de todos y los sumas en parejas +siempre con el grande, en cada suma se descartará el valor pequeño en el +redondeo y el resultado total será el valor grande que tenías. Sin embargo, si +sumas los valores de tamaño similar entre ellos primero, obtendrás un valor +suficientemente grande como para alterar el resultado de la suma contra el +valor grande al final, dando así un resultado distinto. Te recomiendo entonces, +que si te encuentras en una situación como ésta tengas cuidado y, por ejemplo, +ordenes los números de menor a mayor antes de sumarlos, para obtener una suma +de buena precisión. + +En realidad, el exponente en el caso de python (y en casi todos los demás +lenguajes) no está elevando un 10 a la enésima potencia, si no que lo hace con +un 2. Por lo que lo expresado anteriormente es un poco distinto. Esto provoca +que algunos números de coma flotante no sean tan redondos como deberían (por +ejemplo, `2.999999999`, cuando debería ser `3.0`) y compararlos entre ellos +puede ser desastroso. Para evitarlo, te recomiendo que *siempre* redondees los +valores a una precisión que puedas controlar. + +Aunque realmente es algo más complejo[^1], lo que sabes ahora te evitará +problemas en el futuro, sobre todo cuando analices datos, uno de los sectores +donde python se usa de forma extensiva. + +[^1]: https://en.wikipedia.org/wiki/Floating-point_arithmetic + +Declarar números de coma flotante es natural porque usa una sintaxis a la que +estamos acostumbrados: + +``` python +>>> 1E10 +10000000000.0 +>>> 1.0 +1.0 +>>> 0.2E10 +2000000000.0 +>>> +``` + + +#### Complex + +Python soporta números complejos y los expresa utilizando la letra `j`. Como +suponen un caso bastante concreto no los analizaremos en detalle. Pero tienes +disponible la documentación de python para lo que quieras. + +``` python +>>> 1-23j +(1-23j) +``` + +#### String + +Un String es una cadena de caracteres. Los Strings en python son, a diferencia +de en otros lenguajes, un escalar, al contrario de lo que la primera definición +que hemos expresado puede hacernos pensar. En python los Strings no son un +conjunto de caracteres alfanuméricos sueltos que se comportan como un valor, es +al revés. El concepto de carácter no existe y ha de expresarse con un String de +longitud 1. + +Los strings se expresan rodeando texto con comillas dobles, `"`, o simples `'` +(el símbolo del apóstrofe). + +``` python +>>> "Hola" +'Hola' +>>> 'Hola' +'Hola' +``` + +El hecho de que haya dos opciones para delimitar los strings facilita el +etiquetado como string de valores que contienen las propias comillas en su +contenido. También puede utilizarse la contrabarra `\` para cancelar la acción +de las comillas. + + +``` python +>>> "Tiene un apóstrofe: Luke's" +"Tiene un apóstrofe: Luke's" +>>> 'Tiene un apóstrofe: Luke's' +SyntaxError: invalid syntax +>>> 'Tiene un apóstrofe: Luke\'s' +"Tiene un apóstrofe: Luke's" +``` + +> NOTA: la contrabarra sirve para introducir caracteres especiales o caracteres +> de escape[^2]: `\n` salto de línea, `\t` tabulador, etc. Que son una herencia +> de los tiempos de las máquinas de escribir, pero son aún útiles y muy usados. +> Para expresar la propia contrabarra ha de escaparse a sí misma con otra +> contrabarra para que no evalúe el siguiente caracter como un caracter de +> escape.: `\\`. + +[^2]: https://en.wikipedia.org/wiki/Escape_character + + +### Tipos compuestos + +Hemos denominado tipos compuestos a los que pueden incluir diferentes +combinaciones de tipos simples. Estos tipos dejan de ser escalares y se +comportan como vectores o conjuntos de datos. Estos tipos de dato pueden +contenerse a sí mismos, por lo que pueden crear estructuras anidadas complejas. + +#### Tuple + +Las tuplas o *tuple* en inglés son el tipo compuesto más sencillo en python. +Las tuplas definen un conjunto de valores de cualquier tipo. + +Se declaran utilizando paréntesis añadiendo sus elementos separados por comas. +Y se accede a sus contenidos utilizando los corchetes e introduciendo el índice +del elemento que se quiere extraer. + +``` python +>>> (2, 3, "HOLA") +(2, 3, 'HOLA') +>>> (2, 3, "HOLA")[0] +2 +``` +En python los índices comienzan en `0`. + +#### List + +Las listas o *list* son muy similares a las tuplas, pero son algo más complejas +porque pueden alterarse así mismas. A diferencia de todos los tipos que hemos +visto hasta ahora, tanto las listas como los diccionarios que veremos a +continuación son mutables. Esto significa que puede transformarse su valor, más +adelante trataremos esto en detalle. + +De momento, recuerda que las listas se construyen de forma similar a las +tuplas, pero utilizando corchetes en lugar de paréntesis. La forma de acceder a +los índices es idéntica. + +``` python +>>> [2, 3, "HOLA"] +[2, 3, 'HOLA'] +>>> [2, 3, "HOLA"][0] +2 +``` + +#### Dictionary + +Los diccionarios o *dictionary* son un tipo de dato similar a los dos +anteriores, pero que en lugar de utilizar índices basados en la posición de sus +elementos usan claves arbitrarias definidas por el usuario. + +Además, los diccionarios no están ordenados así que no se puede suponer que las +claves siempre van a estar en el orden en el que se introducen. + +Para declarar diccionarios es necesario indicar qué claves se quieren usar. Las +claves son de tipo string. En el caso de los diccionarios, además, se utilizan +llaves para definirlos. El acceso a sus valores se realiza con los corchetes, +del mismo modo que en las listas, pero es necesario seleccionar la clave para +acceder. + +Los diccionarios, al igual que las listas, son mutables. Como veremos en +seguida. + +``` python +>>> {"nombre": "Guido", "apellido": "Van Rossum", "popularidad": 8.0} +{'nombre': 'Guido', 'apellido': 'Van Rossum', 'popularidad': 8.0} +>>> {"nombre": "Guido", "apellido": "Van Rossum", "popularidad": 8.0}["popularidad"] +8.0 +``` + +#### Set + +Los *sets* son muy similares a las listas y tuplas, pero con dos +peculiaridades: + +- Sus valores son únicos. No pueden repetirse. +- No están ordenados + +Estas dos características tan estrictas, lejos de ser limitantes, aportan una +mejora radical en su rendimiento. Buscar elementos en un set es extremadamente +eficiente y se usan principalmente para esa labor. + +Si quieres validar en algún momento que un valor pertenece a un conjunto de +valores, el set es el tipo de dato que estás buscando. + +Los sets se declaran también usando las llaves, como un diccionario, pero no +usan claves. + +``` python +>>> {"a", "b", 1} +{'a', 1, 'b'} +``` + +## Conversión + +Ahora que conoces los valores sé que quieres saber cómo cambiar de uno a otro. +Cómo leer un Integer desde un String etc. Python tiene funciones para construir +sus diferentes tipos de datos a partir de los diferentes inputs posibles. + +Aunque aún no sabes ejecutar funciones te adelanto cómo se hace con algunos +ejemplos: + +``` python +>>> bool(1) +True +>>> bool(0) +False +>>> int("hola") +Traceback (most recent call last): + File "", line 1, in + int("hola") +ValueError: invalid literal for int() with base 10: 'hola' +>>> int("10") +10 +>>> float(10) +10.0 +>>> complex(19) +(19+0j) +>>> str(10) +'10' +>>> tuple([1,2,3]) +(1, 2, 3) +>>> list((1,2,3)) +[1, 2, 3] +>>> dict((("a", 1),("b", 2))) +{'a': 1, 'b': 2} +>>> set([1,2,2,3,4,4,4,4,4]) +{1, 2, 3, 4} +``` + +Los propios nombres de las funciones son bastante representativos de a qué tipo +convierten. Si quieres saber más puedes ejecutar `help(nombre)` y ver qué te +cuenta la ayuda. + +> NOTA: Fíjate que si conviertes una secuencia de valores repetidos a *set* +> únicamente almacena los que no se repiten. Es uno de los usos más comunes que +> tienen. + + +## Operadores + +Ahora que sabes el contexto en el que vas a jugar, necesitas poder alterar los +datos. + +Existen operadores básicos que te permiten transformar los datos, algunos ya +los has visto antes, el operador `=`, que sirve para nombrar cosas, la suma +(`+`) o la resta (`-`), pero hay otros. + +``` python +>>> 1 + 1 +2 +>>> 10 - 9 +1 +>>> 10 ** 2 +100 +>>> +``` + +Los siguientes apartados muestran algunos operadores que es interesante +memorizar. + + +### Pruebas de verdad + +Las pruebas de verdad generan un valor booleano desde una pareja de valores. A +continuación una lista de las pruebas de verdad con unos ejemplos: + +| operator | meaning | +|---|---| +| < | strictly less than | +| <= | less than or equal | +| > | strictly greater than | +| >= | greater than or equal | +| == | equal | +| != | not equal | +| is | object identity | +| is not | negated object identity | +| in | containment test | +| not in | negated containment test | + +``` python +>>> 1 > 1 +False +>>> 1 >= 1 +True +>>> 1 not in (0, 2, 3) +True +``` + +Aunque en otros lenguajes no es posible, la notación matemática habitual se +puede utilizar en python concatenando pruebas de verdad: + +``` python +>>> x = 1.2 +>>> 1 < x < 2 +True +``` + +### Operadores lógicos + +Los operadores lógicos mezclan booleanos y son muy importantes, sobre todo para +combinar las pruebas de verdad. + +| operator | meaning | +|---|---| +| and | logical and, returns True if both True, False otherwise | +| or | logical or, returns True if any True, False otherwise | +| not | logical not, returns True if False, True otherwise | + +La mayor parte de los operadores son binarios (como la suma), necesitan dos +valores y devuelven otro, pero existe al menos una excepción que funciona con +un único valor. El operador `not` sirve para darle la vuelta a un Booleano. + +``` python +>>> not True +False +>>> True and True +True +>>> False and True +False +>>> False or True +True +>>> 1 > 0 and 2 > 1 +True +``` + +### Matemáticos + +Los operadores matemáticos transforman números entre ellos. Casi todos son +conocidos por su operación matemática. + +| operator | meaning | +|---|---| +| + | addition | +| - | subtraction or negative | +| `*` | multiplication | +| / | division | +| `**` | power | +| % | remainder | + +### Ternary operator + +Existe además un operador que puede usar tres parámetros, el *inline-if* (*if +en línea*), o *ternary operator*[^ternary]. El *ternary operator* se comporta +así: + +``` python +>>> 1 if 1 > 9 else 9 +9 +>>> 1 if 1 < 9 else 9 +1 +``` + +[^ternary]: + + +### Operación en función del tipo + +Python simplifica muchas tareas transformando el comportamiento de los +operadores en función del tipo de dato sobre el que trabajan. Los operadores +matemáticos están preparados para trabajar sobre números (de cualquier tipo) +pero la verdad es que algunos pueden ejecutarse sobre otros formatos. Por +ejemplo: + +``` python +>>> "a" + "a" +'aa' +>>> [1,2] + [3,4] +[1, 2, 3, 4] +``` + +Esto se debe a que la funcionalidad del operador `+` ha sido diseñada para +operar de forma especial en Strings y en Listas, haciendo una concatenación. + +En el futuro, cuando aprendas a diseñar tus propios tipos podrás hacer que los +operadores les afecten de forma especial, tal y como pasa aquí. + +### Precedencia + +La precedencia en python es muy similar a la matemática y usa las mismas reglas +para marcarla de forma explícita. Recuerda, en matemáticas se utilizan los +paréntesis para esto. + +Los operadores siempre trabajan con sus correspondientes valores y python los +resuelve de forma ordenada. Si generas una operación muy compleja, python la +irá desgranando paso a paso y resolviendo las parejas una a una, cuanto más te +acostumbres a hacerlo en tu mente menos errores cometerás. + +``` python +>>> 8 + 7 * 10 == (8 + 7) * 10 +False +>>> 8 + 7 * 10 +78 +>>> (8 + 7) * 10 +150 +>>> 78 == 150 +False +``` + +## Mutabilidad + +Ya adelantamos que el operador `=` sirve para nombrar cosas. Ahora vamos a +combinar esa propiedad con la mutabilidad, o la propiedad de las cosas de +alterarse a sí mismas. Empecemos con un ejemplo: + +``` python +>>> a = 10 +>>> b = a + 10 +>>> b +20 +>>> a = b +>>> a +20 +``` + +En este ejemplo hacemos que `a` haga referencia al valor 10, y después creamos +`b` a partir de `a` y otro 10. Gracias a la precedencia, primero se resuelve el +lado derecho completo obteniendo un 20 y después se asigna la referencia `b` a +ese valor. + +Si queremos, después podemos reasignar el símbolo `a` a otro valor nuevo, en +este caso al que hacer referencia `b` que es 20. + +En este primer ejemplo, ningún valor está siendo alterado, si te fijas, sólo +estamos creando nuevos valores y cambiando las referencias a éstos. + +Con los datos mutables podemos alterar los valores. Lo vemos con otro ejemplo: + +``` python +>>> a = {} +>>> b = a +>>> a["cambio"] = "hola!" +>>> b +{'cambio': 'hola!'} +``` + +Primero creamos un diccionario vacío y le asignamos la referencia `a`. Después +le asignamos la referencia `b` a quien referenciaba `a`, es decir, al +diccionario vacío. Ambas referencias apuntan al mismo dato. Si después usamos +alguna alteración en el diccionario `a` como asignarle una nueva clave, `b`, +que hace referencia al mismo diccionario, también ve los cambios. Esto mismo +podría hacerse si se tratara de listas, ya que tienen la capacidad de alterarse +a sí mismas, pero nunca podría hacerse en tuplas, porque son inmutables. + +> NOTA: Los diccionarios y las listas soportan un montón de funciones y +> alteraciones, no se mencionan en este apartado porque nunca terminaría. Se +> dejan para el futuro y para los ejemplos que se verán durante el documento. + +Si intentásemos un ejemplo similar en tupla, no nos dejaría: + +``` python +>>> a = () +>>> b = a +>>> a[0] = 1 +Traceback (most recent call last): + File "", line 1, in + a[0] = 1 +TypeError: 'tuple' object does not support item assignment +``` + +Ten cuidado cuando trates con elementos mutables, sobre todo si tienen muchas +referencias, porque puede que estés alterando los valores en lugares que no te +interesa. Para evitar este tipo de problemas, puedes generar copias de los +objetos, pero el proceso es poco eficiente y tedioso. + +En este segundo caso, creamos una copia de `a` para que `b` sea independiente +de los cambios que ocurran en ésta. Aquí ya no estamos haciendo referencia +desde `b` a los datos que había en `a`, sino a una copia de éstos, almacenada +en otro lugar. + +``` python +>>> a = {} +>>> b = dict(a) +>>> a["cambio"] = "hola!" +>>> b +{} +>>> a +{'cambio': 'hola!'} +``` + +## Lo que has aprendido + +En este apartado has conocido los tipos fundamentales de python y cómo +convertir de uno a otro. Además, al conocer los operadores y su funcionamiento +ya eres más o menos capaz de usar python como una calculadora. + +Además, has tenido ocasión de entender de forma superficial varios conceptos +avanzados como el manejo automático de memoria, el tipado dinámico y los +números de coma flotante, que son muy interesantes a la hora de trabajar porque +te permiten comprender la realidad que tienes a tu alrededor. diff --git a/es/03_estructura.md b/es/03_estructura.md new file mode 100644 index 0000000..9d8cd5e --- /dev/null +++ b/es/03_estructura.md @@ -0,0 +1,550 @@ +# Estructura del lenguaje + +Aunque ya sabes usar python de forma sencilla, aún no hemos tratado el +comportamiento del lenguaje y cómo se estructura su sintaxis más allá de varios +ejemplos sencillos y planos. En este apartado trataremos la estructura y las +diferentes formas de controlar el flujo del programa. + +Como en la mayor parte de lenguajes de programación conocidos, python ejecuta +las órdenes de arriba a abajo, línea por línea. + +Para demostrarlo, prueba a abrir un nuevo fichero, llenarlo con este contenido +y ejecutarlo (`F5`). + +``` python +print("Esta línea va primero") +print("Esta línea va segundo") +print("Esta línea va tercero") +print("Esta línea va cuarto") +``` + +Verás que el resultado del programa es siempre el mismo para todas las veces +que lo ejecutes y siempre salen los resultados en el mismo orden. + +Para poder alterar el orden de los comandos, o elegir en función de una +condición cuales se ejecutan, python dispone de unas estructuras. Pero, antes +de contarte cuales son, te adelanto su forma general. Normalmente se declaran +en una línea terminada en `:` y su cuerpo se sangra hacia dentro. La sangría (o +indentación si lo calcamos del inglés[^indent]) es lo que define dónde empieza +o termina un *bloque* en python. Las líneas consecutivas sangradas al mismo +nivel se consideran el mismo *bloque*. + +#### Bloques + +Los *bloques* de código son conjuntos de órdenes que pertenecen al mismo +contexto. Sirven para delimitar zonas del programa, cuerpos de sentencias, etc. + +- Puedes comenzar nuevos bloques incrementando el nivel de sangría. +- Los bloques pueden contener otros bloques. +- Los bloques terminan cuando el sangrado disminuye. + +Es **muy importante** sangrar los bloques correctamente, usando una sangría +coherente. Puedes usar dos espacios, el tabulador, cuatro espacios o lo que +desees, pero elijas lo que elijas debe ser coherente en todo el documento. +IDLE y los editores de código en general puede configurarse para usar una +anchura de indentación concreta, que se insertará cuando pulses la tecla +tabulador. El estándar de python es cuatro espacios. + +[^indent]: + +## Sintaxis general + +La sintaxis de python es sencilla en su concepción pero ha ido complicándose a +medida que el lenguaje ha ido creciendo. En este apartado únicamente se +mencionan los puntos más comunes de la sintaxis, dejando para futuros apartados +los detalles específicos de conceptos que aún no se han explicado. + +### Comentarios + +Los comentarios son *fundamentales* en el código fuente. El intérprete los +ignora pero son primordiales para explicar detalles de nuestro código a otros +programadores o a nosotros mismos en el futuro. Comentar bien el código fuente +es un arte en sí mismo. + +Los comentarios en python se introducen con el símbolo `#`. Desde su aparición +hasta el final de la línea se considera un comentario y python lo descarta. +Pueden iniciarse a mitad de línea o en el inicio, tal y como se muestra a +continuación: + +``` python +# En este ejemplo se muestran comentarios, como este mismo +print("Hola") # Esto es otro comentario +``` + +### Control de flujo + +Como ya se ha adelantado, es posible cambiar el orden de ejecución del programa +en función de unas normas o evitar que el programa ejecute ciertas sentencias. +A esto se le conoce como *control de flujo*. + +#### Condicionales + +Las condicionales son herramientas de *control de flujo* que permiten separar +la ejecución en diferentes ramas en función de unas condiciones. En python sólo +existe el condicional `if` («si», en castellano), aunque existen otras +estructuras para conseguir el mismo efecto, no las trataremos aún. + +Ésta es la sintaxis del `if`: + +``` python +if condición: + # Este bloque se ejecuta si la condición se cumple +elif condiciónN: + # Este bloque (opcional) se ejecuta si las condiciones previas no se + # cumplen y la condiciónN sí se cumple +else: + # Este bloque (opcional) se ejecuta si no se cumplen todas las condiciones + # previas +``` + +Tal y como se muestra en el ejemplo, los bloques `elif` y el bloque `else` son +opcionales. Es posible, y muy común además, hacer un `if` únicamente con el +apartado inicial. + +Si te preguntas qué condiciones debes usar, es tan simple como usar sentencias +de python cuyo resultado sea `True` o `False`. Cuando la sentencia resulte en +un `True` la condición se cumplirá y el bloque interior se ejecutará. + +En resumen, el *if* ejecuta el bloque *si* la condición se cumple. + +#### Bucles + +Existen dos tipos de sentencia para hacer repeticiones en python, ambas son +similares al `if`, pero en lugar de elegir si una pieza de código se ejecuta o +no, lo que deciden es si es necesario repetirla en función de una condición. + +##### While + +El `while` («mientras que») es la más sencilla de estas estructuras, y la menos +usada. + +``` python +while condición: + # Este bloque se ejecutará siempre que la condición se considere verdadera +``` + +El `while` comprueba la condición en primer lugar, si resulta en `True` ejecuta +el bloque interno y vuelve a comprobar la condición. Si es `True`, ejecuta el +bloque de nuevo, y así sucesivamente. + +Es decir, ejecuta el bloque *mientras que* la condición se cumple. + +##### For + +Los bucles *for* («para») son los más complejos y más usados, + +``` python +for preparación: + # Bloque a repetir si la preparación funciona con el contexto creado por la + # preparación +else: + # Bloque (opcional) a ejecutar si el bloque cuerpo no termina de forma + # abrupta con un `break` +``` + +No te preocupes ahora mismo por el `else`, ya que se suele considerar python +avanzado y no suele usarse. Más adelante en este capítulo analizaremos un +ejemplo. + +En lo que a la preparación se refiere, el `for` es relativamente peculiar, +sirve para ejecutar el primer bloque para un contexto concreto, el creado por +una sentencia de preparación. Si la preparación falla el bucle se rompe. + +El uso más común del `for` es el de iterar en secuencias gracias al operador +`in` que mencionamos anteriormente pero que en este caso toma un uso distinto: + +``` python +>>> for i in [0,1,2,3]: +... i+2 +... +2 +3 +4 +5 +``` + +Como puedes ver, el bloque interno `i+2` se ejecuta en cuatro ocasiones, +cambiando el valor de `i` a los valores internos de la lista `[0,1,2,3]`. +Cuando la lista termina, la preparación falla porque no quedan elementos y el +bucle se rompe. + +Este último ejemplo es una receta de amplio uso que te aconsejo memorizar. + + +#### Truthy and Falsey + +En este tipo de sentencias donde se comprueba si una condición es verdadera o +falsa, python automáticamente trata de convertir el resultado a Boolean usando +la función `bool` que ya conoces del apartado sobre tipos. No es necesario que +conviertas a Boolean manualmente ya que estas sentencias disponen de una +conversión implícita a Boolean[^truthy]. + +Por tanto, cualquier resultado que tras pasar por la función `bool` dé como +resultado `True` será considerado verdadero y viceversa. A estos valores se les +conoce habitualmente como *truthy* y *falsey* porque no son `True` o `False` +pero se comportan como si lo fueran. Algunos ejemplos curiosos: + +``` python +>>> bool([]) +False +>>> bool(None) +False +>>> bool("") +False +>>> bool(" ") +True +>>> bool([None]) +True +``` + +Es relativamente sencillo prever qué valores son *truthy* o *falsey*, +normalmente los valores que representan un vacío se consideran `False`. + +[^truthy]: + + +### List comprehensions + +Una de las excepciones sintácticas que sí que podemos explicar en este momento, +en el que ya sabes hacer bucles, son las *list comprehensions*. Python dispone +de un sistema para crear secuencias y transformarlas muy similar a la notación +de construcción de sets de las matemáticas[^set-notation]. + +Como mejor se entiende es con unos ejemplos, en este caso vamos usar la función +`range` para crear una lista de números del `0` (inclusive) al `10` (no +inclusive). Usando la ayuda puedes saber más sobre la función `range`. + + +> TODO: +> Mejorar este ejemplo, es demasiado simple + +``` python +>>> [i**2 for i in range(0, 10)] +[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] +>>> tuple(i**2 for i in range(0, 10)) +(0, 1, 4, 9, 16, 25, 36, 49, 64, 81) +>>> { str(i): i**2 for i in range(0, 10)} +{'0': 0, '1': 1, '2': 4, '3': 9, '4': 16, '5': 25, '6': 36, '7': 49, '8': 64, +'9': 81} + +>>> [i**2 for i in range(0, 10) if i > 5 ] +[36, 49, 64, 81] +``` + +Como ves, en el caso de los diccionarios es necesario crear las claves también. +En este caso las creamos convirtiendo el propio número a string con la función +`str`. + +En los primeros ejemplos, de una secuencia de números hemos creado una +secuencia de números al cuadrado. Pero las *list comprehensions* son más +poderosas que eso, pudiendo a llegar a complicarse sobremanera añadiendo +condiciones, como en el último de los ejemplos, para filtrar algunos casos. + +Te habrá mosqueado el uso de `tuple` para crear la tupla. Todo tiene una razón. +La tupla, al estar formada por paréntesis, python no tiene claro si son +paréntesis de precedencia o de creación de tupla, y considera que son los +primeros, dando como resultado un generador, un paso intermedio de nuestro +proceso que dejamos para el futuro. + +``` python +>>> (i**2 for i in range(0, 10)) + at 0x7f779d9b2d58> +``` + +[^set-notation]: + +### Excepciones + +Las excepciones o *exception* son errores del programa, python lanza +excepciones cuando hay problemas. Por ejemplo, cuando intentas acceder a un +índice inexistente en una lista. + +Las excepciones terminan la ejecución del programa a no ser que se gestionen. +Se consideran fallos de los que el programa no puede arreglarse a no ser que se +le indique cómo. Algunas funciones y librerías lanzan excepciones que nosotros +debemos gestionar, por ejemplo: que un archivo no exista, o que no se tenga +permisos de edición en el directorio, etc. Es nuestra responsabilidad como +programadores tener un plan be o aceptar la excepción deportivamente a +sabiendas que nuestro programa terminará indicando un error. + +Hay ocasiones en las que las excepciones pueden capturarse y otras no, por +ejemplo, los fallos de sintaxis no pueden solventarse. + +Las excepciones se capturan con un `try-except` que, si programas en otros +lenguajes como Java, probablemente conozcas como `try-catch`. + +```python +try: + # Bloque donde pueden ocurrir excepciones. +except tipo1: + # Bloque a ejecutar en caso de que se dé una excepción de tipo1. + # Especificar el tipo también es opcional, si no se añade captura todos. +except tipoN: + # Bloques adicionales (opcionales) a ejecutar en caso de que se dé una + # excepción de tipoN, que no haya sido capturada por los bloques + # anteriores. +finally: + # Bloque (opcional) a ejecutar después de lo anterior, haya o no haya + # habido excepción. +``` + +Además de capturarse, las excepciones pueden lanzarse con la sentencia `raise`. + +``` python +if not input: + raise ValueError("Invalid input") +``` + +Si el ejemplo anterior se diera dentro de una pieza de código que no podemos +controlar, podríamos capturar el `ValueError` y evitar que la ejecución de +nuestro programa terminara. + +``` python +try: + # Bloque que puede lanzar un ValueError +except ValueError: + print("Not value in input, using default") + input = None +``` + +Aunque aún no hemos entrado en la programación orientada a objetos, te adelanto +que las excepciones se controlan como tal. Hay excepciones que serán hijas de +otras, por lo que usando la excepción genérica de la familia seremos capaces de +capturarlas, o podremos crear nuevas excepciones como hijas de las que python +ya dispone de serie. Por ahora recuerda que debes ordenar los bloques `except` +de más concreto a más genérico, porque si lo haces al revés, los primeros +bloques te capturarán todas las excepciones y los demás no tendrán ocasión de +capturar ninguna, perdiendo así el detalle de los fallos. + +Cuando aprendas sobre programación orientada a objetos en el apartado +correspondiente puedes volver a visitar este punto y leer la documentación de +python[^exception] para entender cómo hacerlo. Te adelanto que python tiene una +larga lista de excepciones y que está considerado una mala práctica crear +nuevas si las excepciones por defecto cubren un caso similar al que se +encuentra en nuestro programa. + +[^exception]: + +### Funciones + +Las funciones sirven, sobre todo, para reutilizar código. Si una pieza de +código se utiliza en más de una ocasión en tu programa, es una buena candidata +para agruparse en una función y poder reutilizarla sin necesidad de duplicar +el código fuente. Aunque sirven para más fines y tienen detalles que +aclararemos en un capítulo propio, en este apartado adelantaremos cómo se +definen y cómo lanzan y posteriormente las analizaremos en detalle. + +Las definición de funciones se realiza con una estructura similar a las +anteriores, una línea descriptiva terminada en dos puntos (`:`) y después un +bloque con mayor sangría para definir el cuerpo. + +``` python +def nombre_de_funcion (argumentos): + """ + docstring, un string multilínea que sirve como documentación + de la función. Es opcional y no tiene efecto en el funcionamiento + de la función. + Es lo que se visualiza al ejecutar `help(nombre_de_función)` + """ + # Cuerpo de la función +``` + +Para llamar a esta función que acabamos de crear: + +``` python +nombre_de_función(argumentos) +``` + +El nombre de la función debe cumplir las mismas normas que los nombres de las +referencias de las que ya hemos hablando anteriormente. Y esto debe ser así +básicamente porque... ¡el nombre de la función también es una referencia a un +valor de tipo función! + +Pronto ahondaremos más en este tema. De momento recuerda la declaración de las +funciones. Dentro de ellas podrás incluir todas las estructuras definidas en +este apartado, incluso podrás definir funciones. + +Aunque en el próximo capítulo tocará hablar de los argumentos de entrada, de +momento te adelanto que cuando llamaste a la función `range` anteriormente, le +introdujiste dos argumentos. Esos dos argumentos deben declararse como +argumentos de entrada. Probablemente de una forma similar a esta: + +``` python +def range (inicio, fin): + contador = inicio + salida = [] + while contador < fin: + salida.append(contador) + contador = contador + 1 + return salida +``` + +Lo que va a ocurrir al llamar a la función, por ejemplo, con esta llamada: +`range(1, 10)` es que el argumento `inicio` tomará el valor `1` para esta +ejecución y el argumento `fin` tomará el valor `10`, como si en el propio +cuerpo de la función alguien hubiese hecho: + +``` python +inicio = 1 +fin = 10 +``` + +El contenido de la función se ejecutará, por tanto, con esas referencias +asignadas a un valor. Con lo que sabes ya puedes intentar descifrar el +comportamiento de la función `range` que hemos definido, que es similar, pero +no igual, a la que define python. + +Sólo necesitas entender lo que hace la función `list.append` que puedes +comprobar en la ayuda haciendo `help( list.append )` y la sentencia `return`, +que se explica en el siguiente apartado. + +Prueba a leer ambas y a crear un archivo de python donde construyes la función +y le lanzas unas llamadas. A ver si lo entiendes. + +### Sentencias útiles + +Python dispone de un conjunto de sentencias que pueden facilitar y flexibilizar +mucho el uso de las estructuras que acabamos de visitar. + +#### Pass + +`pass` es una sentencia vacía, que no ejecuta nada. Es necesaria debido a las +normas de sangría de python. Si construyes un bloque y no quieres rellenarlo +por la razón que sea, debes usar `pass` en su interior porque, si no lo haces y +simplemente lo dejas vacío, la sintaxis será incorrecta y python lanzará una +excepción grave diciéndote que esperaba un bloque con sangría y no se lo diste. + +``` python +>>> if True: +... + File "", line 2 + + ^ +IndentationError: expected an indented block +>>> if True: +... pass +... +``` + +Suele utilizarse cuando no quiere tratarse una excepción o cuando se ha hecho +un boceto de una función que aún no quiere desarrollarse, para que el +intérprete no falle de forma inevitable. + +> NOTA: Las excepciones de sintaxis son las más graves, implican que el +> intérprete no es capaz de entender lo que le pedimos así que la ejecución del +> programa no llega a realizarse. La sintaxis se comprueba en una etapa previa +> a la ejecución. + +#### Continue + +`continue` sirve para terminar el bucle actual y volver a comprobar la +condición para decidir si volver a ejecutarlo. + +En el siguiente ejemplo salta la ejecución para el caso en el que `i` es `2`. + +``` python +>>> for i in [0, 1, 2, 3]: +... if i == 2: +... continue +... i +... +0 +1 +3 +``` + +#### Break + +`break` rompe el bucle actual. A diferencia del `continue`, no se intenta +ejecutar la siguiente sentencia, simplemente se rompe el bucle completo. En el +siguiente ejemplo se rompe el bucle cuando `i` es `2` y no se recupera. + +``` python +>>> for i in [0, 1, 2, 3]: +... if i == 2: +... break +... i +... +0 +1 +``` + +El `break` se usa mucho para romper bucles que son infinitos por definición. En +lugar de añadir una condición compleja a un bucle `while`, por ejemplo, puedes +usar un `True` en su condición e introducir un `if` más simple dentro de éste +con un `break` que termine el bucle en los casos que te interesen. O puedes +capturar una excepción y romper el bucle si ocurre. + +Además, es muy usado en búsquedas y habilita el uso del `else` en las +sentencias `for`. Te propongo como ejercicio que trates de ejecutar y +comprender el funcionamiento de esta pieza de código de python avanzado antes +de seguir leyendo: + +``` python +text = "texto de prueba" +pos = 0 + +for i in text: + if i == "b": + print("Found b in position: " + str(pos)) + break + pos = pos + 1 +else: + print("b not found :( ") +``` + +Si cambias el texto de la variable `text` por uno que no tenga letra `b` verás +que el bloque `else` se ejecuta. Esto se debe a que el `for` no termina de +forma abrupta, sino que itera por el string completo. + +Si quieres, puedes memorizar esta estructura para cuando quieras hacer +búsquedas. Es elegante y te será útil. + + +#### Return + +La sentencia `return` sólo tiene sentido dentro de las funciones. Sirve para +finalizar la ejecución de una función sustituyendo su llamada por el resultado +indicado en el `return`. Esta operación rompe todos los bucles por completo. + +En el apartado de las funciones profundizaremos en el uso del `return` pero es +importante mencionarlo aquí porque su funcionalidad puede sustituir al `break` +en muchas ocasiones. + +## Lo que has aprendido + +Es difícil resumir todo lo que has aprendido en este capítulo porque, la verdad +es que es mucha información. Pero no pasa nada porque este capítulo se ha +creado más como referencia que como otra cosa. No tengas miedo a volver a +leerlo todas las veces que necesites. A todos se nos olvida cómo hay que +declarar las funciones si llevamos mucho tiempo sin tocar python, o si era +`try-catch` o `try-except`. Este capítulo pretende, por un lado, darte una +pincelada de cómo se escribe en python y, por otro, servir como manual de +consulta posterior. + +Pero, por hacer la labor de resumen, has aprendido el orden de ejecución de las +órdenes y como alterarlo con bucles y condicionales. Para ello, has tenido que +aprender lo que es un *bloque* de código, una pieza fundamental para entender +la sintaxis. Tras ver varios ejemplos de bucles y condicionales, te has +sumergido en la verdad y la mentira mediante los valores *truthy* y *falsey*, +para después avanzar a las *list comprehensions*, cuyo nombre contiene *list* +pero valen para cualquier dato complejo. + +Has cambiado un poco de tema después, saltando a las excepciones, que no has +podido ver en detalle por no ser, aún, un experto en programación orientada a +objetos. Pero tranquilo, pronto lo serás. + +Una pincelada sobre funciones ha sido suficiente para que no les tengas miedo +nunca más y que podamos atacar el siguiente capítulo con energía, ya que ahora +toca jugar con funciones hasta entenderlas por completo. + +Las sentencias útiles que hemos recopilado al final permiten juguetear con +todas las estructuras que hemos definido en el capítulo de modo que puedas +usarlas a tu antojo de forma cómoda. Algunas de ellas como `continue` y `break` +no son realmente necesarias, puede programarse evitándolas y, de hecho, en +algunos lugares enseñan a no usarlas, como si de una buena práctica se tratara, +cambiando las condiciones de los bucles para que hagan esta labor. En este +documento se muestran porque, en primer lugar, si lees código escrito por otras +personas las encontrarás y tendrás que entender qué hacen y, en segundo, porque +son sentencias que simplifican el código haciéndolo más legible o más sencillo +por muy impuras que a algunos programadores les puedan parecer. diff --git a/es/04_funciones.md b/es/04_funciones.md new file mode 100644 index 0000000..618ccd6 --- /dev/null +++ b/es/04_funciones.md @@ -0,0 +1,654 @@ +# Funciones + +El objetivo de este capítulo es que te familiarices con el uso de las +funciones. Parece sencillo pero es una tarea un tanto complicada porque, visto +como nos gusta hacer las cosas, tenemos una gran cantidad de complejidad que +abordar. + +Antes de entrar, vamos a definir una función y a usarla un par de veces: + +``` python +def inc(a): + b = a + 1 + return b +``` + +Si la llamamos: + +``` python +>>> inc(1) +2 +>>> inc(10) +11 +``` + +Cuidado con las declaraciones internas en las funciones. Si preguntamos por +`b`: + +``` python +>>> b +Traceback (most recent call last): + File "", line 1, in +NameError: name 'b' is not defined +``` + +Parece que no conoce el nombre `b`. Esto es un tema relacionado con el *scope*. + +## Scope + +Anteriormente se ha dicho que python es un lenguaje de programación con gestión +automática de la memoria. Esto significa que él mismo es capaz de saber cuando +necesita pedir más memoria al sistema operativo y cuando quiere liberarla. +El *scope* es un resultado este sistema. Para que python pueda liberar +memoria, necesita de un proceso conocido como *garbage collector* (recolector +de basura), que se encarga de buscar cuando las referencias ya no van a poder +usarse más para pedir una liberación de esa memoria. Por tanto, las referencias +tienen un tiempo de vida, desde que se crean hasta que el recolector de basura +las elimina. Ese tiempo de vida se conoce como *scope* y, más que en tiempo, se +trata en términos de espacio en el programa. + +El recolector de basura tiene unas normas muy estrictas y conociéndolas es +fácil saber en qué espacio se puede mover una referencia sin ser disuelta. + +Resumiendo mucho, las referencias que crees se mantienen vivas hasta que la +función termine. Como en el caso de arriba la función en la que se había creado +`b` había terminado, `b` había sido limpiada por el recolector de basura. `b` +era una referencia *local*, asociada a la función `inc`. + +Puede haber referencias declaradas fuera de cualquier función, que se llaman +*globales*. Éstas se mantienen accesibles desde cualquier punto del programa, y +se mantienen vivas hasta que éste se cierre. Considera que el propio programa +es una función gigante que engloba todo. + +Python define que cualquier declaración está disponible +en bloques internos, pero no al revés. El siguiente ejemplo lo muestra: + +``` python +c = 100 +def funcion(): + a = 1 + # Se conoce c aquí dentro +# Aquí fuera no se conoce a +``` + +El *scope* es peculiar en algunos casos que veremos ahora, pero mientras tengas +claro que se extiende hacia dentro y no hacia fuera, todo irá bien. + +## First-class citizens + +Antes de seguir jugando con el *scope*, necesitas saber que las funciones en +python son lo que se conoce como *first-class citizens* (ciudadanos de primera +clase). Esto significa que pueden hacer lo mismo que cualquier otro valor. + +Las funciones son un valor más del sistema, como puede ser un string, y su +nombre no es más que una referencia a ellas. + +Por esto mismo, pueden ser enviadas como argumento de entrada a otras +funciones, devueltas con sentencias `return` o incluso ser declaradas dentro de +otras funciones. + +Por ejemplo: + +``` python +>>> def filtra_lista(list): +... def mayor_que_4(a): +... return a > 4 +... return list( filter(mayor_que_4, lista) ) +... +>>> filtra_lista( [1,2,3,4,5,6,7] ) +[5, 6, 7] +``` + +En este ejemplo, haciendo uso de la función `filter` (usa la ayuda para ver lo +que hace), filtramos todos los elementos mayores que `4` de la lista. Pero para +ello hemos creado una función que sirve para compararlos y se la hemos +entregado a la función `filter`. + +Este ejemplo no tiene más interés que intentar enseñarte que puedes crear +funciones como cualquier otro valor y asignarles un nombre, para después +pasarlas como argumento de entrada a otra función. + +## Lambdas + +Las funciones *lambda*[^lambda] o funciones anónimas son una forma sencilla de +declarar funciones simples sin tener que escribir tanto. La documentación +oficial de python las define como funciones para vagos. + +La sintaxis de una función lambda te la enseño con un ejemplo: + +``` python +>>> lambda x,y: x + y + at 0x7f035b879950> +>>> (lambda x,y: x + y)(1,2) +3 +``` + +En el ejemplo primero se muestra la declaración de una función y después +colocando los paréntesis de precedencia y después de llamada a función se +construye una función a la izquierda y se ejecuta con los valores `1` y `2`. + +Es fácil de entender la sintaxis de la función lambda, básicamente es una +función reducida de sólo una sentencia con un `return` implícito. + +El ejemplo de la función `filtra_lista` puede reducirse mucho usando una +función lambda: + +``` python +>>> def filtra_lista( lista ): +... return list( filter(lambda x: x > 4, lista) ) +... +>>> filtra_lista( [1,2,3,4,5,6,7] ) +[5, 6, 7] +``` + +No necesitábamos una función con nombre en este caso, porque sólo iba a +utilizarse esta vez, así que resumimos y reducimos tecleos. + +De todos modos, podemos asignarlas a una referencia para poder repetir su uso: + +``` python +>>> f = lambda x: x + 1 +>>> f(1) +2 +>>> f(10) +11 +>>> f + at 0x7f02184febf8> +``` + +Las funciones lambda se usan un montón como *closure*, un concepto donde el +*scope* se trabaja más allá de lo que hemos visto. Sigamos visitando el +*scope*, para entender sus usos más en detalle. + +[^lambda]: Toman su nombre del Lambda + Calculus: + + +## Scope avanzado + +Cada vez que se crea una función, python crea un nuevo contexto para ella. +Puedes entender el concepto de contexto como una tabla donde se van guardando +las referencias que se declaran en la función. Cuando la función termina, su +contexto asociado se elimina, y el recolector de basura se encarga de liberar +la memoria de sus variables, tal y como vimos anteriormente. + +Lo que ocurre es que estos contextos son jerárquicos, por lo que, al crear una +función, el padre del contexto que se crea es el contexto de la función madre. +Python utiliza esto como método para encontrar las referencias. Si una +referencia no se encuentra en el contexto actual, python la buscará en el +contexto padre y así sucesivamente hasta encontrarla o lanzar un error diciendo +que no la conoce. Esto explica por qué las variables declaradas en la función +madre pueden encontrarse y accederse y no al revés. + +Aunque hemos explicado el *scope* como un concepto asociado a las funciones, la +realidad es que hay varias estructuras que crean nuevos contextos en python. El +comportamiento sería el mismo del que se ha hablado anteriormente, las +referencias que se creen en ellos no se verán en el *scope* de nivel superior, +pero sí al revés. Los casos son los siguientes: + +- Los módulos. Ver capítulo correspondiente +- Las clases. Ver capítulo de Programación Orientada a Objetos. +- Las funciones, incluidas las funciones anónimas o lambda. +- Las expresiones generadoras[^generator-expression], que normalmente se + encuentran en las *list-comprehension* que ya se han tratado en el capítulo + previo. + +[^generator-expression]: + + +### Scope léxico, Closures + +Hemos dicho que las funciones pueden declararse dentro de funciones, pero no +hemos hablado de qué ocurre con el *scope* cuando la función declarada se +devuelve y tiene una vida más larga que la función en la que se declaró. El +siguiente ejemplo te pone en contexto: + +``` python +def create_incrementer_function(increment): + def incrementer (val): + # Recuerda que esta función puede ver el valor `increment` por + # por haber nacido en un contexto superior. + return val + increment + return incrementer + +increment10 = create_incrementer_function(10) +increment10(10) # Returns 20 +increment1 = create_incrementer_function(1) +increment1(10) # Returns 11 +``` +En este ejemplo hemos creado una función que construye funciones que sirven +para incrementar valores. + +Las funciones devueltas viven durante más tiempo que la función que las +albergaba por lo que saber qué pasa con la variable `increment` es difícil a +simple vista. + +Python no destruirá ninguna variable que todavía pueda ser accedida, si lo +hiciera, las funciones devueltas no funcionarían porque no podrían incrementar +el valor. Habrían olvidado con qué valor debían incrementarlo. + +Para que esto pueda funcionar, las funciones guardan el contexto del momento de +su creación, así que la función `incrementer` recuerda la primera vez que fue +construida en un contexto en el que `increment` valía `10` y la nueva +`incrementer` creada en la segunda ejecución de `create_incrementer_function` +recuerda que cuando se creó `increment` tomó el valor `1`. Ambas funciones son +independientes, aunque se llamen de la misma forma en su concepción, no se +pisaron la una a la otra, porque pertenecían a contextos distintos ya que la +función que las creaba terminó y luego volvió a iniciarse. + +Este funcionamiento donde el comportamiento de las funciones depende del lugar +donde se crearon y no del contexto donde se ejecutan se conoce como *scope +léxico*. + +Las *closures* son una forma de implementar el *scope léxico* en un lenguaje +cuyas funciones sean *first-class citizens*, como es el caso de python, y su +funcionamiento se basa en la construcción de los contextos y su asociación a +una función capaz de recordarlos aunque la función madre haya terminado. + +Python analiza cada función y revisa qué referencias del contexto superior +deben mantenerse en la función. Si encuentra alguna, las asocia a la propia +función creando así lo que se conoce como *closure*, una función que recuerda +una parte del contexto. No todas las funciones necesitan del contexto previo +así que sólo se crean *closures* en función de lo necesario. + +Puedes comprobar si una función es una *closure* analizando su campo +`__closure__`. Si no está vacío (valor `None`), significará que la función es +una *closure* como la que ves a continuación. Una *closure* que recuerda un +*int* del contexto padre: + +``` python +>>> f.__closure__ +(,) +``` + +Lo que estás viendo lo entenderás mejor cuando llegues al apartado de +programación orientada a objetos. Pero, para empezar, ves que contiene una +tupla con una `cell` de tipo *integer*. + +A nivel práctico, las *closures* son útiles para muchas labores que iremos +desgranando de forma accidental. Si tienes claro el concepto te darás cuenta +dónde aparecen en los futuros ejemplos. + +### `global` y `nonlocal` + +Hemos hablado de qué sentencias crean nuevos contextos, pero no hemos hablado +de qué pasa si esos nuevos contextos crean referencias cuyo nombre es idéntico +al de las referencias que aparecen en contextos superiores. + +Partiendo de lo que se acaba de explicar, y antes de adentrarnos en ejemplos, +si se crea una función (o cualquiera de las otras estructuras) python creará un +contexto para ella. Una vez creado, al crear una variable en este nuevo +contexto, python añadirá una nueva entrada en su tabla hija con el nombre de la +variable. Al intentar consultarla, python encontrará que en su tabla hija +existe la variable y tomará el valor con el que la declaramos. Cuando la +función termine, la tabla de contexto asociada a la función será eliminada. +Esto siempre es así, independientemente del nombre de referencia que hayamos +seleccionado. Por tanto, si el nombre ya existía en alguno de los contextos +padre, lo ocultaremos, haciendo que dentro de esta función se encuentre el +nombre recién declarado y no se llegue a buscar más allá. Cuando la función +termine, como el contexto asociado a ésta no está en la zona de búsqueda de la +función madre, en la función madre el valor seguirá siendo el que era. + +Ilustrándolo en un ejemplo: + +``` python +>>> a = 1 +>>> def f(): +... a = 2 +... print(a) +... +>>> f() +2 +>>> a +1 +``` + +Aunque el nombre de la referencia declarada en el interior sea el mismo que el +de una referencia externa su declaración no afecta, lógicamente, al exterior +ya que ocurre en un contexto independiente. + +Para afectar a la referencia global, python dispone de la sentencia `global`. +La sentencia `global` afecta al bloque de código actual, indicando que los +identificadores listados deben interpretarse como globales. De esta manera, si +se reasigna una referencia dentro de la función, no será el contexto propio el +que se altere, sino el contexto global, el padre de todos los contextos. + +``` python +>>> a = 1 +>>> def f(): +... global a +... a = 2 +... print(a) +... +>>> f() +2 +>>> a +2 +``` + +> NOTA: Te recomiendo, de todas formas, que nunca edites valores globales desde +> el cuerpo de funciones. Es más elegante y comprensible si los efectos de las +> funciones sólo se aprecian en los argumentos de entrada y salida. + +Para más detalles sobre limitaciones y excepciones, puedes buscar en la ayuda +ejecutando `help("global")`. + +El caso de `nonlocal` es similar, sin embargo, está diseñado para trabajar en +contextos anidados. Es decir, en lugar de saltar a acceder a una variable +global, `nonlocal` la busca en cualquier contexto que no sea el actual. +`nonlocal` comienza a buscar las referencias en el contexto padre y va saltando +hacia arriba en la jerarquía en busca de la referencia. Para saber más: +`help("nonlocal")`. + +La diferencia principal entre ambas es que `global` puede crear nuevas +referencias, ya que se sabe a qué contexto debe afectar: al global. Sin +embargo, `nonlocal` necesita que la referencia a la que se pretende acceder +esté creada, ya que no es posible saber a qué contexto se pretende acceder. + +Las sentencias `global` y `nonlocal` son tramposas, ya que son capaces de +alterar el comportamiento del *scope léxico* y convertirlo en *scope dinámico* +en casos extraños. El *scope dinámico* es el caso opuesto al léxico, en el que +las funciones acceden a valores definidos en el contexto donde se ejecutan, no +donde se crean. + +## Argumentos de entrada y llamadas + +Los argumentos de entrada se definen en la declaración de la función y se ha +dado por hecho que es evidente que se separan por comas (`,`) y que, a la hora +de llamar a la función, deben introducirse en el orden en el que se han +declarado. Por mucho que esto sea cierto, requiere de una explicación más +profunda. + +### Callable + +En python las funciones son un tipo de *callable*, «cosa que puede ser llamada» +en inglés. Esto significa, de algún modo que hay otras cosas que pueden ser +llamadas que no sean funciones. Y así es. + +Para python cualquier valor que soporte la aplicación de los paréntesis se +considera «llamable». En el apartado de programación orientada a objetos +entenderás esto en detalle. De momento, piensa que, igual que pasa al acceder a +los campos de una colección usando los corchetes, siempre que python se +encuentre unos paréntesis después de un valor tratará de ejecutar el valor. Así +que los paréntesis no son una acción que únicamente pueda aplicarse en nombres +de función[^lambdas-ejemplo] y python no lanzará un fallo de sintaxis cuando +los usemos fuera de lugar, si no que será un fallo de tiempo de ejecución al +darse cuenta de lo que se intenta ejecutar no es ejecutable. + +``` python +>>> 1() +Traceback (most recent call last): + File "", line 1, in +TypeError: 'int' object is not callable +``` + +[^lambdas-ejemplo]: Aunque en realidad esto ya lo has visto en los ejemplos de + las funciones lambda. + +#### Caso de estudio: Switch Case + +Si quieres ver un ejemplo avanzado de esto, te propongo la creación de la +estructura *switch-case* [^switch-case], que puede encontrarse en otros +lenguajes, pero que en lugar de usar una estructura basada en un *if* con +múltiples *elif* uses un diccionario de funciones. + +Las funciones son valores, por lo que pueden ocupar un diccionario como +cualquier otro valor. Construyendo un diccionario en cuyas claves se encuentran +los casos del *switch-case* y en cuyos valores se encuentran sus funciones +asociadas se puede crear una sentencia con el mismo comportamiento. + +En el siguiente ejemplo se plantea una aplicación por comandos. Captura el +tecleo del usuario y ejecuta la función asociada al comando. Las funciones no +están escritas, pero puedes completarlas y analizar su comportamiento. Las +palabras que no entiendas puedes consultarlas en la ayuda. + +``` python +def borrar(*args): + pass +def crear(*args): + pass +def renombrar(*args): + pass + +casos = { + "borrar": borrar, + "crear": crear, + "renombrar": renombrar +} + +comando = input("introduce el comando> ") + +try: + casos[comando]() +except KeyError: + print("comando desconocido") + +``` + +[^switch-case]: + +### Positional vs Keyword Arguments + +Las funciones tienen dos tipos de argumentos de entrada, aunque sólo hayamos +mostrado uno de ellos de momento. + +El que ya conoces se denomina *positional argument* y se refiere a que son +argumentos que se definen en función de su posición. Los argumentos +posicionales deben ser situados siempre en el mismo orden, si no, los +resultados de la función serán distintos. Las referencias `source` y `target` +toman el primer argumento y el segundo respectivamente. Darles la vuelta +resulta en el resultado opuesto al que se pretendía. + +``` python +def move_file ( source, target ): + "Mueve archivo de `source` a `target" + pass + +move_file("file.txt", "/home/guido/doc.txt") + # "file.txt" -> "/home/guido/doc.txt" +move_file("/home/guido/doc.txt", "file.txt") + # "/home/guido/doc.txt"-> "file.txt" +``` + +Los *keyword argument* o argumentos con nombre, por otro lado, se comportan +como un diccionario. Su orden no importa pero es necesario marcarlos con su +respectiva clave. Además, son opcionales porque en el momento de la declaración +de la función python te obliga a que les asocies un valor por defecto +(*default*). En el siguiente ejemplo se convierte la función a una basada en +argumentos con nombre. No se han utilizado valores por defecto especiales, pero +pueden usarse otros. + +``` python +def move_file( source=None, target=None): + "Mueve archivo de `source` a `target" + pass + +move_file(source="file.txt", target="/home/guido/doc.txt") + # "file.txt" -> "/home/guido/doc.txt" +move_file(target="/home/guido/doc.txt", source="file.txt") + # "file.txt" -> "/home/guido/doc.txt" +``` + +> NOTA: Si quieres que sean obligatorios, siempre puedes lanzar una excepción. + +Para funciones que acepten ambos tipos de argumento, es obligatorio declarar e +introducir todos los argumentos posicionales primero. Es lógico, porque son +los que requieren de una posición. + +También es posible declarar funciones que acepten cualquier cantidad de +argumentos de un tipo u otro. Ésta es la sintaxis: + +``` python +def argument_catcher( *args, **kwargs ) + "Función ejecutable con cualquier número de argumentos de entrada, tanto + posicionales como con nombre." + print( args ) + print( kwargs ) +``` + +Los nombres `args` y `kwargs` son convenciones que casi todos los programadores +de python utilizan, pero puedes seleccionar los que quieras. Lo importante es +usar `*` para los argumentos posicionales y `**` para los argumentos con +nombre. + +Prueba a ejecutar la función del ejemplo, verás que los argumentos posicionales +se capturan en una tupla y los argumentos con nombre en un diccionario. + +Este tipo de funciones multiargumento se utilizan mucho en los *decorators*, +caso que estudiaremos al final de este capítulo. + +#### Peligro: Mutable Defaults + +Existe un caso en el que tienes que tener mucho cuidado. Los valores por +defecto en los argumentos con nombre se memorizan de una ejecución de la +función a otra. En caso de que sean valores inmutables no tendrás problemas, +porque su valor nunca cambiará, pero si almacenas en ellos valores mutables y +los modificas, la próxima vez que ejecutes la función los valores por defecto +habrán cambiado. + +La razón por la que los valores por defecto se recuerdan es que esos valores se +construyen en la creación de la función, no en su llamada. Lógicamente, puesto +que es en la sentencia `def` donde aparecen. + +``` python +>>> def warning(default=[]): +... default.append(1) +... return default +... +>>> warning() +[1] +>>> warning() +[1, 1] +>>> warning() +[1, 1, 1] +>>> warning() +[1, 1, 1, 1] +``` + +## Decorators + +Los *decorators* son un concepto que, a pesar de ser bastante concreto, nos +permite descubrir todo el potencial de lo que se acaba de tratar en este +apartado. Sirven para dotar a las funciones de características adicionales. + +Por ejemplo, éste es un decorador que permite crear funciones que se ejecutan +en un *thread* independiente. Tiene sentido para realizar acciones de las que +se quiere que se ejecuten por su cuenta sin ralentizar el hilo principal del +programa, como el envío de un email desde un servidor web. + +``` python +import threading + +def run_in_thread(fn): + def run(*args, **kwargs): + t = threading.Thread(target=fn, args=args, kwargs=kwargs) + t.start() + return t + return run + +@run_in_thread +def send_mail(): + """ + Envía un email a un usuario, sin esperar confirmación. + """ + pass +``` + +Hay muchos detalles que te habrán llamado la atención del ejemplo, el uso de +`@run_in_thread` probablemente sea uno de ellos. Éste es, sin embargo, el +detalle menos importante ya que únicamente se trata de un poco de *syntactic +sugar*. + +> NOTA: el *syntactic sugar* son simplificaciones sintácticas que el lenguaje +> define para acortar expresiones muy utilizadas. El ejemplo clásico de +> *syntactic sugar* es: +> `a += b` +> Que es equivalente a: +> `a = a + b` + +Los *decorators* pueden entenderse como un envoltorio para una función. No son +más que una función que devuelve otra. En el caso del decorador del ejemplo, +el *decorator* `run_in_thread` es función que recibe otra función como +argumento de entrada y devuelve la función `run`. Este decorador, al aplicarlo +a una función con `@run_in_thread` está haciendo lo siguiente: + +``` python +send_mail = run_in_thread(send_mail) +``` + +> NOTA: `@decorator` es *syntactic sugar* de `fn = decorator(fn)`. Simplemente, +> es más corto y más bonito. + +Por lo que la función `send_mail` ya no es lo que creíamos, sino la función +`run`. En el ejemplo, la función `run` llama a la función `fn` de la función +madre (`run` es una *closure*), que resulta ser `send_mail`, a modo de thread +independiente. + +Como puedes apreciar, el hecho de capturar todos los posibles argumentos de +entrada en la función `run` permite a `run_in_thread` decorar cualquier +función, sabiendo que funcionará. + +El principal problema que los decoradores generan es que la función que hemos +decorado ya no es la que parecía ser, así que su *docstring*, sus argumentos de +entrada, etc. ya no pueden comprobarse desde la REPL usando la ayuda, ya que la +ayuda buscaría la ayuda de la función devuelta por el decorador (`run` en el +ejemplo). Usando `@functools.wraps`[^functools] podemos resolver este +problema. + +[^functools]: Puedes leer por qué y cómo en la documentación oficial de + python: + + +La realidad es que los *decorators* son una forma muy elegante de añadir +funcionalidades a las funciones sin complicar demasiado el código. Permiten +añadir capacidad de depuración, *profiling* y todo tipo de funcionalidades que +se te ocurran. + +Este apartado se deja varias cosas en el tintero, como los decoradores con +parámetros de entrada, pero no pretende ser una referencia de cómo se usan, +sino una introducción a un concepto útil que resume perfectamente lo tratado +durante todo el capítulo. + +Te animo, como ejercicio, a que analices el decorador `@lru_cache` del módulo +`functools` y comprendas su interés y su funcionamiento. Para leerlo en la +ayuda debes importar el módulo `functools` primero. Como aún no sabes hacerlo, +aquí tienes la receta: + +``` python +>>> import functools +>>> help(functools.lru_cache) +``` + + +## Lo que has aprendido + +Este capítulo puede que sea el más complejo de todos los que te has encontrado +y te encontrarás. En él has aprendido a declarar y a usar funciones, cosa +sencilla, y todos los conceptos importantes relacionados con ellas. Un +conocimiento que es útil en python, pero que puede ser extendido a casi +cualquier lenguaje. + +Tras un sencillo acercamiento al *scope*, has comprendido que las funciones en +python son sólo un valor más, como puede ser un `int`, y que pueden declararse +en cualquier lugar, lo que te abre la puerta a querer declarar funciones +sencillas sin nombre, que se conocen como funciones *lambda*. + +Una vez has aclarado que las funciones son ciudadanos de primera clase +(*first-class citizens*) ya estabas preparado para afrontar la realidad del +*scope* donde has tratado los contextos y cómo funcionan definiendo el concepto +del *scope léxico* que, colateralmente, te ha enseñado lo que es una *closure*, +un método para implementarlo. Pero también has tenido ocasión de aprender que +en python es posible crear casos de *scope dinámico* mediante las sentencias +`global` y `nonlocal`, que pueden ser útiles, pero es mejor no abusar de ellas. + +Pero no había quedado claro en su momento cómo funcionaban los argumentos de +entrada y las llamadas a las funciones, así que has tenido ocasión de ver por +primera vez lo que es un *callable* en python, aunque se te ha prometido +analizarlo en el futuro. Lo que sí que has tenido ocasión de tratar son los +argumentos *positional* y *keyword*, y cómo se utilizan en todas sus posibles +formas. + +Finalmente, para agrupar todo esto en un único concepto, se te han mostrado los +*decorators*, aunque de forma muy general, con el fin de que vieras que todo lo +que se ha tratado en este capítulo aparece en conceptos avanzados y es +necesario entenderlo si quieren llegar a usarse de forma eficiente. diff --git a/es/05_oop.md b/es/05_oop.md new file mode 100644 index 0000000..6fdb9ce --- /dev/null +++ b/es/05_oop.md @@ -0,0 +1,1093 @@ +# Programación Orientada a Objetos + +La *programación orientada a objetos* u *object oriented programming* (OOP) es +un paradigma de programación que envuelve python de pies a cabeza. A pesar de +que python se define como un lenguaje de programación multiparadigma, la +programación orientada a objetos es el paradigma principal de éste. A pesar de +que varias de las características que tratamos en el apartado anterior se +corresponden más con un lenguaje de programación funcional, en python **todo** +(o casi todo) es una clase. + +Python usa una programación orientada a objetos basada en clases[^class], a +diferencia de otros lenguajes como JavaScript, donde la orientación a objetos +está basada en prototipos[^proto]. No es el objetivo de este documento el de +contarte cuales son las diferencias entre ambas, pero es interesante que sepas +de su existencia, ya que es una de las pocas diferencias que existen entre +estos dos lenguajes de amplio uso en la actualidad. + +[^class]: +[^proto]: + +## Programación basada en clases + +Tras haber hecho una afirmación tan categórica como que en python todo son +clases, es nuestra obligación entrar a definir lo que son y qué implica la +programación basada en clases. + +Los objetos, *objects*, son entidades que encapsulan un estado, un +comportamiento y una identidad capaz de separarlos de otras entidades. Una +clase, *class*, es la definición de estos objetos. + +Saliendo de la definición filosófica y trayéndola a un nivel de andar por casa, +puedes aclararte sabiendo que las clases son la definición enciclopédica de +algo, mientras que los objetos son el propio objeto, persona o animal descrito. + +Llevándolo al ejemplo de un perro, la clase es la definición de qué es un perro +y los objetos son los distintos perros que te puedes encontrar en el mundo. La +definición de perro indica qué características ha de tener un ente para ser un +perro, como ser un animal, concretamente doméstico, qué anatomía debe tener, +cómo debe comportarse, etc. Mientras que el propio perro es uno de los casos de +esa definición. + +Cada perro tiene una **identidad propia** y es independiente de los otros, +tiene un **comportamiento** concreto (corre, salta, ladra...) y tiene un +**estado** (está despierto o dormido, tiene una edad determinada...). + +La diferencia entre una clase y un objeto tiene lógica si lo piensas desde la +perspectiva de que python no tiene ni idea de lo que es un perro y tú tienes +que explicárselo. Una vez lo haces, declarando tu clase, puedes crear +diferentes perros y ponerlos a jugar. Lo bonito de programar es que tu programa +es tu mundo y tú decides lo que es para ti (o para tu programa) un perro. + +A nivel práctico, los objetos son grupos de datos (el *estado*) y funciones (la +*funcionalidad*). Estas funciones son capaces de alterar los datos del propio +objeto y no de otro (se intuye el concepto de *identidad*). Analizándolo desde +el conocimiento que ya tienes, es lógico pensar que un objeto es, por tanto, +una combinación de valores y funciones accesible a modo de elemento único. +Exactamente de eso se trata. + +Existe una terminología técnica, eso sí, para referirse a esos valores y a esas +funciones. Normalmente los valores se conocen como *propiedades* del objeto y +las funciones se conocen como *métodos*. Así que siempre que hagamos referencia +a cualquiera de estas dos palabras clave debes recordar que hacen referencia a +la programación orientada a objetos. + +### Fundamento teórico + +La programación basada en clases se basa en tres conceptos fundamentales que +repasaremos aquí de forma rápida para razonar el interés de la programación +orientada a objetos sobre otros paradigmas. + +La **encapsulación**[^encapsulation] trata de crear datos con sus métodos +propios para alterarlos de modo que restrinjan el acceso directo al contenido +de estos datos con el fin de asegurar una coherencia o robustez interna. Puedes +entender esto como una forma de esconder información o como mi profesor de +programación II en la universidad solía decir: «Las patatas se pelan en la +cocina del restaurante, no en el comedor». La utilidad de la encapsulación es +la de aislar secciones del programa para tener total control sobre su +contenido gracias a tener total control de la vía de acceso a estos datos. A +nivel práctico este concepto puede usarse para, por ejemplo, obligar a que un +objeto sólo pueda ser alterado en incrementos controlados en lugar de poder +pisarse con un valor arbitrario. + +La **herencia**[^inheritance] es un truco para reutilizar código de forma +agresiva que, casualmente, sirve como una buena forma de razonar. Aporta la +posibilidad de crear nuevas *clases* a partir de clases ya existentes. +Volviendo a la simplificación anterior, si una clase es una definición +enciclopédica de un concepto, como un perro, puede estar basada en otra +descripción para evitar contar todo lo relacionado con ella. En el caso del +perro, el perro es un animal. Animal podría ser otra clase definida previamente +de la que el perro heredara y recibiera gran parte de su descripción genérica +para sólo cubrir puntos que necesite especificar como el tamaño, la forma, el +tipo de animal, el comportamiento concreto, etc. Existe la posibilidad de hacer +herencias múltiples también ya que algunos conceptos pueden describirse en dos +superclases distintas: un perro es un animal (vive, muere, se alimenta, se +reproduce) y también es terrestre (camina sobre una superficie, etc). Ambos +conceptos son independientes: los coches también son terrestres pero no son +animales y los peces también son animales pero no terrestres. + +Y, finalmente, el **polimorfismo**[^polymorphism]. La propia etimología de la +palabra define con bastante precisión el concepto, pero aplicarlo a la +programación orientada a objetos no es tan evidente. Existen varios tipos de +polimorfismo pero el más sencillo es entender el *subtyping*[^subtyping]. Una +vez lo comprendas el resto será evidente. Si volvemos al ejemplo del perro, +para ciertos comportamientos, nos da igual que tratemos de perros, de peces o +de pájaros, todos son animales y todos los animales se comportan de la misma +forma. Es decir, todas las subclases señaladas comparten el comportamiento de +la superclase animal. Si esto es cierto, puede suponerse que en cualquier caso +en el que se espere un objeto de la clase animal es seguro usar una subclase de +ésta. + +Visto desde otra perspectiva, las subclases comparten comportamiento porque +reutilizan las funciones de la clase principal o las redefinen (*herencia*), +pero podemos asegurar que todas las subclases tienen un conjunto de funciones +con la misma estructura, independientemente de lo que hagan, que aseguran que +siempre van a ser compatibles. El nombre de esta cualidad viene a que un perro +puede tomar la forma de un animal. + +Los otros tipos de polimorfismo explotan el mismo comportamiento de diferentes +maneras, mientras que recuerdes que es posible programar de modo que el tipo de +los datos que trates sea indiferente o pueda variar es suficiente. Otro ejemplo +de esto son los operadores matemáticos, que son capaces de funcionar en +cualquier tipo de número (integer, float, complex, etc.) de la misma manera, ya +que todos son números, al fin y al cabo. + +Entender estos conceptos a nivel intuitivo, sin necesidad de entrar en los +detalles específicos de cada uno, es interesante para cualquier programador y +facilita de forma radical la comprensión de muchas de las decisiones de diseño +tomadas en python y en proyectos relacionados aunque también, por supuesto, de +otros lenguajes y herramientas. + +[^encapsulation]: https://en.wikipedia.org/wiki/Encapsulation_(computer_programming) +[^inheritance]: https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming) +[^polymorphism]: https://en.wikipedia.org/wiki/Polymorphism_(computer_science) +[^subtyping]: https://en.wikipedia.org/wiki/Subtyping + + +## Sintaxis + +En el siguiente ejemplo se muestra la sintaxis básica a la hora de crear una +clase y después instanciar dos nuevos objetos `bobby` y `beltza`. Los puntos +(`.`) se utilizan para indicar a quién pertenece el método o propiedad al que +se hace referencia (*identidad*). De este modo, no ocurrirá lo mismo cuando el +perro (`Dog`) `bobby` ladre (`bark`) que cuando lo haga el perro `beltza`. + +Los métodos describen la *funcionalidad* asociada a los perros en general, pero +además, la función `bark` los describe en particular, haciendo que cada perro +tome su nombre (`name`), una propiedad o dicho de otro modo, su *estado*. + +``` python +class Dog: + type = "canine" + def __init__(self, name): + self.name = name + def bark(self): + print("Woof! My name is " + self.name) + +bobby = Dog("Bobby") # New Dog called Bobby +beltza = Dog("Beltza") # New Dog called Beltza + +bobby.name # Bobby +beltza.name # Beltza + +bobby.type # canine +beltza.type # canine + +bobby.bark() # Prints "Woof! My name is Bobby" +beltza.bark() # Prints "Woof! My name is Beltza" +``` + +### Creación de objetos + +El ejemplo muestra cómo crear nuevos *objetos* de la clase `Dog`. Las llamadas +a `Dog("Bobby")` y `Dog("Beltza")` crean las diferentes instancias de la clase. + +Llamar a los nombres de clase como si de funciones se tratara crea una +instancia de éstas. Los argumentos de entrada de la llamada se envían como +argumentos de la función `__init__` declarada también en el propio ejemplo. +Entiende de momento que los argumentos posicionales se introducen a partir de +la segunda posición, dejando el argumento llamado `self` en el ejemplo para un +concepto que más adelante entenderás. + +En el ejemplo, por tanto, se introduce el nombre (`name`) de cada `Dog` en su +creación y la función `__init__` se encarga de asignárselo a la instancia +recién creada mediante una metodología que se explica más adelante en este +mismo capítulo. De momento no es necesario comentar en más profundidad estos +detalles, con lo que sabes es suficiente para entender el funcionamiento +general. + +Queda por aclarar, sin embargo, qué es la función `__init__` y por qué tiene un +nombre tan extraño y qué es `type = canine`, que lo trataremos en próximos +apartados de este capítulo. + +### Herencia + +Antes de entrar en los detalles propuestos en el apartado anterior, que tratan +conceptos algo más avanzados, es interesante ver cómo definir clases mediante +la herencia. Basta con introducir una lista de clases de las que heredar en la +definición de la clase, entre paréntesis, como si de argumentos de entrada de +una función se tratara, tal y como se muestra en la clase `Dog` del siguiente +ejemplo ejecutado en la REPL: + +``` python +>>> class Animal: +... def live(self): +... print("I'm living") +... +>>> class Terrestrial: +... def move(self): +... print("I'm moving on the surface") +... +>>> class Dog(Animal, Terrestrial): +... def bark(self): +... print("woof!") +... def move(self): +... print("I'm walking on the surface") +... +>>> bobby = Dog() +>>> bobby.bark() +woof! +>>> bobby.live() +"I'm living" +>>> bobby.move() +"I'm walking on the surface" +``` + +El ejemplo muestra un claro uso de la herencia. La clase `Dog` hereda +automáticamente las funciones asociadas a las superclases, pero es capaz de +definir las propias e incluso redefinir algunas. Independientemente de la +redefinición del método `move`, cualquier perro (`Dog`) va a ser capaz de +moverse por la superficie, porque la superclase `Terrestrial` ya le da los +métodos necesarios para hacerlo. Lo que ocurre es que cualquier subclase de +`Terrestrial` tiene la ocasión moverse (`move`) a su manera: en el caso del +perro, caminando. + +> NOTA: La herencia es interesante, pero tampoco debe caerse en la psicosis de +> añadir demasiadas superclases. En ocasiones las superclases son necesarias, +> sobre todo cuando aprovechar el polimorfismo facilita el trabajo, pero +> usarlas de forma agresiva genera código extremadamente complejo sin razón. + + +### Métodos de objeto o funciones de clase: `self` + +Los métodos reciben un parámetro de entrada llamado `self` que no se utiliza a +la hora de llamarlos: al hacer `bobby.bark()` no se introduce ningún argumento +de entrada a la función `bark`. + +Sin embargo, si no se añade el argumento de entrada a la definición del método +`bark` y se llama a `bobby.bark()` pasa lo siguiente: + +``` python +>>> class Dog: +... def bark(): +... pass +... +>>> bobby = Dog() +>>> bobby.bark() +Traceback (most recent call last): + File "", line 1, in +TypeError: bark() takes 0 positional arguments but 1 was given +``` + +Python dice que `bark` espera `0` argumentos posicionales pero se le ha +entregado `1`, que nosotros no hemos metido en la llamada, claro está. Así que +ha debido de ser él. + +Efectivamente, python introduce un argumento de entrada en los métodos, el +argumento de entrada que por convención se suele llamar `self`. Este parámetro +es el propio `bobby` en este caso. + +> NOTA: Por convención se le denomina `self`. Tú le puedes llamar como te +> apetezca pero, si pretendes que otros programadores te entiendan, mejor +> `self`. + +Para explicar por qué ocurre esto es necesario diferenciar bien entre clase y +objeto. Tal y como hemos hecho antes con las definiciones enciclopédicas +(*clase*) y los conceptos del mundo real que encajan en la definición +(*objeto*). Los objetos también se conocen como instancias, son piezas de +información independiente que han sido creadas a partir de la definición que la +clase aportaba. + +En python las clases tienen la posibilidad de tener funciones, que definen el +comportamiento de la clase y no el de los objetos que se crean desde ellas. +Ten en cuenta que las clases también deben procesarse y ocupan un espacio en la +memoria, igual que te ocurre a ti, puedes conocer un concepto y su +comportamiento y luego muchos casos que cumplan ese concepto y ambas cosas +son independientes. Esta posibilidad aporta mucha flexibilidad y permite +definir clases complejas. + +Ahora bien, para python las funciones de clase y los métodos (de los objetos, +si no no se llamarían métodos), se implementan de la misma manera. Para la +clase ambas cosas son lo mismo. Sin embargo, el comportamiento del operador +punto (`.`), que dice a quién pertenece la función o método, es diferente si el +valor de la izquierda es una clase o un objeto. Introduciendo en el segundo +caso el propio objeto como primer parámetro de entrada, el `self` del que +hablamos, para que la clase sepa qué objeto tiene que alterar. Este es el +mecanismo de la *identidad* del que antes hablamos y no llegamos a definir en +detalle. Cada objeto es único, y a través del `self` se accede a él. + +Es un truco interesante para no almacenar las funciones en cada uno de los +objetos como método. En lugar de eso, se mantienen en la definición de la clase +y cuando se llama al método, se busca de qué clase es el objeto y se llama a la +función de la clase con el objeto como argumento de entrada. + +Dicho de otra forma, `bobby.bark()` es equivalente a `Dog.bark( bobby )`. + +Ilustrado en un ejemplo más agresivo, puedes comprobar que en función de a +través de qué elemento se acceda a la función `bark` python la interpreta de +forma distinta. A veces como función (*function*) y otras veces como método +(*method*), en función de si se accede desde la clase o desde el objeto: + +``` python +>>> class Dog: +... def bark(self): +... pass +... +>>> type ( Dog.bark) + +>>> type ( bobby.bark ) + +``` + +> NOTA: También te habrás fijado, y si no lo has hecho es momento de hacerlo, +> que los nombres de las clases empiezan por mayúscula en los ejemplos (`Dog`) +> mientras que los objetos comienzan en minúscula (`bobby`). Se trata de otra +> convención ampliamente utilizada para saber diferenciar entre uno y otro de +> forma sencilla. Es evidente cuál es la clase y el objeto con los nombres que +> hemos tratado en los ejemplos, pero en otros casos puede no serlo y con este +> sencillo truco facilitas la lectura de tu código. Hay muchas ocasiones en las +> que esta convención se ignora, así que cuidado. +> Prueba a hacer `type(int)` en la terminal. + +### Variables de clase + +En el primer ejemplo del capítulo hemos postergado la explicación de `type = +canine` y ahora que ya manejas la mayor parte de la terminología y dominas la +diferencia entre una clase y una instancia de ésta (un *objeto*) es momento de +recogerla. A continuación se recupera la sección del ejemplo para facilitar la +consulta, fíjate en la línea 2. + +``` {.python .numberLines} +class Dog: + type = "canine" + def __init__(self, name): + self.name = name + def bark(self): + print("Woof! My name is " + self.name) +``` + +`type` es lo que se conoce como una *variable de clase* (*class variable*). + +> NOTA: En este documento se ha evitado de forma premeditada usar la palabra +> *variable* para referirse a los valores y sus referencias con la intención de +> marcar la diferencia entre ambos conceptos. En este apartado, sin embargo, a +> pesar de que se siga tratando de una referencia, se usa el nombre *class +> variable* porque es como se le llama en la documentación[^class_var] y así +> será más fácil que lo encuentres si en algún momento necesitas buscar +> información al respecto. De esto ya hemos discutido en el capítulo sobre +> datos, donde decimos que *todo es una referencia*. + +[^class_var]: + +Previamente hemos hablado de que los objetos pueden tener propiedades +asociadas, y cada objeto tendrá las suyas. Es decir, que cada instancia de la +clase puede tener sus propias propiedades independientes. El caso que tratamos +en este momento es el contrario, el `type` es un valor que comparten **todas** +las instancias de `Dog`. Cualquier cambio en esos valores los verán todos los +objetos de la clase, así que hay que ser cuidadoso. + +El acceso es idéntico al que ocurriría en un valor asociado al objeto, como en +el caso `name` del ejemplo, pero en este caso observas que en su declaración en +la clase no es necesario indicar `self`, ya no es necesario decir cuál es la +instancia concreta a la que se le asigna el valor: se le asigna a todas. + +A parte de poder acceder a través de los objetos de la clase, es posible +acceder directamente desde la clase a través de su nombre, como a la hora de +acceder a las funciones de clase: `Dog.type` resultaría en `"canine"`. + +> NOTA: Si en algún caso python viera que un objeto tiene propiedades y +> variables de clase definidas con el mismo nombre, cosa que no debería ocurrir +> a menudo, tendrán preferencia las propiedades. + +### Encapsulación explícita + +Es posible que te encuentres en alguna ocasión con métodos o propiedades, +*campos* en general, cuyo nombre comience por `_` o por `__`. Se trata de casos +en los que esas propiedades o métodos quieren ocultarse del exterior. + +El uso de `_` al inicio del nombre de un campo es una convención que avisa de +que este campo no debe accederse desde el exterior de la clase y su objetivo es +usarlo desde el interior de ésta. + +Esta convención se llevó al extremo en algún momento y se decidió crear un caso +en el que esta convención inicial tuviera cierta funcionalidad añadida para las +dobles barras bajas (`__`) que impidiera un acceso accidental a esos campos +conocido como *name mangling*. + +#### Campos privados: *name mangling* + +El *name mangling* es un truco que hace python para asegurarse de que no se +entra por accidente a las secciones que empiezan por `__`. Añade +`_nombredeclase` al inicio de los campos, transformando su nombre final y +dificultando el acceso por accidente. + +Ese acceso accidental no sólo es para que el programador no acceda, ya que, si +se esfuerza la suficiente, va a poder hacerlo de igual modo, si no para que el +propio python no acceda al campo que no corresponde. El hecho de añadir el +nombre de la clase al campo crea una brecha en la herencia, haciendo que los +campos no se hereden de la forma esperada. + +En una subclase en la que los campos de la clase madre han sido marcados con +`__`, la herencia hace que estos campos se hereden con el nombre cambiado que +contiene el nombre de la superclase. De este modo, es difícil para la subclase +pisar estos campos ya que tendría que definirlos manualmente con el nombre +cambiado. Crear nuevos campos con `__` no funcionaría, ya que, al haber +cambiado de clase, el nombre generado será distinto. + +Este mecanismo es un truco para crear *campos privados*, concepto bastante +común en otros lenguajes como Java o C++, que en python es inexistente. + +El concepto de los *campos privados* es interesante en la programación +orientada a objetos. Pensando en la *encapsulación*, es lógico que a veces las +clases definan métodos o propiedades que sólo los objetos creados a partir de +ellas conozcan y que los objetos creados de clases heredadas no. Este es el +método que python tiene para aportar esta funcionalidad. + +Es interesante añadir, por otro lado, que python es un lenguaje de programación +muy dinámico por lo que la propia definición de las clases, y muchas cosas más, +puede alterarse una vez creadas. Esto significa que el hecho de ocultar campos +no es más que un acuerdo tácito entre programadores porque, si quisieran, +podrían definir todo de nuevo. Trucos como este sirven para que el programador +sea consciente de que está haciendo cosas que se supone que no debería hacer. +Cuando programes en python, tómate esto como pistas que te indican cómo se +supone que deberías estar usando las clases. + +### Acceso a la superclase + +A pesar de la herencia, no siempre se desea eliminar por completo la +funcionalidad de un método o pisar una propiedad. A veces es interesante +simplemente añadir funcionalidad sobre un método o recordar algún valor +definido en la superclase. + +Python soporta la posibilidad de llamar a la superclase mediante la función +`super`, que permite el acceso a cualquier campo definido en la superclase. + +``` python +class Clase( SuperClase ): + def metodo(self, arg): + super().metodo(arg) # Llama a la definición de + # `metodo` de `SuperClase` +``` + +> NOTA: `super` busca la clase previa por preferencia, si usas herencias +> múltiples y pisas los campos puede complicarse. + + +## Interfaces estándar: Duck Typing + +Una de las razones principales para usar programación orientada a objetos es +que, si se eligen los métodos con precisión, pueden crearse estructuras de +datos que se comporten de similar forma pero que tengan cualidades diferentes. +Independientemente de cómo estén definidas sus clases, si dos objetos disponen +de los mismos métodos podrán ser sustituidos el uno por el otro en el programa +y seguirá funcionando aunque su funcionalidad cambie. + +Dicho de otra forma, dos objetos (o dos cosas, en general) podrán ser +intercambiados si disponen de la misma *interfaz*. *Interfaz*, de *inter*: +entre; y *faz*: cara, viene a significar algo así como «superficie de contacto» +y es la palabra que se usa principalmente para definir la frontera compartida +entre dos componentes o, centrándonos en el caso que nos ocupa, su conexión +funcional. + +Si recuerdas la *herencia* y la combinas con estos conceptos, puedes +interpretar que además de una metodología para reutilizar código es una forma +de crear nuevas definiciones que soporten la misma interfaz. + +En otros lenguajes de programación, Java, por ejemplo, existe el concepto +*interfaz* que serían una especie pequeñas clases que definen qué funciones +debe cumplir una clase para que cumpla la interfaz. A la hora de crear las +clases se les puede indicar qué interfaces implementan y el lenguaje se encarga +de asegurarse de que el programador ha hecho todo como debe. + +El dinamismo de python hace que esto sea mucho más flexible. Debido a que +python no hace casi comprobaciones antes de ejecutarse, necesita un método para +mucho más directo. Para python, *si anda como un pato, vuela como un pato y +nada como un pato: es un pato*. + +Python usa lo que en la terminología del lenguaje se conoce como +*protocolos*[^protocol] (*protocol*) para que los objetos creados por el +programador puedan comportarse como los que el propio sistema aporta. Por +ejemplo, que sea posible utilizarlos como iterable en un `for`, que el sistema +pueda cerrarlos de forma automática, buscar en ellos usando el operador `in`, +etc. Simplemente, el sistema define qué funciones se deben cumplir en cada uno +de esos casos y cuando se encuentre con ellos intentará llamarlas +automáticamente. Si el elemento no dispone de esas funciones lanzará una +excepción como la que lanza cuando intentamos acceder a un método que no existe +(que es básicamente lo que estamos haciendo en este caso). + +> TODO: En realidad no se llaman protocolos todos ellos. Se llama así sólo al +> *iterator protocol*. En realidad se llaman: [Special Method +> Names](https://docs.python.org/3/reference/datamodel.html#special-method-names) + +En general, python, con el fin de diferenciar claramente qué nombres elige el +programador y cuales han sido seleccionados por el lenguaje, suele utilizar una +convención para la nomenclatura: comienzan y terminan por: `__` + +A continuación se describen algunos de los protocolos más comunes, algunos ya +han aparecido a lo largo de los ejemplos del documento, otros las verás por +primera vez ahora. Existen muchos más, y todos están extremadamente bien +documentados. Si en algún momento necesitas crear algunos nuevos, la +documentación de python es una buena fuente donde empezar. + +Todos las protocolos se presentan con un nombre, en muchos casos inventado, +terminado en *-able*. Python utiliza también este tipo de nombres, como el ya +aparecido *llamable*, o *callable* en inglés, que se refiere a cualquier cosa +que puede ser llamada. Representar los nombres de esta manera sirve para +expresar el interés de los protocolos. Si en algún momento necesitas crear una +clase que defina un objeto en el que se puede buscar necesitas que sea un +*buscable*, es decir, que soporte el protocolo que define ese comportamiento. + +[^protocol]: **Protocolo**: 5. m. Inform. Conjunto de reglas que se establecen + en el proceso de comunicación entre dos sistemas. — RAE [Consultado + 01-12-2019]: + +### *Representable*: `__repr__` + +Este protocolo sirve para otorgar a python una forma de representar estos +objetos. Al ejecutar la función `print` o al exponer valores en la REPL +(recuerda que la P significa print), python trata de visualizarlos. + +La el método `__repr__` se ejecuta justo antes de imprimirse el objeto, de +forma automática. La función requiere que se devuelva un elemento de tipo +string, que será el que después se visualice. + +En el ejemplo a continuación se comienza con la clase `Dog` vacía y se +visualiza una de sus instancias. Posteriormente, se reasigna la función +`__repr__` de `Dog` con una función que devuelve un string. Al volver a mostrar +a `bobby` el resultado cambia. + +Como se ve en el ejemplo, es interesante tener una buena función de +representación si lo que se pretende es entender el contenido de los objetos. + +> NOTA: Python ya aporta una forma estándar de representar los objetos, si la +> función `__repr__` no se define simplemente se usará la forma estándar. + +``` python +>>> class Dog: +... pass +... +>>> bobby = Dog() +>>> bobby +<__main__.Dog object at 0x7fb7fba1b908> + +>>> Dog.__repr__ = lambda self: "Dog called: " + self.name +>>> bobby.name = "Bobby" +>>> bobby +Dog called: Bobby +>>> +``` + +### *Contable*: `__len__` + +En python se utiliza la función `len` para comprobar la longitud de cualquier +elemento contable. Por ejemplo: + +``` python +>>> len( (1,2,3) ) +3 +``` + +Las objetos que soporten esta función podrán contarse para conocer su longitud +mediante la función `len`. Python llamará al método `__len__` del objeto (que +se espera que devuelva un número entero) y ésta será su longitud. Siguiendo con +el ejemplo del protocolo anterior: + +``` python +>>> Dog.__len__ = lambda self: 12 # Siempre devuelve 12 +>>> len(bobby) +12 +``` + +Este protocolo permite crear elementos contables, en lugar de los típicos +diccionario, tupla y lista. Como por ejemplo los ya existentes `NamedTuple`, +`OrderedDict` y otros. Los protocolos para el *buscable* e *iterable* también +son muy interesantes para esta labor. + +### *Buscable*: `__contains__` + +El método `__contains__` debe devolver `True` o `False` y recibir un argumento +de entrada. Con esto el objeto será capaz de comprobarse con sentencias que +hagan uso del operador `in` (y `not in`). Las dos llamadas del ejemplo son +equivalentes. La segunda es lo que python realiza internamente al encontrarse +el operador `in` o el operador `not in`. + +``` python +>>> 1 in [1,2,3] +True +>>> [1,2,3].__contains__(1) +True +``` + +### *Iterable*: `__next__` e `__iter__` + +El protocolo iterable permite crear objetos con los que es posible iterar en +bucles `for` y otras estructuras. Por ejemplo, los archivos de texto en python +soportan este protocolo, por lo que pueden leerse línea a línea en un bucle +`for`. + +Igual que en el caso del protocolo `__len__`, que servía para habilitar la +llamada a la función `len`, `__iter__` y `__next__` sirven, respectivamente, +para habilitar las llamadas a `iter` y `next`. + +La función `iter` sirve para convertir el elemento a *iterable*, que es una +clase que soporte el funcionamiento de la función `next`. Y `next` sirve para +pasar al siguiente elemento de un iterable. Ejemplificado: + +``` python +>>> l = [1,2,3] +>>> next(l) +Traceback (most recent call last): + File "", line 1, in +TypeError: 'list' object is not an iterator +>>> it = iter(l) +>>> it + +>>> +>>> next(it) +1 +>>> next(it) +2 +>>> next(it) +3 +>>> next(it) +Traceback (most recent call last): + File "", line 1, in +StopIteration +``` + +La función `__next__` tiene un comportamiento muy sencillo. Si hay un próximo +elemento, lo devuelve. Si no lo hay lanza la excepción `StopIteration`, para +que la capa superior la capture. + +Fíjate que la lista por defecto no es un iterable y que se debe construir un +elemento iterable desde ella con `iter` para poder hacer `next`. Esto se debe a +que la función `iter` está pensada para restaurar la posición del cursor en el +primer elemento y poder volver a iniciar la iteración. + +Sorprendentemente, este es el procedimiento de cualquier `for` en python. El +`for` es una estructura creada sobre un `while` que construye iterables e +itera sobre ellos automáticamente. + +Este bucle `for`: + +``` python +for el in secuencia: + # hace algo con `el` +``` + +Realmente se implementa de la siguiente manera: + +``` python +# Construye un iterable desde la secuencia +iter_obj = iter(secuencia) + +# Bucle infinito que se rompe cuando `next` lanza una +# excepción de tipo `StopIteration` +while True: + try: + el = next(iter_obj) + # hace algo con `el` + except StopIteration: + break +``` + +Así que, si necesitas una clase con capacidad para iterarse sobre ella, puedes +crear un pequeño iterable que soporte el método `__next__` y devolver una +instancia nueva de éste en el método `__iter__`. + +### *Creable*: `__init__` + +El método `__init__` es uno de los más usados e interesantes de esta lista, esa +es la razón por la que ha aparecido en más de una ocasión durante este +capítulo. + +El método `__init__` es a quién se llama al crear nuevas instancias de una +clase y sirve para *ini*cializar las propiedades del recién creado objeto. + +Cuando se crean nuevos objetos, python construye su estructura en memoria, +pidiéndole al sistema operativo el espacio necesario. Una vez la tiene, envía +esa estructura vacía a la función `__init__` como primer argumento para que sea +ésta la encargada de rellenarla. + +Como se ha visto en algún ejemplo previo, el método `__init__` (es un método, +porque el objeto, aunque vacío, ya está creado) puede recibir argumentos de +entrada adicionales, que serán los que la llamada al nombre de la clase reciba, +a la hora de crear los nuevos objetos. Es muy habitual que el inicializador +reciba argumentos de entrada, sobre todo argumentos con nombre, para que el +programador que crea las instancias tenga la opción de inicializar los campos +que le interesen. + +Volviendo a un ejemplo previo: + +``` python +class Dog: + type = "canine" + def __init__(self, name): + self.name = name + def bark(self): + print("Woof! My name is " + self.name) + +bobby = Dog("Bobby") # Aquí se llama a __init__ +``` + +El nombre del perro, `"Bobby"` será recibido por `__init__` en el argumento +`name` e insertado al `self` mediante `self.name = name`. De este modo, esa +instancia de `Dog`, `bobby`, tomará el nombre `Bobby`. + +> NOTA: En muchas ocasiones, el método `__init__` inicializa a valores vacíos +> todas las posibles propiedades del objeto con el fin de que quien lea el +> código de la clase sea capaz de ver cuáles son los campos que se utilizan en +> un primer vistazo. Es una buena práctica listar todos los campos posibles en +> `__init__`, a pesar de que no se necesite inicializarlos aún, con el fin de +> facilitar la lectura. + +> NOTA: Quien tenga experiencia con C++ puede equivocarse pensando que +> `__init__` es un constructor. Tal y como se ha explicado anteriormente, al +> método `__init__` ya llega un objeto construido. El objetivo de `__init__` es +> inicializar. En python el constructor, que se encarga de crear las instancias +> de la clase, es la función `__new__`. + +> NOTA: Si creas una clase a partir de la herencia y sobreescribes su método +> `__init__` es posible que tengas que llamar al método `__init__` de la +> superclase para inicializar los campos asociados a la superclase. Recuerda +> que puedes acceder a la superclase usando `super`. + +### *Abrible* y *cerrable*: `__enter__` y `__exit__` + +Este protocolo permite que los objetos puedan ser abiertos y cerrados de forma +segura y con una sintaxis eficiente. Aunque no se van a listar en profundidad, +el objetivo de este punto es mostrar la sentencia `with` que se habilita +gracias a estos protocolos y mostrar cómo facilitan la apertura y cierre. + +El PEP 343[^pep343] muestra en detalle la implementación de la sentencia +`with`. Simplificándolo y resumiéndolo, `with` sirve para abrir elementos y +cerrarlos de forma automática. + +> NOTA: Los PEP (*Python Enhancement Proposals*) son propuestas de mejora para +> el lenguaje. Puedes consultar todos en la web de python. Son una fuente +> interesante de información y conocimiento del lenguaje y de programación en +> general. +> + +Pensando en, por ejemplo, la lectura de un archivo, se requieren varias etapas +para tratar con él, por ejemplo: + +``` python +f = open("file.txt") # apertura del fichero +f.read() # lectura +f.close() # cierre +``` + +Este método es un poco arcaico y peligroso. Si durante la lectura del fichero +ocurriera alguna excepción el fichero no se cerraría, ya que la excepción +bloquearía la ejecución del programa. Para evitar estos problemas, lo lógico +sería hacer una estructura `try-except` y añadir el cierre del fichero en un +`finally`. + +La sentencia `with` se encarga básicamente de hacer eso y facilita la escritura +de todo el proceso quedándose así: + +``` python +with f as open("file.txt"): # apertura + f.read() # en este cuerpo `f` está abierto + +# Al terminar el cuerpo, de forma normal o forzada, +# `f` se cierra. +``` + +Ahora bien, para que el fichero pueda ser abierto y cerrado automáticamente, +deberá tener implementados los métodos `__enter__` y `__exit__`. En el PEP 343 +se muestra la equivalencia entre la sentencia `with` y el uso de `__enter__`, +`__close__` y el `try-except`. + +[^pep343]: Puedes leer el contenido completo del PEP en: + + +### *Callable*: `__call__` + +Queda pendiente desde el capítulo sobre funciones, responder a lo que es un +*callable* o *llamable*. Una vez llegados a este punto, tiene una respuesta +fácil: un *llamable* es un objeto que soporta el protocolo correspondiente, +definido por el método `__call__`. + +Aunque pueda parecer sorprendente, las funciones en python también se llaman de +este modo, así que realmente son objetos que se llaman porque soportan este +protocolo. Es lógico, porque las funciones, recuerda el capítulo previo, pueden +guardar valores, como el contexto en el que se crean (*closure*). Las funciones +son meros *llamables* y como tales se comportan. + +Llevado más allá, los tipos básicos de python están definidos en clases +también, lógicamente, pero pueden ser llamados para hacer conversiones tal y +como vimos en el capítulo sobre datos. Simplemente, soportan el protocolo +*llamable*. + +``` python +>>> class Dog: +... def __call__(self): +... print("Dog called") +... +>>> dog = Dog() +>>> dog() +Dog called +``` + +Ten en cuenta que el método `__call__` puede recibir cualquier cantidad de +argumentos como ya hemos visto en apartados anteriores, pero el primero será el +propio objeto que está siendo llamado, el `self` que ya conocemos. + +Resumiendo, el método `__call__` describe cómo se comporta el objeto cuando se +le aplican las paréntesis. + +### *Subscriptable*: `__getitem__`, `__setitem__` y `__delitem__` + +Tal y como el método anterior describía cómo se aplican las paréntesis a un +objeto, el protocolo que se muestra en este apartado describe el comportamiento +del objeto cuando se le aplican los corchetes. Recordando el capítulo sobre +datos, los corchetes sirven para acceder a valores de las listas, tuplas, +diccionarios y sets, que resultan ser también un tipo de objeto que describe +este comportamiento mediante el protocolo que tenemos entre manos. + +Cuando python encuentra que se está tratando de acceder a un campo de un objeto +mediante los corchetes llama automáticamente al método `__getitem__` y cuando +se intenta asociar un campo a un valor llama al método `__setitem__` del +objeto. Al pedir la eliminación de un campo del objeto con la sentencia `del`, +se llama al método `__delitem__`. + +Aunque en otros protocolos aquí descritos hemos inventado un nombre para este +documento, Python a este protocolo le denomina *subscriptable* así que cuando +intentes acceder usando corchetes a un objeto que no soporta el protocolo, el +error que saltará te utilizará la misma nomenclatura que nosotros. + +El siguiente ejemplo muestra el protocolo en funcionamiento en una clase sin +funcionamiento alguno. Lo lógico y funcional sería utilizar estos dos métodos +para facilitar el acceso a campos de estas clases o para crear clases que +pudiesen sustituir a listas, tuplas, diccionarios o sets de forma sencilla. + +``` python +>>> class Dog: +... def __getitem__(self, k): +... print(k) +... def __setitem__(self, k, v): +... print(k, v) +... +>>> bobby = Dog() +>>> bobby["field"] +field +>>> bobby["field"] = 10 +field 10 +``` + +Fíjate en que reciben diferente cantidad de argumentos de entrada cada uno de +los métodos. El método `__setitem__` necesita indicar no sólo qué *item* desea +alterarse, sino su también su valor. + +#### *Slice notation* + +Se trata de una forma avanzada de seleccionar las posiciones de un objeto, el +nombre viene de *slice*, rebanada, y significa que puede coger secciones del +objeto en lugar de valores únicos. Piénsalo como en una barra de pan cortada en +rebanadas de la que quieres seleccionar qué rebanadas te interesan en bloque. + +No todos los objetos soportan *slicing*, pero los que lo hacen permiten acceder +a grupos de valores en el orden en el que están indicando el inicio del grupo +(inclusive), el final (no inclusive) y el salto de un elemento al siguiente. + +Además, los valores del *slice* pueden ser negativos. Añadir un número negativo +al salto implica que el salto se hace hacia atrás. Añadirlo en cualquier de los +otros dos valores, inicio o final de grupo, implica que se cuenta el elemento +desde el final de la colección en dirección opuesta a la normal. + +La sintaxis de los *slice*s es la siguiente: `[inicio:fin:salto]`. +Cada uno de los valores es opcional y si no se añaden se comportan de la +siguiente manera: + +- Inicio: primer elemento +- Fin: último elemento inclusive +- Salto: un único elemento en orden de cabeza a cola + +> NOTA: El índice para representar el último elemento es -1, pero si se quiere +> indicar como final, usar -1 descartará el último elemento porque el final no +> es inclusivo. Para que sea inclusivo es necesario dejar el campo fin vacío. + +Dada una lista de los números naturales del 1 al 99, ambos incluidos, de +nombre `l` se muestran unos casos de *slicing*. + +``` python +>>> l[-5:] +[95, 96, 97, 98, 99] +>>> l[6:80:5] +[6, 11, 16, 21, 26, 31, 36, 41, 46, 51, 56, 61, 66, 71, 76] +>>> l[60:0:-5] +[60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5] +``` + +La sintaxis de los *slice*s mostrada sólo tiene sentido a la hora de acceder a +los campos de un objeto, si se trata de escribir suelta lanza un error de +sintaxis. Para crear *slice*s de forma separada se construyen mediante la +clase `slice` de la siguiente manera: `slice(inicio, fin, salto)`. + +En los métodos del protocolo *subscriptable* (`__getitem__`, `__setitem__` y +`__delitem__`) a la hora de elegir un *slice* se recibe una instancia del tipo +*slice* en lugar de una selección única como en el ejemplo previo: + +``` python +>>> class Dog: +... def __getitem__(self, item): +... print(item) +... +>>> bobby = Dog() +>>> bobby[1:100] +slice(1, 100, None) +>>> bobby[1:100:9] +slice(1, 100, 9) +>>> bobby[1:100:-9] +slice(1, 100, -9) +``` + +Por complicarlo todavía más, los campos del *slice* creado desde la clase +`slice` pueden ser del tipo que se quiera. El formato de los `:` es únicamente +*sintactic sugar* para crear *slices* de tipo integer o string. Aunque después +es responsabilidad del quien implemente el protocolo soportar el tipo de +*slice* definido, es posible crear *slices* de lo que sea, incluso anidarlos. + +Como ejemplo de un caso que utiliza *slices* no integer, los tipos de datos +como los que te puedes encontrar en la librería `pandas` soportan *slicing* +basado en claves, como si de un diccionario se tratara. + +### Ejemplo de uso + +Para ejemplificar varios de estos protocolos, tomamos como ejemplo una pieza de +código fuente que quien escribe este documento ha usado en alguna ocasión en su +trabajo como desarrollador. + +Se trata de un iterable que es capaz de iterar en un sistema de ficheros +estructurado en carpetas *año-mes-día* con la estructura `AAAA/MM/DD`. Este +código se creó para analizar datos que se almacenaban de forma diaria en +carpetas con esta estructura. Diariamente se insertaban fichero a fichero por +un proceso previo y después se realizaban análisis semanales y mensuales de los +datos. Esta clase permitía buscar por las carpetas de forma sencilla y obtener +rápidamente un conjunto de carpetas que procesar. + +El ejemplo hace uso del módulo `datetime`, un módulo de la librería estándar +que sirve para procesar fechas y horas. Por ahora, puedes ver la forma de +importarlo como una receta y en el siguiente capítulo la entenderás a fondo. El +funcionamiento del módulo es sencillo y puedes usar la ayuda para comprobar las +funciones que no conozcas. + +Te animo a que analices el comportamiento del ejemplo, viendo en detalle cómo +se comporta. Como referencia, fuera de la estructura de la clase, en las +últimas líneas, tienes disponible un bucle que puedes probar a ejecutar para +ver cómo se comporta. + +``` {.python .numberLines} +from datetime import timedelta +from datetime import date + +class dateFileSystemIterator: + + """ + Iterate over YYYY/MM/DD filesystems or similar. + """ + def __init__( self, start = date.today(), end = date.today(), + days_step = 1, separator = '/'): + self.start = start + self.current = start + self.end = end + self.separator = separator + self.step = timedelta( days = days_step ) + + def __iter__( self ): + self.current = self.start + return self + + def __next__( self ): + if self.current >= self.end: + raise StopIteration + else: + self.current += self.step + datestring = self.current - self.step + datestring = datestring.strftime( "%Y" \ + + self.separator \ + + "%m"+self.separator \ + +"%d") + return datestring + + def __repr__( self ): + out = self.current - self.step + tostring = lambda x: x.strftime("%Y" \ + + self.separator \ + + "%m" \ + + self.separator + "%d") + return "" \ + + "," \ + + "," \ + + "," + + +it = dateFileSystemIterator(start = date.today() - timedelta(days=30)) +print(it) +for i in it: + print(i) +``` + +#### Ejercicio libre: `yield` y los generadores + +La parte de la iteración del ejemplo previo puede realizarse forma más breve +mediante el uso de la sentencia `yield`. Aunque no la trataremos, `yield` +habilita muchos conceptos interesantes, entre ellos los *generadores*. + +A continuación tienes un ejemplo de cómo resolver el problema anterior mediante +el uso de esta sentencia. Te propongo como ejercicio que investigues cómo +funciona buscando información sobre los *generadores* (*generator*) y la +propia sentencia `yield`. + +``` python +from datetime import datetime, timedelta + +def iterate_dates( date_start, date_end=datetime.today(), + separator='/', step=timedelta(days=1)): + date = date_start + while date < date_end: + yield date.strftime('%Y'+separator+'%m'+separator+'%d') + date += step +``` + +`yield` tiene mucha relación con las *corrutinas* (*coroutine*) que, aunque no +se tratarán en este documento, son un concepto muy interesante que te animo a +investigar. Si lo haces, verás que los generadores son un caso simple de una +corrutina. + +## Lo que has aprendido + +Este capítulo también ha sido intenso como el anterior, pero te prometo que no +volverá a pasar. El interés principal de este capítulo es el de hacerte conocer +la programación orientada a objetos y enseñarte que en python lo inunda todo. +Todo son objetos. + +Para entenderlo has comenzado aprendiendo lo que es la programación orientada a +objetos, concretamente la orientada a clases, donde has visto por primera vez +los conceptos de identidad propia, comportamiento y estado. + +Desde ahí has saltado al fundamento teórico de la programación orientada a +objetos y has visitado la encapsulación, la herencia y el polimorfismo para +luego, una vez comprendidos, comenzar a definir clases en python. + +Esto te ha llevado a necesitar conocer qué es el argumento que suele llamarse +`self`, una excusa perfecta para definir qué son las variables y funciones de +clase y en qué se diferencian de las propiedades y métodos. + +Como la encapsulación no se había tratado en detalle aún, lo próximo que has +hecho ha sido zambullirte en los campos privados viendo cómo python los crea +mediante un truco llamado *name mangling* y su impacto en la herencia. + +Aunque en este punto conocías el comportamiento general de la herencia hacia +abajo, necesitabas conocerlo hacia arriba. Por eso, ha tocado visitar la +función `super` en este punto, función que te permite acceder a la superclase +de la clase en la que te encuentras. En lugar de contártela en detalle, se te +ha dado una pincelada sobre ella para que tú investigues cuando lo veas +necesario, pero que sepas por dónde empezar. + +Para describir más en detalle lo calado que está python de programación +orientada a objetos necesitabas un ejemplo mucho más agresivo: los protocolos. +A través de ellos has visto cómo python recoge las funcionalidades estándar y +te permite crear objetos que las cumplan. Además, te ha servido para ver que +**todo** en python es un objeto (hasta las clases lo son[^objects]) y para ver +formas elegantes de resolver problemas comunes, como los iteradores, `with` y +otros. + +También, te recuerdo que, aunque sea de forma colateral y sin prestarle +demasiada atención, se te ha sugerido que cuando programamos no lo hacemos +únicamente para nosotros mismos y que la facilidad de lectura del código y la +preparación de éste para que otros lo usen es primordial. Los próximos +capítulos tratan en parte de ésto: de hacer uso del patrimonio tecnológico de +la humanidad, y de ser parte de él. + +[^objects]: Puedes preguntárselo a python: + ``` python + >>> class C: pass + ... + + >>> isinstance(C, object) + True + ``` diff --git a/es/06_ejec_mod.md b/es/06_ejec_mod.md new file mode 100644 index 0000000..0c54f4f --- /dev/null +++ b/es/06_ejec_mod.md @@ -0,0 +1,271 @@ +# Módulos y ejecución + +Hasta ahora, has ejecutado el código en la REPL y de vez en cuando has usado +`F5` en IDLE para ejecutar. Aunque te ha permitido salir del paso, necesitas +saber más en detalle cómo funciona la ejecución para empezar a hacer tus +programas. Además, es absurdo que te pelees contra todo, hay que saber qué +batallas librar, así que necesitarás aprender a importar código realizado por +otras personas para poder centrarte en lo que más te interesa: resolver tu +problema. + +Este capítulo trata ambas cosas, que están muy relacionadas, y sirve como +trampolín para el siguiente, la instalación de nuevos paquetes y la gestión de +dependencias, y los posteriores sobre librerías interesantes que te facilitarán +el desarrollo de tus proyectos. + +Este capítulo ya te capacita casi al cien por cien para la programación aunque +aún no hemos trabajado su utilidad, pero seguro que alguna idea se te habrá +ocurrido, si no no estarías leyendo este documento. + +## Terminología: módulos y paquetes + +En python a cualquier fichero de código (extensión `.py`) se le denomina +*módulo* (*module*). A cualquier directorio que contenga módulos de código +python y un fichero llamado `__init__.py`, que puede estar vacío, se le +denomina *paquete* (*package*). El uso del fichero `__init__.py` permite que +python busque módulos dentro del directorio. + +Piensa en los paquetes como grupos de módulos que incluso pueden anidarse con +subpaquetes. En la documentación oficial verás en más de una ocasión que se +trata a los paquetes como si fueran módulos, y tiene cierto sentido, porque, +normalmente, existe un módulo principal que permite el acceso a un subpaquete. +Así que, principalmente estás trabajando con un único módulo de un paquete, que +contiene subpaquetes a los que este módulo hace referencia. Aunque pueda sonar +algo enrevesado, no te preocupes por ahora: los módulos son ficheros únicos y +los paquetes conjuntos de ellos. + +## Ejecución + +Ya conoces un par de maneras de ejecutar tus módulos de python. Usar la REPL, +introduciéndole el código que quieres ejecutar, llamar a la función «ejecutar +módulo» de IDLE con la tecla `F5` e incluso llamar a la shell de sistema con un +comando similar a este: + +``` bash +python mi_archivo.py +``` + +Normalmente, los ficheros de python se ejecutan de este último modo en +producción, mientras que los dos anteriores son más usados a la hora de +desarrollar. Realmente son métodos similares, en todos ellos el intérprete +accede al fichero o contenido que recibe y ejecuta las líneas una a una. + +Existe también una opción adicional muy usada que sirve para ejecutar módulos +que el sistema sea capaz de encontrar por sí mismo, en lugar de indicarle la +ruta, se le puede indicar simplemente el nombre del módulo usando la opción +`-m`: + +``` bash +python -m nombre_de_modulo +``` + +## Importación y *namespaces* + +Anteriormente hemos pasado sobre la sintaxis de la importación de forma muy +superficial pero tampoco es mucho más compleja a lo que ha aparecido. La +sentencia `import` permite importar diferentes módulos a nuestro programa como +en el siguiente ejemplo: + +``` python +>>> import datetime +>>> datetime + +``` + +Han pasado, sin embargo, muchas cosas interesantes en el ejemplo. En primer +lugar, python ha buscado y encontrado el módulo `datetime` en el sistema y, en +segundo lugar, ha creado un objeto módulo llamado `datetime` que atesora todas +las definiciones globales del módulo `datetime`. + +Empezando por el final, python usa lo que se conoce como *namespace* de forma +muy extendida. Los *namespaces*, de nombre (*name*) y espacio (*space*), son +una herramienta para separar contextos ampliamente usada. Los objetos, en +realidad, son una caso de *namespace* ya que cuando se llama a un método se le +dice cuál es el contexto de la llamada, es decir: al método de qué objeto se +llama. + +Para los módulos el proceso es el mismo. La sentencia `import` trae un módulo +al programa pero lo esconde tras su *namespace*, de este modo, para acceder a +algo definido en el recién importado módulo es necesario indicarle el nombre de +éste de la siguiente manera: + +``` python +>>> import datetime +>>> datetime.date.today() +datetime.date(2019, 12, 3) +``` + +En el ejemplo se accede a la clase `date` dentro del módulo `datetime`, y se +lanza su función `today`, que indica el día de hoy. Como puedes apreciar, el +operador `.` se utiliza del mismo modo que en las clases y objetos, y en +realidad es difícil saber cuándo se está accediendo a una clase y cuándo a un +módulo, aunque tampoco es necesario saberlo. + +Para no tener que escribir el nombre del módulo completo, existe otra versión +de la sentencia `import` que tiene un comportamiento muy similar: + +``` python +>>> import datetime as dt +>>> dt.date.today() +datetime.date(2019, 12, 3) +``` + +En este ejemplo, se ha cambiado el nombre del módulo a uno más corto decidido +por el programador, `dt`. El funcionamiento es el mismo, simplemente se ha +cambiado el nombre para simplificar. Este cambio de nombre también es útil +cuando se va a importar un módulo cuyo nombre es igual que alguna otra +definición. Cambiando el nombre se evitan colisiones. + +Existen versiones además, que permiten importar únicamente las funciones y +clases seleccionadas, pero que las añaden al *namespace* actual, para evitar +tener que usar el prefijo. + +``` python +>>> from datetime import date +>>> date.today() +datetime.date(2019, 12, 3) +``` + +En este último ejemplo, se trae la clase `date` al contexto actual. También +existe la posibilidad de importar más de una definición del módulo, usando la +coma para separarlas, o todo lo que el módulo exponga mediante el símbolo `*`. +Es peligroso, sin embargo, traer definiciones al namespace actual de forma +descuidada, sobre todo con la última opción, porque, es posible que se repitan +nombres por accidente y se pisen definiciones. Los namespaces se inventan con +el fin de separar las definiciones y evitar colisiones de este tipo. + +### Búsqueda + +Una vez descrito cómo se interactúa con los módulos importados, es necesario +describir dónde se buscan estos módulos. + +Los módulos se buscan en los siguientes lugares: + +1. El directorio del fichero ejecutado o el directorio de trabajo de la REPL +2. Los directorios indicados en el entorno. +3. La configuración por defecto (depende de la instalación) + +Esto significa que si guardas un archivo de python en IDLE y guardas otro más +en el mismo directorio con el nombre `modulo.py` podrás importarlo usando +`import modulo` en el primero, ya que comparten directorio. Lo mismo ocurre con +los paquetes, crear un directorio con nombre `paquete` y añadirle un fichero +vacío llamado `init.py` te permitirá hacer `import paquete`. Si añadieras más +módulos dentro del paquete, podrías importar cada uno de ellos mediante +`paquete.modulo`. + +> NOTA: Los nombres de los ficheros deben coincidir con el el nombre del módulo +> más la extensión `.py`. En el caso de los directorios, saltar a un +> subdirectorio implica acceder a un paquete, por lo que se añadirá un punto +> (`.`). + +El primer punto sirve para facilitar que organices tu proyecto de python en +varios módulos, separando así la funcionalidad en diferentes archivos. + +Los últimos dos puntos son los que permiten a python encontrar su librería +estándar y los módulos de sistema. El tercero depende de la instalación y del +formato de ésta: si python está instalado como portable no será igual que si se +instala en el sistema del modo habitual. El segundo punto también puede variar +de un sistema a otro, pero en resumen se trata de varias variables de entorno +de sistema que le indican a python dónde buscar (normalmente toman el nombre +`PYTHONPATH`, pero no es siempre así). El segundo punto puede alterarse de modo +que en función de lo que se le indique, se puede pedir a python que busque los +módulos en un lugar u otro. + +Estos lugares de búsqueda se pueden mostrar de la siguiente manera: + +``` python +>>> import sys +>>> print(sys.path) +[ '', + '/usr/lib/python36.zip', + '/usr/lib/python3.6', + '/usr/lib/python3.6/lib-dynload', + '/usr/local/lib/python3.6/dist-packages', + '/usr/lib/python3/dist-packages'] +``` + +En función del sistema en el que te encuentres y la configuración que tengas, +python mostrará diferente resultado. + +Rescatando un ejemplo previo: + +``` python +>>> import datetime +>>> datetime + +``` + +Ahora entiendes por qué es capaz de encontrar `datetime` en +`/usr/lib/python3.6`, carpeta listada en `sys.path`, bajo el nombre +`datetime.py`. + +## Ejecución vs Importación: `__main__` *guard* + +A la hora de importar un módulo, python procesa el contenido de éste ya que +necesita definir las funciones, clases, valores, etc. a exportar: ejecuta el +módulo. + +Python define una forma de separar la funcionalidad del código de sus +definiciones con el fin de poder crear código cuyas definiciones sean +reutilizables mediante la importación en otro módulo, sin que tenga ninguna +funcionalidad cuando esto ocurra, pero habilitando que tenga funcionalidades +cuando sea llamado directamente. + +Un ejemplo de uso de esto puede ser un módulo de acceso a ficheros, por +ejemplo, que visualice el contenido del fichero cuando se llame de forma +directa pero que cuando se importe únicamente aporte las funciones de lectura y +escritura sin leer y mostrar ningún fichero. + +Para que los módulos puedan tener esta doble vida, python define la variable +`__name__` que representa en qué nivel del *scope* se está ejecutando el módulo +actual. La variable `__name__` toma el valor del nombre del módulo cuando éste +está siendo importado y el valor `__main__` cuando ha sido llamado de forma +directa o está siendo ejecutado en la REPL. `__main__` es el *scope* global de +los programas, por lo que cuando algo se declara en él, implica que es el +programa principal. + +Para poder diferenciar cuándo se ha ejecutado un módulo de forma directa y +cuando se ha importado se utiliza lo que se conoce como `__main__` *guard*: + +``` python +if __name__ == "__main__": + # Este bloque sólo se ejecuta cuando el módulo es el principal +``` + +Aunque igual es un poco incómodo de entender de primeras, encontrarás esta +estructura en casi cualquier módulo de código python. Se utiliza +constantemente, incluso para los casos en los que no se pretende que el código +pueda importarse. Es una buena práctica incluir el *guard* para separar la +ejecución de las definiciones, de este modo, quien quiera saber cuál es la +funcionalidad del módulo tendrá mucho más fácil la búsqueda. + +Puedes leer más sobre este tema en la documentación de python[^main-guard]. + +[^main-guard]: + + +Siguiendo este concepto, también existe el un estándar de nomenclatura de +ficheros. El nombre `__main__.py` hace referencia al fichero que contiene el +código que se incluiría dentro del *guard* y será el fichero que python buscará +ejecutar siempre que se le pida ejecutar un paquete o un directorio sin +especificar qué módulo debe lanzar. Por ejemplo, ejecutar `python .`[^dot] en +la shell de sistema es equivalente a ejecutar `python __main__.py`. + +[^dot]: `.` significa directorio actual en cualquiera de los sistemas + operativos comunes. + + +## Lo que has aprendido + +En este capítulo corto has aprendido lo necesario sobre importación de módulos +y ejecución de código. Conocer en detalle el patrón de búsqueda de módulos de +python es primordial para evitar problemas en el futuro y organizar los +proyectos de forma elegante. + +Además, el `__main__` *guard* era una de las últimas convenciones de uso común +que quedaban por explicar y una vez vista ya eres capaz de leer proyectos de +código fuente que te encuentres por ahí sin demasiados problemas. + +Los próximos capítulos, basándose en lo aprendido en éste, te mostrarán cómo +instalar nuevas dependencias y cómo preparar tu propio código para que pueda +ser instalado de forma limpia y elegante. diff --git a/es/07_install.md b/es/07_install.md new file mode 100644 index 0000000..3da8ae0 --- /dev/null +++ b/es/07_install.md @@ -0,0 +1,243 @@ +# Instalación y dependencias + +Ahora que sabes lidiar con módulos, necesitas aprender a instalarlos en tu +propio sistema, porque es bastante tedioso que tengas que copiar el código de +todas tus dependencias en la carpeta de tu proyecto. + +En la introducción no aseguramos de instalar con python la herramienta `pip`. +Que sirve para instalar paquetes nuevos en el sistema de forma sencilla. + +## Funcionamiento de `pip` + +`pip` es una herramienta extremadamente flexible, capaz de instalar módulos de +python de diferentes fuentes: repositorios de git, carpetas de sistema o, la +más interesante quizás de todas, el *Python Package Index* (*PyPI*)[^pypi]. + +[^pypi]: https://pypi.org/ + +`pip` buscará la descripción del paquete en la fuente que se le indique y, de +esta descripción, obtendrá las dependencias necesarias y las indicaciones de +cómo debe instalarlo. Una vez procesadas las normas, procederá a instalar el +paquete en el directorio de sistema con todas las dependencias de éste para que +funcione correctamente. + +Una vez instalado el paquete en el directorio de sistema está listo para ser +importado. + +### PyPI + +El *Python Package Index* o *PyPI* es un repositorio que contiene software +programado en python. En él se listan miles de librerías creadas por +programadores de python para que cualquiera pueda descargarlas e instalarlas. +Más adelante veremos algunas de ellas y nos acostumbraremos a usarl PyPI como +recurso. + +Ahora que sabes programar en python tú también puedes publicar tus proyectos +ahí para que otras personas los usen para crear los suyos. + +### Reglas de instalación: `setuptools` y `setup.py` + +Para que `pip` pueda hacer su trabajo correctamente hay que indicarle cómo debe +hacerlo, ya que cada paquete es un mundo y tiene necesidades distintas. El +módulo `setuptools` permite crear un conjunto de reglas comprensible por `pip` +que facilita la distribución e instalación. + +Normalmente este conjunto de reglas suele almacenarse en un fichero de nombre +`setup.py`. Como ejemplo de `setup.py` puedes ver el de la librería +`BeautifulSoup4`[^bs4-setup] que se adjunta a continuación con ligeras +alteraciones para que encaje en la página: + +[^bs4-setup]: https://www.crummy.com/software/BeautifulSoup/bs4/doc/ + +``` {.python .numberLines} +from setuptools import ( + setup, + find_packages, +) + +with open("README.md", "r") as fh: + long_description = fh.read() + +setup( + name="beautifulsoup4", + # NOTE: We can't import __version__ from bs4 because bs4/__init__.py + # is Python 2 code, and converting it to Python 3 means going through + # this code to run 2to3. + # So we have to specify it twice for the time being. + version = '4.8.1', + author="Leonard Richardson", + author_email='leonardr@segfault.org', + url="http://www.crummy.com/software/BeautifulSoup/bs4/", + download_url = "http://www.crummy.com/software/BeautifulSoup/bs4/download/", + description="Screen-scraping library", + install_requires=["soupsieve>=1.2"], + long_description=long_description, + long_description_content_type="text/markdown", + license="MIT", + packages=find_packages(exclude=['tests*']), + extras_require = { + 'lxml' : [ 'lxml'], + 'html5lib' : ['html5lib'], + }, + use_2to3 = True, + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + 'Programming Language :: Python :: 3', + "Topic :: Text Processing :: Markup :: HTML", + "Topic :: Text Processing :: Markup :: XML", + "Topic :: Text Processing :: Markup :: SGML", + "Topic :: Software Development :: Libraries :: Python Modules", + ], +) +``` + +En el ejemplo se aprecia a la perfección el tipo de información que es +necesario aportarle a `setuptools`. Dependiendo del proyecto, esta +configuración puede ser más compleja o más sencilla, y tendrás que indagar a +través de la configuración de `setuptools` para ajustar la herramienta a tu +proyecto. + + +## Entornos virtuales y dependencias: `pipenv` + +La herramienta `pip` es interesante para instalar herramientas en el sistema +pero tiene ciertas carencias. La primera, que no es capaz de resolver las +dependencias a la hora de desinstalar paquetes, por lo que, si se instala un +paquete que dependa de otro paquete, `pip` instalará todas las dependencias +necesarias, pero por miedo a romper paquetes, no las desinstalará si se le pide +desinstalar el paquete que las arrastró. + +Por otro lado, si quieres trabajar en proyectos de desarrollo, probablemente +tengas que instalar sus dependencias. Si tienes varios proyectos en marcha +simultáneamente o si tus sistema necesita de alguna herramienta escrita en +python, es posible que tengas colisiones. + +Imagina que dos de los proyectos, por ejemplo, usan versiones diferentes de una +de sus librerías. Si instalas sus dependencias usando `pip`, se mezclaran en tu +sistema y no podrán coexistir. Además, cuando termines los proyectos o +abandones su desarrollo, te interesará limpiar sus dependencias de tu sistema, +cosa complicada si `pip` no gestiona la liberación de paquetes de forma +correcta. + +Para evitar estos problemas y algún otro adicional, existen herramientas +adicionales que alteran el comportamiento de `pip` y del propio python, creando +lo que se conoce como *entornos virtuales* (*virtual environments*) que quedan +aislados entre ellos y el sistema. + +El funcionamiento de los entornos virtuales es muy sencillo. Cuando se activan, +crean un nuevo contexto en el que alteran las variables de entorno que +describen dónde debe buscarse el intérprete de python, dónde busca éste los +módulos y dónde instala `pip` los paquetes. Cuando se desactivan, se restaura +el entorno por defecto. De este modo, cada entorno virtual queda perfectamente +aislado de otros o incluso de la instalación del sistema, permitiéndote hasta +tener diferentes versiones de python en tu sistema y que no colisionen entre +ellas. Cuando has terminado con tu entorno virtual puedes borrarlo de forma +segura sabiendo que no va a afectar a tu sistema y que va a llevarse todas las +dependencias con él. + +Históricamente se han utilizado varias herramientas para esta labor, como +`virtualenv`, que como era poco amigable se simplificaba con +`virtualenv-wrapper` u otras. Hoy en día `pipenv` es la herramienta +recomendada. + +`pipenv` es una combinación de `virtualenv` y `pip` creada para gestionar +entornos virtuales y dependencias de desarrollo. Puedes considerarla un gestor +de paquetes de desarrollo como `npm` en JavaScript, `composer` en PHP o +cualquier otro que conozcas. Aporta la mayor parte de funcionalidades +habituales como ficheros de dependencias, lockfiles etc. mientras que expone +una interfaz de comandos sencilla y bien documentada. + +### Instalación + +Para instalar pipenv, podemos usar `pip`, que instalamos en la introducción. +En la shell de sistema ejecutando: + +``` bash +pip install pipenv +``` + +> NOTA: en función del sistema que utilices, puede que `pip` se llame +> `pip3`. El funcionamiento es idéntico. + +### Uso + +Una vez instalado `pipenv`, puedes usarlo en el directorio que desees para +crear un conjunto de nuevas dependencias pidiéndole que instale un nuevo +paquete lanzando la orden `pipenv install` en la shell de sistema. + +Para ejecutar módulos en el entorno virtual recién creado dispones de dos +opciones: `pipenv shell` que prepara una shell de sistema en el entorno o +`pipenv run` que ejecuta el comando que se le envíe en el entorno. + +Puedes seguir añadiendo dependencias al proyecto con `pipenv install` y +eliminar las que no te gusten con `pipenv uninstall`. Además, dispones de +muchas opciones adicionales que te animo que ojees ejecutando `pipenv --help`. + + +### Usar IDLE desde un entorno virtual + +Si utilizas entornos virtuales deberás preparar IDLE para verlos y que su REPL +y su intérprete encuentren los paquetes del entorno virtual. + +Puedes lanzar IDLE desde el entorno virtual usando el siguiente truco en la +shell de sistema: + +``` bash +pipenv run python -m idlelib.idle +``` + +Recordando el capítulo anterior y lo descrito en este, ese comando ejecuta +`python -m idlelib.idle` en el entorno virtual en el que te encuentres. El +comando, por su parte le pide a python que ejecute el módulo `idle` del paquete +`idlelib`, que contiene el propio programa IDLE. + +Si trabajas con otros editores integrados de código tendrás que aprender a +hacer que sus intérpretes busquen en el entorno virtual actual, pero casi todos +los editores actuales soportarán esta opción de una forma u otra. + +## Otras herramientas + +La realidad es que las herramientas propuestas no son las únicas que existen +para estas tareas. Históricamente, python ha tenido otras herramientas con +comportamientos similares a `pip`, como `easy_install` y otras con parecidas a +`setuptools` como `distutils` y otras que asemejan a `pipenv`. Este capítulo +trata únicamente las herramientas que se consideran las recomendadas en el +momento en el que se está escribiendo[^recommended-tools], aunque los conceptos +descritos son extrapolables, no sólo a otras herramientas sino también a otros +lenguajes y entornos. + +[^recommended-tools]: Para un listado más extenso, visitar: + + + +## Lo que has aprendido + +En este capítulo has aprendido lo necesario sobre las herramientas que rodean +a python y su uso. De este modo, no te vas a sentir perdido en un maremágnum de +nombres extraños y comandos cuando trabajes en proyectos que ya las usan. + +Más que convertirte en un experto de cómo trabajar con estas herramientas, cosa +que te dará la práctica, este episodio te ha dado las referencias que necesitas +para ir creciendo por tu cuenta en su uso, explicándote el interés de cada una +de ellas. + +Te encontrarás, probablemente, proyectos que utilicen las herramientas de un +modo peculiar, usen otras herramientas o hasta proyectos que no las usen +correctamente. Este es un mundo complejo, y la historia de python no facilita +la elección. Durante mucho tiempo la comunidad creó nuevas herramientas +para suplir las carencias de otras y el ecosistema se complicó. A pesar de que +hoy en día se recomiende el uso de unas herramientas sobre otras, no siempre +fue así y muchos proyectos se han quedado obsoletos en este sentido. + +Para evitar atarte a las herramientas que aquí se muestran, se ha preferido +darte una ligera noción, haciéndote recordar que las herramientas no son +realmente lo importante, sino el conocimiento subyacente. + +Con la herramienta principal que has conocido en este apartado, `pipenv`, +podrás instalar los paquetes que quieras de forma aislada y jugar con ellos +todo lo que desees sin manchar tu sistema por lo que podrás adentrarte en los +próximos capítulos sin miedo a estropear tu pulcra configuración inicial. diff --git a/es/08_stdlib.md b/es/08_stdlib.md new file mode 100644 index 0000000..fbaa974 --- /dev/null +++ b/es/08_stdlib.md @@ -0,0 +1,372 @@ +# La librería estándar + +La librería estándar se refiere a todas las utilidades que un lenguaje de +programación trae consigo. Los lenguajes de programación, a parte de aportar la +propia funcionalidad del lenguaje en sí mismo que simplemente sería la +ejecución del código fuente que se le indique, suelen incluir funcionalidades +que no están necesariamente relacionadas con ese proceso. + +El motivo de esto es facilitar el uso del lenguaje para las labores más +comunes, incluyendo el código necesario para realizarlas sin necesitar añadidos +externos. + +En lenguajes de programación de dominio específico la librería estándar suele +contemplar muchos casos de ese dominio concreto. Por ejemplo, el lenguaje de +programación Julia, aunque esté diseñado con el objetivo de ser un lenguaje +válido para cualquier uso, su área de trabajo se centra en el entorno +científico. Es por eso que incluye en su librería estándar paquetes de álgebra +lineal, estadística y otros. + +Existen muchas diferentes aproximaciones a la librería estándar. Algunos +lenguajes las mantienen extremadamente reducidas con el fin de que la +implementación del lenguaje sea más liviana, con la contrapartida de forzar a +quien los use a tener que utilizar herramientas externas o a desarrollarlas. + +Python es un lenguaje de propósito general con una extensísima librería +estándar. Esto dificulta la selección de apartados a mostrar en este capítulo, +pero facilita la programación de cualquier aplicación en éste lenguaje. + +Los paquetes estándar de python facilitan la lectura de infinidad de tipos de +fichero, la conversión y el tratamiento de datos, el acceso a la red, la +ejecución concurrente, etc. por lo que librería estándar es más que suficiente +para muchas aplicaciones y no se requiere añadir módulos externos. + +Conocer la librería estándar y ser capaz de buscar en ella los paquetes que +necesites te facilitará mucho la tarea, evitando, por un lado, que dediques +tiempo a desarrollar funcionalidades que el propio lenguaje ya aporta y, por +otro, que instales paquetes externos que no necesitas. + +Todos los apartados aquí listados están extremadamente bien documentados en la +página oficial de la documentación de python y en la propia ayuda. Eso sí, +tendrás que importarlos para poder leer la ayuda, pero ya sabes cómo se hace. + +A continuación se recogen los módulos más interesantes, aunque en función del +proyecto puede que necesites algún otro. Puedes acceder al listado completo en +la página oficial de la documentación[^stdlibdoc]. + +[^stdlibdoc]: + + +## Interfaz al sistema operativo: `os` + +Ya definimos previamente el concepto *interfaz* como superficie de contacto +entre dos entidades. Esta librería facilita la interacción entre python y el +sistema operativo. + +La comunicación con el sistema operativo es primordial para cualquier lenguaje +de programación de uso general, ya que es necesario tener la capacidad de hacer +peticiones directas al sistema operativo. Por ejemplo, cambiar de directorio +actual para facilitar la inclusión rutas relativas en el programa, resolver +rutas a directorios y un largo etc. + +En capítulos previos hemos hablado de las diferencias entre sistemas +operativos. Esta librería, además, facilita el trabajo para esos casos. Por +ejemplo, que el separador de directorios en UNIX es `/` y en Windows `\`, si +quisiéramos programar una aplicación multiplataforma, nos encontraríamos con +problemas. Sin embargo, el módulo `os.path` dispone de herramientas para +gestionar las rutas de los directorios por nosotros, por ejemplo, la variable +`os.path.sep` (copiada en `os.sep` por abreviar), guarda el valor del separador +del sistema en el que se esté ejecutando la aplicación: `/` en UNIX y `\` en +Windows. + +Este paquete es muy interesante para desarrollar código portable entre las +diferentes plataformas. + +## Funciones relacionadas con el intérprete: `sys` + +Aunque el nombre de este módulo suene complicado, su uso principal es el de +acceder a funcionalidades que el propio python controla. Concretamente, se usa +sobre todo para la recepción de argumentos de entrada al programa principal, +la redirección de entradas y salidas del programa y la terminación del +programa. + +### Salida forzada: `sys.exit()` + +Para salir de forma abrupta del programa y terminar su ejecución, python +facilita la función `sys.exit()`. Al ejecutarla la + +### *Standard streams*: `sys.stdin`, `sys.stdout` y `sys.stderr` + +Cuando se trabaja en programas que funcionan en la terminal se pueden describir +tres vías de comunicación con el usuario: + +1. El teclado: de donde se obtiene lo que el usuario teclee mediante la función + `input`. +2. La pantalla: a donde se escribe al ejecutar la función `print`. +3. Y la salida de errores: Una salida especial para los fallos, similar a la + anterior, pero que se diferencia para poder hacer un tratamiento distinto y + porque tiene ciertas peculiaridades distintas. Los mensajes de error de + excepciones se envían aquí. + +Estas tres vías se conocen comúnmente como entrada estándar (*standard input*), +`stdin`, salida estándar (*standard output*), `stdout`, y error estándar +(*standard error*), `stderr`. Este concepto responde al nombre de *standard +streams* y es una abstracción de los dispositivos físicos que antiguamente se +utilizaban para comunicarse con una computadora. Hoy en día, estos tres +*streams* son una abstracción de esa infraestructura y se comportan como +simples ficheros de lectura en el primer caso y de escritura en los otros dos. + +Los diferentes sistemas operativos los implementan a su modo, pero desde el +interior de python son simplemente ficheros abiertos, como si se hubiese +ejecutado la función `open` en ellos y ellos se encargan de leer o escribir a +la vía de interacción con el usuario correspondiente. Es decir, en realidad +`print`, `input`, etc. son únicamente funciones que facilitan la labor de +escribir manualmente en estos *streams*: + +``` python +>>> import sys +>>> chars = sys.stdout.write("Hola\n") +Hola +>>> chars # Recoge el número de caracteres escritos +5 +``` + +La realidad es que estos *streams* dan una flexibilidad adicional muy +interesante. Como son únicamente variables de ficheros abiertos pueden +cerrarse y sustituirse por otros, permitiéndote redireccionar la entrada o la +salida de un programa a un fichero. + +El siguiente ejemplo redirecciona la salida de errores a un fichero llamado +`exceptions.txt` y trata de mostrar una variable que no está definida. Python +en lugar de mostrar el mensaje de una excepción, la escribe en el fichero al +que se ha redireccionado la salida: + +``` python +>>> sys.stderr = open("exceptions.txt", "w") +>>> aasda # No muestra el mensaje de error +>>> +>>> with open("exceptions.txt") as f: +... f.read() # Muestra el contenido del archivo +... +'Traceback (most recent call last):\n File "", line 1, in \ +\nNameError: name \'aasda\' is not defined\n' +``` + + +### Argumentos de entrada: `sys.argv` + +Es posible añadir argumentos de entrada a la ejecución de los programas. Piensa +en el propio programa llamado python, al ejecutarlo en la shell de sistema se +le pueden mandar diferentes opciones que, por ejemplo, digan qué módulo debe +ejecutar: + +``` bash +python test.py +``` + +Para la shell de sistema, todo lo que se escriba después de la primera palabra +es considerado un argumento de entrada. + +Cuando se ejecutan nuestros programas escritos en python, es posible también +enviarles argumentos de entrada: + +``` bash +python test.py argumento1 argumento2 ... +``` + +Lo que el python reciba después del nombre del módulo a ejecutar lo considerará +argumentos de entrada de nuestro módulo y nos lo ordenará y dejará disponible +en la variable `sys.argv`, una lista de todos los argumentos de entrada. + +Si muestras el contenido de la variable en la REPL, te responderá `['']` ya que +la REPL se ejecuta sin argumentos. Sin embargo, si creas un módulo y le añades +este contenido: + +``` python +import sys +print(sys.argv) +``` + +Verás que se imprime una lista con el nombre del archivo en su primer elemento. + +Si ejecutas el módulo desde la shell de sistema añadiéndole argumentos de +entrada: + +``` bash +python modulo.py arg1 arg2 arg3 +``` + +La lista `sys.argv` recibirá todos, siempre a modo de string. + +Los argumentos de entrada son extremadamente interesantes para permitir que los +programas sean configurables por quien los use sin necesidad de tener que +editar el código fuente, cosa que nunca debería ser necesaria a menos que +exista un error en éste o quiera añadirse una funcionalidad. + +## Procesamiento de argumentos de entrada: `argparse` + +Como la variable `sys.argv` entrega los argumentos de entrada tal y como los +recibe y no comprueba si son coherentes, python dispone de una librería +adicional para estas labores. El módulo `argparse` permite definir qué tipo de +argumentos de entrada y opciones tiene tu programa y qué reglas deben seguir. +Además, facilita la creación de ayudas como la que se muestra cuando ejecutas +`python -h` o `pip -h` en la shell de tu sistema. + +Es una librería bastante compleja con infinidad de opciones que es mejor que +leas en la propia documentación cuando necesites utilizarla. + +## Expresiones regulares: `re` + +Las expresiones regulares (*regular expression*, también conocidas como +*regexp* y *regex*) son secuencias de caracteres que describen un patrón de +búsqueda. + +En python se soportan mediante el módulo `re` de la librería estándar. + +## Matemáticas y estadística: `math` y `statistics` + +El módulo `math` soporta gran cantidad de operaciones matemáticas avanzadas +para coma flotante. En él puedes encontrar logaritmos, raíces, etc. + +El módulo `statistics` soporta estadística básica como medias, medianas, +desviación típica, etc. + +Ambos módulos tienen mucho interés ya que python se usa extensivamente en el +análisis de datos. Aunque tiene librerías de terceros mucho más adecuadas para +esta labor, para proyectos pequeños puede que sea suficiente con estos módulos. + +## Protocolos de internet: `urllib` + +`urllib` es un conjunto de paquetes que permiten seguir URLs o enlaces, +principalmente para HTTP y su versión segura HTTPs. Soporta cookies, +redirecciones, autenticación básica, etc. + +El siguiente ejemplo te muestra cómo descargar el primer boceto del estándar +del protocolo HTTP de la página web del IETF. Recordando los apartados previos, +fíjate en el uso de la sentencia `with` y en el uso de los corchetes para +obtener un número limitado de caracteres de la recién decodificada respuesta. + +``` python +>>> from urllib.request import urlopen +>>> with urlopen("https://tools.ietf.org/rfc/rfc2068.txt") as resp: +... print( resp.read().decode("utf-8")[:1750] ) +... +' + + + + + +Network Working Group R. Fielding +Request for Comments: 2068 UC Irvine +Category: Standards Track J. Gettys + J. Mogul + DEC + H. Frystyk + T. Berners-Lee + MIT/LCS + January 1997 + + + Hypertext Transfer Protocol -- HTTP/1.1 + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + The Hypertext Transfer Protocol (HTTP) is an application-level + protocol for distributed, collaborative, hypermedia information + systems. It is a generic, stateless, object-oriented protocol which + can be used for many tasks, such as name servers and distributed + object management systems, through extension of its request methods. + A feature of HTTP is the typing and negotiation of data + representation, allowing systems to be built independently of the + data being transferred. + + HTTP has been in use by the World-Wide Web global information + initiative since 1990. This specification defines the protocol + referred to as "HTTP/1.1". + +' +>>> +``` + +## Fechas y horas: `datetime` + +`datetime`, que ya ha aparecido anteriormente, es un módulo para gestión de +fecha y hora. `datetime` soporta gran cantidad de operaciones y tipos de dato. +Entre ellos los `timedeltas`, diferencias temporales que te permiten sumar y +restar fechas con facilidad con los operadores habituales. + +Con las facilidades de `datetime`, es poco probable que necesites importar una +librería de gestión de fecha y hora independiente. + +## Procesamiento de ficheros: `json` y `sqlite3` + +Python habilita gran cantidad de procesadores de formatos de fichero, los dos +que se lista en este apartado tienen especial interés. + +El primer módulo, `json`, sirve para manipular datos en formato JSON. Sus dos +funciones principales `dumps` y `loads` vuelcan o cargan datos de JSON a +diccionarios o listas en una sola orden debido que la propia estructura de JSON +está formada por parejas clave valor o listas de valores ordenadas por índices. +Esta es la razón por la que en muchas ocasiones se utilizan ficheros de formato +JSON para intercambiar información entre aplicaciones de python: su lectura y +escritura es extremadamente sencilla. + +El segundo módulo, `sqlite3`, facilita el acceso a ficheros en formato SQLite3, +un formato binario con una interfaz de acceso que permite consultas SQL. El +módulo `sqlite3` es capaz de convertir las tablas que SQLite3 retorna a +estructuras de python de forma transparente y cómoda por lo que es un aliado +interesante para aplicaciones que requieren una base de datos pequeña y +resiliente. + +## Aritmética de coma flotante decimal: `decimal` + +En el apartado sobre datos tratamos la complejidad de los números de coma +flotante y que su representación binaria puede dar lugar a problemas. Este +módulo de aritmética decimal aporta una solución rigurosa a este problema con +el fin de facilitar el uso de python en entornos que requieren precisión +estricta que incluso puede requerir cumplir con normativas, como puede ser la +banca. + +La documentación del módulo muestra un par de ejemplos muy interesantes usando +la clase `Decimal` aportada por este. Se adjuntan a continuación para que los +estudies y los disecciones en busca de las diferencias con el uso de los +números de coma flotante normales: + +``` python +>>> from decimal import * +>>> round(Decimal('0.70') * Decimal('1.05'), 2) +Decimal('0.74') +>>> round(.70 * 1.05, 2) +0.73 + +>>> Decimal('1.00') % Decimal('.10') +Decimal('0.00') +>>> 1.00 % 0.10 +0.09999999999999995 + +>>> sum([Decimal('0.1')]*10) == Decimal('1.0') +True +>>> sum([0.1]*10) == 1.0 +False +``` + +## Lo que has aprendido + +Más que aprender, en este apartado has sobrevolado la librería estándar de +python desde la distancia y te has parado a observar qué problemas comunes ya +están resueltos en python. + +La realidad es que la librería estándar es mucho más extensa que lo listado en +este apartado, pero aquí se han mencionado los módulos que más frecuentemente +se suelen usar en entornos normales y algunos otros que tienen interés a la +hora de afianzar conocimiento que has obtenido en capítulos previos, como es el +caso del módulo `decimal`. + +Este capítulo empieza a mostrarte el interés de programar en python y las +posibilidades que tienes con él, únicamente con la librería estándar. En el +próximo verás la cantidad de funcionalidad adicional que le puedes añadir con +un par de librerías escritas por terceros. + +Sirva también este capítulo como recordatorio de lo extensa que es la librería +estándar de python. En el futuro, cuando tengas intención de buscar una +librería para resolver un problema que te despista de trabajar en la +funcionalidad principal de tu aplicación, empieza por la librería estándar. diff --git a/es/09_extralib.md b/es/09_extralib.md new file mode 100644 index 0000000..65ed418 --- /dev/null +++ b/es/09_extralib.md @@ -0,0 +1,162 @@ +# Librerías útiles + +Ahora que ya sabes cómo instalar librerías y que has visto que muchas +funcionalidades están contenidas en la librería estándar de python es un buen +momento para que visites varios proyectos que aportan recursos muy interesantes +a la hora de resolver problemas. Debido al carácter de uso general de python, +estas librerías aportan facilidades muy diversas. El criterio para escogerlas +parte de la experiencia personal del autor de este documento y añade algunas +librerías y herramientas que pueden ser interesantes debido a su amplio uso en +la industria. + +## Librerías científicas: ecosistema SciPy + +SciPy es un ecosistema de librerías de cálculo que tiene como objetivo +facilitar la tarea de ingenieros, científicos y matemáticos en sus +respectivos trabajos. Puedes considerarlo como un **Matlab** para python +separado por componentes independientes. + +Además de ser el nombre del ecosistema, comparte nombre con una de las +librerías fundamentales de éste. El ecosistema está formado por varias +librerías, entre ellas se encuentran: + +- Numpy: un paquete de computación científica para python. Soporta matrices + multidimensionales, funciones sofisticadas y álgebra lineal, entre otras. + +- SciPy: librería basada en Numpy que aporta rutinas numéricas eficientes para + interpolación, optimización, algebra lineal, estadística y otros. + +- SymPy: una librería de matemática simbólica para python que complementa el + resto de librerías del ecosistema, ya que casi todas están orientadas al + análisis numérico. + +- Matplotlib: librería para representar gráficos y figuras científicas en 2D. + +- Pandas: aporta unas estructuras de datos muy potentes basadas en tablas, su + objetivo es reforzar a python a la hora de tratar datos. El área de esta + librería es el del análisis de datos, pero puede combinarse con otras áreas + de estudio como la econometría (ver el proyecto statsmodels). Esta librería + dispone de un ecosistema muy poderoso a su alrededor debido al auge del + análisis de datos en la industria del software. Muchas librerías comparten la + interfaz de Pandas por lo que tener nociones de su comportamiento abre muchas + puertas en el sector del análisis de datos. Al igual que ocurre en SciPy, Las + estructuras de datos de Pandas también se basan en Numpy. + +- IPython: más que una librería, IPython es una herramienta. Se trata de una + REPL interactiva (su propio nombre viene de *Interactive Python*) que añade + diversas funcionalidades sobre la REPL de python habitual: histórico de + comandos, sugerencias, comandos mágicos (*magic*) que permiten alterar el + comportamiento de la propia interfaz y un larguísimo etcétera. IPython, + además, sirve como núcleo de los cuadernos Jupyter que integran una shell de + python con visualización de tablas y gráficos para generar documentos estilo + *literate programming*. + +## Machine Learning: ScikitLearn + +ScikitLearn es una librería de machine learning muy potente, implementa gran +cantidad de algoritmos y tiene una documentación extensa, sencilla y educativa. + +Encaja a la perfección con el ecosistema SciPy, ya que se basa en NumPy, SciPy +y Matplotlib. + +## Peticiones web: Requests + +Requests es una librería alternativa al módulo `urllib` aportado por la +librería estándar de python. Se describe a sí misma como «HTTP para seres +humanos», sugiriendo que `urllib` no es cómoda de usar. + +Requests gestiona automáticamente las URL-s de las peticiones a partir de los +datos que se le entreguen, sigue redirecciones, almacena cookies, descomprime +automáticamente, decodifica las respuestas de forma automática, etc. En general +es una ayuda cuando no se quiere dedicar tiempo a controlar cada detalle de la +conexión de modo manual. + +## Manipulación de HTML: Beautifulsoup + +Beautifulsoup es una librería de procesado de HTML extremadamente potente. +Simplifica el acceso a campos de ficheros HTML con una sintaxis similar a los +objetos de python, permitiendo acceder a la estructura anidada mediante el uso +del punto en lugar de tener que lidiar con consultas extrañas o expresiones +regulares. Esta funcionalidad puede combinarse con selectores CSS4 para un +acceso mucho más cómodo. + +Muy usada en conjunción con Requests, para el desarrollo de aplicaciones de +web-scraping. Aunque, si se desea usar algo más avanzado, se recomienda usar el +framework Scrapy. + +## Tratamiento de imagen: Pillow + +Pillow es un fork de la librería PIL (*Python Imaging Library*) cuyo desarrollo +fue abandonado. En sus diversos submódulos, Pillow permite acceder a datos de +imágenes, aplicarles transformaciones y filtros, dibujar sobre ellas, cambiar +su formato, etc. + +## Desarrollo web: Django, Flask + +Existen infinidad de frameworks y herramientas de desarrollo web en python. Así +como en el caso del análisis de datos la industria ha convergido en una +herramienta principal, Pandas, en el caso del desarrollo web hay muchas +opciones donde elegir. + +Los dos frameworks web más usados son Django y Flask, siendo el segundo menos +común que el primero pero digno de mención en conjunción con el otro por varias +razones. + +La primera es la diferencia en la filosofía: así como Django decide con qué +herramientas se debe trabajar, Flask, que se define a sí mismo como +microframework, deja en manos de quien lo usa la elección de qué herramientas +desea aplicarle. Cada una de las dos filosofías tiene ventajas y desventajas y +es tu responsabilidad elegir las que más te convengan para tu proyecto. + +La segunda razón para mencionar Flask es que su código fuente es uno de los más +interesantes a la hora de usar como referencia de cómo se debe programar en +python. Define su propia norma de estilo de programación, basada en la +sugerencia de estilo de python[^pep8] y su desarrollo es extremadamente +elegante. + +Django, por su parte, ha sido muy influyente y muchas de sus decisiones de +diseño han sido adoptadas por otros frameworks, tanto en python como en otros +lenguajes. Lo que sugiere que está extremadamente bien diseñado. + +A pesar de las diferencias filosóficas, existen muchas similitudes entre ambos +proyectos por lo que aprender a usar uno de ellos facilita mucho el uso del +otro y no es aprendizaje perdido. No tengas miedo en lanzarte a uno. + +[^pep8]: + + +## Protocolos de red: Twisted + +Twisted es motor de red asíncrono para python. Sobre él se han escrito +diferentes librerías para gestión de protocolos de Internet como DNS, via +Twisted-Names, IMAP y POP3, via Twisted-Mail, HTTP, via Twisted-Web, IRC y +XMPP, via Twisted-Words, etc. + +El diseño asíncrono del motor facilita sobremanera las comunicaciones +eficientes. Programar código asíncrono en python es relativamente sencillo, +pero ha preferido dejarse fuera de este documento por diversas razones. Te +animo a indagar en esta libreria para valorar el interés del código asíncrono. + +## Interfaces gráficas: PyQt, PyGTK, wxPython, PySimpleGUI + +A pesar de que python dispone de un módulo en su librería estándar para tratar +interfaces gráficas llamado TKinter, es recomendable utilizar librerías más +avanzadas para esto. TKinter es una interfaz a la herramienta Tk del lenguaje +de programación Tcl y acompaña a python desde hace años. + +En programas simples TKinter es más que suficiente (IDLE, por ejemplo, está +desarrollado con TKinter) pero a medida que se necesita complejidad o capacidad +del usuario para configurar detalles de su sistema suele quedarse pequeño. + +Para programas complejos se recomienda usar otro tipo de librerías más +avanzadas como PyQt, PyGTK o wxPython, todas ellas interfaces a librerías +escritas en C/C++ llamadas Qt, GTK y wxWidgets respectivamente. Estas librerías +aportan una visualización más elegante, en algunos casos usando widgets nativos +del sistema operativo en el que funcionan. + +Debido a la complejidad del ecosistema nace el proyecto PySimpleGUI, que +pretende aunar las diferentes herramientas en una sola, sirviendo de interfaz a +cualquiera de las anteriores y alguna otra. Además, el proyecto aporta gran +cantidad de ejemplos de uso. PySimpleGUI aún está en desarrollo y el soporte de +algunos de los motores no está terminado, pero es una fuente interesante de +información y recursos. diff --git a/es/10_closing_words.md b/es/10_closing_words.md new file mode 100644 index 0000000..404a58d --- /dev/null +++ b/es/10_closing_words.md @@ -0,0 +1,123 @@ +# Lo que has aprendido + +Rescatando la definición de la introducción: + +> Python es un lenguaje de programación de alto nivel orientado al uso general. +> Fue creado por Guido Van Rossum y publicado en 1991. La filosofía de python +> hace hincapié en la limpieza y la legibilidad del código fuente con una +> sintaxis que facilita expresar conceptos en menos líneas de código que en +> otros lenguajes. +> +> Python es un lenguaje de tipado dinámico y gestión de memoria automática. +> Soporta múltiples paradigmas de programación, incluyendo la programación +> orientada a objetos, imperativa, funcional y procedural e incluye una extensa +> librería estándar. + +Ahora sí que estás en condición de entenderla no sólo para python sino para +cualquier otro lenguaje que se te presente de este modo. Ahora tienes la +habilidad de poder comprender de un vistazo qué te aporta el lenguaje que +tienes delante únicamente leyendo su descripción. + +Desgranándola poco a poco, has conocido la sintaxis de python en bastante +detalle y has visto cómo hace uso de las sangrías para delimitar bloques de +código, cosa que otros lenguajes hacen con llaves (`{}`) u otros símbolos. + +La facilidad de expresar conceptos complejos en pocas líneas de código puede +verse en las *list comprehensions*, la sentencia `with` y muchas otras +estructuras del sistema. Python es un lenguaje elegante y directo, similar al +lenguaje natural. + +El tipado dinámico trata lo que estudiaste en el apartado sobre datos, donde se +te cuenta que las referencias pueden cambiar de tipo en cualquier momento ya +que son los propios valores los que son capaces de recordar qué tipo tienen. + +La gestión de memoria automática también se presenta en el mismo apartado, +contándote que python hace uso de un *garbage collector* o recolector de basura +para limpiar de la memoria los datos que ya no usa. + +Los diferentes paradigmas de programación no se han tratado de forma explícita +en este documento, más allá de la programación orientada a objetos, que inunda +python por completo. Sin embargo, el apartado sobre funciones adelanta varios +de los conceptos básicos del paradigma de programación funcional: que las +funciones sean ciudadanos de primera clase (*first-class citizens*), el uso de +funciones anónimas (*lambda*) y las *closures*. + +Los paradigmas procedural e imperativo son la base para los dos +paradigmas de los que hemos hablado. La programación imperativa implica que se +programa mediante órdenes (el caso de python, recuerda) en lugar de +declaraciones (como puede ser la programación lógica, donde se muestran un +conjunto de normas que el programa debe cumplir). La programación procedural es +un paradigma cuyo fundamento es el uso de bloques de código y su *scope*, +creando funciones, estructuras de datos y variables aunque, a diferencia de la +programación funcional, en la programación procedural no es necesario que las +funciones sean ciudadanos de primera clase y pueden tener restricciones. + +Estos dos últimos paradigmas, en realidad, se soportan casi por accidente al +habilitar los dos anteriores. + +En muchas ocasiones, te encontrarás escribiendo pequeñas herramientas y no +necesitarás mucho más que usar las estructuras básicas de python y varias +funciones para alterarlas, por lo que estarás pensando de forma procedural +accidentalmente. + +Los paradigmas no son más que patrones de diseño que nos permiten clasificar +los lenguajes y sus filosofías, pero son muy interesantes a la hora de diseñar +nuestras aplicaciones. + +Además de todo esto, has tenido ocasión de conocer de forma superficial la +librería estándar del lenguaje y un conjunto de librerías adicionales que te +aportan los puntos de los que la librería estándar carece. Ahora sabes instalar +dependencias y usarlas en entornos virtuales (*virtual environments*) para +mantener limpia tu instalación. + +A parte de lo mencionado en la definición del lenguaje, has aprendido a +ejecutar, cargar y distribuir módulos de python, algo primordial si pretendes +crear paquetes o nuevas librerías y usar las de terceros. + +Con todo esto, tienes una visión general pero bastante detallada a nivel +técnico de lo que python aporta y cómo. Lo que necesitas para compensarla es +trabajar con él, acostumbrarte a su ecosistema y leer mucho código de buena +calidad para acostumbrarte a seguir las convenciones y recetas habituales. + + +## El código pythónico + +A lo largo del documento se tratan temas que puede que no te esperases +encontrar al leer sobre programación, ya que tu interés principal es resolver +tus problemas de forma efectiva y construir aplicaciones. Hacer robots que te +hagan la vida más fácil, en definitiva. + +Sin embargo, quien se dedica a la programación tiene una vida muy ligada a la +vida de quien se dedica a la filosofía o al diseño y es por eso que esas dos +disciplinas aparecen de vez en cuando en cualquier conversación un poco seria +sobre el trabajo con software. + +Las tres disciplinas, en primer lugar, ocurren en la mente de las personas y no +en sus manos. Es por eso que los patrones mentales y los modos de pensamiento +son parte fundamental de ellas. Ninguno de ellos son trabajos para los que se +pueda entrenar una memoria muscular. Es necesario pensar. Y es necesario pensar +de forma consciente y premeditada. + +Ver cómo desarrollan otras personas su actividad es valioso para realizar tu +tarea con elegancia. + +Otro detalle que has debido de observar, sobre todo porque acaba de aparecer, +es la *elegancia*. La elegancia es, hasta cierto punto, subjetiva y depende del +gusto de quien la mira. Sin embargo, esto sólo es así hasta cierto punto, la +realidad es que alguien puede considerar algo elegante y aun así no gustarle. +Python es un ejemplo de algo así. Guste o no guste, python es un lenguaje de +programación elegante, cuya elegancia forma parte primordial de la filosofía +del lenguaje. + +El autor de este documento, por ejemplo, no es un entusiasta de python, pero a +lo largo de la travesía de escribir este documento ha podido reencontrarse, una +vez más, con su elegancia. + +El concepto del *código pythónico* (*pythonic code*) es un resultado de esto. +Cuando se habla de código pythónico, se habla de un código que sigue los +estándares de elegancia de python. Que es bonito, comprensible y claro. Un +código que la comunidad de desarrollo de python aprobaría. + +Cuando programes en python, trata de programar código pythónico, pues es esa la +verdadera razón por la que se creó el lenguaje y es la forma en la que el +lenguaje más fácil te lo va a poner. diff --git a/es/A_devtools.md b/es/A_devtools.md new file mode 100644 index 0000000..e856373 --- /dev/null +++ b/es/A_devtools.md @@ -0,0 +1,90 @@ +# Anexo I: Herramientas de desarrollo {-} + +IDLE es una herramienta de desarrollo muy limitada, suficiente para seguir los +ejemplos que se recogen en este documento pero insuficiente para desarrollar +aplicaciones avanzadas. + +## Desarrollo de código fuente + +Existen gran cantidad de entornos de desarrollo (IDE) avanzados y editores que +pueden ser recomendables, aunque es cuestión de gusto personal decantarse por +uno de ellos o por otro. + +La diferencia entre un entorno de desarrollo integrado y un editor es la +siguiente: los entornos de desarrollo cumplen varias funciones adicionales, +como en el caso de IDLE, dar acceso a una REPL de python y la posibilidad de +analizar las variables en memoria. Los editores únicamente sirven para escribir +el código, aunque en muchos casos la línea que separa ambos conceptos es +bastante borrosa: existen editores con funcionalidades avanzadas y entornos +integrados muy sencillos que parecen un simple editor. Resumiendo, los entornos +integrados de desarrollo (IDE *integrated development environment*) tienen +editores entre sus herramientas. + +### Entornos de desarrollo integrados + +Quien no utiliza entornos de desarrollo avanzados (una cuestión de gusto +personal), por lo que se le hace difícil recomendar alguno en particular. Sin +embargo, el wiki de python recoge una larguísima lista de editores y entornos +de desarrollo integrado interesantes[^ides]. + +[^ides]: + +En el entorno del análisis de datos, la distribución *Anaconda* es muy usada. +Anaconda es más que un entorno de desarrollo integrado. Incluye un entorno de +desarrollo llamado Spyder, una shell propia, un gestor de paquetes y +dependencias propio llamado Conda, posibilidad de integración con el lenguaje +de programación R y gran cantidad de paquetes instalados por defecto. + +En otros entornos PyCharm es bastante común, aunque también son muy comunes los +entornos de desarrollo integrados pensados para otros lenguajes que han +comenzado a soportar python posteriormente como KDevelop, NetBeans y otros. + +Te recomiendo que, si usas un entorno de desarrollo integrado en otros +lenguajes, investigues si soporta python. De este modo no tendrás que aprender +una nueva herramienta. Al ser un lenguaje tan común, probablemente lo soporte. +Si no lo soporta prueba con un IDE que siga una filosofía similar al que uses. + +### Editores de código + +Quien te escribe usa Vim, un editor de texto muy antiguo con muchas +características que le hacen ser un editor muy eficiente. Existe gran variedad +de editores de código que recomendar: Emacs, gEdit, Kate, Sublime, Atom... Todo +dependerá de tus gustos personales. + +La ventaja principal de los editores de código es que conociendo uno en +profundidad es más que suficiente para cualquier lenguaje, ya que están +diseñados únicamente para escribir el contenido de tus programas, dejando las +peculiaridades de ejecución de cada lenguaje a parte. Esto les aporta una +ligereza difícilmente alcanzable por los IDEs. + +Sin embargo, esta virtud también es su mayor defecto. Al no integrar ninguna +herramienta adicional, es necesario trabajar todo manualmente. En el caso de +python, te fuerzan a usar una shell independiente y a interactuar con ella de +forma manual. Al principio puede ser tedioso, pero aprender a gestionar los +detalles manualmente es interesante ya que te permite obtener un gran +conocimiento del sistema y lenguaje en el que trabajas. + +## Herramientas de depuración + +El propio diseño de python permite que sea fácilmente depurable. La REPL +facilita que se pruebe la aplicación a medida que se va desarrollando, función +a función, para asegurar que su comportamiento es el correcto. Además, la +capacidad introspectiva del lenguaje es una buena herramienta, en conjunción +con la REPL para comprobar el comportamiento. + +Además de estas características, la instalación de python viene acompañada del +programa `pdb` (*Python Debugger*), un depurador de código similar al conocido +[GNU-Debugger](https://en.wikipedia.org/wiki/GNU_Debugger). Existen otros +depuradores de código más amigables que este, pero la realidad es que no suelen +ser necesarios. + +## Testeo de aplicaciones + +Hoy en día el testeo de software es muy común, en parte gracias al desarrollo +guiado por pruebas, o TDD (*Test Driven Development*). + +La librería estándar de python incluye un módulo para pruebas unitarias llamado +`unittest` en que la librería Nose, muy conocida y usada, se basa para +facilitar el trabajo de modo similar a lo que ocurre con Requests y `urllib`. + +Por supuesto, existen otras alternativas, pero estas son las principales. diff --git a/es/Metadata.yaml b/es/Metadata.yaml new file mode 100644 index 0000000..bed3ffc --- /dev/null +++ b/es/Metadata.yaml @@ -0,0 +1,10 @@ +links-as-notes: true +toc: true +title: Programación en Python +subtitle: +author: "Ekaitz Zárraga" +author-meta: Ekaitz Zárraga +lang: es-ES +polyglossia-lang: + name: spanish +license: CC-BY-SA diff --git a/es/Z_license.md b/es/Z_license.md new file mode 100644 index 0000000..46d9792 --- /dev/null +++ b/es/Z_license.md @@ -0,0 +1,337 @@ +# Anexo II: Licencia CC BY-SA 4.0 {- #licencia-appendix} + +#### Creative Commons Atribución/Reconocimiento-CompartirIgual 4.0 Licencia Pública Internacional + +Al ejercer los Derechos Licenciados (definidos a continuación), Usted acepta y +acuerda estar obligado por los términos y condiciones de esta Licencia +Internacional Pública de Atribución/Reconocimiento-CompartirIgual 4.0 de +Creative Commons ("Licencia Pública"). En la medida en que esta Licencia +Pública pueda ser interpretada como un contrato, a Usted se le otorgan los +Derechos Licenciados en consideración a su aceptación de estos términos y +condiciones, y el Licenciante le concede a Usted tales derechos en +consideración a los beneficios que el Licenciante recibe por poner a +disposición el Material Licenciado bajo estos términos y condiciones. + +##### Sección 1 – Definiciones. + +a. **Material Adaptado** es aquel material protegido por Derechos de + Autor y Derechos Similares que se deriva o se crea en base al + Material Licenciado y en el cual el Material Licenciado se traduce, + altera, arregla, transforma o modifica de manera tal que dicho + resultado sea de aquellos que requieran autorización de acuerdo con + los Derechos de Autor y Derechos Similares que ostenta el + Licenciante. A los efectos de esta Licencia Pública, cuando el + Material Licenciado se trate de una obra musical, una interpretación + o una grabación sonora, la sincronización temporal de este material + con una imagen en movimiento siempre producirá Material Adaptado. + +b. **Licencia de adaptador** es aquella licencia que Usted aplica a Sus + Derechos de Autor y Derechos Similares en Sus contribuciones + consideradas como Material Adaptado de acuerdo con los términos y + condiciones de esta Licencia Pública. + +c. **Una Licencia Compatible con BY-SA** es aquella que aparece en la + lista disponible en + [creativecommons.org/compatiblelicenses](//creativecommons.org/compatiblelicenses), + aprobada por Creative Commons, como una licencia esencialmente + equivalente a esta Licencia Pública. +d. **Derechos de Autor y Derechos Similares** son todos aquellos + derechos estrechamente vinculados a los derechos de autor, + incluidos, de manera enunciativa y no taxativa, los derechos sobre + las interpretaciones, las emisiones, las grabaciones sonoras y los + Derechos "Sui Generis" sobre Bases de Datos, sin importar cómo estos + derechos se encuentren enunciados o categorizados. A los efectos de + esta Licencia Pública, los derechos especificados en las secciones + [2(b)(1)-(2)](#s2b) no se consideran Derechos de Autor y Derechos + Similares. +e. **Medidas Tecnológicas Efectivas** son aquellas medidas que, en + ausencia de la debida autorización, no pueden ser eludidas en virtud + de las leyes que cumplen las obligaciones del artículo 11 del + Tratado de la OMPI sobre Derecho de Autor adoptado el 20 de + diciembre de 1996, y/o acuerdos internacionales similares. +f. **Excepciones y Limitaciones** son el uso justo (fair use), el trato + justo (fair dealing) y/o cualquier otra excepción o limitación a los + Derechos de Autor y Derechos Similares que se apliquen al uso el + Material Licenciado. +g. **Elementos de la Licencia** son los atributos que figuran en el + nombre de la Licencia Pública de Creative Commons. Los Elementos de + la Licencia de esta Licencia Pública son Atribución/Reconocimiento y + CompartirIgual. +h. **Material Licenciado** es obra artística o literaria, base de datos + o cualquier otro material al cual el Licenciante aplicó esta + Licencia Pública. +i. **Derechos Licenciados** son derechos otorgados a Usted bajo los + términos y condiciones de esta Licencia Pública, los cuales se + limitan a todos los Derechos de Autor y Derechos Similares que + apliquen al uso del Material Licenciado y que el Licenciante tiene + potestad legal para licenciar. +j. **Licenciante** es el individuo(s) o la entidad(es) que concede + derechos bajo esta Licencia Pública. +k. **Compartir** significa proporcionar material al público por + cualquier medio o procedimiento que requiera permiso conforme a los + Derechos Licenciados, tales como la reproducción, exhibición + pública, presentación pública, distribución, difusión, comunicación + o importación, así como también su puesta a disposición, incluyendo + formas en que el público pueda acceder al material desde un lugar y + momento elegido individualmente por ellos. +l. **Derechos "Sui Generis" sobre Bases de Datos** son aquellos + derechos diferentes a los derechos de autor, resultantes de la + Directiva 96/9/EC del Parlamento Europeo y del Consejo, de 11 de + marzo de 1996 sobre la protección jurídica de las bases de datos, en + sus versiones modificadas y/o posteriores, así como otros derechos + esencialmente equivalentes en cualquier otra parte del mundo. +m. **Usted** es el individuo o la entidad que ejerce los Derechos + Licenciados en esta Licencia Pública. La palabra **Su** tiene un + significado equivalente. + +##### Sección 2 – Ámbito de Aplicación. + +a. **Otorgamiento de la licencia**. + 1. Sujeto a los términos y condiciones de esta Licencia Pública, el + Licenciante le otorga a Usted una licencia de carácter global, + gratuita, no transferible a terceros, no exclusiva e irrevocable + para ejercer los Derechos Licenciados sobre el Material + Licenciado para: + A. reproducir y Compartir el Material Licenciado, en su + totalidad o en parte; y + B. producir, reproducir y Compartir Material Adaptado. + + 2. [Excepciones y + Limitaciones]{style="text-decoration: underline;"}. Para evitar + cualquier duda, donde se apliquen Excepciones y Limitaciones al + uso del Material Licenciado, esta Licencia Pública no será + aplicable, y Usted no tendrá necesidad de cumplir con sus + términos y condiciones. + 3. [Vigencia]{style="text-decoration: underline;"}. La vigencia de + esta Licencia Pública está especificada en la sección + [6(a)](#s6a). + 4. [Medios y formatos; modificaciones técnicas + permitidas]{style="text-decoration: underline;"}. El Licenciante + le autoriza a Usted a ejercer los Derechos Licenciados en todos + los medios y formatos, actualmente conocidos o por crearse en el + futuro, y a realizar las modificaciones técnicas necesarias para + ello. El Licenciante renuncia y/o se compromete a no hacer valer + cualquier derecho o potestad para prohibirle a Usted realizar + las modificaciones técnicas necesarias para ejercer los Derechos + Licenciados, incluyendo las modificaciones técnicas necesarias + para eludir las Medidas Tecnológicas Efectivas. A los efectos de + esta Licencia Pública, la mera realización de modificaciones + autorizadas por esta sección [2(a)(4)](#s2a4) nunca produce + Material Adaptado. + 5. [Receptores posteriores]{style="text-decoration: underline;"}. + A. [Oferta del Licenciante – Material + Licenciado]{style="text-decoration: underline;"}. Cada + receptor de Material Licenciado recibe automáticamente una + oferta del Licenciante para ejercer los Derechos Licenciados + bajo los términos y condiciones de esta Licencia Pública. + B. [Oferta adicional por parte del Licenciante – Material + Adaptado]{style="text-decoration: underline;"}. Cada + receptor del Material Adaptado por Usted recibe + automáticamente una oferta del Licenciante para ejercer los + Derechos Licenciados en el Material Adaptado bajo las + condiciones de la Licencia del Adaptador que Usted aplique. + C. [Sin restricciones a receptores + posteriores]{style="text-decoration: underline;"}. Usted no + puede ofrecer o imponer ningún término ni condición + diferente o adicional, ni puede aplicar ninguna Medida + Tecnológica Efectiva al Material Licenciado si haciéndolo + restringe el ejercicio de los Derechos Licenciados a + cualquier receptor del Material Licenciado. + 6. [Sin endoso]{style="text-decoration: underline;"}. Nada de lo + contenido en esta Licencia Pública constituye o puede + interpretarse como un permiso para afirmar o implicar que Usted, + o que Su uso del Material Licenciado, está conectado, + patrocinado, respaldado o reconocido con estatus oficial por el + Licenciante u otros designados para recibir la + Atribución/Reconocimiento según lo dispuesto en la sección + [3(a)(1)(A)(i)](#s3a1Ai). + +b. **Otros derechos**. + + 1. Los derechos morales, tales como el derecho a la integridad, no + están comprendidos bajo esta Licencia Pública ni tampoco los + derechos de publicidad y privacidad ni otros derechos personales + similares. Sin embargo, en la medida de lo posible, el + Licenciante renuncia y/o se compromete a no hacer valer ninguno + de estos derechos que ostenta como Licenciante, limitándose a lo + necesario para que Usted pueda ejercer los Derechos Licenciados, + pero no de otra manera. + 2. Los derechos de patentes y marcas no son objeto de esta Licencia + Pública. + 3. En la medida de lo posible, el Licenciante renuncia al derecho + de cobrarle regalías a Usted por el ejercicio de los Derechos + Licenciados, ya sea directamente o a través de una entidad de + gestión colectiva bajo cualquier esquema de licenciamiento + voluntario, renunciable o no renunciable. En todos los demás + casos, el Licenciante se reserva expresamente cualquier derecho + de cobrar esas regalías. + +##### Sección 3 – Condiciones de la Licencia. + +Su ejercicio de los Derechos Licenciados está expresamente sujeto a las +condiciones siguientes. + +a. **Atribución/Reconocimiento**. + + 1. Si Usted comparte el Material Licenciado (incluyendo en forma + modificada), Usted debe: + + A. Conservar lo siguiente si es facilitado por el Licenciante + con el Material Licenciado: + i. identificación del creador o los creadores del Material + Licenciado y de cualquier otra persona designada para + recibir Atribución/Reconocimiento, de cualquier manera + razonable solicitada por el Licenciante (incluyendo por + seudónimo si este ha sido designado); + ii. un aviso sobre derecho de autor; + iii. un aviso que se refiera a esta Licencia Pública; + iv. un aviso que se refiera a la limitación de garantías; + v. un URI o un hipervínculo al Material Licenciado en la + medida razonablemente posible; + + B. Indicar si Usted modificó el Material Licenciado y conservar + una indicación de las modificaciones anteriores; e + C. Indicar que el Material Licenciado está bajo esta Licencia + Pública, e incluir el texto, el URI o el hipervínculo a esta + Licencia Pública. + + 2. Usted puede satisfacer las condiciones de la sección + [3(a)(1)](#s3a1) de cualquier forma razonable según el medio, + las maneras y el contexto en los cuales Usted Comparta el + Material Licenciado. Por ejemplo, puede ser razonable satisfacer + las condiciones facilitando un URI o un hipervínculo a un + recurso que incluya la información requerida. + 3. Bajo requerimiento del Licenciante, Usted debe eliminar + cualquier información requerida por la sección + [3(a)(1)(A)](#s3a1A) en la medida razonablemente posible. + +b. **CompartirIgual**. + + Además de las condiciones de la sección [3(a)](#s3a), si Usted + Comparte Material Adaptado producido por Usted, también aplican las + condiciones siguientes. + + 1. La Licencia del Adaptador que Usted aplique debe ser una + licencia de Creative Commons con los mismos Elementos de la + Licencia, ya sea de esta versión o una posterior, o una Licencia + Compatible con la BY-SA. + 2. Usted debe incluir el texto, el URI o el hipervínculo a la + Licencia del Adaptador que aplique. Usted puede satisfacer esta + condición de cualquier forma razonable según el medio, las + maneras y el contexto en los cuales Usted Comparta el Material + Adaptado. + 3. Usted no puede ofrecer o imponer ningún término o condición + adicional o diferente, o aplicar ninguna Medida Tecnológica + Efectiva al Material Adaptado que restrinja el ejercicio de los + derechos concedidos en virtud de la Licencia de Adaptador que + Usted aplique. + +##### Sección 4 – Derechos "Sui Generis" sobre Bases de Datos. + +Cuando los Derechos Licenciados incluyan Derechos "Sui Generis" sobre +Bases de Datos que apliquen a Su uso del Material Licenciado: + +a. para evitar cualquier duda, la sección [2(a)(1)](#s2a1) le concede a + Usted el derecho a extraer, reutilizar, reproducir y Compartir todo + o una parte sustancial de los contenidos de la base de datos; +b. si Usted incluye la totalidad o una parte sustancial del contenido + de una base de datos en otra sobre la cual Usted ostenta Derecho + "Sui Generis" sobre Bases de Datos, entonces ella (pero no sus + contenidos individuales) se entenderá como Material Adaptado para + efectos de la sección [3(b)](#s3b); y +c. Usted debe cumplir con las condiciones de la sección [3(a)](#s3a) si + Usted Comparte la totalidad o una parte sustancial de los contenidos + de la base de datos. + +Para evitar dudas, esta sección [4](#s4) complementa y no sustituye Sus +obligaciones bajo esta Licencia Pública cuando los Derechos Licenciados +incluyen otros Derechos de Autor y Derechos Similares. + +##### Sección 5 – Exención de Garantías y Limitación de Responsabilidad. + +a. **Salvo que el Licenciante se haya comprometido mediante un acuerdo + por separado, en la medida de lo posible el Licenciante ofrece el + Material Licenciado tal como es y tal como está disponible y no se + hace responsable ni ofrece garantías de ningún tipo respecto al + Material Licenciado, ya sea de manera expresa, implícita, legal u + otra. Esto incluye, de manera no taxativa, las garantías de título, + comerciabilidad, idoneidad para un propósito en particular, no + infracción, ausencia de vicios ocultos u otros defectos, la + exactitud, la presencia o la ausencia de errores, sean o no + conocidos o detectables. Cuando no se permita, totalmente o en + parte, la declaración de ausencia de garantías, a Usted puede no + aplicársele esta exclusión.** +b. **En la medida de lo posible, en ningún caso el Licenciante será + responsable ante Usted por ninguna teoría legal (incluyendo, de + manera no taxativa, la negligencia) o de otra manera por cualquier + pérdida, coste, gasto o daño directo, especial, indirecto, + incidental, consecuente, punitivo, ejemplar u otro que surja de esta + Licencia Pública o del uso del Material Licenciado, incluso cuando + el Licenciante haya sido advertido de la posibilidad de tales + pérdidas, costes, gastos o daños. Cuando no se permita la limitación + de responsabilidad, ya sea totalmente o en parte, a Usted puede no + aplicársele esta limitación.** +c. La renuncia de garantías y la limitación de responsabilidad + descritas anteriormente deberán ser interpretadas, en la medida de + lo posible, como lo más próximo a una exención y renuncia absoluta a + todo tipo de responsabilidad. + +##### Sección 6 – Vigencia y Terminación. + +a. Esta Licencia Pública tiene una vigencia de aplicación igual al + plazo de protección de los Derechos de Autor y Derechos Similares + licenciados aquí. Sin embargo, si Usted incumple las condiciones de + esta Licencia Pública, los derechos que se le conceden mediante esta + Licencia Pública terminan automáticamente. +b. En aquellos casos en que Su derecho a utilizar el Material + Licenciado se haya terminado conforme a la sección [6(a)](#s6a), + este será restablecido: + + 1. automáticamente a partir de la fecha en que la violación sea + subsanada, siempre y cuando esta se subsane dentro de los 30 + días siguientes a partir de Su descubrimiento de la violación; o + 2. tras el restablecimiento expreso por parte del Licenciante. + + Para evitar dudas, esta sección [6(b)](#s6b) no afecta ningún + derecho que pueda tener el Licenciante a buscar resarcimiento por + Sus violaciones de esta Licencia Pública. + +c. Para evitar dudas, el Licenciante también puede ofrecer el Material + Licenciado bajo términos o condiciones diferentes, o dejar de + distribuir el Material Licenciado en cualquier momento; sin embargo, + hacer esto no pondrá fin a esta Licencia Pública. +d. Las secciones [1](#s1), [5](#s5), [6](#s6), [7](#s7), y [8](#s8) + permanecerán vigentes a la terminación de esta Licencia Pública. + +##### Sección 7 – Otros Términos y Condiciones. + +a. El Licenciante no estará obligado por ningún término o condición + adicional o diferente que Usted le comunique a menos que se acuerde + expresamente. +b. Cualquier arreglo, convenio o acuerdo en relación con el Material + Licenciado que no se indique en este documento se considera separado + e independiente de los términos y condiciones de esta Licencia + Pública. + +##### Sección 8 – Interpretación. + +a. Para evitar dudas, esta Licencia Pública no es ni deberá + interpretarse como una reducción, limitación, restricción, o una + imposición de condiciones al uso de Material Licenciado que + legalmente pueda realizarse sin permiso del titular, más allá de lo + contemplado en esta Licencia Pública. +b. En la medida de lo posible, si alguna disposición de esta Licencia + Pública se considera inaplicable, esta será automáticamente + modificada en la medida mínima necesaria para hacerla aplicable. Si + la disposición no puede ser reformada, deberá ser eliminada de esta + Licencia Pública sin afectar la exigibilidad de los términos y + condiciones restantes. +c. No se podrá renunciar a ningún término o condición de esta Licencia + Pública, ni se consentirá ningún incumplimiento, a menos que se + acuerde expresamente con el Licenciante. +d. Nada en esta Licencia Pública constituye ni puede ser interpretado + como una limitación o una renuncia a los privilegios e inmunidades + que aplican al Licenciante o a Usted, incluyendo aquellos surgidos a + partir de procesos legales de cualquier jurisdicción o autoridad. + + diff --git a/src/00_meta.md b/src/00_meta.md deleted file mode 100644 index 7dfbcd0..0000000 --- a/src/00_meta.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -links-as-notes: true -toc: true -title: Python -subtitle: -author: Ekaitz Zárraga -author-meta: Ekaitz Zárraga -lang: es-ES -polyglossia-lang: - name: spanish -license: CC-BY-SA ---- diff --git a/src/01_intro.md b/src/01_intro.md deleted file mode 100644 index b2a1752..0000000 --- a/src/01_intro.md +++ /dev/null @@ -1,207 +0,0 @@ -# Introducción - -Python es un lenguaje de programación de alto nivel orientado al uso general. -Fue creado por Guido Van Rossum y publicado en 1991. La filosofía de python -hace hincapié en la limpieza y la legibilidad del código fuente con una -sintaxis que facilita expresar conceptos en menos líneas de código que en otros -lenguajes. - -Python es un lenguaje de tipado dinámico y gestión de memoria automática. -Soporta múltiples paradigmas de programación, incluyendo la programación -orientada a objetos, imperativa, funcional y procedural e incluye una extensa -librería estándar. - -Pronto entenderás lo que todo esto significa, pero antes hay que instalar las -herramientas necesarias y trastear con ellas. - -## Instalación - -Para trabajar con python se necesita: - -- python3: el intérprete de python, en su versión 3. Verás que hay muchas - subversiones. Este documento cubre cualquiera de ellas. - -- pip: el gestor de paquetería de python. También se conoce como pip3 para - diferenciarlo del pip de python2. - -Nosotros añadiremos un par de amigos a la lista: - -- idle3: un editor de código python muy sencillo. Usaremos este porque - representa el ecosistema de forma muy simple. En el futuro, te recomiendo - usar algún otro editor más avanzado. - -- pipenv: el estándar de facto para gestionar entornos virtuales en python3. - Luego entenderás qué es eso. - - -### Instalación en distribuciones de Linux - -La instalación puede realizarse desde el gestor de paquetes habitual, ya que -python suele distribuirse en todos los repositorios de paquetes. - -En las distribuciones que usan el sistema de paquetes de Debian, puede -instalarse desde la terminal con el siguiente comando: - -``` bash -sudo apt-get install python3 python3-pip idle3 -``` - -### Instalación en otros sistemas - -Como siempre instalar en otros sistemas es más farragoso. Pero no es demasiado -difícil en este caso. La instalación puede realizarse con una descarga desde la -página web oficial de python: - - - -Una vez ahí seleccionar la versión necesaria, descargar el instalador y seguir -las instrucciones de éste. Recuerda seleccionar **instalar pip** entre las -opciones y activar la casilla de **añadir python al PATH**, que permitirá que -que ejecutes programas de python sin problemas. También puedes añadir -**IDLE**, el programa que sirve para editar el código, pero te recuerdo que -es un programa muy sencillo, que nos servirá para entender lo básico del -entorno sin ocultarnos el proceso, pero que más adelante podrás utilizar otros -editores que simplifiquen tareas. - - -## Admira el paisaje - -Una vez que has instalado python, es interesante ver lo que eso significa. -Python es un intérprete de código fuente del lenguaje del mismo nombre. -Concretamente, la que has instalado es una de las posibles implementaciones (la -implementación de referencia, en este caso) de este intérprete, conocida como -CPython, en su versión 3. Existen otras implementaciones, cada una con sus -peculiaridades, pero ésta es la principal y la más usada. - -Como intérprete que es, python es capaz de leer un archivo escrito en su -lenguaje y ejecutar sus órdenes en tu computadora. Ésta es principalmente su -labor. Sin embargo, también es capaz de realizar esta operación de forma -interactiva recibiendo las órdenes una por una y devolviendo el resultado de su -ejecución como respuesta. Este proceso se conoce como REPL, acrónimo de -read-eval-print-loop (lee-evalúa-imprime-repite), aunque en otros lugares se le -conoce como la shell de python. - -> NOTA: La shell de python (o REPL) y la shell del sistema son cosas -> diferentes. La shell de sistema también es un intérprete pero del lenguaje -> que el sistema ha definido (Bash, PowerShell...) y no suele ser capaz de -> entender python. - -Para acostumbrarte a la shell te propongo que abras IDLE. Lo primero que verás -será parecido a esto: - -``` python -Python 3.6.8 (default, Oct 7 2019, 12:59:55) -[GCC 8.3.0] on linux -Type "help", "copyright", "credits" or "license()" for more information. ->>> -``` - -Todo lo que escribas tras el símbolo `>>>` será interpretado como una orden y -cuando la termines pulsando la tecla `ENTER` de tu teclado, recibirás el -resultado de la ejecución de la orden insertada. El acrónimo REPL define el -comportamiento de este ciclo a las mil maravillas: - -> NOTA: En este documento, siempre que veas el símbolo `>>>` significa que se -> trata de un ejemplo ejecutado en la REPL. Si no lo ves, se tratará del -> contenido de un archivo de código python ejecutado de forma independiente. - -1. Lee lo que introduces. -2. Lo evalúa, obteniendo así un valor como resultado. -3. Lo imprime. -4. Repite el ciclo volviendo a leer. - -Por tanto, si introduces un valor directamente será devuelto: - -``` python ->>> 1 -1 -``` - -Y si lo alteras, por ejemplo, con una operación matemática sencilla, devuelve -el resultado correspondiente: - -``` python ->>> 2+2 -4 -``` - -Como ejercicio te propongo lo siguiente: - -1. Abre la shell de python (puedes hacerlo en IDLE o desde la shell de sistema - ejecutando `python` o `python3`). -2. Entra en la ayuda interactiva. PISTA: el mensaje que aparece al abrir la - REPL te dice cómo. -3. Sal de la ayuda (descubre tú mismo cómo se hace). -4. Ejecuta `import this` y lee el resultado. - - -### Tu primer archivo de código fuente - -La REPL es interesante para probar y depurar tus programas (o para usarla como -calculadora), pero es necesario grabar tus programas en ficheros si quieres -poder volver a ejecutarlos más adelante o compartirlos. - -En IDLE puedes abrir un nuevo documento de código en el menú de archivo. Una -vez lo tengas, como aún no sabes python puedes introducir lo siguiente: - -``` python -nombre = "Guido" -print("Hola, " + nombre) -``` - -Si guardas el fichero y pulsas `F5` (Ejecutar módulo), verás que en la pantalla -de la REPL aparece el resultado `Hola, Guido`. - -Como ves, el resultado de ejecutar los ficheros de código fuente aparece en la -shell, pero únicamente aparece lo que explícitamente le has pedido que imprima -con la orden `print`. - -Para entender el valor de la REPL, te sugiero que vayas a su ventana, y justo -después del resultado de la ejecución hagas lo siguiente: - -``` python -Hola, Guido ->>> nombre -'Guido' ->>> -``` - -La REPL conoce el valor `nombre` a pesar de que tu programa ha terminado de -ejecutarse. Esto es interesante a la hora de probar y analizar el programa. - - -### La vida real - -En realidad, los programas de producción no se ejecutan en una shell como la -que IDLE nos brinda. IDLE sólo está facilitando nuestro trabajo como -desarrolladores, como otros entornos de desarrollo hacen, cada uno a su manera. -En producción el código se levantará ejecutando el intérprete de python -directamente con nuestro programa como input. Por ejemplo en la shell **de -sistema** usando el siguiente comando. - -``` bash -python ejemplo.py -``` - -También es posible ejecutar los programas de python desde la interfaz gráfica, -pero internamente el resultado será el mismo. Siempre que todo esté bien -instalado y configurado, el sistema operativo despertará un intérprete de -python que ejecute las órdenes del fichero. - -Es importante ser consciente de lo que ocurre bajo la alfombra, para así ser -capaces de intervenir si encontramos errores. - -Más adelante, en la sección sobre módulos e importación volveremos aquí y -estudiaremos cómo se cargan y se interpretan los programas. - - -## Lo que has aprendido - -Has instalado python y te has acostumbrado a la herramienta (IDLE) que usarás -durante tu aprendizaje. Has ejecutado tu primer fichero y encontrado la -potencia de la REPL. - -Además, has abierto la ayuda y te has leído el Zen de Python, que pronto iremos -desgranando juntos. - -Para ser una introducción no está nada mal. diff --git a/src/02_datos.md b/src/02_datos.md deleted file mode 100644 index 1eb2f1a..0000000 --- a/src/02_datos.md +++ /dev/null @@ -1,662 +0,0 @@ -# Trabajando con datos - -Programar es, principalmente, tratar con datos y los datos no son más que -piezas de información que se estructura de una forma concreta. - -## Nombrando datos - -Los datos se almacenan en la memoria principal de la computadora. Puedes -imaginarte la memoria como un conjunto de cajas identificadas con direcciones, -como si fuesen los buzones de un portal con muchos pisos. Para utilizar datos, -éstos se guardan en los diferentes cajones y se van extrayendo y actualizando. -La importancia de nombrar las cosas es evidente, si no guardamos un listado de -dónde hemos guardado qué no podemos recuperar los datos que hemos creado. - -Al igual que en las matemáticas se utilizan los símbolos para representar -posibles valores y simplificar los procesos, en los lenguajes de programación -se utilizan símbolos para definir referencias a los datos y así poder referirse -a los mismos datos por su nombre sin tener que introducir su contenido -constantemente. Los lenguajes de programación aportan al programador la -facilidad de poder crear sus propios nombres para no tener que usar las -direcciones propias de la memoria, que normalmente son número sin más -significado que su posición, la mayor parte de las veces permitiendo al -programador abstraerse de estos detalles internos. - -En python existe un operador para declarar símbolos que hacen referencia a un -valor: el operador `=`. Este operador enlaza el valor de la derecha con el -nombre de la izquierda. Con un ejemplo es más fácil de comprender. Probando en -la REPL: - -``` python ->>> a = 10 ->>> a -10 ->>> a + 1 -11 -``` - -Como ves, en el primer paso se asocia el valor `10` al identificador `a` y más -adelante se puede utilizar `a` para hacer referencia al valor enlazado. - -Las referencias han de ser declaradas antes de usarse, si no, el intérprete no -las conoce y lanza una excepción: - -``` python ->>> b -Traceback (most recent call last): - File "", line 1, in - b -NameError: name 'b' is not defined -``` - -> NOTA: Los nombres para poder ser interpretados correctamente por python deben -> cumplir unas normas estrictas: -> -> - No pueden tener espacios. -> - Sólo pueden estar formados por combinaciones de letras, números y el -> símbolo `_`. -> - No pueden empezar por un dígito. - -### Todo es una referencia - -El comportamiento de estas referencias puede no ser intuitivo si vienes de -otros lenguajes. El operador `=` enlaza, como se dice anteriormente, un nombre -a un valor concreto, pero eso no significa que copie su contenido. En python -los valores y los nombres son conceptos independientes. Los valores ocupan su -lugar en la memoria y los nombres hacen referencia a dónde se encuentran estos -valores. Es muy común tener muchas referencias a la misma estructura de datos y -transformar su contenido desde todas las referencias. - -A nivel técnico, lo que en python se conoce como variable y en este documento -hemos hecho el esfuerzo de llamar referencia es lo que en otros lenguajes se -conoce como un puntero y la labor del operador `=` es la de asignar el puntero -a la dirección donde se encuentran los datos a los que debe apuntar. - -Volviendo a la metáfora de los buzones, en lenguajes de más bajo nivel como C -estás obligado a seleccionar qué buzón vas a utilizar para introducir cada -dato. Por lo que si cambia el tipo de dato a gestionar, puede que el buzón se -quede pequeño o que los datos se interpreten de forma incorrecta. Sin embargo, -python guarda las referencias de forma independiente a los valores y adecua el -número de buzones en uso al tamaño de los datos de los que dispones -reordenando, si es necesario, la estructura completa de valores y referencias. -A este concepto se le conoce como gestión automática de memoria (*automatic -memory management*). - -En resumen, las referencias en python son únicamente un recordatorio que sirve -para poder acceder a un valor. Esto cobra importancia más adelante, y no vamos -a rehuir la responsabilidad de enfangarnos en ello. - -## Tipos - -Tal y como aclaraba el texto de la introducción, python tiene un sistema de -tipos dinámico (*dynamic type system*). Lo que significa que gestiona los tipos -de forma automática, permitiendo a los nombres hacer referencia a diferentes -tipos de valor durante la ejecución del programa a diferencia de otros -lenguajes como, por ejemplo, C, donde el tipo de las variables debe ser -declarado de antemano y no puede cambiarse. - -Esto es posible debido al fenómeno explicado en el apartado anterior por un -lado, y, por el otro, a que los datos de python son un poco más complejos que -en otros lenguajes y guardan una pequeña nota que indica cómo deben ser -interpretados. - -Si en algún momento se le pide a python que asigne un valor de un tipo distinto -al que una referencia tenía no habrá problemas porque es el propio dato quien -guarda la información suficiente para saber cómo entenderlo. Las referencias -sólo almacenan dónde se guarda este dato. - -> NOTA: Seguramente te habrás dado cuenta de que el funcionamiento de python es -> más ineficiente que el de C o lenguajes similares pero mucho más flexible. Y -> así es. A la hora de elegir el lenguaje debemos valorar cuál nos interesa -> más para la labor que vamos a realizar. - -### Tipos simples - -Hemos denominado tipos simples a los que no empaquetan más de un valor -internamente. En otros lenguajes o contextos se les conoce como escalares. - - -#### La nada - -La nada en python se representa con el valor `None` y es útil en innumerables -ocasiones. - -#### Boolean - -Los valores booleanos expresan *verdad* (`True`) o *mentira* (`False`) y sirven -para gestionar lógica desde esos términos. Mas adelante los veremos en acción. - -#### Integer - -Los Integer, números enteros en inglés, ya han aparecido anteriormente. Para -usar un número entero puedes introducirlo tal cual. Recuerda que hay enteros -positivos y negativos. El símbolo para marcar números negativos en python es el -`-`, que coincide con el operador de la resta. - -``` python ->>> 14 -14 ->>> -900 --900 ->>> -``` - -Los números enteros también pueden expresarse en otras bases, como en -hexadecimal. Dependiendo de la aplicación en la que te encuentres igual -necesitas mirarlo más en detalle: - -``` python ->>> 0x10 -16 -``` - -#### Float - -Los números Float, o de coma flotante, son números no-enteros. Ten cuidado -con ellos porque la coma flotante es peculiar. - -El nombre coma flotante viene de que la coma no siempre se mantiene con la -misma precisión. En realidad estos números se guardan como los números en -notación científica (2,997E8 m/s para la velocidad de la luz, por ejemplo). -La notación científica siempre implica tener una coma, pero cuya posición se -varía con el exponente posterior. - -El modo de almacenamiento de los números de coma flotante es muy similar a la -notación científica: se almacenan dos valores de tamaño fijo, la *mantisa* y el -*exponente* para, de este modo, poder ajustar la precisión en función del -tamaño del número almacenado. No es lo mismo expresar la velocidad de la luz, -2,997E8 m/s, que la longitud de onda del color rojo, 6,250E-7 m, ambos -valores tienen un tamaño muy distinto, pero usando dos *mantisas* de tamaño -similar, cuatro dígitos (2997 y 6250), y dos exponentes de tamaño similar, un -dígito (8 y -7), hemos expresado valores muy diferentes, ambos con la misma -precisión relativa. - -El problema viene cuando nos apetece mezclarlos, por ejemplo, sumándolos. -Imagina que tienes dos valores de esas dimensiones, uno de millones y otro de -millonésimas partes de la unidad, y los quieres sumar entre ellos. Si sólo -tienes una mantisa limitada para representarlos, la suma resultará en el -redondeo de ambos a la precisión que tienes disponible. Es decir: el resultado -será el valor grande y el pequeño se perderá en el redondeo. - -Aunque en este caso la suma es precisa, si tratas con un billón de valores -pequeños y uno grande y quieres obtener la suma de todos y los sumas en parejas -siempre con el grande, en cada suma se descartará el valor pequeño en el -redondeo y el resultado total será el valor grande que tenías. Sin embargo, si -sumas los valores de tamaño similar entre ellos primero, obtendrás un valor -suficientemente grande como para alterar el resultado de la suma contra el -valor grande al final, dando así un resultado distinto. Te recomiendo entonces, -que si te encuentras en una situación como ésta tengas cuidado y, por ejemplo, -ordenes los números de menor a mayor antes de sumarlos, para obtener una suma -de buena precisión. - -En realidad, el exponente en el caso de python (y en casi todos los demás -lenguajes) no está elevando un 10 a la enésima potencia, si no que lo hace con -un 2. Por lo que lo expresado anteriormente es un poco distinto. Esto provoca -que algunos números de coma flotante no sean tan redondos como deberían (por -ejemplo, `2.999999999`, cuando debería ser `3.0`) y compararlos entre ellos -puede ser desastroso. Para evitarlo, te recomiendo que *siempre* redondees los -valores a una precisión que puedas controlar. - -Aunque realmente es algo más complejo[^1], lo que sabes ahora te evitará -problemas en el futuro, sobre todo cuando analices datos, uno de los sectores -donde python se usa de forma extensiva. - -[^1]: https://en.wikipedia.org/wiki/Floating-point_arithmetic - -Declarar números de coma flotante es natural porque usa una sintaxis a la que -estamos acostumbrados: - -``` python ->>> 1E10 -10000000000.0 ->>> 1.0 -1.0 ->>> 0.2E10 -2000000000.0 ->>> -``` - - -#### Complex - -Python soporta números complejos y los expresa utilizando la letra `j`. Como -suponen un caso bastante concreto no los analizaremos en detalle. Pero tienes -disponible la documentación de python para lo que quieras. - -``` python ->>> 1-23j -(1-23j) -``` - -#### String - -Un String es una cadena de caracteres. Los Strings en python son, a diferencia -de en otros lenguajes, un escalar, al contrario de lo que la primera definición -que hemos expresado puede hacernos pensar. En python los Strings no son un -conjunto de caracteres alfanuméricos sueltos que se comportan como un valor, es -al revés. El concepto de carácter no existe y ha de expresarse con un String de -longitud 1. - -Los strings se expresan rodeando texto con comillas dobles, `"`, o simples `'` -(el símbolo del apóstrofe). - -``` python ->>> "Hola" -'Hola' ->>> 'Hola' -'Hola' -``` - -El hecho de que haya dos opciones para delimitar los strings facilita el -etiquetado como string de valores que contienen las propias comillas en su -contenido. También puede utilizarse la contrabarra `\` para cancelar la acción -de las comillas. - - -``` python ->>> "Tiene un apóstrofe: Luke's" -"Tiene un apóstrofe: Luke's" ->>> 'Tiene un apóstrofe: Luke's' -SyntaxError: invalid syntax ->>> 'Tiene un apóstrofe: Luke\'s' -"Tiene un apóstrofe: Luke's" -``` - -> NOTA: la contrabarra sirve para introducir caracteres especiales o caracteres -> de escape[^2]: `\n` salto de línea, `\t` tabulador, etc. Que son una herencia -> de los tiempos de las máquinas de escribir, pero son aún útiles y muy usados. -> Para expresar la propia contrabarra ha de escaparse a sí misma con otra -> contrabarra para que no evalúe el siguiente caracter como un caracter de -> escape.: `\\`. - -[^2]: https://en.wikipedia.org/wiki/Escape_character - - -### Tipos compuestos - -Hemos denominado tipos compuestos a los que pueden incluir diferentes -combinaciones de tipos simples. Estos tipos dejan de ser escalares y se -comportan como vectores o conjuntos de datos. Estos tipos de dato pueden -contenerse a sí mismos, por lo que pueden crear estructuras anidadas complejas. - -#### Tuple - -Las tuplas o *tuple* en inglés son el tipo compuesto más sencillo en python. -Las tuplas definen un conjunto de valores de cualquier tipo. - -Se declaran utilizando paréntesis añadiendo sus elementos separados por comas. -Y se accede a sus contenidos utilizando los corchetes e introduciendo el índice -del elemento que se quiere extraer. - -``` python ->>> (2, 3, "HOLA") -(2, 3, 'HOLA') ->>> (2, 3, "HOLA")[0] -2 -``` -En python los índices comienzan en `0`. - -#### List - -Las listas o *list* son muy similares a las tuplas, pero son algo más complejas -porque pueden alterarse así mismas. A diferencia de todos los tipos que hemos -visto hasta ahora, tanto las listas como los diccionarios que veremos a -continuación son mutables. Esto significa que puede transformarse su valor, más -adelante trataremos esto en detalle. - -De momento, recuerda que las listas se construyen de forma similar a las -tuplas, pero utilizando corchetes en lugar de paréntesis. La forma de acceder a -los índices es idéntica. - -``` python ->>> [2, 3, "HOLA"] -[2, 3, 'HOLA'] ->>> [2, 3, "HOLA"][0] -2 -``` - -#### Dictionary - -Los diccionarios o *dictionary* son un tipo de dato similar a los dos -anteriores, pero que en lugar de utilizar índices basados en la posición de sus -elementos usan claves arbitrarias definidas por el usuario. - -Además, los diccionarios no están ordenados así que no se puede suponer que las -claves siempre van a estar en el orden en el que se introducen. - -Para declarar diccionarios es necesario indicar qué claves se quieren usar. Las -claves son de tipo string. En el caso de los diccionarios, además, se utilizan -llaves para definirlos. El acceso a sus valores se realiza con los corchetes, -del mismo modo que en las listas, pero es necesario seleccionar la clave para -acceder. - -Los diccionarios, al igual que las listas, son mutables. Como veremos en -seguida. - -``` python ->>> {"nombre": "Guido", "apellido": "Van Rossum", "popularidad": 8.0} -{'nombre': 'Guido', 'apellido': 'Van Rossum', 'popularidad': 8.0} ->>> {"nombre": "Guido", "apellido": "Van Rossum", "popularidad": 8.0}["popularidad"] -8.0 -``` - -#### Set - -Los *sets* son muy similares a las listas y tuplas, pero con dos -peculiaridades: - -- Sus valores son únicos. No pueden repetirse. -- No están ordenados - -Estas dos características tan estrictas, lejos de ser limitantes, aportan una -mejora radical en su rendimiento. Buscar elementos en un set es extremadamente -eficiente y se usan principalmente para esa labor. - -Si quieres validar en algún momento que un valor pertenece a un conjunto de -valores, el set es el tipo de dato que estás buscando. - -Los sets se declaran también usando las llaves, como un diccionario, pero no -usan claves. - -``` python ->>> {"a", "b", 1} -{'a', 1, 'b'} -``` - -## Conversión - -Ahora que conoces los valores sé que quieres saber cómo cambiar de uno a otro. -Cómo leer un Integer desde un String etc. Python tiene funciones para construir -sus diferentes tipos de datos a partir de los diferentes inputs posibles. - -Aunque aún no sabes ejecutar funciones te adelanto cómo se hace con algunos -ejemplos: - -``` python ->>> bool(1) -True ->>> bool(0) -False ->>> int("hola") -Traceback (most recent call last): - File "", line 1, in - int("hola") -ValueError: invalid literal for int() with base 10: 'hola' ->>> int("10") -10 ->>> float(10) -10.0 ->>> complex(19) -(19+0j) ->>> str(10) -'10' ->>> tuple([1,2,3]) -(1, 2, 3) ->>> list((1,2,3)) -[1, 2, 3] ->>> dict((("a", 1),("b", 2))) -{'a': 1, 'b': 2} ->>> set([1,2,2,3,4,4,4,4,4]) -{1, 2, 3, 4} -``` - -Los propios nombres de las funciones son bastante representativos de a qué tipo -convierten. Si quieres saber más puedes ejecutar `help(nombre)` y ver qué te -cuenta la ayuda. - -> NOTA: Fíjate que si conviertes una secuencia de valores repetidos a *set* -> únicamente almacena los que no se repiten. Es uno de los usos más comunes que -> tienen. - - -## Operadores - -Ahora que sabes el contexto en el que vas a jugar, necesitas poder alterar los -datos. - -Existen operadores básicos que te permiten transformar los datos, algunos ya -los has visto antes, el operador `=`, que sirve para nombrar cosas, la suma -(`+`) o la resta (`-`), pero hay otros. - -``` python ->>> 1 + 1 -2 ->>> 10 - 9 -1 ->>> 10 ** 2 -100 ->>> -``` - -Los siguientes apartados muestran algunos operadores que es interesante -memorizar. - - -### Pruebas de verdad - -Las pruebas de verdad generan un valor booleano desde una pareja de valores. A -continuación una lista de las pruebas de verdad con unos ejemplos: - -| operator | meaning | -|---|---| -| < | strictly less than | -| <= | less than or equal | -| > | strictly greater than | -| >= | greater than or equal | -| == | equal | -| != | not equal | -| is | object identity | -| is not | negated object identity | -| in | containment test | -| not in | negated containment test | - -``` python ->>> 1 > 1 -False ->>> 1 >= 1 -True ->>> 1 not in (0, 2, 3) -True -``` - -Aunque en otros lenguajes no es posible, la notación matemática habitual se -puede utilizar en python concatenando pruebas de verdad: - -``` python ->>> x = 1.2 ->>> 1 < x < 2 -True -``` - -### Operadores lógicos - -Los operadores lógicos mezclan booleanos y son muy importantes, sobre todo para -combinar las pruebas de verdad. - -| operator | meaning | -|---|---| -| and | logical and, returns True if both True, False otherwise | -| or | logical or, returns True if any True, False otherwise | -| not | logical not, returns True if False, True otherwise | - -La mayor parte de los operadores son binarios (como la suma), necesitan dos -valores y devuelven otro, pero existe al menos una excepción que funciona con -un único valor. El operador `not` sirve para darle la vuelta a un Booleano. - -``` python ->>> not True -False ->>> True and True -True ->>> False and True -False ->>> False or True -True ->>> 1 > 0 and 2 > 1 -True -``` - -### Matemáticos - -Los operadores matemáticos transforman números entre ellos. Casi todos son -conocidos por su operación matemática. - -| operator | meaning | -|---|---| -| + | addition | -| - | subtraction or negative | -| `*` | multiplication | -| / | division | -| `**` | power | -| % | remainder | - -### Ternary operator - -Existe además un operador que puede usar tres parámetros, el *inline-if* (*if -en línea*), o *ternary operator*[^ternary]. El *ternary operator* se comporta -así: - -``` python ->>> 1 if 1 > 9 else 9 -9 ->>> 1 if 1 < 9 else 9 -1 -``` - -[^ternary]: - - -### Operación en función del tipo - -Python simplifica muchas tareas transformando el comportamiento de los -operadores en función del tipo de dato sobre el que trabajan. Los operadores -matemáticos están preparados para trabajar sobre números (de cualquier tipo) -pero la verdad es que algunos pueden ejecutarse sobre otros formatos. Por -ejemplo: - -``` python ->>> "a" + "a" -'aa' ->>> [1,2] + [3,4] -[1, 2, 3, 4] -``` - -Esto se debe a que la funcionalidad del operador `+` ha sido diseñada para -operar de forma especial en Strings y en Listas, haciendo una concatenación. - -En el futuro, cuando aprendas a diseñar tus propios tipos podrás hacer que los -operadores les afecten de forma especial, tal y como pasa aquí. - -### Precedencia - -La precedencia en python es muy similar a la matemática y usa las mismas reglas -para marcarla de forma explícita. Recuerda, en matemáticas se utilizan los -paréntesis para esto. - -Los operadores siempre trabajan con sus correspondientes valores y python los -resuelve de forma ordenada. Si generas una operación muy compleja, python la -irá desgranando paso a paso y resolviendo las parejas una a una, cuanto más te -acostumbres a hacerlo en tu mente menos errores cometerás. - -``` python ->>> 8 + 7 * 10 == (8 + 7) * 10 -False ->>> 8 + 7 * 10 -78 ->>> (8 + 7) * 10 -150 ->>> 78 == 150 -False -``` - -## Mutabilidad - -Ya adelantamos que el operador `=` sirve para nombrar cosas. Ahora vamos a -combinar esa propiedad con la mutabilidad, o la propiedad de las cosas de -alterarse a sí mismas. Empecemos con un ejemplo: - -``` python ->>> a = 10 ->>> b = a + 10 ->>> b -20 ->>> a = b ->>> a -20 -``` - -En este ejemplo hacemos que `a` haga referencia al valor 10, y después creamos -`b` a partir de `a` y otro 10. Gracias a la precedencia, primero se resuelve el -lado derecho completo obteniendo un 20 y después se asigna la referencia `b` a -ese valor. - -Si queremos, después podemos reasignar el símbolo `a` a otro valor nuevo, en -este caso al que hacer referencia `b` que es 20. - -En este primer ejemplo, ningún valor está siendo alterado, si te fijas, sólo -estamos creando nuevos valores y cambiando las referencias a éstos. - -Con los datos mutables podemos alterar los valores. Lo vemos con otro ejemplo: - -``` python ->>> a = {} ->>> b = a ->>> a["cambio"] = "hola!" ->>> b -{'cambio': 'hola!'} -``` - -Primero creamos un diccionario vacío y le asignamos la referencia `a`. Después -le asignamos la referencia `b` a quien referenciaba `a`, es decir, al -diccionario vacío. Ambas referencias apuntan al mismo dato. Si después usamos -alguna alteración en el diccionario `a` como asignarle una nueva clave, `b`, -que hace referencia al mismo diccionario, también ve los cambios. Esto mismo -podría hacerse si se tratara de listas, ya que tienen la capacidad de alterarse -a sí mismas, pero nunca podría hacerse en tuplas, porque son inmutables. - -> NOTA: Los diccionarios y las listas soportan un montón de funciones y -> alteraciones, no se mencionan en este apartado porque nunca terminaría. Se -> dejan para el futuro y para los ejemplos que se verán durante el documento. - -Si intentásemos un ejemplo similar en tupla, no nos dejaría: - -``` python ->>> a = () ->>> b = a ->>> a[0] = 1 -Traceback (most recent call last): - File "", line 1, in - a[0] = 1 -TypeError: 'tuple' object does not support item assignment -``` - -Ten cuidado cuando trates con elementos mutables, sobre todo si tienen muchas -referencias, porque puede que estés alterando los valores en lugares que no te -interesa. Para evitar este tipo de problemas, puedes generar copias de los -objetos, pero el proceso es poco eficiente y tedioso. - -En este segundo caso, creamos una copia de `a` para que `b` sea independiente -de los cambios que ocurran en ésta. Aquí ya no estamos haciendo referencia -desde `b` a los datos que había en `a`, sino a una copia de éstos, almacenada -en otro lugar. - -``` python ->>> a = {} ->>> b = dict(a) ->>> a["cambio"] = "hola!" ->>> b -{} ->>> a -{'cambio': 'hola!'} -``` - -## Lo que has aprendido - -En este apartado has conocido los tipos fundamentales de python y cómo -convertir de uno a otro. Además, al conocer los operadores y su funcionamiento -ya eres más o menos capaz de usar python como una calculadora. - -Además, has tenido ocasión de entender de forma superficial varios conceptos -avanzados como el manejo automático de memoria, el tipado dinámico y los -números de coma flotante, que son muy interesantes a la hora de trabajar porque -te permiten comprender la realidad que tienes a tu alrededor. diff --git a/src/03_estructura.md b/src/03_estructura.md deleted file mode 100644 index 9d8cd5e..0000000 --- a/src/03_estructura.md +++ /dev/null @@ -1,550 +0,0 @@ -# Estructura del lenguaje - -Aunque ya sabes usar python de forma sencilla, aún no hemos tratado el -comportamiento del lenguaje y cómo se estructura su sintaxis más allá de varios -ejemplos sencillos y planos. En este apartado trataremos la estructura y las -diferentes formas de controlar el flujo del programa. - -Como en la mayor parte de lenguajes de programación conocidos, python ejecuta -las órdenes de arriba a abajo, línea por línea. - -Para demostrarlo, prueba a abrir un nuevo fichero, llenarlo con este contenido -y ejecutarlo (`F5`). - -``` python -print("Esta línea va primero") -print("Esta línea va segundo") -print("Esta línea va tercero") -print("Esta línea va cuarto") -``` - -Verás que el resultado del programa es siempre el mismo para todas las veces -que lo ejecutes y siempre salen los resultados en el mismo orden. - -Para poder alterar el orden de los comandos, o elegir en función de una -condición cuales se ejecutan, python dispone de unas estructuras. Pero, antes -de contarte cuales son, te adelanto su forma general. Normalmente se declaran -en una línea terminada en `:` y su cuerpo se sangra hacia dentro. La sangría (o -indentación si lo calcamos del inglés[^indent]) es lo que define dónde empieza -o termina un *bloque* en python. Las líneas consecutivas sangradas al mismo -nivel se consideran el mismo *bloque*. - -#### Bloques - -Los *bloques* de código son conjuntos de órdenes que pertenecen al mismo -contexto. Sirven para delimitar zonas del programa, cuerpos de sentencias, etc. - -- Puedes comenzar nuevos bloques incrementando el nivel de sangría. -- Los bloques pueden contener otros bloques. -- Los bloques terminan cuando el sangrado disminuye. - -Es **muy importante** sangrar los bloques correctamente, usando una sangría -coherente. Puedes usar dos espacios, el tabulador, cuatro espacios o lo que -desees, pero elijas lo que elijas debe ser coherente en todo el documento. -IDLE y los editores de código en general puede configurarse para usar una -anchura de indentación concreta, que se insertará cuando pulses la tecla -tabulador. El estándar de python es cuatro espacios. - -[^indent]: - -## Sintaxis general - -La sintaxis de python es sencilla en su concepción pero ha ido complicándose a -medida que el lenguaje ha ido creciendo. En este apartado únicamente se -mencionan los puntos más comunes de la sintaxis, dejando para futuros apartados -los detalles específicos de conceptos que aún no se han explicado. - -### Comentarios - -Los comentarios son *fundamentales* en el código fuente. El intérprete los -ignora pero son primordiales para explicar detalles de nuestro código a otros -programadores o a nosotros mismos en el futuro. Comentar bien el código fuente -es un arte en sí mismo. - -Los comentarios en python se introducen con el símbolo `#`. Desde su aparición -hasta el final de la línea se considera un comentario y python lo descarta. -Pueden iniciarse a mitad de línea o en el inicio, tal y como se muestra a -continuación: - -``` python -# En este ejemplo se muestran comentarios, como este mismo -print("Hola") # Esto es otro comentario -``` - -### Control de flujo - -Como ya se ha adelantado, es posible cambiar el orden de ejecución del programa -en función de unas normas o evitar que el programa ejecute ciertas sentencias. -A esto se le conoce como *control de flujo*. - -#### Condicionales - -Las condicionales son herramientas de *control de flujo* que permiten separar -la ejecución en diferentes ramas en función de unas condiciones. En python sólo -existe el condicional `if` («si», en castellano), aunque existen otras -estructuras para conseguir el mismo efecto, no las trataremos aún. - -Ésta es la sintaxis del `if`: - -``` python -if condición: - # Este bloque se ejecuta si la condición se cumple -elif condiciónN: - # Este bloque (opcional) se ejecuta si las condiciones previas no se - # cumplen y la condiciónN sí se cumple -else: - # Este bloque (opcional) se ejecuta si no se cumplen todas las condiciones - # previas -``` - -Tal y como se muestra en el ejemplo, los bloques `elif` y el bloque `else` son -opcionales. Es posible, y muy común además, hacer un `if` únicamente con el -apartado inicial. - -Si te preguntas qué condiciones debes usar, es tan simple como usar sentencias -de python cuyo resultado sea `True` o `False`. Cuando la sentencia resulte en -un `True` la condición se cumplirá y el bloque interior se ejecutará. - -En resumen, el *if* ejecuta el bloque *si* la condición se cumple. - -#### Bucles - -Existen dos tipos de sentencia para hacer repeticiones en python, ambas son -similares al `if`, pero en lugar de elegir si una pieza de código se ejecuta o -no, lo que deciden es si es necesario repetirla en función de una condición. - -##### While - -El `while` («mientras que») es la más sencilla de estas estructuras, y la menos -usada. - -``` python -while condición: - # Este bloque se ejecutará siempre que la condición se considere verdadera -``` - -El `while` comprueba la condición en primer lugar, si resulta en `True` ejecuta -el bloque interno y vuelve a comprobar la condición. Si es `True`, ejecuta el -bloque de nuevo, y así sucesivamente. - -Es decir, ejecuta el bloque *mientras que* la condición se cumple. - -##### For - -Los bucles *for* («para») son los más complejos y más usados, - -``` python -for preparación: - # Bloque a repetir si la preparación funciona con el contexto creado por la - # preparación -else: - # Bloque (opcional) a ejecutar si el bloque cuerpo no termina de forma - # abrupta con un `break` -``` - -No te preocupes ahora mismo por el `else`, ya que se suele considerar python -avanzado y no suele usarse. Más adelante en este capítulo analizaremos un -ejemplo. - -En lo que a la preparación se refiere, el `for` es relativamente peculiar, -sirve para ejecutar el primer bloque para un contexto concreto, el creado por -una sentencia de preparación. Si la preparación falla el bucle se rompe. - -El uso más común del `for` es el de iterar en secuencias gracias al operador -`in` que mencionamos anteriormente pero que en este caso toma un uso distinto: - -``` python ->>> for i in [0,1,2,3]: -... i+2 -... -2 -3 -4 -5 -``` - -Como puedes ver, el bloque interno `i+2` se ejecuta en cuatro ocasiones, -cambiando el valor de `i` a los valores internos de la lista `[0,1,2,3]`. -Cuando la lista termina, la preparación falla porque no quedan elementos y el -bucle se rompe. - -Este último ejemplo es una receta de amplio uso que te aconsejo memorizar. - - -#### Truthy and Falsey - -En este tipo de sentencias donde se comprueba si una condición es verdadera o -falsa, python automáticamente trata de convertir el resultado a Boolean usando -la función `bool` que ya conoces del apartado sobre tipos. No es necesario que -conviertas a Boolean manualmente ya que estas sentencias disponen de una -conversión implícita a Boolean[^truthy]. - -Por tanto, cualquier resultado que tras pasar por la función `bool` dé como -resultado `True` será considerado verdadero y viceversa. A estos valores se les -conoce habitualmente como *truthy* y *falsey* porque no son `True` o `False` -pero se comportan como si lo fueran. Algunos ejemplos curiosos: - -``` python ->>> bool([]) -False ->>> bool(None) -False ->>> bool("") -False ->>> bool(" ") -True ->>> bool([None]) -True -``` - -Es relativamente sencillo prever qué valores son *truthy* o *falsey*, -normalmente los valores que representan un vacío se consideran `False`. - -[^truthy]: - - -### List comprehensions - -Una de las excepciones sintácticas que sí que podemos explicar en este momento, -en el que ya sabes hacer bucles, son las *list comprehensions*. Python dispone -de un sistema para crear secuencias y transformarlas muy similar a la notación -de construcción de sets de las matemáticas[^set-notation]. - -Como mejor se entiende es con unos ejemplos, en este caso vamos usar la función -`range` para crear una lista de números del `0` (inclusive) al `10` (no -inclusive). Usando la ayuda puedes saber más sobre la función `range`. - - -> TODO: -> Mejorar este ejemplo, es demasiado simple - -``` python ->>> [i**2 for i in range(0, 10)] -[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] ->>> tuple(i**2 for i in range(0, 10)) -(0, 1, 4, 9, 16, 25, 36, 49, 64, 81) ->>> { str(i): i**2 for i in range(0, 10)} -{'0': 0, '1': 1, '2': 4, '3': 9, '4': 16, '5': 25, '6': 36, '7': 49, '8': 64, -'9': 81} - ->>> [i**2 for i in range(0, 10) if i > 5 ] -[36, 49, 64, 81] -``` - -Como ves, en el caso de los diccionarios es necesario crear las claves también. -En este caso las creamos convirtiendo el propio número a string con la función -`str`. - -En los primeros ejemplos, de una secuencia de números hemos creado una -secuencia de números al cuadrado. Pero las *list comprehensions* son más -poderosas que eso, pudiendo a llegar a complicarse sobremanera añadiendo -condiciones, como en el último de los ejemplos, para filtrar algunos casos. - -Te habrá mosqueado el uso de `tuple` para crear la tupla. Todo tiene una razón. -La tupla, al estar formada por paréntesis, python no tiene claro si son -paréntesis de precedencia o de creación de tupla, y considera que son los -primeros, dando como resultado un generador, un paso intermedio de nuestro -proceso que dejamos para el futuro. - -``` python ->>> (i**2 for i in range(0, 10)) - at 0x7f779d9b2d58> -``` - -[^set-notation]: - -### Excepciones - -Las excepciones o *exception* son errores del programa, python lanza -excepciones cuando hay problemas. Por ejemplo, cuando intentas acceder a un -índice inexistente en una lista. - -Las excepciones terminan la ejecución del programa a no ser que se gestionen. -Se consideran fallos de los que el programa no puede arreglarse a no ser que se -le indique cómo. Algunas funciones y librerías lanzan excepciones que nosotros -debemos gestionar, por ejemplo: que un archivo no exista, o que no se tenga -permisos de edición en el directorio, etc. Es nuestra responsabilidad como -programadores tener un plan be o aceptar la excepción deportivamente a -sabiendas que nuestro programa terminará indicando un error. - -Hay ocasiones en las que las excepciones pueden capturarse y otras no, por -ejemplo, los fallos de sintaxis no pueden solventarse. - -Las excepciones se capturan con un `try-except` que, si programas en otros -lenguajes como Java, probablemente conozcas como `try-catch`. - -```python -try: - # Bloque donde pueden ocurrir excepciones. -except tipo1: - # Bloque a ejecutar en caso de que se dé una excepción de tipo1. - # Especificar el tipo también es opcional, si no se añade captura todos. -except tipoN: - # Bloques adicionales (opcionales) a ejecutar en caso de que se dé una - # excepción de tipoN, que no haya sido capturada por los bloques - # anteriores. -finally: - # Bloque (opcional) a ejecutar después de lo anterior, haya o no haya - # habido excepción. -``` - -Además de capturarse, las excepciones pueden lanzarse con la sentencia `raise`. - -``` python -if not input: - raise ValueError("Invalid input") -``` - -Si el ejemplo anterior se diera dentro de una pieza de código que no podemos -controlar, podríamos capturar el `ValueError` y evitar que la ejecución de -nuestro programa terminara. - -``` python -try: - # Bloque que puede lanzar un ValueError -except ValueError: - print("Not value in input, using default") - input = None -``` - -Aunque aún no hemos entrado en la programación orientada a objetos, te adelanto -que las excepciones se controlan como tal. Hay excepciones que serán hijas de -otras, por lo que usando la excepción genérica de la familia seremos capaces de -capturarlas, o podremos crear nuevas excepciones como hijas de las que python -ya dispone de serie. Por ahora recuerda que debes ordenar los bloques `except` -de más concreto a más genérico, porque si lo haces al revés, los primeros -bloques te capturarán todas las excepciones y los demás no tendrán ocasión de -capturar ninguna, perdiendo así el detalle de los fallos. - -Cuando aprendas sobre programación orientada a objetos en el apartado -correspondiente puedes volver a visitar este punto y leer la documentación de -python[^exception] para entender cómo hacerlo. Te adelanto que python tiene una -larga lista de excepciones y que está considerado una mala práctica crear -nuevas si las excepciones por defecto cubren un caso similar al que se -encuentra en nuestro programa. - -[^exception]: - -### Funciones - -Las funciones sirven, sobre todo, para reutilizar código. Si una pieza de -código se utiliza en más de una ocasión en tu programa, es una buena candidata -para agruparse en una función y poder reutilizarla sin necesidad de duplicar -el código fuente. Aunque sirven para más fines y tienen detalles que -aclararemos en un capítulo propio, en este apartado adelantaremos cómo se -definen y cómo lanzan y posteriormente las analizaremos en detalle. - -Las definición de funciones se realiza con una estructura similar a las -anteriores, una línea descriptiva terminada en dos puntos (`:`) y después un -bloque con mayor sangría para definir el cuerpo. - -``` python -def nombre_de_funcion (argumentos): - """ - docstring, un string multilínea que sirve como documentación - de la función. Es opcional y no tiene efecto en el funcionamiento - de la función. - Es lo que se visualiza al ejecutar `help(nombre_de_función)` - """ - # Cuerpo de la función -``` - -Para llamar a esta función que acabamos de crear: - -``` python -nombre_de_función(argumentos) -``` - -El nombre de la función debe cumplir las mismas normas que los nombres de las -referencias de las que ya hemos hablando anteriormente. Y esto debe ser así -básicamente porque... ¡el nombre de la función también es una referencia a un -valor de tipo función! - -Pronto ahondaremos más en este tema. De momento recuerda la declaración de las -funciones. Dentro de ellas podrás incluir todas las estructuras definidas en -este apartado, incluso podrás definir funciones. - -Aunque en el próximo capítulo tocará hablar de los argumentos de entrada, de -momento te adelanto que cuando llamaste a la función `range` anteriormente, le -introdujiste dos argumentos. Esos dos argumentos deben declararse como -argumentos de entrada. Probablemente de una forma similar a esta: - -``` python -def range (inicio, fin): - contador = inicio - salida = [] - while contador < fin: - salida.append(contador) - contador = contador + 1 - return salida -``` - -Lo que va a ocurrir al llamar a la función, por ejemplo, con esta llamada: -`range(1, 10)` es que el argumento `inicio` tomará el valor `1` para esta -ejecución y el argumento `fin` tomará el valor `10`, como si en el propio -cuerpo de la función alguien hubiese hecho: - -``` python -inicio = 1 -fin = 10 -``` - -El contenido de la función se ejecutará, por tanto, con esas referencias -asignadas a un valor. Con lo que sabes ya puedes intentar descifrar el -comportamiento de la función `range` que hemos definido, que es similar, pero -no igual, a la que define python. - -Sólo necesitas entender lo que hace la función `list.append` que puedes -comprobar en la ayuda haciendo `help( list.append )` y la sentencia `return`, -que se explica en el siguiente apartado. - -Prueba a leer ambas y a crear un archivo de python donde construyes la función -y le lanzas unas llamadas. A ver si lo entiendes. - -### Sentencias útiles - -Python dispone de un conjunto de sentencias que pueden facilitar y flexibilizar -mucho el uso de las estructuras que acabamos de visitar. - -#### Pass - -`pass` es una sentencia vacía, que no ejecuta nada. Es necesaria debido a las -normas de sangría de python. Si construyes un bloque y no quieres rellenarlo -por la razón que sea, debes usar `pass` en su interior porque, si no lo haces y -simplemente lo dejas vacío, la sintaxis será incorrecta y python lanzará una -excepción grave diciéndote que esperaba un bloque con sangría y no se lo diste. - -``` python ->>> if True: -... - File "", line 2 - - ^ -IndentationError: expected an indented block ->>> if True: -... pass -... -``` - -Suele utilizarse cuando no quiere tratarse una excepción o cuando se ha hecho -un boceto de una función que aún no quiere desarrollarse, para que el -intérprete no falle de forma inevitable. - -> NOTA: Las excepciones de sintaxis son las más graves, implican que el -> intérprete no es capaz de entender lo que le pedimos así que la ejecución del -> programa no llega a realizarse. La sintaxis se comprueba en una etapa previa -> a la ejecución. - -#### Continue - -`continue` sirve para terminar el bucle actual y volver a comprobar la -condición para decidir si volver a ejecutarlo. - -En el siguiente ejemplo salta la ejecución para el caso en el que `i` es `2`. - -``` python ->>> for i in [0, 1, 2, 3]: -... if i == 2: -... continue -... i -... -0 -1 -3 -``` - -#### Break - -`break` rompe el bucle actual. A diferencia del `continue`, no se intenta -ejecutar la siguiente sentencia, simplemente se rompe el bucle completo. En el -siguiente ejemplo se rompe el bucle cuando `i` es `2` y no se recupera. - -``` python ->>> for i in [0, 1, 2, 3]: -... if i == 2: -... break -... i -... -0 -1 -``` - -El `break` se usa mucho para romper bucles que son infinitos por definición. En -lugar de añadir una condición compleja a un bucle `while`, por ejemplo, puedes -usar un `True` en su condición e introducir un `if` más simple dentro de éste -con un `break` que termine el bucle en los casos que te interesen. O puedes -capturar una excepción y romper el bucle si ocurre. - -Además, es muy usado en búsquedas y habilita el uso del `else` en las -sentencias `for`. Te propongo como ejercicio que trates de ejecutar y -comprender el funcionamiento de esta pieza de código de python avanzado antes -de seguir leyendo: - -``` python -text = "texto de prueba" -pos = 0 - -for i in text: - if i == "b": - print("Found b in position: " + str(pos)) - break - pos = pos + 1 -else: - print("b not found :( ") -``` - -Si cambias el texto de la variable `text` por uno que no tenga letra `b` verás -que el bloque `else` se ejecuta. Esto se debe a que el `for` no termina de -forma abrupta, sino que itera por el string completo. - -Si quieres, puedes memorizar esta estructura para cuando quieras hacer -búsquedas. Es elegante y te será útil. - - -#### Return - -La sentencia `return` sólo tiene sentido dentro de las funciones. Sirve para -finalizar la ejecución de una función sustituyendo su llamada por el resultado -indicado en el `return`. Esta operación rompe todos los bucles por completo. - -En el apartado de las funciones profundizaremos en el uso del `return` pero es -importante mencionarlo aquí porque su funcionalidad puede sustituir al `break` -en muchas ocasiones. - -## Lo que has aprendido - -Es difícil resumir todo lo que has aprendido en este capítulo porque, la verdad -es que es mucha información. Pero no pasa nada porque este capítulo se ha -creado más como referencia que como otra cosa. No tengas miedo a volver a -leerlo todas las veces que necesites. A todos se nos olvida cómo hay que -declarar las funciones si llevamos mucho tiempo sin tocar python, o si era -`try-catch` o `try-except`. Este capítulo pretende, por un lado, darte una -pincelada de cómo se escribe en python y, por otro, servir como manual de -consulta posterior. - -Pero, por hacer la labor de resumen, has aprendido el orden de ejecución de las -órdenes y como alterarlo con bucles y condicionales. Para ello, has tenido que -aprender lo que es un *bloque* de código, una pieza fundamental para entender -la sintaxis. Tras ver varios ejemplos de bucles y condicionales, te has -sumergido en la verdad y la mentira mediante los valores *truthy* y *falsey*, -para después avanzar a las *list comprehensions*, cuyo nombre contiene *list* -pero valen para cualquier dato complejo. - -Has cambiado un poco de tema después, saltando a las excepciones, que no has -podido ver en detalle por no ser, aún, un experto en programación orientada a -objetos. Pero tranquilo, pronto lo serás. - -Una pincelada sobre funciones ha sido suficiente para que no les tengas miedo -nunca más y que podamos atacar el siguiente capítulo con energía, ya que ahora -toca jugar con funciones hasta entenderlas por completo. - -Las sentencias útiles que hemos recopilado al final permiten juguetear con -todas las estructuras que hemos definido en el capítulo de modo que puedas -usarlas a tu antojo de forma cómoda. Algunas de ellas como `continue` y `break` -no son realmente necesarias, puede programarse evitándolas y, de hecho, en -algunos lugares enseñan a no usarlas, como si de una buena práctica se tratara, -cambiando las condiciones de los bucles para que hagan esta labor. En este -documento se muestran porque, en primer lugar, si lees código escrito por otras -personas las encontrarás y tendrás que entender qué hacen y, en segundo, porque -son sentencias que simplifican el código haciéndolo más legible o más sencillo -por muy impuras que a algunos programadores les puedan parecer. diff --git a/src/04_funciones.md b/src/04_funciones.md deleted file mode 100644 index 618ccd6..0000000 --- a/src/04_funciones.md +++ /dev/null @@ -1,654 +0,0 @@ -# Funciones - -El objetivo de este capítulo es que te familiarices con el uso de las -funciones. Parece sencillo pero es una tarea un tanto complicada porque, visto -como nos gusta hacer las cosas, tenemos una gran cantidad de complejidad que -abordar. - -Antes de entrar, vamos a definir una función y a usarla un par de veces: - -``` python -def inc(a): - b = a + 1 - return b -``` - -Si la llamamos: - -``` python ->>> inc(1) -2 ->>> inc(10) -11 -``` - -Cuidado con las declaraciones internas en las funciones. Si preguntamos por -`b`: - -``` python ->>> b -Traceback (most recent call last): - File "", line 1, in -NameError: name 'b' is not defined -``` - -Parece que no conoce el nombre `b`. Esto es un tema relacionado con el *scope*. - -## Scope - -Anteriormente se ha dicho que python es un lenguaje de programación con gestión -automática de la memoria. Esto significa que él mismo es capaz de saber cuando -necesita pedir más memoria al sistema operativo y cuando quiere liberarla. -El *scope* es un resultado este sistema. Para que python pueda liberar -memoria, necesita de un proceso conocido como *garbage collector* (recolector -de basura), que se encarga de buscar cuando las referencias ya no van a poder -usarse más para pedir una liberación de esa memoria. Por tanto, las referencias -tienen un tiempo de vida, desde que se crean hasta que el recolector de basura -las elimina. Ese tiempo de vida se conoce como *scope* y, más que en tiempo, se -trata en términos de espacio en el programa. - -El recolector de basura tiene unas normas muy estrictas y conociéndolas es -fácil saber en qué espacio se puede mover una referencia sin ser disuelta. - -Resumiendo mucho, las referencias que crees se mantienen vivas hasta que la -función termine. Como en el caso de arriba la función en la que se había creado -`b` había terminado, `b` había sido limpiada por el recolector de basura. `b` -era una referencia *local*, asociada a la función `inc`. - -Puede haber referencias declaradas fuera de cualquier función, que se llaman -*globales*. Éstas se mantienen accesibles desde cualquier punto del programa, y -se mantienen vivas hasta que éste se cierre. Considera que el propio programa -es una función gigante que engloba todo. - -Python define que cualquier declaración está disponible -en bloques internos, pero no al revés. El siguiente ejemplo lo muestra: - -``` python -c = 100 -def funcion(): - a = 1 - # Se conoce c aquí dentro -# Aquí fuera no se conoce a -``` - -El *scope* es peculiar en algunos casos que veremos ahora, pero mientras tengas -claro que se extiende hacia dentro y no hacia fuera, todo irá bien. - -## First-class citizens - -Antes de seguir jugando con el *scope*, necesitas saber que las funciones en -python son lo que se conoce como *first-class citizens* (ciudadanos de primera -clase). Esto significa que pueden hacer lo mismo que cualquier otro valor. - -Las funciones son un valor más del sistema, como puede ser un string, y su -nombre no es más que una referencia a ellas. - -Por esto mismo, pueden ser enviadas como argumento de entrada a otras -funciones, devueltas con sentencias `return` o incluso ser declaradas dentro de -otras funciones. - -Por ejemplo: - -``` python ->>> def filtra_lista(list): -... def mayor_que_4(a): -... return a > 4 -... return list( filter(mayor_que_4, lista) ) -... ->>> filtra_lista( [1,2,3,4,5,6,7] ) -[5, 6, 7] -``` - -En este ejemplo, haciendo uso de la función `filter` (usa la ayuda para ver lo -que hace), filtramos todos los elementos mayores que `4` de la lista. Pero para -ello hemos creado una función que sirve para compararlos y se la hemos -entregado a la función `filter`. - -Este ejemplo no tiene más interés que intentar enseñarte que puedes crear -funciones como cualquier otro valor y asignarles un nombre, para después -pasarlas como argumento de entrada a otra función. - -## Lambdas - -Las funciones *lambda*[^lambda] o funciones anónimas son una forma sencilla de -declarar funciones simples sin tener que escribir tanto. La documentación -oficial de python las define como funciones para vagos. - -La sintaxis de una función lambda te la enseño con un ejemplo: - -``` python ->>> lambda x,y: x + y - at 0x7f035b879950> ->>> (lambda x,y: x + y)(1,2) -3 -``` - -En el ejemplo primero se muestra la declaración de una función y después -colocando los paréntesis de precedencia y después de llamada a función se -construye una función a la izquierda y se ejecuta con los valores `1` y `2`. - -Es fácil de entender la sintaxis de la función lambda, básicamente es una -función reducida de sólo una sentencia con un `return` implícito. - -El ejemplo de la función `filtra_lista` puede reducirse mucho usando una -función lambda: - -``` python ->>> def filtra_lista( lista ): -... return list( filter(lambda x: x > 4, lista) ) -... ->>> filtra_lista( [1,2,3,4,5,6,7] ) -[5, 6, 7] -``` - -No necesitábamos una función con nombre en este caso, porque sólo iba a -utilizarse esta vez, así que resumimos y reducimos tecleos. - -De todos modos, podemos asignarlas a una referencia para poder repetir su uso: - -``` python ->>> f = lambda x: x + 1 ->>> f(1) -2 ->>> f(10) -11 ->>> f - at 0x7f02184febf8> -``` - -Las funciones lambda se usan un montón como *closure*, un concepto donde el -*scope* se trabaja más allá de lo que hemos visto. Sigamos visitando el -*scope*, para entender sus usos más en detalle. - -[^lambda]: Toman su nombre del Lambda - Calculus: - - -## Scope avanzado - -Cada vez que se crea una función, python crea un nuevo contexto para ella. -Puedes entender el concepto de contexto como una tabla donde se van guardando -las referencias que se declaran en la función. Cuando la función termina, su -contexto asociado se elimina, y el recolector de basura se encarga de liberar -la memoria de sus variables, tal y como vimos anteriormente. - -Lo que ocurre es que estos contextos son jerárquicos, por lo que, al crear una -función, el padre del contexto que se crea es el contexto de la función madre. -Python utiliza esto como método para encontrar las referencias. Si una -referencia no se encuentra en el contexto actual, python la buscará en el -contexto padre y así sucesivamente hasta encontrarla o lanzar un error diciendo -que no la conoce. Esto explica por qué las variables declaradas en la función -madre pueden encontrarse y accederse y no al revés. - -Aunque hemos explicado el *scope* como un concepto asociado a las funciones, la -realidad es que hay varias estructuras que crean nuevos contextos en python. El -comportamiento sería el mismo del que se ha hablado anteriormente, las -referencias que se creen en ellos no se verán en el *scope* de nivel superior, -pero sí al revés. Los casos son los siguientes: - -- Los módulos. Ver capítulo correspondiente -- Las clases. Ver capítulo de Programación Orientada a Objetos. -- Las funciones, incluidas las funciones anónimas o lambda. -- Las expresiones generadoras[^generator-expression], que normalmente se - encuentran en las *list-comprehension* que ya se han tratado en el capítulo - previo. - -[^generator-expression]: - - -### Scope léxico, Closures - -Hemos dicho que las funciones pueden declararse dentro de funciones, pero no -hemos hablado de qué ocurre con el *scope* cuando la función declarada se -devuelve y tiene una vida más larga que la función en la que se declaró. El -siguiente ejemplo te pone en contexto: - -``` python -def create_incrementer_function(increment): - def incrementer (val): - # Recuerda que esta función puede ver el valor `increment` por - # por haber nacido en un contexto superior. - return val + increment - return incrementer - -increment10 = create_incrementer_function(10) -increment10(10) # Returns 20 -increment1 = create_incrementer_function(1) -increment1(10) # Returns 11 -``` -En este ejemplo hemos creado una función que construye funciones que sirven -para incrementar valores. - -Las funciones devueltas viven durante más tiempo que la función que las -albergaba por lo que saber qué pasa con la variable `increment` es difícil a -simple vista. - -Python no destruirá ninguna variable que todavía pueda ser accedida, si lo -hiciera, las funciones devueltas no funcionarían porque no podrían incrementar -el valor. Habrían olvidado con qué valor debían incrementarlo. - -Para que esto pueda funcionar, las funciones guardan el contexto del momento de -su creación, así que la función `incrementer` recuerda la primera vez que fue -construida en un contexto en el que `increment` valía `10` y la nueva -`incrementer` creada en la segunda ejecución de `create_incrementer_function` -recuerda que cuando se creó `increment` tomó el valor `1`. Ambas funciones son -independientes, aunque se llamen de la misma forma en su concepción, no se -pisaron la una a la otra, porque pertenecían a contextos distintos ya que la -función que las creaba terminó y luego volvió a iniciarse. - -Este funcionamiento donde el comportamiento de las funciones depende del lugar -donde se crearon y no del contexto donde se ejecutan se conoce como *scope -léxico*. - -Las *closures* son una forma de implementar el *scope léxico* en un lenguaje -cuyas funciones sean *first-class citizens*, como es el caso de python, y su -funcionamiento se basa en la construcción de los contextos y su asociación a -una función capaz de recordarlos aunque la función madre haya terminado. - -Python analiza cada función y revisa qué referencias del contexto superior -deben mantenerse en la función. Si encuentra alguna, las asocia a la propia -función creando así lo que se conoce como *closure*, una función que recuerda -una parte del contexto. No todas las funciones necesitan del contexto previo -así que sólo se crean *closures* en función de lo necesario. - -Puedes comprobar si una función es una *closure* analizando su campo -`__closure__`. Si no está vacío (valor `None`), significará que la función es -una *closure* como la que ves a continuación. Una *closure* que recuerda un -*int* del contexto padre: - -``` python ->>> f.__closure__ -(,) -``` - -Lo que estás viendo lo entenderás mejor cuando llegues al apartado de -programación orientada a objetos. Pero, para empezar, ves que contiene una -tupla con una `cell` de tipo *integer*. - -A nivel práctico, las *closures* son útiles para muchas labores que iremos -desgranando de forma accidental. Si tienes claro el concepto te darás cuenta -dónde aparecen en los futuros ejemplos. - -### `global` y `nonlocal` - -Hemos hablado de qué sentencias crean nuevos contextos, pero no hemos hablado -de qué pasa si esos nuevos contextos crean referencias cuyo nombre es idéntico -al de las referencias que aparecen en contextos superiores. - -Partiendo de lo que se acaba de explicar, y antes de adentrarnos en ejemplos, -si se crea una función (o cualquiera de las otras estructuras) python creará un -contexto para ella. Una vez creado, al crear una variable en este nuevo -contexto, python añadirá una nueva entrada en su tabla hija con el nombre de la -variable. Al intentar consultarla, python encontrará que en su tabla hija -existe la variable y tomará el valor con el que la declaramos. Cuando la -función termine, la tabla de contexto asociada a la función será eliminada. -Esto siempre es así, independientemente del nombre de referencia que hayamos -seleccionado. Por tanto, si el nombre ya existía en alguno de los contextos -padre, lo ocultaremos, haciendo que dentro de esta función se encuentre el -nombre recién declarado y no se llegue a buscar más allá. Cuando la función -termine, como el contexto asociado a ésta no está en la zona de búsqueda de la -función madre, en la función madre el valor seguirá siendo el que era. - -Ilustrándolo en un ejemplo: - -``` python ->>> a = 1 ->>> def f(): -... a = 2 -... print(a) -... ->>> f() -2 ->>> a -1 -``` - -Aunque el nombre de la referencia declarada en el interior sea el mismo que el -de una referencia externa su declaración no afecta, lógicamente, al exterior -ya que ocurre en un contexto independiente. - -Para afectar a la referencia global, python dispone de la sentencia `global`. -La sentencia `global` afecta al bloque de código actual, indicando que los -identificadores listados deben interpretarse como globales. De esta manera, si -se reasigna una referencia dentro de la función, no será el contexto propio el -que se altere, sino el contexto global, el padre de todos los contextos. - -``` python ->>> a = 1 ->>> def f(): -... global a -... a = 2 -... print(a) -... ->>> f() -2 ->>> a -2 -``` - -> NOTA: Te recomiendo, de todas formas, que nunca edites valores globales desde -> el cuerpo de funciones. Es más elegante y comprensible si los efectos de las -> funciones sólo se aprecian en los argumentos de entrada y salida. - -Para más detalles sobre limitaciones y excepciones, puedes buscar en la ayuda -ejecutando `help("global")`. - -El caso de `nonlocal` es similar, sin embargo, está diseñado para trabajar en -contextos anidados. Es decir, en lugar de saltar a acceder a una variable -global, `nonlocal` la busca en cualquier contexto que no sea el actual. -`nonlocal` comienza a buscar las referencias en el contexto padre y va saltando -hacia arriba en la jerarquía en busca de la referencia. Para saber más: -`help("nonlocal")`. - -La diferencia principal entre ambas es que `global` puede crear nuevas -referencias, ya que se sabe a qué contexto debe afectar: al global. Sin -embargo, `nonlocal` necesita que la referencia a la que se pretende acceder -esté creada, ya que no es posible saber a qué contexto se pretende acceder. - -Las sentencias `global` y `nonlocal` son tramposas, ya que son capaces de -alterar el comportamiento del *scope léxico* y convertirlo en *scope dinámico* -en casos extraños. El *scope dinámico* es el caso opuesto al léxico, en el que -las funciones acceden a valores definidos en el contexto donde se ejecutan, no -donde se crean. - -## Argumentos de entrada y llamadas - -Los argumentos de entrada se definen en la declaración de la función y se ha -dado por hecho que es evidente que se separan por comas (`,`) y que, a la hora -de llamar a la función, deben introducirse en el orden en el que se han -declarado. Por mucho que esto sea cierto, requiere de una explicación más -profunda. - -### Callable - -En python las funciones son un tipo de *callable*, «cosa que puede ser llamada» -en inglés. Esto significa, de algún modo que hay otras cosas que pueden ser -llamadas que no sean funciones. Y así es. - -Para python cualquier valor que soporte la aplicación de los paréntesis se -considera «llamable». En el apartado de programación orientada a objetos -entenderás esto en detalle. De momento, piensa que, igual que pasa al acceder a -los campos de una colección usando los corchetes, siempre que python se -encuentre unos paréntesis después de un valor tratará de ejecutar el valor. Así -que los paréntesis no son una acción que únicamente pueda aplicarse en nombres -de función[^lambdas-ejemplo] y python no lanzará un fallo de sintaxis cuando -los usemos fuera de lugar, si no que será un fallo de tiempo de ejecución al -darse cuenta de lo que se intenta ejecutar no es ejecutable. - -``` python ->>> 1() -Traceback (most recent call last): - File "", line 1, in -TypeError: 'int' object is not callable -``` - -[^lambdas-ejemplo]: Aunque en realidad esto ya lo has visto en los ejemplos de - las funciones lambda. - -#### Caso de estudio: Switch Case - -Si quieres ver un ejemplo avanzado de esto, te propongo la creación de la -estructura *switch-case* [^switch-case], que puede encontrarse en otros -lenguajes, pero que en lugar de usar una estructura basada en un *if* con -múltiples *elif* uses un diccionario de funciones. - -Las funciones son valores, por lo que pueden ocupar un diccionario como -cualquier otro valor. Construyendo un diccionario en cuyas claves se encuentran -los casos del *switch-case* y en cuyos valores se encuentran sus funciones -asociadas se puede crear una sentencia con el mismo comportamiento. - -En el siguiente ejemplo se plantea una aplicación por comandos. Captura el -tecleo del usuario y ejecuta la función asociada al comando. Las funciones no -están escritas, pero puedes completarlas y analizar su comportamiento. Las -palabras que no entiendas puedes consultarlas en la ayuda. - -``` python -def borrar(*args): - pass -def crear(*args): - pass -def renombrar(*args): - pass - -casos = { - "borrar": borrar, - "crear": crear, - "renombrar": renombrar -} - -comando = input("introduce el comando> ") - -try: - casos[comando]() -except KeyError: - print("comando desconocido") - -``` - -[^switch-case]: - -### Positional vs Keyword Arguments - -Las funciones tienen dos tipos de argumentos de entrada, aunque sólo hayamos -mostrado uno de ellos de momento. - -El que ya conoces se denomina *positional argument* y se refiere a que son -argumentos que se definen en función de su posición. Los argumentos -posicionales deben ser situados siempre en el mismo orden, si no, los -resultados de la función serán distintos. Las referencias `source` y `target` -toman el primer argumento y el segundo respectivamente. Darles la vuelta -resulta en el resultado opuesto al que se pretendía. - -``` python -def move_file ( source, target ): - "Mueve archivo de `source` a `target" - pass - -move_file("file.txt", "/home/guido/doc.txt") - # "file.txt" -> "/home/guido/doc.txt" -move_file("/home/guido/doc.txt", "file.txt") - # "/home/guido/doc.txt"-> "file.txt" -``` - -Los *keyword argument* o argumentos con nombre, por otro lado, se comportan -como un diccionario. Su orden no importa pero es necesario marcarlos con su -respectiva clave. Además, son opcionales porque en el momento de la declaración -de la función python te obliga a que les asocies un valor por defecto -(*default*). En el siguiente ejemplo se convierte la función a una basada en -argumentos con nombre. No se han utilizado valores por defecto especiales, pero -pueden usarse otros. - -``` python -def move_file( source=None, target=None): - "Mueve archivo de `source` a `target" - pass - -move_file(source="file.txt", target="/home/guido/doc.txt") - # "file.txt" -> "/home/guido/doc.txt" -move_file(target="/home/guido/doc.txt", source="file.txt") - # "file.txt" -> "/home/guido/doc.txt" -``` - -> NOTA: Si quieres que sean obligatorios, siempre puedes lanzar una excepción. - -Para funciones que acepten ambos tipos de argumento, es obligatorio declarar e -introducir todos los argumentos posicionales primero. Es lógico, porque son -los que requieren de una posición. - -También es posible declarar funciones que acepten cualquier cantidad de -argumentos de un tipo u otro. Ésta es la sintaxis: - -``` python -def argument_catcher( *args, **kwargs ) - "Función ejecutable con cualquier número de argumentos de entrada, tanto - posicionales como con nombre." - print( args ) - print( kwargs ) -``` - -Los nombres `args` y `kwargs` son convenciones que casi todos los programadores -de python utilizan, pero puedes seleccionar los que quieras. Lo importante es -usar `*` para los argumentos posicionales y `**` para los argumentos con -nombre. - -Prueba a ejecutar la función del ejemplo, verás que los argumentos posicionales -se capturan en una tupla y los argumentos con nombre en un diccionario. - -Este tipo de funciones multiargumento se utilizan mucho en los *decorators*, -caso que estudiaremos al final de este capítulo. - -#### Peligro: Mutable Defaults - -Existe un caso en el que tienes que tener mucho cuidado. Los valores por -defecto en los argumentos con nombre se memorizan de una ejecución de la -función a otra. En caso de que sean valores inmutables no tendrás problemas, -porque su valor nunca cambiará, pero si almacenas en ellos valores mutables y -los modificas, la próxima vez que ejecutes la función los valores por defecto -habrán cambiado. - -La razón por la que los valores por defecto se recuerdan es que esos valores se -construyen en la creación de la función, no en su llamada. Lógicamente, puesto -que es en la sentencia `def` donde aparecen. - -``` python ->>> def warning(default=[]): -... default.append(1) -... return default -... ->>> warning() -[1] ->>> warning() -[1, 1] ->>> warning() -[1, 1, 1] ->>> warning() -[1, 1, 1, 1] -``` - -## Decorators - -Los *decorators* son un concepto que, a pesar de ser bastante concreto, nos -permite descubrir todo el potencial de lo que se acaba de tratar en este -apartado. Sirven para dotar a las funciones de características adicionales. - -Por ejemplo, éste es un decorador que permite crear funciones que se ejecutan -en un *thread* independiente. Tiene sentido para realizar acciones de las que -se quiere que se ejecuten por su cuenta sin ralentizar el hilo principal del -programa, como el envío de un email desde un servidor web. - -``` python -import threading - -def run_in_thread(fn): - def run(*args, **kwargs): - t = threading.Thread(target=fn, args=args, kwargs=kwargs) - t.start() - return t - return run - -@run_in_thread -def send_mail(): - """ - Envía un email a un usuario, sin esperar confirmación. - """ - pass -``` - -Hay muchos detalles que te habrán llamado la atención del ejemplo, el uso de -`@run_in_thread` probablemente sea uno de ellos. Éste es, sin embargo, el -detalle menos importante ya que únicamente se trata de un poco de *syntactic -sugar*. - -> NOTA: el *syntactic sugar* son simplificaciones sintácticas que el lenguaje -> define para acortar expresiones muy utilizadas. El ejemplo clásico de -> *syntactic sugar* es: -> `a += b` -> Que es equivalente a: -> `a = a + b` - -Los *decorators* pueden entenderse como un envoltorio para una función. No son -más que una función que devuelve otra. En el caso del decorador del ejemplo, -el *decorator* `run_in_thread` es función que recibe otra función como -argumento de entrada y devuelve la función `run`. Este decorador, al aplicarlo -a una función con `@run_in_thread` está haciendo lo siguiente: - -``` python -send_mail = run_in_thread(send_mail) -``` - -> NOTA: `@decorator` es *syntactic sugar* de `fn = decorator(fn)`. Simplemente, -> es más corto y más bonito. - -Por lo que la función `send_mail` ya no es lo que creíamos, sino la función -`run`. En el ejemplo, la función `run` llama a la función `fn` de la función -madre (`run` es una *closure*), que resulta ser `send_mail`, a modo de thread -independiente. - -Como puedes apreciar, el hecho de capturar todos los posibles argumentos de -entrada en la función `run` permite a `run_in_thread` decorar cualquier -función, sabiendo que funcionará. - -El principal problema que los decoradores generan es que la función que hemos -decorado ya no es la que parecía ser, así que su *docstring*, sus argumentos de -entrada, etc. ya no pueden comprobarse desde la REPL usando la ayuda, ya que la -ayuda buscaría la ayuda de la función devuelta por el decorador (`run` en el -ejemplo). Usando `@functools.wraps`[^functools] podemos resolver este -problema. - -[^functools]: Puedes leer por qué y cómo en la documentación oficial de - python: - - -La realidad es que los *decorators* son una forma muy elegante de añadir -funcionalidades a las funciones sin complicar demasiado el código. Permiten -añadir capacidad de depuración, *profiling* y todo tipo de funcionalidades que -se te ocurran. - -Este apartado se deja varias cosas en el tintero, como los decoradores con -parámetros de entrada, pero no pretende ser una referencia de cómo se usan, -sino una introducción a un concepto útil que resume perfectamente lo tratado -durante todo el capítulo. - -Te animo, como ejercicio, a que analices el decorador `@lru_cache` del módulo -`functools` y comprendas su interés y su funcionamiento. Para leerlo en la -ayuda debes importar el módulo `functools` primero. Como aún no sabes hacerlo, -aquí tienes la receta: - -``` python ->>> import functools ->>> help(functools.lru_cache) -``` - - -## Lo que has aprendido - -Este capítulo puede que sea el más complejo de todos los que te has encontrado -y te encontrarás. En él has aprendido a declarar y a usar funciones, cosa -sencilla, y todos los conceptos importantes relacionados con ellas. Un -conocimiento que es útil en python, pero que puede ser extendido a casi -cualquier lenguaje. - -Tras un sencillo acercamiento al *scope*, has comprendido que las funciones en -python son sólo un valor más, como puede ser un `int`, y que pueden declararse -en cualquier lugar, lo que te abre la puerta a querer declarar funciones -sencillas sin nombre, que se conocen como funciones *lambda*. - -Una vez has aclarado que las funciones son ciudadanos de primera clase -(*first-class citizens*) ya estabas preparado para afrontar la realidad del -*scope* donde has tratado los contextos y cómo funcionan definiendo el concepto -del *scope léxico* que, colateralmente, te ha enseñado lo que es una *closure*, -un método para implementarlo. Pero también has tenido ocasión de aprender que -en python es posible crear casos de *scope dinámico* mediante las sentencias -`global` y `nonlocal`, que pueden ser útiles, pero es mejor no abusar de ellas. - -Pero no había quedado claro en su momento cómo funcionaban los argumentos de -entrada y las llamadas a las funciones, así que has tenido ocasión de ver por -primera vez lo que es un *callable* en python, aunque se te ha prometido -analizarlo en el futuro. Lo que sí que has tenido ocasión de tratar son los -argumentos *positional* y *keyword*, y cómo se utilizan en todas sus posibles -formas. - -Finalmente, para agrupar todo esto en un único concepto, se te han mostrado los -*decorators*, aunque de forma muy general, con el fin de que vieras que todo lo -que se ha tratado en este capítulo aparece en conceptos avanzados y es -necesario entenderlo si quieren llegar a usarse de forma eficiente. diff --git a/src/05_oop.md b/src/05_oop.md deleted file mode 100644 index 6fdb9ce..0000000 --- a/src/05_oop.md +++ /dev/null @@ -1,1093 +0,0 @@ -# Programación Orientada a Objetos - -La *programación orientada a objetos* u *object oriented programming* (OOP) es -un paradigma de programación que envuelve python de pies a cabeza. A pesar de -que python se define como un lenguaje de programación multiparadigma, la -programación orientada a objetos es el paradigma principal de éste. A pesar de -que varias de las características que tratamos en el apartado anterior se -corresponden más con un lenguaje de programación funcional, en python **todo** -(o casi todo) es una clase. - -Python usa una programación orientada a objetos basada en clases[^class], a -diferencia de otros lenguajes como JavaScript, donde la orientación a objetos -está basada en prototipos[^proto]. No es el objetivo de este documento el de -contarte cuales son las diferencias entre ambas, pero es interesante que sepas -de su existencia, ya que es una de las pocas diferencias que existen entre -estos dos lenguajes de amplio uso en la actualidad. - -[^class]: -[^proto]: - -## Programación basada en clases - -Tras haber hecho una afirmación tan categórica como que en python todo son -clases, es nuestra obligación entrar a definir lo que son y qué implica la -programación basada en clases. - -Los objetos, *objects*, son entidades que encapsulan un estado, un -comportamiento y una identidad capaz de separarlos de otras entidades. Una -clase, *class*, es la definición de estos objetos. - -Saliendo de la definición filosófica y trayéndola a un nivel de andar por casa, -puedes aclararte sabiendo que las clases son la definición enciclopédica de -algo, mientras que los objetos son el propio objeto, persona o animal descrito. - -Llevándolo al ejemplo de un perro, la clase es la definición de qué es un perro -y los objetos son los distintos perros que te puedes encontrar en el mundo. La -definición de perro indica qué características ha de tener un ente para ser un -perro, como ser un animal, concretamente doméstico, qué anatomía debe tener, -cómo debe comportarse, etc. Mientras que el propio perro es uno de los casos de -esa definición. - -Cada perro tiene una **identidad propia** y es independiente de los otros, -tiene un **comportamiento** concreto (corre, salta, ladra...) y tiene un -**estado** (está despierto o dormido, tiene una edad determinada...). - -La diferencia entre una clase y un objeto tiene lógica si lo piensas desde la -perspectiva de que python no tiene ni idea de lo que es un perro y tú tienes -que explicárselo. Una vez lo haces, declarando tu clase, puedes crear -diferentes perros y ponerlos a jugar. Lo bonito de programar es que tu programa -es tu mundo y tú decides lo que es para ti (o para tu programa) un perro. - -A nivel práctico, los objetos son grupos de datos (el *estado*) y funciones (la -*funcionalidad*). Estas funciones son capaces de alterar los datos del propio -objeto y no de otro (se intuye el concepto de *identidad*). Analizándolo desde -el conocimiento que ya tienes, es lógico pensar que un objeto es, por tanto, -una combinación de valores y funciones accesible a modo de elemento único. -Exactamente de eso se trata. - -Existe una terminología técnica, eso sí, para referirse a esos valores y a esas -funciones. Normalmente los valores se conocen como *propiedades* del objeto y -las funciones se conocen como *métodos*. Así que siempre que hagamos referencia -a cualquiera de estas dos palabras clave debes recordar que hacen referencia a -la programación orientada a objetos. - -### Fundamento teórico - -La programación basada en clases se basa en tres conceptos fundamentales que -repasaremos aquí de forma rápida para razonar el interés de la programación -orientada a objetos sobre otros paradigmas. - -La **encapsulación**[^encapsulation] trata de crear datos con sus métodos -propios para alterarlos de modo que restrinjan el acceso directo al contenido -de estos datos con el fin de asegurar una coherencia o robustez interna. Puedes -entender esto como una forma de esconder información o como mi profesor de -programación II en la universidad solía decir: «Las patatas se pelan en la -cocina del restaurante, no en el comedor». La utilidad de la encapsulación es -la de aislar secciones del programa para tener total control sobre su -contenido gracias a tener total control de la vía de acceso a estos datos. A -nivel práctico este concepto puede usarse para, por ejemplo, obligar a que un -objeto sólo pueda ser alterado en incrementos controlados en lugar de poder -pisarse con un valor arbitrario. - -La **herencia**[^inheritance] es un truco para reutilizar código de forma -agresiva que, casualmente, sirve como una buena forma de razonar. Aporta la -posibilidad de crear nuevas *clases* a partir de clases ya existentes. -Volviendo a la simplificación anterior, si una clase es una definición -enciclopédica de un concepto, como un perro, puede estar basada en otra -descripción para evitar contar todo lo relacionado con ella. En el caso del -perro, el perro es un animal. Animal podría ser otra clase definida previamente -de la que el perro heredara y recibiera gran parte de su descripción genérica -para sólo cubrir puntos que necesite especificar como el tamaño, la forma, el -tipo de animal, el comportamiento concreto, etc. Existe la posibilidad de hacer -herencias múltiples también ya que algunos conceptos pueden describirse en dos -superclases distintas: un perro es un animal (vive, muere, se alimenta, se -reproduce) y también es terrestre (camina sobre una superficie, etc). Ambos -conceptos son independientes: los coches también son terrestres pero no son -animales y los peces también son animales pero no terrestres. - -Y, finalmente, el **polimorfismo**[^polymorphism]. La propia etimología de la -palabra define con bastante precisión el concepto, pero aplicarlo a la -programación orientada a objetos no es tan evidente. Existen varios tipos de -polimorfismo pero el más sencillo es entender el *subtyping*[^subtyping]. Una -vez lo comprendas el resto será evidente. Si volvemos al ejemplo del perro, -para ciertos comportamientos, nos da igual que tratemos de perros, de peces o -de pájaros, todos son animales y todos los animales se comportan de la misma -forma. Es decir, todas las subclases señaladas comparten el comportamiento de -la superclase animal. Si esto es cierto, puede suponerse que en cualquier caso -en el que se espere un objeto de la clase animal es seguro usar una subclase de -ésta. - -Visto desde otra perspectiva, las subclases comparten comportamiento porque -reutilizan las funciones de la clase principal o las redefinen (*herencia*), -pero podemos asegurar que todas las subclases tienen un conjunto de funciones -con la misma estructura, independientemente de lo que hagan, que aseguran que -siempre van a ser compatibles. El nombre de esta cualidad viene a que un perro -puede tomar la forma de un animal. - -Los otros tipos de polimorfismo explotan el mismo comportamiento de diferentes -maneras, mientras que recuerdes que es posible programar de modo que el tipo de -los datos que trates sea indiferente o pueda variar es suficiente. Otro ejemplo -de esto son los operadores matemáticos, que son capaces de funcionar en -cualquier tipo de número (integer, float, complex, etc.) de la misma manera, ya -que todos son números, al fin y al cabo. - -Entender estos conceptos a nivel intuitivo, sin necesidad de entrar en los -detalles específicos de cada uno, es interesante para cualquier programador y -facilita de forma radical la comprensión de muchas de las decisiones de diseño -tomadas en python y en proyectos relacionados aunque también, por supuesto, de -otros lenguajes y herramientas. - -[^encapsulation]: https://en.wikipedia.org/wiki/Encapsulation_(computer_programming) -[^inheritance]: https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming) -[^polymorphism]: https://en.wikipedia.org/wiki/Polymorphism_(computer_science) -[^subtyping]: https://en.wikipedia.org/wiki/Subtyping - - -## Sintaxis - -En el siguiente ejemplo se muestra la sintaxis básica a la hora de crear una -clase y después instanciar dos nuevos objetos `bobby` y `beltza`. Los puntos -(`.`) se utilizan para indicar a quién pertenece el método o propiedad al que -se hace referencia (*identidad*). De este modo, no ocurrirá lo mismo cuando el -perro (`Dog`) `bobby` ladre (`bark`) que cuando lo haga el perro `beltza`. - -Los métodos describen la *funcionalidad* asociada a los perros en general, pero -además, la función `bark` los describe en particular, haciendo que cada perro -tome su nombre (`name`), una propiedad o dicho de otro modo, su *estado*. - -``` python -class Dog: - type = "canine" - def __init__(self, name): - self.name = name - def bark(self): - print("Woof! My name is " + self.name) - -bobby = Dog("Bobby") # New Dog called Bobby -beltza = Dog("Beltza") # New Dog called Beltza - -bobby.name # Bobby -beltza.name # Beltza - -bobby.type # canine -beltza.type # canine - -bobby.bark() # Prints "Woof! My name is Bobby" -beltza.bark() # Prints "Woof! My name is Beltza" -``` - -### Creación de objetos - -El ejemplo muestra cómo crear nuevos *objetos* de la clase `Dog`. Las llamadas -a `Dog("Bobby")` y `Dog("Beltza")` crean las diferentes instancias de la clase. - -Llamar a los nombres de clase como si de funciones se tratara crea una -instancia de éstas. Los argumentos de entrada de la llamada se envían como -argumentos de la función `__init__` declarada también en el propio ejemplo. -Entiende de momento que los argumentos posicionales se introducen a partir de -la segunda posición, dejando el argumento llamado `self` en el ejemplo para un -concepto que más adelante entenderás. - -En el ejemplo, por tanto, se introduce el nombre (`name`) de cada `Dog` en su -creación y la función `__init__` se encarga de asignárselo a la instancia -recién creada mediante una metodología que se explica más adelante en este -mismo capítulo. De momento no es necesario comentar en más profundidad estos -detalles, con lo que sabes es suficiente para entender el funcionamiento -general. - -Queda por aclarar, sin embargo, qué es la función `__init__` y por qué tiene un -nombre tan extraño y qué es `type = canine`, que lo trataremos en próximos -apartados de este capítulo. - -### Herencia - -Antes de entrar en los detalles propuestos en el apartado anterior, que tratan -conceptos algo más avanzados, es interesante ver cómo definir clases mediante -la herencia. Basta con introducir una lista de clases de las que heredar en la -definición de la clase, entre paréntesis, como si de argumentos de entrada de -una función se tratara, tal y como se muestra en la clase `Dog` del siguiente -ejemplo ejecutado en la REPL: - -``` python ->>> class Animal: -... def live(self): -... print("I'm living") -... ->>> class Terrestrial: -... def move(self): -... print("I'm moving on the surface") -... ->>> class Dog(Animal, Terrestrial): -... def bark(self): -... print("woof!") -... def move(self): -... print("I'm walking on the surface") -... ->>> bobby = Dog() ->>> bobby.bark() -woof! ->>> bobby.live() -"I'm living" ->>> bobby.move() -"I'm walking on the surface" -``` - -El ejemplo muestra un claro uso de la herencia. La clase `Dog` hereda -automáticamente las funciones asociadas a las superclases, pero es capaz de -definir las propias e incluso redefinir algunas. Independientemente de la -redefinición del método `move`, cualquier perro (`Dog`) va a ser capaz de -moverse por la superficie, porque la superclase `Terrestrial` ya le da los -métodos necesarios para hacerlo. Lo que ocurre es que cualquier subclase de -`Terrestrial` tiene la ocasión moverse (`move`) a su manera: en el caso del -perro, caminando. - -> NOTA: La herencia es interesante, pero tampoco debe caerse en la psicosis de -> añadir demasiadas superclases. En ocasiones las superclases son necesarias, -> sobre todo cuando aprovechar el polimorfismo facilita el trabajo, pero -> usarlas de forma agresiva genera código extremadamente complejo sin razón. - - -### Métodos de objeto o funciones de clase: `self` - -Los métodos reciben un parámetro de entrada llamado `self` que no se utiliza a -la hora de llamarlos: al hacer `bobby.bark()` no se introduce ningún argumento -de entrada a la función `bark`. - -Sin embargo, si no se añade el argumento de entrada a la definición del método -`bark` y se llama a `bobby.bark()` pasa lo siguiente: - -``` python ->>> class Dog: -... def bark(): -... pass -... ->>> bobby = Dog() ->>> bobby.bark() -Traceback (most recent call last): - File "", line 1, in -TypeError: bark() takes 0 positional arguments but 1 was given -``` - -Python dice que `bark` espera `0` argumentos posicionales pero se le ha -entregado `1`, que nosotros no hemos metido en la llamada, claro está. Así que -ha debido de ser él. - -Efectivamente, python introduce un argumento de entrada en los métodos, el -argumento de entrada que por convención se suele llamar `self`. Este parámetro -es el propio `bobby` en este caso. - -> NOTA: Por convención se le denomina `self`. Tú le puedes llamar como te -> apetezca pero, si pretendes que otros programadores te entiendan, mejor -> `self`. - -Para explicar por qué ocurre esto es necesario diferenciar bien entre clase y -objeto. Tal y como hemos hecho antes con las definiciones enciclopédicas -(*clase*) y los conceptos del mundo real que encajan en la definición -(*objeto*). Los objetos también se conocen como instancias, son piezas de -información independiente que han sido creadas a partir de la definición que la -clase aportaba. - -En python las clases tienen la posibilidad de tener funciones, que definen el -comportamiento de la clase y no el de los objetos que se crean desde ellas. -Ten en cuenta que las clases también deben procesarse y ocupan un espacio en la -memoria, igual que te ocurre a ti, puedes conocer un concepto y su -comportamiento y luego muchos casos que cumplan ese concepto y ambas cosas -son independientes. Esta posibilidad aporta mucha flexibilidad y permite -definir clases complejas. - -Ahora bien, para python las funciones de clase y los métodos (de los objetos, -si no no se llamarían métodos), se implementan de la misma manera. Para la -clase ambas cosas son lo mismo. Sin embargo, el comportamiento del operador -punto (`.`), que dice a quién pertenece la función o método, es diferente si el -valor de la izquierda es una clase o un objeto. Introduciendo en el segundo -caso el propio objeto como primer parámetro de entrada, el `self` del que -hablamos, para que la clase sepa qué objeto tiene que alterar. Este es el -mecanismo de la *identidad* del que antes hablamos y no llegamos a definir en -detalle. Cada objeto es único, y a través del `self` se accede a él. - -Es un truco interesante para no almacenar las funciones en cada uno de los -objetos como método. En lugar de eso, se mantienen en la definición de la clase -y cuando se llama al método, se busca de qué clase es el objeto y se llama a la -función de la clase con el objeto como argumento de entrada. - -Dicho de otra forma, `bobby.bark()` es equivalente a `Dog.bark( bobby )`. - -Ilustrado en un ejemplo más agresivo, puedes comprobar que en función de a -través de qué elemento se acceda a la función `bark` python la interpreta de -forma distinta. A veces como función (*function*) y otras veces como método -(*method*), en función de si se accede desde la clase o desde el objeto: - -``` python ->>> class Dog: -... def bark(self): -... pass -... ->>> type ( Dog.bark) - ->>> type ( bobby.bark ) - -``` - -> NOTA: También te habrás fijado, y si no lo has hecho es momento de hacerlo, -> que los nombres de las clases empiezan por mayúscula en los ejemplos (`Dog`) -> mientras que los objetos comienzan en minúscula (`bobby`). Se trata de otra -> convención ampliamente utilizada para saber diferenciar entre uno y otro de -> forma sencilla. Es evidente cuál es la clase y el objeto con los nombres que -> hemos tratado en los ejemplos, pero en otros casos puede no serlo y con este -> sencillo truco facilitas la lectura de tu código. Hay muchas ocasiones en las -> que esta convención se ignora, así que cuidado. -> Prueba a hacer `type(int)` en la terminal. - -### Variables de clase - -En el primer ejemplo del capítulo hemos postergado la explicación de `type = -canine` y ahora que ya manejas la mayor parte de la terminología y dominas la -diferencia entre una clase y una instancia de ésta (un *objeto*) es momento de -recogerla. A continuación se recupera la sección del ejemplo para facilitar la -consulta, fíjate en la línea 2. - -``` {.python .numberLines} -class Dog: - type = "canine" - def __init__(self, name): - self.name = name - def bark(self): - print("Woof! My name is " + self.name) -``` - -`type` es lo que se conoce como una *variable de clase* (*class variable*). - -> NOTA: En este documento se ha evitado de forma premeditada usar la palabra -> *variable* para referirse a los valores y sus referencias con la intención de -> marcar la diferencia entre ambos conceptos. En este apartado, sin embargo, a -> pesar de que se siga tratando de una referencia, se usa el nombre *class -> variable* porque es como se le llama en la documentación[^class_var] y así -> será más fácil que lo encuentres si en algún momento necesitas buscar -> información al respecto. De esto ya hemos discutido en el capítulo sobre -> datos, donde decimos que *todo es una referencia*. - -[^class_var]: - -Previamente hemos hablado de que los objetos pueden tener propiedades -asociadas, y cada objeto tendrá las suyas. Es decir, que cada instancia de la -clase puede tener sus propias propiedades independientes. El caso que tratamos -en este momento es el contrario, el `type` es un valor que comparten **todas** -las instancias de `Dog`. Cualquier cambio en esos valores los verán todos los -objetos de la clase, así que hay que ser cuidadoso. - -El acceso es idéntico al que ocurriría en un valor asociado al objeto, como en -el caso `name` del ejemplo, pero en este caso observas que en su declaración en -la clase no es necesario indicar `self`, ya no es necesario decir cuál es la -instancia concreta a la que se le asigna el valor: se le asigna a todas. - -A parte de poder acceder a través de los objetos de la clase, es posible -acceder directamente desde la clase a través de su nombre, como a la hora de -acceder a las funciones de clase: `Dog.type` resultaría en `"canine"`. - -> NOTA: Si en algún caso python viera que un objeto tiene propiedades y -> variables de clase definidas con el mismo nombre, cosa que no debería ocurrir -> a menudo, tendrán preferencia las propiedades. - -### Encapsulación explícita - -Es posible que te encuentres en alguna ocasión con métodos o propiedades, -*campos* en general, cuyo nombre comience por `_` o por `__`. Se trata de casos -en los que esas propiedades o métodos quieren ocultarse del exterior. - -El uso de `_` al inicio del nombre de un campo es una convención que avisa de -que este campo no debe accederse desde el exterior de la clase y su objetivo es -usarlo desde el interior de ésta. - -Esta convención se llevó al extremo en algún momento y se decidió crear un caso -en el que esta convención inicial tuviera cierta funcionalidad añadida para las -dobles barras bajas (`__`) que impidiera un acceso accidental a esos campos -conocido como *name mangling*. - -#### Campos privados: *name mangling* - -El *name mangling* es un truco que hace python para asegurarse de que no se -entra por accidente a las secciones que empiezan por `__`. Añade -`_nombredeclase` al inicio de los campos, transformando su nombre final y -dificultando el acceso por accidente. - -Ese acceso accidental no sólo es para que el programador no acceda, ya que, si -se esfuerza la suficiente, va a poder hacerlo de igual modo, si no para que el -propio python no acceda al campo que no corresponde. El hecho de añadir el -nombre de la clase al campo crea una brecha en la herencia, haciendo que los -campos no se hereden de la forma esperada. - -En una subclase en la que los campos de la clase madre han sido marcados con -`__`, la herencia hace que estos campos se hereden con el nombre cambiado que -contiene el nombre de la superclase. De este modo, es difícil para la subclase -pisar estos campos ya que tendría que definirlos manualmente con el nombre -cambiado. Crear nuevos campos con `__` no funcionaría, ya que, al haber -cambiado de clase, el nombre generado será distinto. - -Este mecanismo es un truco para crear *campos privados*, concepto bastante -común en otros lenguajes como Java o C++, que en python es inexistente. - -El concepto de los *campos privados* es interesante en la programación -orientada a objetos. Pensando en la *encapsulación*, es lógico que a veces las -clases definan métodos o propiedades que sólo los objetos creados a partir de -ellas conozcan y que los objetos creados de clases heredadas no. Este es el -método que python tiene para aportar esta funcionalidad. - -Es interesante añadir, por otro lado, que python es un lenguaje de programación -muy dinámico por lo que la propia definición de las clases, y muchas cosas más, -puede alterarse una vez creadas. Esto significa que el hecho de ocultar campos -no es más que un acuerdo tácito entre programadores porque, si quisieran, -podrían definir todo de nuevo. Trucos como este sirven para que el programador -sea consciente de que está haciendo cosas que se supone que no debería hacer. -Cuando programes en python, tómate esto como pistas que te indican cómo se -supone que deberías estar usando las clases. - -### Acceso a la superclase - -A pesar de la herencia, no siempre se desea eliminar por completo la -funcionalidad de un método o pisar una propiedad. A veces es interesante -simplemente añadir funcionalidad sobre un método o recordar algún valor -definido en la superclase. - -Python soporta la posibilidad de llamar a la superclase mediante la función -`super`, que permite el acceso a cualquier campo definido en la superclase. - -``` python -class Clase( SuperClase ): - def metodo(self, arg): - super().metodo(arg) # Llama a la definición de - # `metodo` de `SuperClase` -``` - -> NOTA: `super` busca la clase previa por preferencia, si usas herencias -> múltiples y pisas los campos puede complicarse. - - -## Interfaces estándar: Duck Typing - -Una de las razones principales para usar programación orientada a objetos es -que, si se eligen los métodos con precisión, pueden crearse estructuras de -datos que se comporten de similar forma pero que tengan cualidades diferentes. -Independientemente de cómo estén definidas sus clases, si dos objetos disponen -de los mismos métodos podrán ser sustituidos el uno por el otro en el programa -y seguirá funcionando aunque su funcionalidad cambie. - -Dicho de otra forma, dos objetos (o dos cosas, en general) podrán ser -intercambiados si disponen de la misma *interfaz*. *Interfaz*, de *inter*: -entre; y *faz*: cara, viene a significar algo así como «superficie de contacto» -y es la palabra que se usa principalmente para definir la frontera compartida -entre dos componentes o, centrándonos en el caso que nos ocupa, su conexión -funcional. - -Si recuerdas la *herencia* y la combinas con estos conceptos, puedes -interpretar que además de una metodología para reutilizar código es una forma -de crear nuevas definiciones que soporten la misma interfaz. - -En otros lenguajes de programación, Java, por ejemplo, existe el concepto -*interfaz* que serían una especie pequeñas clases que definen qué funciones -debe cumplir una clase para que cumpla la interfaz. A la hora de crear las -clases se les puede indicar qué interfaces implementan y el lenguaje se encarga -de asegurarse de que el programador ha hecho todo como debe. - -El dinamismo de python hace que esto sea mucho más flexible. Debido a que -python no hace casi comprobaciones antes de ejecutarse, necesita un método para -mucho más directo. Para python, *si anda como un pato, vuela como un pato y -nada como un pato: es un pato*. - -Python usa lo que en la terminología del lenguaje se conoce como -*protocolos*[^protocol] (*protocol*) para que los objetos creados por el -programador puedan comportarse como los que el propio sistema aporta. Por -ejemplo, que sea posible utilizarlos como iterable en un `for`, que el sistema -pueda cerrarlos de forma automática, buscar en ellos usando el operador `in`, -etc. Simplemente, el sistema define qué funciones se deben cumplir en cada uno -de esos casos y cuando se encuentre con ellos intentará llamarlas -automáticamente. Si el elemento no dispone de esas funciones lanzará una -excepción como la que lanza cuando intentamos acceder a un método que no existe -(que es básicamente lo que estamos haciendo en este caso). - -> TODO: En realidad no se llaman protocolos todos ellos. Se llama así sólo al -> *iterator protocol*. En realidad se llaman: [Special Method -> Names](https://docs.python.org/3/reference/datamodel.html#special-method-names) - -En general, python, con el fin de diferenciar claramente qué nombres elige el -programador y cuales han sido seleccionados por el lenguaje, suele utilizar una -convención para la nomenclatura: comienzan y terminan por: `__` - -A continuación se describen algunos de los protocolos más comunes, algunos ya -han aparecido a lo largo de los ejemplos del documento, otros las verás por -primera vez ahora. Existen muchos más, y todos están extremadamente bien -documentados. Si en algún momento necesitas crear algunos nuevos, la -documentación de python es una buena fuente donde empezar. - -Todos las protocolos se presentan con un nombre, en muchos casos inventado, -terminado en *-able*. Python utiliza también este tipo de nombres, como el ya -aparecido *llamable*, o *callable* en inglés, que se refiere a cualquier cosa -que puede ser llamada. Representar los nombres de esta manera sirve para -expresar el interés de los protocolos. Si en algún momento necesitas crear una -clase que defina un objeto en el que se puede buscar necesitas que sea un -*buscable*, es decir, que soporte el protocolo que define ese comportamiento. - -[^protocol]: **Protocolo**: 5. m. Inform. Conjunto de reglas que se establecen - en el proceso de comunicación entre dos sistemas. — RAE [Consultado - 01-12-2019]: - -### *Representable*: `__repr__` - -Este protocolo sirve para otorgar a python una forma de representar estos -objetos. Al ejecutar la función `print` o al exponer valores en la REPL -(recuerda que la P significa print), python trata de visualizarlos. - -La el método `__repr__` se ejecuta justo antes de imprimirse el objeto, de -forma automática. La función requiere que se devuelva un elemento de tipo -string, que será el que después se visualice. - -En el ejemplo a continuación se comienza con la clase `Dog` vacía y se -visualiza una de sus instancias. Posteriormente, se reasigna la función -`__repr__` de `Dog` con una función que devuelve un string. Al volver a mostrar -a `bobby` el resultado cambia. - -Como se ve en el ejemplo, es interesante tener una buena función de -representación si lo que se pretende es entender el contenido de los objetos. - -> NOTA: Python ya aporta una forma estándar de representar los objetos, si la -> función `__repr__` no se define simplemente se usará la forma estándar. - -``` python ->>> class Dog: -... pass -... ->>> bobby = Dog() ->>> bobby -<__main__.Dog object at 0x7fb7fba1b908> - ->>> Dog.__repr__ = lambda self: "Dog called: " + self.name ->>> bobby.name = "Bobby" ->>> bobby -Dog called: Bobby ->>> -``` - -### *Contable*: `__len__` - -En python se utiliza la función `len` para comprobar la longitud de cualquier -elemento contable. Por ejemplo: - -``` python ->>> len( (1,2,3) ) -3 -``` - -Las objetos que soporten esta función podrán contarse para conocer su longitud -mediante la función `len`. Python llamará al método `__len__` del objeto (que -se espera que devuelva un número entero) y ésta será su longitud. Siguiendo con -el ejemplo del protocolo anterior: - -``` python ->>> Dog.__len__ = lambda self: 12 # Siempre devuelve 12 ->>> len(bobby) -12 -``` - -Este protocolo permite crear elementos contables, en lugar de los típicos -diccionario, tupla y lista. Como por ejemplo los ya existentes `NamedTuple`, -`OrderedDict` y otros. Los protocolos para el *buscable* e *iterable* también -son muy interesantes para esta labor. - -### *Buscable*: `__contains__` - -El método `__contains__` debe devolver `True` o `False` y recibir un argumento -de entrada. Con esto el objeto será capaz de comprobarse con sentencias que -hagan uso del operador `in` (y `not in`). Las dos llamadas del ejemplo son -equivalentes. La segunda es lo que python realiza internamente al encontrarse -el operador `in` o el operador `not in`. - -``` python ->>> 1 in [1,2,3] -True ->>> [1,2,3].__contains__(1) -True -``` - -### *Iterable*: `__next__` e `__iter__` - -El protocolo iterable permite crear objetos con los que es posible iterar en -bucles `for` y otras estructuras. Por ejemplo, los archivos de texto en python -soportan este protocolo, por lo que pueden leerse línea a línea en un bucle -`for`. - -Igual que en el caso del protocolo `__len__`, que servía para habilitar la -llamada a la función `len`, `__iter__` y `__next__` sirven, respectivamente, -para habilitar las llamadas a `iter` y `next`. - -La función `iter` sirve para convertir el elemento a *iterable*, que es una -clase que soporte el funcionamiento de la función `next`. Y `next` sirve para -pasar al siguiente elemento de un iterable. Ejemplificado: - -``` python ->>> l = [1,2,3] ->>> next(l) -Traceback (most recent call last): - File "", line 1, in -TypeError: 'list' object is not an iterator ->>> it = iter(l) ->>> it - ->>> ->>> next(it) -1 ->>> next(it) -2 ->>> next(it) -3 ->>> next(it) -Traceback (most recent call last): - File "", line 1, in -StopIteration -``` - -La función `__next__` tiene un comportamiento muy sencillo. Si hay un próximo -elemento, lo devuelve. Si no lo hay lanza la excepción `StopIteration`, para -que la capa superior la capture. - -Fíjate que la lista por defecto no es un iterable y que se debe construir un -elemento iterable desde ella con `iter` para poder hacer `next`. Esto se debe a -que la función `iter` está pensada para restaurar la posición del cursor en el -primer elemento y poder volver a iniciar la iteración. - -Sorprendentemente, este es el procedimiento de cualquier `for` en python. El -`for` es una estructura creada sobre un `while` que construye iterables e -itera sobre ellos automáticamente. - -Este bucle `for`: - -``` python -for el in secuencia: - # hace algo con `el` -``` - -Realmente se implementa de la siguiente manera: - -``` python -# Construye un iterable desde la secuencia -iter_obj = iter(secuencia) - -# Bucle infinito que se rompe cuando `next` lanza una -# excepción de tipo `StopIteration` -while True: - try: - el = next(iter_obj) - # hace algo con `el` - except StopIteration: - break -``` - -Así que, si necesitas una clase con capacidad para iterarse sobre ella, puedes -crear un pequeño iterable que soporte el método `__next__` y devolver una -instancia nueva de éste en el método `__iter__`. - -### *Creable*: `__init__` - -El método `__init__` es uno de los más usados e interesantes de esta lista, esa -es la razón por la que ha aparecido en más de una ocasión durante este -capítulo. - -El método `__init__` es a quién se llama al crear nuevas instancias de una -clase y sirve para *ini*cializar las propiedades del recién creado objeto. - -Cuando se crean nuevos objetos, python construye su estructura en memoria, -pidiéndole al sistema operativo el espacio necesario. Una vez la tiene, envía -esa estructura vacía a la función `__init__` como primer argumento para que sea -ésta la encargada de rellenarla. - -Como se ha visto en algún ejemplo previo, el método `__init__` (es un método, -porque el objeto, aunque vacío, ya está creado) puede recibir argumentos de -entrada adicionales, que serán los que la llamada al nombre de la clase reciba, -a la hora de crear los nuevos objetos. Es muy habitual que el inicializador -reciba argumentos de entrada, sobre todo argumentos con nombre, para que el -programador que crea las instancias tenga la opción de inicializar los campos -que le interesen. - -Volviendo a un ejemplo previo: - -``` python -class Dog: - type = "canine" - def __init__(self, name): - self.name = name - def bark(self): - print("Woof! My name is " + self.name) - -bobby = Dog("Bobby") # Aquí se llama a __init__ -``` - -El nombre del perro, `"Bobby"` será recibido por `__init__` en el argumento -`name` e insertado al `self` mediante `self.name = name`. De este modo, esa -instancia de `Dog`, `bobby`, tomará el nombre `Bobby`. - -> NOTA: En muchas ocasiones, el método `__init__` inicializa a valores vacíos -> todas las posibles propiedades del objeto con el fin de que quien lea el -> código de la clase sea capaz de ver cuáles son los campos que se utilizan en -> un primer vistazo. Es una buena práctica listar todos los campos posibles en -> `__init__`, a pesar de que no se necesite inicializarlos aún, con el fin de -> facilitar la lectura. - -> NOTA: Quien tenga experiencia con C++ puede equivocarse pensando que -> `__init__` es un constructor. Tal y como se ha explicado anteriormente, al -> método `__init__` ya llega un objeto construido. El objetivo de `__init__` es -> inicializar. En python el constructor, que se encarga de crear las instancias -> de la clase, es la función `__new__`. - -> NOTA: Si creas una clase a partir de la herencia y sobreescribes su método -> `__init__` es posible que tengas que llamar al método `__init__` de la -> superclase para inicializar los campos asociados a la superclase. Recuerda -> que puedes acceder a la superclase usando `super`. - -### *Abrible* y *cerrable*: `__enter__` y `__exit__` - -Este protocolo permite que los objetos puedan ser abiertos y cerrados de forma -segura y con una sintaxis eficiente. Aunque no se van a listar en profundidad, -el objetivo de este punto es mostrar la sentencia `with` que se habilita -gracias a estos protocolos y mostrar cómo facilitan la apertura y cierre. - -El PEP 343[^pep343] muestra en detalle la implementación de la sentencia -`with`. Simplificándolo y resumiéndolo, `with` sirve para abrir elementos y -cerrarlos de forma automática. - -> NOTA: Los PEP (*Python Enhancement Proposals*) son propuestas de mejora para -> el lenguaje. Puedes consultar todos en la web de python. Son una fuente -> interesante de información y conocimiento del lenguaje y de programación en -> general. -> - -Pensando en, por ejemplo, la lectura de un archivo, se requieren varias etapas -para tratar con él, por ejemplo: - -``` python -f = open("file.txt") # apertura del fichero -f.read() # lectura -f.close() # cierre -``` - -Este método es un poco arcaico y peligroso. Si durante la lectura del fichero -ocurriera alguna excepción el fichero no se cerraría, ya que la excepción -bloquearía la ejecución del programa. Para evitar estos problemas, lo lógico -sería hacer una estructura `try-except` y añadir el cierre del fichero en un -`finally`. - -La sentencia `with` se encarga básicamente de hacer eso y facilita la escritura -de todo el proceso quedándose así: - -``` python -with f as open("file.txt"): # apertura - f.read() # en este cuerpo `f` está abierto - -# Al terminar el cuerpo, de forma normal o forzada, -# `f` se cierra. -``` - -Ahora bien, para que el fichero pueda ser abierto y cerrado automáticamente, -deberá tener implementados los métodos `__enter__` y `__exit__`. En el PEP 343 -se muestra la equivalencia entre la sentencia `with` y el uso de `__enter__`, -`__close__` y el `try-except`. - -[^pep343]: Puedes leer el contenido completo del PEP en: - - -### *Callable*: `__call__` - -Queda pendiente desde el capítulo sobre funciones, responder a lo que es un -*callable* o *llamable*. Una vez llegados a este punto, tiene una respuesta -fácil: un *llamable* es un objeto que soporta el protocolo correspondiente, -definido por el método `__call__`. - -Aunque pueda parecer sorprendente, las funciones en python también se llaman de -este modo, así que realmente son objetos que se llaman porque soportan este -protocolo. Es lógico, porque las funciones, recuerda el capítulo previo, pueden -guardar valores, como el contexto en el que se crean (*closure*). Las funciones -son meros *llamables* y como tales se comportan. - -Llevado más allá, los tipos básicos de python están definidos en clases -también, lógicamente, pero pueden ser llamados para hacer conversiones tal y -como vimos en el capítulo sobre datos. Simplemente, soportan el protocolo -*llamable*. - -``` python ->>> class Dog: -... def __call__(self): -... print("Dog called") -... ->>> dog = Dog() ->>> dog() -Dog called -``` - -Ten en cuenta que el método `__call__` puede recibir cualquier cantidad de -argumentos como ya hemos visto en apartados anteriores, pero el primero será el -propio objeto que está siendo llamado, el `self` que ya conocemos. - -Resumiendo, el método `__call__` describe cómo se comporta el objeto cuando se -le aplican las paréntesis. - -### *Subscriptable*: `__getitem__`, `__setitem__` y `__delitem__` - -Tal y como el método anterior describía cómo se aplican las paréntesis a un -objeto, el protocolo que se muestra en este apartado describe el comportamiento -del objeto cuando se le aplican los corchetes. Recordando el capítulo sobre -datos, los corchetes sirven para acceder a valores de las listas, tuplas, -diccionarios y sets, que resultan ser también un tipo de objeto que describe -este comportamiento mediante el protocolo que tenemos entre manos. - -Cuando python encuentra que se está tratando de acceder a un campo de un objeto -mediante los corchetes llama automáticamente al método `__getitem__` y cuando -se intenta asociar un campo a un valor llama al método `__setitem__` del -objeto. Al pedir la eliminación de un campo del objeto con la sentencia `del`, -se llama al método `__delitem__`. - -Aunque en otros protocolos aquí descritos hemos inventado un nombre para este -documento, Python a este protocolo le denomina *subscriptable* así que cuando -intentes acceder usando corchetes a un objeto que no soporta el protocolo, el -error que saltará te utilizará la misma nomenclatura que nosotros. - -El siguiente ejemplo muestra el protocolo en funcionamiento en una clase sin -funcionamiento alguno. Lo lógico y funcional sería utilizar estos dos métodos -para facilitar el acceso a campos de estas clases o para crear clases que -pudiesen sustituir a listas, tuplas, diccionarios o sets de forma sencilla. - -``` python ->>> class Dog: -... def __getitem__(self, k): -... print(k) -... def __setitem__(self, k, v): -... print(k, v) -... ->>> bobby = Dog() ->>> bobby["field"] -field ->>> bobby["field"] = 10 -field 10 -``` - -Fíjate en que reciben diferente cantidad de argumentos de entrada cada uno de -los métodos. El método `__setitem__` necesita indicar no sólo qué *item* desea -alterarse, sino su también su valor. - -#### *Slice notation* - -Se trata de una forma avanzada de seleccionar las posiciones de un objeto, el -nombre viene de *slice*, rebanada, y significa que puede coger secciones del -objeto en lugar de valores únicos. Piénsalo como en una barra de pan cortada en -rebanadas de la que quieres seleccionar qué rebanadas te interesan en bloque. - -No todos los objetos soportan *slicing*, pero los que lo hacen permiten acceder -a grupos de valores en el orden en el que están indicando el inicio del grupo -(inclusive), el final (no inclusive) y el salto de un elemento al siguiente. - -Además, los valores del *slice* pueden ser negativos. Añadir un número negativo -al salto implica que el salto se hace hacia atrás. Añadirlo en cualquier de los -otros dos valores, inicio o final de grupo, implica que se cuenta el elemento -desde el final de la colección en dirección opuesta a la normal. - -La sintaxis de los *slice*s es la siguiente: `[inicio:fin:salto]`. -Cada uno de los valores es opcional y si no se añaden se comportan de la -siguiente manera: - -- Inicio: primer elemento -- Fin: último elemento inclusive -- Salto: un único elemento en orden de cabeza a cola - -> NOTA: El índice para representar el último elemento es -1, pero si se quiere -> indicar como final, usar -1 descartará el último elemento porque el final no -> es inclusivo. Para que sea inclusivo es necesario dejar el campo fin vacío. - -Dada una lista de los números naturales del 1 al 99, ambos incluidos, de -nombre `l` se muestran unos casos de *slicing*. - -``` python ->>> l[-5:] -[95, 96, 97, 98, 99] ->>> l[6:80:5] -[6, 11, 16, 21, 26, 31, 36, 41, 46, 51, 56, 61, 66, 71, 76] ->>> l[60:0:-5] -[60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5] -``` - -La sintaxis de los *slice*s mostrada sólo tiene sentido a la hora de acceder a -los campos de un objeto, si se trata de escribir suelta lanza un error de -sintaxis. Para crear *slice*s de forma separada se construyen mediante la -clase `slice` de la siguiente manera: `slice(inicio, fin, salto)`. - -En los métodos del protocolo *subscriptable* (`__getitem__`, `__setitem__` y -`__delitem__`) a la hora de elegir un *slice* se recibe una instancia del tipo -*slice* en lugar de una selección única como en el ejemplo previo: - -``` python ->>> class Dog: -... def __getitem__(self, item): -... print(item) -... ->>> bobby = Dog() ->>> bobby[1:100] -slice(1, 100, None) ->>> bobby[1:100:9] -slice(1, 100, 9) ->>> bobby[1:100:-9] -slice(1, 100, -9) -``` - -Por complicarlo todavía más, los campos del *slice* creado desde la clase -`slice` pueden ser del tipo que se quiera. El formato de los `:` es únicamente -*sintactic sugar* para crear *slices* de tipo integer o string. Aunque después -es responsabilidad del quien implemente el protocolo soportar el tipo de -*slice* definido, es posible crear *slices* de lo que sea, incluso anidarlos. - -Como ejemplo de un caso que utiliza *slices* no integer, los tipos de datos -como los que te puedes encontrar en la librería `pandas` soportan *slicing* -basado en claves, como si de un diccionario se tratara. - -### Ejemplo de uso - -Para ejemplificar varios de estos protocolos, tomamos como ejemplo una pieza de -código fuente que quien escribe este documento ha usado en alguna ocasión en su -trabajo como desarrollador. - -Se trata de un iterable que es capaz de iterar en un sistema de ficheros -estructurado en carpetas *año-mes-día* con la estructura `AAAA/MM/DD`. Este -código se creó para analizar datos que se almacenaban de forma diaria en -carpetas con esta estructura. Diariamente se insertaban fichero a fichero por -un proceso previo y después se realizaban análisis semanales y mensuales de los -datos. Esta clase permitía buscar por las carpetas de forma sencilla y obtener -rápidamente un conjunto de carpetas que procesar. - -El ejemplo hace uso del módulo `datetime`, un módulo de la librería estándar -que sirve para procesar fechas y horas. Por ahora, puedes ver la forma de -importarlo como una receta y en el siguiente capítulo la entenderás a fondo. El -funcionamiento del módulo es sencillo y puedes usar la ayuda para comprobar las -funciones que no conozcas. - -Te animo a que analices el comportamiento del ejemplo, viendo en detalle cómo -se comporta. Como referencia, fuera de la estructura de la clase, en las -últimas líneas, tienes disponible un bucle que puedes probar a ejecutar para -ver cómo se comporta. - -``` {.python .numberLines} -from datetime import timedelta -from datetime import date - -class dateFileSystemIterator: - - """ - Iterate over YYYY/MM/DD filesystems or similar. - """ - def __init__( self, start = date.today(), end = date.today(), - days_step = 1, separator = '/'): - self.start = start - self.current = start - self.end = end - self.separator = separator - self.step = timedelta( days = days_step ) - - def __iter__( self ): - self.current = self.start - return self - - def __next__( self ): - if self.current >= self.end: - raise StopIteration - else: - self.current += self.step - datestring = self.current - self.step - datestring = datestring.strftime( "%Y" \ - + self.separator \ - + "%m"+self.separator \ - +"%d") - return datestring - - def __repr__( self ): - out = self.current - self.step - tostring = lambda x: x.strftime("%Y" \ - + self.separator \ - + "%m" \ - + self.separator + "%d") - return "" \ - + "," \ - + "," \ - + "," - - -it = dateFileSystemIterator(start = date.today() - timedelta(days=30)) -print(it) -for i in it: - print(i) -``` - -#### Ejercicio libre: `yield` y los generadores - -La parte de la iteración del ejemplo previo puede realizarse forma más breve -mediante el uso de la sentencia `yield`. Aunque no la trataremos, `yield` -habilita muchos conceptos interesantes, entre ellos los *generadores*. - -A continuación tienes un ejemplo de cómo resolver el problema anterior mediante -el uso de esta sentencia. Te propongo como ejercicio que investigues cómo -funciona buscando información sobre los *generadores* (*generator*) y la -propia sentencia `yield`. - -``` python -from datetime import datetime, timedelta - -def iterate_dates( date_start, date_end=datetime.today(), - separator='/', step=timedelta(days=1)): - date = date_start - while date < date_end: - yield date.strftime('%Y'+separator+'%m'+separator+'%d') - date += step -``` - -`yield` tiene mucha relación con las *corrutinas* (*coroutine*) que, aunque no -se tratarán en este documento, son un concepto muy interesante que te animo a -investigar. Si lo haces, verás que los generadores son un caso simple de una -corrutina. - -## Lo que has aprendido - -Este capítulo también ha sido intenso como el anterior, pero te prometo que no -volverá a pasar. El interés principal de este capítulo es el de hacerte conocer -la programación orientada a objetos y enseñarte que en python lo inunda todo. -Todo son objetos. - -Para entenderlo has comenzado aprendiendo lo que es la programación orientada a -objetos, concretamente la orientada a clases, donde has visto por primera vez -los conceptos de identidad propia, comportamiento y estado. - -Desde ahí has saltado al fundamento teórico de la programación orientada a -objetos y has visitado la encapsulación, la herencia y el polimorfismo para -luego, una vez comprendidos, comenzar a definir clases en python. - -Esto te ha llevado a necesitar conocer qué es el argumento que suele llamarse -`self`, una excusa perfecta para definir qué son las variables y funciones de -clase y en qué se diferencian de las propiedades y métodos. - -Como la encapsulación no se había tratado en detalle aún, lo próximo que has -hecho ha sido zambullirte en los campos privados viendo cómo python los crea -mediante un truco llamado *name mangling* y su impacto en la herencia. - -Aunque en este punto conocías el comportamiento general de la herencia hacia -abajo, necesitabas conocerlo hacia arriba. Por eso, ha tocado visitar la -función `super` en este punto, función que te permite acceder a la superclase -de la clase en la que te encuentras. En lugar de contártela en detalle, se te -ha dado una pincelada sobre ella para que tú investigues cuando lo veas -necesario, pero que sepas por dónde empezar. - -Para describir más en detalle lo calado que está python de programación -orientada a objetos necesitabas un ejemplo mucho más agresivo: los protocolos. -A través de ellos has visto cómo python recoge las funcionalidades estándar y -te permite crear objetos que las cumplan. Además, te ha servido para ver que -**todo** en python es un objeto (hasta las clases lo son[^objects]) y para ver -formas elegantes de resolver problemas comunes, como los iteradores, `with` y -otros. - -También, te recuerdo que, aunque sea de forma colateral y sin prestarle -demasiada atención, se te ha sugerido que cuando programamos no lo hacemos -únicamente para nosotros mismos y que la facilidad de lectura del código y la -preparación de éste para que otros lo usen es primordial. Los próximos -capítulos tratan en parte de ésto: de hacer uso del patrimonio tecnológico de -la humanidad, y de ser parte de él. - -[^objects]: Puedes preguntárselo a python: - ``` python - >>> class C: pass - ... - - >>> isinstance(C, object) - True - ``` diff --git a/src/06_ejec_mod.md b/src/06_ejec_mod.md deleted file mode 100644 index 0c54f4f..0000000 --- a/src/06_ejec_mod.md +++ /dev/null @@ -1,271 +0,0 @@ -# Módulos y ejecución - -Hasta ahora, has ejecutado el código en la REPL y de vez en cuando has usado -`F5` en IDLE para ejecutar. Aunque te ha permitido salir del paso, necesitas -saber más en detalle cómo funciona la ejecución para empezar a hacer tus -programas. Además, es absurdo que te pelees contra todo, hay que saber qué -batallas librar, así que necesitarás aprender a importar código realizado por -otras personas para poder centrarte en lo que más te interesa: resolver tu -problema. - -Este capítulo trata ambas cosas, que están muy relacionadas, y sirve como -trampolín para el siguiente, la instalación de nuevos paquetes y la gestión de -dependencias, y los posteriores sobre librerías interesantes que te facilitarán -el desarrollo de tus proyectos. - -Este capítulo ya te capacita casi al cien por cien para la programación aunque -aún no hemos trabajado su utilidad, pero seguro que alguna idea se te habrá -ocurrido, si no no estarías leyendo este documento. - -## Terminología: módulos y paquetes - -En python a cualquier fichero de código (extensión `.py`) se le denomina -*módulo* (*module*). A cualquier directorio que contenga módulos de código -python y un fichero llamado `__init__.py`, que puede estar vacío, se le -denomina *paquete* (*package*). El uso del fichero `__init__.py` permite que -python busque módulos dentro del directorio. - -Piensa en los paquetes como grupos de módulos que incluso pueden anidarse con -subpaquetes. En la documentación oficial verás en más de una ocasión que se -trata a los paquetes como si fueran módulos, y tiene cierto sentido, porque, -normalmente, existe un módulo principal que permite el acceso a un subpaquete. -Así que, principalmente estás trabajando con un único módulo de un paquete, que -contiene subpaquetes a los que este módulo hace referencia. Aunque pueda sonar -algo enrevesado, no te preocupes por ahora: los módulos son ficheros únicos y -los paquetes conjuntos de ellos. - -## Ejecución - -Ya conoces un par de maneras de ejecutar tus módulos de python. Usar la REPL, -introduciéndole el código que quieres ejecutar, llamar a la función «ejecutar -módulo» de IDLE con la tecla `F5` e incluso llamar a la shell de sistema con un -comando similar a este: - -``` bash -python mi_archivo.py -``` - -Normalmente, los ficheros de python se ejecutan de este último modo en -producción, mientras que los dos anteriores son más usados a la hora de -desarrollar. Realmente son métodos similares, en todos ellos el intérprete -accede al fichero o contenido que recibe y ejecuta las líneas una a una. - -Existe también una opción adicional muy usada que sirve para ejecutar módulos -que el sistema sea capaz de encontrar por sí mismo, en lugar de indicarle la -ruta, se le puede indicar simplemente el nombre del módulo usando la opción -`-m`: - -``` bash -python -m nombre_de_modulo -``` - -## Importación y *namespaces* - -Anteriormente hemos pasado sobre la sintaxis de la importación de forma muy -superficial pero tampoco es mucho más compleja a lo que ha aparecido. La -sentencia `import` permite importar diferentes módulos a nuestro programa como -en el siguiente ejemplo: - -``` python ->>> import datetime ->>> datetime - -``` - -Han pasado, sin embargo, muchas cosas interesantes en el ejemplo. En primer -lugar, python ha buscado y encontrado el módulo `datetime` en el sistema y, en -segundo lugar, ha creado un objeto módulo llamado `datetime` que atesora todas -las definiciones globales del módulo `datetime`. - -Empezando por el final, python usa lo que se conoce como *namespace* de forma -muy extendida. Los *namespaces*, de nombre (*name*) y espacio (*space*), son -una herramienta para separar contextos ampliamente usada. Los objetos, en -realidad, son una caso de *namespace* ya que cuando se llama a un método se le -dice cuál es el contexto de la llamada, es decir: al método de qué objeto se -llama. - -Para los módulos el proceso es el mismo. La sentencia `import` trae un módulo -al programa pero lo esconde tras su *namespace*, de este modo, para acceder a -algo definido en el recién importado módulo es necesario indicarle el nombre de -éste de la siguiente manera: - -``` python ->>> import datetime ->>> datetime.date.today() -datetime.date(2019, 12, 3) -``` - -En el ejemplo se accede a la clase `date` dentro del módulo `datetime`, y se -lanza su función `today`, que indica el día de hoy. Como puedes apreciar, el -operador `.` se utiliza del mismo modo que en las clases y objetos, y en -realidad es difícil saber cuándo se está accediendo a una clase y cuándo a un -módulo, aunque tampoco es necesario saberlo. - -Para no tener que escribir el nombre del módulo completo, existe otra versión -de la sentencia `import` que tiene un comportamiento muy similar: - -``` python ->>> import datetime as dt ->>> dt.date.today() -datetime.date(2019, 12, 3) -``` - -En este ejemplo, se ha cambiado el nombre del módulo a uno más corto decidido -por el programador, `dt`. El funcionamiento es el mismo, simplemente se ha -cambiado el nombre para simplificar. Este cambio de nombre también es útil -cuando se va a importar un módulo cuyo nombre es igual que alguna otra -definición. Cambiando el nombre se evitan colisiones. - -Existen versiones además, que permiten importar únicamente las funciones y -clases seleccionadas, pero que las añaden al *namespace* actual, para evitar -tener que usar el prefijo. - -``` python ->>> from datetime import date ->>> date.today() -datetime.date(2019, 12, 3) -``` - -En este último ejemplo, se trae la clase `date` al contexto actual. También -existe la posibilidad de importar más de una definición del módulo, usando la -coma para separarlas, o todo lo que el módulo exponga mediante el símbolo `*`. -Es peligroso, sin embargo, traer definiciones al namespace actual de forma -descuidada, sobre todo con la última opción, porque, es posible que se repitan -nombres por accidente y se pisen definiciones. Los namespaces se inventan con -el fin de separar las definiciones y evitar colisiones de este tipo. - -### Búsqueda - -Una vez descrito cómo se interactúa con los módulos importados, es necesario -describir dónde se buscan estos módulos. - -Los módulos se buscan en los siguientes lugares: - -1. El directorio del fichero ejecutado o el directorio de trabajo de la REPL -2. Los directorios indicados en el entorno. -3. La configuración por defecto (depende de la instalación) - -Esto significa que si guardas un archivo de python en IDLE y guardas otro más -en el mismo directorio con el nombre `modulo.py` podrás importarlo usando -`import modulo` en el primero, ya que comparten directorio. Lo mismo ocurre con -los paquetes, crear un directorio con nombre `paquete` y añadirle un fichero -vacío llamado `init.py` te permitirá hacer `import paquete`. Si añadieras más -módulos dentro del paquete, podrías importar cada uno de ellos mediante -`paquete.modulo`. - -> NOTA: Los nombres de los ficheros deben coincidir con el el nombre del módulo -> más la extensión `.py`. En el caso de los directorios, saltar a un -> subdirectorio implica acceder a un paquete, por lo que se añadirá un punto -> (`.`). - -El primer punto sirve para facilitar que organices tu proyecto de python en -varios módulos, separando así la funcionalidad en diferentes archivos. - -Los últimos dos puntos son los que permiten a python encontrar su librería -estándar y los módulos de sistema. El tercero depende de la instalación y del -formato de ésta: si python está instalado como portable no será igual que si se -instala en el sistema del modo habitual. El segundo punto también puede variar -de un sistema a otro, pero en resumen se trata de varias variables de entorno -de sistema que le indican a python dónde buscar (normalmente toman el nombre -`PYTHONPATH`, pero no es siempre así). El segundo punto puede alterarse de modo -que en función de lo que se le indique, se puede pedir a python que busque los -módulos en un lugar u otro. - -Estos lugares de búsqueda se pueden mostrar de la siguiente manera: - -``` python ->>> import sys ->>> print(sys.path) -[ '', - '/usr/lib/python36.zip', - '/usr/lib/python3.6', - '/usr/lib/python3.6/lib-dynload', - '/usr/local/lib/python3.6/dist-packages', - '/usr/lib/python3/dist-packages'] -``` - -En función del sistema en el que te encuentres y la configuración que tengas, -python mostrará diferente resultado. - -Rescatando un ejemplo previo: - -``` python ->>> import datetime ->>> datetime - -``` - -Ahora entiendes por qué es capaz de encontrar `datetime` en -`/usr/lib/python3.6`, carpeta listada en `sys.path`, bajo el nombre -`datetime.py`. - -## Ejecución vs Importación: `__main__` *guard* - -A la hora de importar un módulo, python procesa el contenido de éste ya que -necesita definir las funciones, clases, valores, etc. a exportar: ejecuta el -módulo. - -Python define una forma de separar la funcionalidad del código de sus -definiciones con el fin de poder crear código cuyas definiciones sean -reutilizables mediante la importación en otro módulo, sin que tenga ninguna -funcionalidad cuando esto ocurra, pero habilitando que tenga funcionalidades -cuando sea llamado directamente. - -Un ejemplo de uso de esto puede ser un módulo de acceso a ficheros, por -ejemplo, que visualice el contenido del fichero cuando se llame de forma -directa pero que cuando se importe únicamente aporte las funciones de lectura y -escritura sin leer y mostrar ningún fichero. - -Para que los módulos puedan tener esta doble vida, python define la variable -`__name__` que representa en qué nivel del *scope* se está ejecutando el módulo -actual. La variable `__name__` toma el valor del nombre del módulo cuando éste -está siendo importado y el valor `__main__` cuando ha sido llamado de forma -directa o está siendo ejecutado en la REPL. `__main__` es el *scope* global de -los programas, por lo que cuando algo se declara en él, implica que es el -programa principal. - -Para poder diferenciar cuándo se ha ejecutado un módulo de forma directa y -cuando se ha importado se utiliza lo que se conoce como `__main__` *guard*: - -``` python -if __name__ == "__main__": - # Este bloque sólo se ejecuta cuando el módulo es el principal -``` - -Aunque igual es un poco incómodo de entender de primeras, encontrarás esta -estructura en casi cualquier módulo de código python. Se utiliza -constantemente, incluso para los casos en los que no se pretende que el código -pueda importarse. Es una buena práctica incluir el *guard* para separar la -ejecución de las definiciones, de este modo, quien quiera saber cuál es la -funcionalidad del módulo tendrá mucho más fácil la búsqueda. - -Puedes leer más sobre este tema en la documentación de python[^main-guard]. - -[^main-guard]: - - -Siguiendo este concepto, también existe el un estándar de nomenclatura de -ficheros. El nombre `__main__.py` hace referencia al fichero que contiene el -código que se incluiría dentro del *guard* y será el fichero que python buscará -ejecutar siempre que se le pida ejecutar un paquete o un directorio sin -especificar qué módulo debe lanzar. Por ejemplo, ejecutar `python .`[^dot] en -la shell de sistema es equivalente a ejecutar `python __main__.py`. - -[^dot]: `.` significa directorio actual en cualquiera de los sistemas - operativos comunes. - - -## Lo que has aprendido - -En este capítulo corto has aprendido lo necesario sobre importación de módulos -y ejecución de código. Conocer en detalle el patrón de búsqueda de módulos de -python es primordial para evitar problemas en el futuro y organizar los -proyectos de forma elegante. - -Además, el `__main__` *guard* era una de las últimas convenciones de uso común -que quedaban por explicar y una vez vista ya eres capaz de leer proyectos de -código fuente que te encuentres por ahí sin demasiados problemas. - -Los próximos capítulos, basándose en lo aprendido en éste, te mostrarán cómo -instalar nuevas dependencias y cómo preparar tu propio código para que pueda -ser instalado de forma limpia y elegante. diff --git a/src/07_install.md b/src/07_install.md deleted file mode 100644 index 3da8ae0..0000000 --- a/src/07_install.md +++ /dev/null @@ -1,243 +0,0 @@ -# Instalación y dependencias - -Ahora que sabes lidiar con módulos, necesitas aprender a instalarlos en tu -propio sistema, porque es bastante tedioso que tengas que copiar el código de -todas tus dependencias en la carpeta de tu proyecto. - -En la introducción no aseguramos de instalar con python la herramienta `pip`. -Que sirve para instalar paquetes nuevos en el sistema de forma sencilla. - -## Funcionamiento de `pip` - -`pip` es una herramienta extremadamente flexible, capaz de instalar módulos de -python de diferentes fuentes: repositorios de git, carpetas de sistema o, la -más interesante quizás de todas, el *Python Package Index* (*PyPI*)[^pypi]. - -[^pypi]: https://pypi.org/ - -`pip` buscará la descripción del paquete en la fuente que se le indique y, de -esta descripción, obtendrá las dependencias necesarias y las indicaciones de -cómo debe instalarlo. Una vez procesadas las normas, procederá a instalar el -paquete en el directorio de sistema con todas las dependencias de éste para que -funcione correctamente. - -Una vez instalado el paquete en el directorio de sistema está listo para ser -importado. - -### PyPI - -El *Python Package Index* o *PyPI* es un repositorio que contiene software -programado en python. En él se listan miles de librerías creadas por -programadores de python para que cualquiera pueda descargarlas e instalarlas. -Más adelante veremos algunas de ellas y nos acostumbraremos a usarl PyPI como -recurso. - -Ahora que sabes programar en python tú también puedes publicar tus proyectos -ahí para que otras personas los usen para crear los suyos. - -### Reglas de instalación: `setuptools` y `setup.py` - -Para que `pip` pueda hacer su trabajo correctamente hay que indicarle cómo debe -hacerlo, ya que cada paquete es un mundo y tiene necesidades distintas. El -módulo `setuptools` permite crear un conjunto de reglas comprensible por `pip` -que facilita la distribución e instalación. - -Normalmente este conjunto de reglas suele almacenarse en un fichero de nombre -`setup.py`. Como ejemplo de `setup.py` puedes ver el de la librería -`BeautifulSoup4`[^bs4-setup] que se adjunta a continuación con ligeras -alteraciones para que encaje en la página: - -[^bs4-setup]: https://www.crummy.com/software/BeautifulSoup/bs4/doc/ - -``` {.python .numberLines} -from setuptools import ( - setup, - find_packages, -) - -with open("README.md", "r") as fh: - long_description = fh.read() - -setup( - name="beautifulsoup4", - # NOTE: We can't import __version__ from bs4 because bs4/__init__.py - # is Python 2 code, and converting it to Python 3 means going through - # this code to run 2to3. - # So we have to specify it twice for the time being. - version = '4.8.1', - author="Leonard Richardson", - author_email='leonardr@segfault.org', - url="http://www.crummy.com/software/BeautifulSoup/bs4/", - download_url = "http://www.crummy.com/software/BeautifulSoup/bs4/download/", - description="Screen-scraping library", - install_requires=["soupsieve>=1.2"], - long_description=long_description, - long_description_content_type="text/markdown", - license="MIT", - packages=find_packages(exclude=['tests*']), - extras_require = { - 'lxml' : [ 'lxml'], - 'html5lib' : ['html5lib'], - }, - use_2to3 = True, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 2.7", - 'Programming Language :: Python :: 3', - "Topic :: Text Processing :: Markup :: HTML", - "Topic :: Text Processing :: Markup :: XML", - "Topic :: Text Processing :: Markup :: SGML", - "Topic :: Software Development :: Libraries :: Python Modules", - ], -) -``` - -En el ejemplo se aprecia a la perfección el tipo de información que es -necesario aportarle a `setuptools`. Dependiendo del proyecto, esta -configuración puede ser más compleja o más sencilla, y tendrás que indagar a -través de la configuración de `setuptools` para ajustar la herramienta a tu -proyecto. - - -## Entornos virtuales y dependencias: `pipenv` - -La herramienta `pip` es interesante para instalar herramientas en el sistema -pero tiene ciertas carencias. La primera, que no es capaz de resolver las -dependencias a la hora de desinstalar paquetes, por lo que, si se instala un -paquete que dependa de otro paquete, `pip` instalará todas las dependencias -necesarias, pero por miedo a romper paquetes, no las desinstalará si se le pide -desinstalar el paquete que las arrastró. - -Por otro lado, si quieres trabajar en proyectos de desarrollo, probablemente -tengas que instalar sus dependencias. Si tienes varios proyectos en marcha -simultáneamente o si tus sistema necesita de alguna herramienta escrita en -python, es posible que tengas colisiones. - -Imagina que dos de los proyectos, por ejemplo, usan versiones diferentes de una -de sus librerías. Si instalas sus dependencias usando `pip`, se mezclaran en tu -sistema y no podrán coexistir. Además, cuando termines los proyectos o -abandones su desarrollo, te interesará limpiar sus dependencias de tu sistema, -cosa complicada si `pip` no gestiona la liberación de paquetes de forma -correcta. - -Para evitar estos problemas y algún otro adicional, existen herramientas -adicionales que alteran el comportamiento de `pip` y del propio python, creando -lo que se conoce como *entornos virtuales* (*virtual environments*) que quedan -aislados entre ellos y el sistema. - -El funcionamiento de los entornos virtuales es muy sencillo. Cuando se activan, -crean un nuevo contexto en el que alteran las variables de entorno que -describen dónde debe buscarse el intérprete de python, dónde busca éste los -módulos y dónde instala `pip` los paquetes. Cuando se desactivan, se restaura -el entorno por defecto. De este modo, cada entorno virtual queda perfectamente -aislado de otros o incluso de la instalación del sistema, permitiéndote hasta -tener diferentes versiones de python en tu sistema y que no colisionen entre -ellas. Cuando has terminado con tu entorno virtual puedes borrarlo de forma -segura sabiendo que no va a afectar a tu sistema y que va a llevarse todas las -dependencias con él. - -Históricamente se han utilizado varias herramientas para esta labor, como -`virtualenv`, que como era poco amigable se simplificaba con -`virtualenv-wrapper` u otras. Hoy en día `pipenv` es la herramienta -recomendada. - -`pipenv` es una combinación de `virtualenv` y `pip` creada para gestionar -entornos virtuales y dependencias de desarrollo. Puedes considerarla un gestor -de paquetes de desarrollo como `npm` en JavaScript, `composer` en PHP o -cualquier otro que conozcas. Aporta la mayor parte de funcionalidades -habituales como ficheros de dependencias, lockfiles etc. mientras que expone -una interfaz de comandos sencilla y bien documentada. - -### Instalación - -Para instalar pipenv, podemos usar `pip`, que instalamos en la introducción. -En la shell de sistema ejecutando: - -``` bash -pip install pipenv -``` - -> NOTA: en función del sistema que utilices, puede que `pip` se llame -> `pip3`. El funcionamiento es idéntico. - -### Uso - -Una vez instalado `pipenv`, puedes usarlo en el directorio que desees para -crear un conjunto de nuevas dependencias pidiéndole que instale un nuevo -paquete lanzando la orden `pipenv install` en la shell de sistema. - -Para ejecutar módulos en el entorno virtual recién creado dispones de dos -opciones: `pipenv shell` que prepara una shell de sistema en el entorno o -`pipenv run` que ejecuta el comando que se le envíe en el entorno. - -Puedes seguir añadiendo dependencias al proyecto con `pipenv install` y -eliminar las que no te gusten con `pipenv uninstall`. Además, dispones de -muchas opciones adicionales que te animo que ojees ejecutando `pipenv --help`. - - -### Usar IDLE desde un entorno virtual - -Si utilizas entornos virtuales deberás preparar IDLE para verlos y que su REPL -y su intérprete encuentren los paquetes del entorno virtual. - -Puedes lanzar IDLE desde el entorno virtual usando el siguiente truco en la -shell de sistema: - -``` bash -pipenv run python -m idlelib.idle -``` - -Recordando el capítulo anterior y lo descrito en este, ese comando ejecuta -`python -m idlelib.idle` en el entorno virtual en el que te encuentres. El -comando, por su parte le pide a python que ejecute el módulo `idle` del paquete -`idlelib`, que contiene el propio programa IDLE. - -Si trabajas con otros editores integrados de código tendrás que aprender a -hacer que sus intérpretes busquen en el entorno virtual actual, pero casi todos -los editores actuales soportarán esta opción de una forma u otra. - -## Otras herramientas - -La realidad es que las herramientas propuestas no son las únicas que existen -para estas tareas. Históricamente, python ha tenido otras herramientas con -comportamientos similares a `pip`, como `easy_install` y otras con parecidas a -`setuptools` como `distutils` y otras que asemejan a `pipenv`. Este capítulo -trata únicamente las herramientas que se consideran las recomendadas en el -momento en el que se está escribiendo[^recommended-tools], aunque los conceptos -descritos son extrapolables, no sólo a otras herramientas sino también a otros -lenguajes y entornos. - -[^recommended-tools]: Para un listado más extenso, visitar: - - - -## Lo que has aprendido - -En este capítulo has aprendido lo necesario sobre las herramientas que rodean -a python y su uso. De este modo, no te vas a sentir perdido en un maremágnum de -nombres extraños y comandos cuando trabajes en proyectos que ya las usan. - -Más que convertirte en un experto de cómo trabajar con estas herramientas, cosa -que te dará la práctica, este episodio te ha dado las referencias que necesitas -para ir creciendo por tu cuenta en su uso, explicándote el interés de cada una -de ellas. - -Te encontrarás, probablemente, proyectos que utilicen las herramientas de un -modo peculiar, usen otras herramientas o hasta proyectos que no las usen -correctamente. Este es un mundo complejo, y la historia de python no facilita -la elección. Durante mucho tiempo la comunidad creó nuevas herramientas -para suplir las carencias de otras y el ecosistema se complicó. A pesar de que -hoy en día se recomiende el uso de unas herramientas sobre otras, no siempre -fue así y muchos proyectos se han quedado obsoletos en este sentido. - -Para evitar atarte a las herramientas que aquí se muestran, se ha preferido -darte una ligera noción, haciéndote recordar que las herramientas no son -realmente lo importante, sino el conocimiento subyacente. - -Con la herramienta principal que has conocido en este apartado, `pipenv`, -podrás instalar los paquetes que quieras de forma aislada y jugar con ellos -todo lo que desees sin manchar tu sistema por lo que podrás adentrarte en los -próximos capítulos sin miedo a estropear tu pulcra configuración inicial. diff --git a/src/08_stdlib.md b/src/08_stdlib.md deleted file mode 100644 index fbaa974..0000000 --- a/src/08_stdlib.md +++ /dev/null @@ -1,372 +0,0 @@ -# La librería estándar - -La librería estándar se refiere a todas las utilidades que un lenguaje de -programación trae consigo. Los lenguajes de programación, a parte de aportar la -propia funcionalidad del lenguaje en sí mismo que simplemente sería la -ejecución del código fuente que se le indique, suelen incluir funcionalidades -que no están necesariamente relacionadas con ese proceso. - -El motivo de esto es facilitar el uso del lenguaje para las labores más -comunes, incluyendo el código necesario para realizarlas sin necesitar añadidos -externos. - -En lenguajes de programación de dominio específico la librería estándar suele -contemplar muchos casos de ese dominio concreto. Por ejemplo, el lenguaje de -programación Julia, aunque esté diseñado con el objetivo de ser un lenguaje -válido para cualquier uso, su área de trabajo se centra en el entorno -científico. Es por eso que incluye en su librería estándar paquetes de álgebra -lineal, estadística y otros. - -Existen muchas diferentes aproximaciones a la librería estándar. Algunos -lenguajes las mantienen extremadamente reducidas con el fin de que la -implementación del lenguaje sea más liviana, con la contrapartida de forzar a -quien los use a tener que utilizar herramientas externas o a desarrollarlas. - -Python es un lenguaje de propósito general con una extensísima librería -estándar. Esto dificulta la selección de apartados a mostrar en este capítulo, -pero facilita la programación de cualquier aplicación en éste lenguaje. - -Los paquetes estándar de python facilitan la lectura de infinidad de tipos de -fichero, la conversión y el tratamiento de datos, el acceso a la red, la -ejecución concurrente, etc. por lo que librería estándar es más que suficiente -para muchas aplicaciones y no se requiere añadir módulos externos. - -Conocer la librería estándar y ser capaz de buscar en ella los paquetes que -necesites te facilitará mucho la tarea, evitando, por un lado, que dediques -tiempo a desarrollar funcionalidades que el propio lenguaje ya aporta y, por -otro, que instales paquetes externos que no necesitas. - -Todos los apartados aquí listados están extremadamente bien documentados en la -página oficial de la documentación de python y en la propia ayuda. Eso sí, -tendrás que importarlos para poder leer la ayuda, pero ya sabes cómo se hace. - -A continuación se recogen los módulos más interesantes, aunque en función del -proyecto puede que necesites algún otro. Puedes acceder al listado completo en -la página oficial de la documentación[^stdlibdoc]. - -[^stdlibdoc]: - - -## Interfaz al sistema operativo: `os` - -Ya definimos previamente el concepto *interfaz* como superficie de contacto -entre dos entidades. Esta librería facilita la interacción entre python y el -sistema operativo. - -La comunicación con el sistema operativo es primordial para cualquier lenguaje -de programación de uso general, ya que es necesario tener la capacidad de hacer -peticiones directas al sistema operativo. Por ejemplo, cambiar de directorio -actual para facilitar la inclusión rutas relativas en el programa, resolver -rutas a directorios y un largo etc. - -En capítulos previos hemos hablado de las diferencias entre sistemas -operativos. Esta librería, además, facilita el trabajo para esos casos. Por -ejemplo, que el separador de directorios en UNIX es `/` y en Windows `\`, si -quisiéramos programar una aplicación multiplataforma, nos encontraríamos con -problemas. Sin embargo, el módulo `os.path` dispone de herramientas para -gestionar las rutas de los directorios por nosotros, por ejemplo, la variable -`os.path.sep` (copiada en `os.sep` por abreviar), guarda el valor del separador -del sistema en el que se esté ejecutando la aplicación: `/` en UNIX y `\` en -Windows. - -Este paquete es muy interesante para desarrollar código portable entre las -diferentes plataformas. - -## Funciones relacionadas con el intérprete: `sys` - -Aunque el nombre de este módulo suene complicado, su uso principal es el de -acceder a funcionalidades que el propio python controla. Concretamente, se usa -sobre todo para la recepción de argumentos de entrada al programa principal, -la redirección de entradas y salidas del programa y la terminación del -programa. - -### Salida forzada: `sys.exit()` - -Para salir de forma abrupta del programa y terminar su ejecución, python -facilita la función `sys.exit()`. Al ejecutarla la - -### *Standard streams*: `sys.stdin`, `sys.stdout` y `sys.stderr` - -Cuando se trabaja en programas que funcionan en la terminal se pueden describir -tres vías de comunicación con el usuario: - -1. El teclado: de donde se obtiene lo que el usuario teclee mediante la función - `input`. -2. La pantalla: a donde se escribe al ejecutar la función `print`. -3. Y la salida de errores: Una salida especial para los fallos, similar a la - anterior, pero que se diferencia para poder hacer un tratamiento distinto y - porque tiene ciertas peculiaridades distintas. Los mensajes de error de - excepciones se envían aquí. - -Estas tres vías se conocen comúnmente como entrada estándar (*standard input*), -`stdin`, salida estándar (*standard output*), `stdout`, y error estándar -(*standard error*), `stderr`. Este concepto responde al nombre de *standard -streams* y es una abstracción de los dispositivos físicos que antiguamente se -utilizaban para comunicarse con una computadora. Hoy en día, estos tres -*streams* son una abstracción de esa infraestructura y se comportan como -simples ficheros de lectura en el primer caso y de escritura en los otros dos. - -Los diferentes sistemas operativos los implementan a su modo, pero desde el -interior de python son simplemente ficheros abiertos, como si se hubiese -ejecutado la función `open` en ellos y ellos se encargan de leer o escribir a -la vía de interacción con el usuario correspondiente. Es decir, en realidad -`print`, `input`, etc. son únicamente funciones que facilitan la labor de -escribir manualmente en estos *streams*: - -``` python ->>> import sys ->>> chars = sys.stdout.write("Hola\n") -Hola ->>> chars # Recoge el número de caracteres escritos -5 -``` - -La realidad es que estos *streams* dan una flexibilidad adicional muy -interesante. Como son únicamente variables de ficheros abiertos pueden -cerrarse y sustituirse por otros, permitiéndote redireccionar la entrada o la -salida de un programa a un fichero. - -El siguiente ejemplo redirecciona la salida de errores a un fichero llamado -`exceptions.txt` y trata de mostrar una variable que no está definida. Python -en lugar de mostrar el mensaje de una excepción, la escribe en el fichero al -que se ha redireccionado la salida: - -``` python ->>> sys.stderr = open("exceptions.txt", "w") ->>> aasda # No muestra el mensaje de error ->>> ->>> with open("exceptions.txt") as f: -... f.read() # Muestra el contenido del archivo -... -'Traceback (most recent call last):\n File "", line 1, in \ -\nNameError: name \'aasda\' is not defined\n' -``` - - -### Argumentos de entrada: `sys.argv` - -Es posible añadir argumentos de entrada a la ejecución de los programas. Piensa -en el propio programa llamado python, al ejecutarlo en la shell de sistema se -le pueden mandar diferentes opciones que, por ejemplo, digan qué módulo debe -ejecutar: - -``` bash -python test.py -``` - -Para la shell de sistema, todo lo que se escriba después de la primera palabra -es considerado un argumento de entrada. - -Cuando se ejecutan nuestros programas escritos en python, es posible también -enviarles argumentos de entrada: - -``` bash -python test.py argumento1 argumento2 ... -``` - -Lo que el python reciba después del nombre del módulo a ejecutar lo considerará -argumentos de entrada de nuestro módulo y nos lo ordenará y dejará disponible -en la variable `sys.argv`, una lista de todos los argumentos de entrada. - -Si muestras el contenido de la variable en la REPL, te responderá `['']` ya que -la REPL se ejecuta sin argumentos. Sin embargo, si creas un módulo y le añades -este contenido: - -``` python -import sys -print(sys.argv) -``` - -Verás que se imprime una lista con el nombre del archivo en su primer elemento. - -Si ejecutas el módulo desde la shell de sistema añadiéndole argumentos de -entrada: - -``` bash -python modulo.py arg1 arg2 arg3 -``` - -La lista `sys.argv` recibirá todos, siempre a modo de string. - -Los argumentos de entrada son extremadamente interesantes para permitir que los -programas sean configurables por quien los use sin necesidad de tener que -editar el código fuente, cosa que nunca debería ser necesaria a menos que -exista un error en éste o quiera añadirse una funcionalidad. - -## Procesamiento de argumentos de entrada: `argparse` - -Como la variable `sys.argv` entrega los argumentos de entrada tal y como los -recibe y no comprueba si son coherentes, python dispone de una librería -adicional para estas labores. El módulo `argparse` permite definir qué tipo de -argumentos de entrada y opciones tiene tu programa y qué reglas deben seguir. -Además, facilita la creación de ayudas como la que se muestra cuando ejecutas -`python -h` o `pip -h` en la shell de tu sistema. - -Es una librería bastante compleja con infinidad de opciones que es mejor que -leas en la propia documentación cuando necesites utilizarla. - -## Expresiones regulares: `re` - -Las expresiones regulares (*regular expression*, también conocidas como -*regexp* y *regex*) son secuencias de caracteres que describen un patrón de -búsqueda. - -En python se soportan mediante el módulo `re` de la librería estándar. - -## Matemáticas y estadística: `math` y `statistics` - -El módulo `math` soporta gran cantidad de operaciones matemáticas avanzadas -para coma flotante. En él puedes encontrar logaritmos, raíces, etc. - -El módulo `statistics` soporta estadística básica como medias, medianas, -desviación típica, etc. - -Ambos módulos tienen mucho interés ya que python se usa extensivamente en el -análisis de datos. Aunque tiene librerías de terceros mucho más adecuadas para -esta labor, para proyectos pequeños puede que sea suficiente con estos módulos. - -## Protocolos de internet: `urllib` - -`urllib` es un conjunto de paquetes que permiten seguir URLs o enlaces, -principalmente para HTTP y su versión segura HTTPs. Soporta cookies, -redirecciones, autenticación básica, etc. - -El siguiente ejemplo te muestra cómo descargar el primer boceto del estándar -del protocolo HTTP de la página web del IETF. Recordando los apartados previos, -fíjate en el uso de la sentencia `with` y en el uso de los corchetes para -obtener un número limitado de caracteres de la recién decodificada respuesta. - -``` python ->>> from urllib.request import urlopen ->>> with urlopen("https://tools.ietf.org/rfc/rfc2068.txt") as resp: -... print( resp.read().decode("utf-8")[:1750] ) -... -' - - - - - -Network Working Group R. Fielding -Request for Comments: 2068 UC Irvine -Category: Standards Track J. Gettys - J. Mogul - DEC - H. Frystyk - T. Berners-Lee - MIT/LCS - January 1997 - - - Hypertext Transfer Protocol -- HTTP/1.1 - -Status of this Memo - - This document specifies an Internet standards track protocol for the - Internet community, and requests discussion and suggestions for - improvements. Please refer to the current edition of the "Internet - Official Protocol Standards" (STD 1) for the standardization state - and status of this protocol. Distribution of this memo is unlimited. - -Abstract - - The Hypertext Transfer Protocol (HTTP) is an application-level - protocol for distributed, collaborative, hypermedia information - systems. It is a generic, stateless, object-oriented protocol which - can be used for many tasks, such as name servers and distributed - object management systems, through extension of its request methods. - A feature of HTTP is the typing and negotiation of data - representation, allowing systems to be built independently of the - data being transferred. - - HTTP has been in use by the World-Wide Web global information - initiative since 1990. This specification defines the protocol - referred to as "HTTP/1.1". - -' ->>> -``` - -## Fechas y horas: `datetime` - -`datetime`, que ya ha aparecido anteriormente, es un módulo para gestión de -fecha y hora. `datetime` soporta gran cantidad de operaciones y tipos de dato. -Entre ellos los `timedeltas`, diferencias temporales que te permiten sumar y -restar fechas con facilidad con los operadores habituales. - -Con las facilidades de `datetime`, es poco probable que necesites importar una -librería de gestión de fecha y hora independiente. - -## Procesamiento de ficheros: `json` y `sqlite3` - -Python habilita gran cantidad de procesadores de formatos de fichero, los dos -que se lista en este apartado tienen especial interés. - -El primer módulo, `json`, sirve para manipular datos en formato JSON. Sus dos -funciones principales `dumps` y `loads` vuelcan o cargan datos de JSON a -diccionarios o listas en una sola orden debido que la propia estructura de JSON -está formada por parejas clave valor o listas de valores ordenadas por índices. -Esta es la razón por la que en muchas ocasiones se utilizan ficheros de formato -JSON para intercambiar información entre aplicaciones de python: su lectura y -escritura es extremadamente sencilla. - -El segundo módulo, `sqlite3`, facilita el acceso a ficheros en formato SQLite3, -un formato binario con una interfaz de acceso que permite consultas SQL. El -módulo `sqlite3` es capaz de convertir las tablas que SQLite3 retorna a -estructuras de python de forma transparente y cómoda por lo que es un aliado -interesante para aplicaciones que requieren una base de datos pequeña y -resiliente. - -## Aritmética de coma flotante decimal: `decimal` - -En el apartado sobre datos tratamos la complejidad de los números de coma -flotante y que su representación binaria puede dar lugar a problemas. Este -módulo de aritmética decimal aporta una solución rigurosa a este problema con -el fin de facilitar el uso de python en entornos que requieren precisión -estricta que incluso puede requerir cumplir con normativas, como puede ser la -banca. - -La documentación del módulo muestra un par de ejemplos muy interesantes usando -la clase `Decimal` aportada por este. Se adjuntan a continuación para que los -estudies y los disecciones en busca de las diferencias con el uso de los -números de coma flotante normales: - -``` python ->>> from decimal import * ->>> round(Decimal('0.70') * Decimal('1.05'), 2) -Decimal('0.74') ->>> round(.70 * 1.05, 2) -0.73 - ->>> Decimal('1.00') % Decimal('.10') -Decimal('0.00') ->>> 1.00 % 0.10 -0.09999999999999995 - ->>> sum([Decimal('0.1')]*10) == Decimal('1.0') -True ->>> sum([0.1]*10) == 1.0 -False -``` - -## Lo que has aprendido - -Más que aprender, en este apartado has sobrevolado la librería estándar de -python desde la distancia y te has parado a observar qué problemas comunes ya -están resueltos en python. - -La realidad es que la librería estándar es mucho más extensa que lo listado en -este apartado, pero aquí se han mencionado los módulos que más frecuentemente -se suelen usar en entornos normales y algunos otros que tienen interés a la -hora de afianzar conocimiento que has obtenido en capítulos previos, como es el -caso del módulo `decimal`. - -Este capítulo empieza a mostrarte el interés de programar en python y las -posibilidades que tienes con él, únicamente con la librería estándar. En el -próximo verás la cantidad de funcionalidad adicional que le puedes añadir con -un par de librerías escritas por terceros. - -Sirva también este capítulo como recordatorio de lo extensa que es la librería -estándar de python. En el futuro, cuando tengas intención de buscar una -librería para resolver un problema que te despista de trabajar en la -funcionalidad principal de tu aplicación, empieza por la librería estándar. diff --git a/src/09_extralib.md b/src/09_extralib.md deleted file mode 100644 index 65ed418..0000000 --- a/src/09_extralib.md +++ /dev/null @@ -1,162 +0,0 @@ -# Librerías útiles - -Ahora que ya sabes cómo instalar librerías y que has visto que muchas -funcionalidades están contenidas en la librería estándar de python es un buen -momento para que visites varios proyectos que aportan recursos muy interesantes -a la hora de resolver problemas. Debido al carácter de uso general de python, -estas librerías aportan facilidades muy diversas. El criterio para escogerlas -parte de la experiencia personal del autor de este documento y añade algunas -librerías y herramientas que pueden ser interesantes debido a su amplio uso en -la industria. - -## Librerías científicas: ecosistema SciPy - -SciPy es un ecosistema de librerías de cálculo que tiene como objetivo -facilitar la tarea de ingenieros, científicos y matemáticos en sus -respectivos trabajos. Puedes considerarlo como un **Matlab** para python -separado por componentes independientes. - -Además de ser el nombre del ecosistema, comparte nombre con una de las -librerías fundamentales de éste. El ecosistema está formado por varias -librerías, entre ellas se encuentran: - -- Numpy: un paquete de computación científica para python. Soporta matrices - multidimensionales, funciones sofisticadas y álgebra lineal, entre otras. - -- SciPy: librería basada en Numpy que aporta rutinas numéricas eficientes para - interpolación, optimización, algebra lineal, estadística y otros. - -- SymPy: una librería de matemática simbólica para python que complementa el - resto de librerías del ecosistema, ya que casi todas están orientadas al - análisis numérico. - -- Matplotlib: librería para representar gráficos y figuras científicas en 2D. - -- Pandas: aporta unas estructuras de datos muy potentes basadas en tablas, su - objetivo es reforzar a python a la hora de tratar datos. El área de esta - librería es el del análisis de datos, pero puede combinarse con otras áreas - de estudio como la econometría (ver el proyecto statsmodels). Esta librería - dispone de un ecosistema muy poderoso a su alrededor debido al auge del - análisis de datos en la industria del software. Muchas librerías comparten la - interfaz de Pandas por lo que tener nociones de su comportamiento abre muchas - puertas en el sector del análisis de datos. Al igual que ocurre en SciPy, Las - estructuras de datos de Pandas también se basan en Numpy. - -- IPython: más que una librería, IPython es una herramienta. Se trata de una - REPL interactiva (su propio nombre viene de *Interactive Python*) que añade - diversas funcionalidades sobre la REPL de python habitual: histórico de - comandos, sugerencias, comandos mágicos (*magic*) que permiten alterar el - comportamiento de la propia interfaz y un larguísimo etcétera. IPython, - además, sirve como núcleo de los cuadernos Jupyter que integran una shell de - python con visualización de tablas y gráficos para generar documentos estilo - *literate programming*. - -## Machine Learning: ScikitLearn - -ScikitLearn es una librería de machine learning muy potente, implementa gran -cantidad de algoritmos y tiene una documentación extensa, sencilla y educativa. - -Encaja a la perfección con el ecosistema SciPy, ya que se basa en NumPy, SciPy -y Matplotlib. - -## Peticiones web: Requests - -Requests es una librería alternativa al módulo `urllib` aportado por la -librería estándar de python. Se describe a sí misma como «HTTP para seres -humanos», sugiriendo que `urllib` no es cómoda de usar. - -Requests gestiona automáticamente las URL-s de las peticiones a partir de los -datos que se le entreguen, sigue redirecciones, almacena cookies, descomprime -automáticamente, decodifica las respuestas de forma automática, etc. En general -es una ayuda cuando no se quiere dedicar tiempo a controlar cada detalle de la -conexión de modo manual. - -## Manipulación de HTML: Beautifulsoup - -Beautifulsoup es una librería de procesado de HTML extremadamente potente. -Simplifica el acceso a campos de ficheros HTML con una sintaxis similar a los -objetos de python, permitiendo acceder a la estructura anidada mediante el uso -del punto en lugar de tener que lidiar con consultas extrañas o expresiones -regulares. Esta funcionalidad puede combinarse con selectores CSS4 para un -acceso mucho más cómodo. - -Muy usada en conjunción con Requests, para el desarrollo de aplicaciones de -web-scraping. Aunque, si se desea usar algo más avanzado, se recomienda usar el -framework Scrapy. - -## Tratamiento de imagen: Pillow - -Pillow es un fork de la librería PIL (*Python Imaging Library*) cuyo desarrollo -fue abandonado. En sus diversos submódulos, Pillow permite acceder a datos de -imágenes, aplicarles transformaciones y filtros, dibujar sobre ellas, cambiar -su formato, etc. - -## Desarrollo web: Django, Flask - -Existen infinidad de frameworks y herramientas de desarrollo web en python. Así -como en el caso del análisis de datos la industria ha convergido en una -herramienta principal, Pandas, en el caso del desarrollo web hay muchas -opciones donde elegir. - -Los dos frameworks web más usados son Django y Flask, siendo el segundo menos -común que el primero pero digno de mención en conjunción con el otro por varias -razones. - -La primera es la diferencia en la filosofía: así como Django decide con qué -herramientas se debe trabajar, Flask, que se define a sí mismo como -microframework, deja en manos de quien lo usa la elección de qué herramientas -desea aplicarle. Cada una de las dos filosofías tiene ventajas y desventajas y -es tu responsabilidad elegir las que más te convengan para tu proyecto. - -La segunda razón para mencionar Flask es que su código fuente es uno de los más -interesantes a la hora de usar como referencia de cómo se debe programar en -python. Define su propia norma de estilo de programación, basada en la -sugerencia de estilo de python[^pep8] y su desarrollo es extremadamente -elegante. - -Django, por su parte, ha sido muy influyente y muchas de sus decisiones de -diseño han sido adoptadas por otros frameworks, tanto en python como en otros -lenguajes. Lo que sugiere que está extremadamente bien diseñado. - -A pesar de las diferencias filosóficas, existen muchas similitudes entre ambos -proyectos por lo que aprender a usar uno de ellos facilita mucho el uso del -otro y no es aprendizaje perdido. No tengas miedo en lanzarte a uno. - -[^pep8]: - - -## Protocolos de red: Twisted - -Twisted es motor de red asíncrono para python. Sobre él se han escrito -diferentes librerías para gestión de protocolos de Internet como DNS, via -Twisted-Names, IMAP y POP3, via Twisted-Mail, HTTP, via Twisted-Web, IRC y -XMPP, via Twisted-Words, etc. - -El diseño asíncrono del motor facilita sobremanera las comunicaciones -eficientes. Programar código asíncrono en python es relativamente sencillo, -pero ha preferido dejarse fuera de este documento por diversas razones. Te -animo a indagar en esta libreria para valorar el interés del código asíncrono. - -## Interfaces gráficas: PyQt, PyGTK, wxPython, PySimpleGUI - -A pesar de que python dispone de un módulo en su librería estándar para tratar -interfaces gráficas llamado TKinter, es recomendable utilizar librerías más -avanzadas para esto. TKinter es una interfaz a la herramienta Tk del lenguaje -de programación Tcl y acompaña a python desde hace años. - -En programas simples TKinter es más que suficiente (IDLE, por ejemplo, está -desarrollado con TKinter) pero a medida que se necesita complejidad o capacidad -del usuario para configurar detalles de su sistema suele quedarse pequeño. - -Para programas complejos se recomienda usar otro tipo de librerías más -avanzadas como PyQt, PyGTK o wxPython, todas ellas interfaces a librerías -escritas en C/C++ llamadas Qt, GTK y wxWidgets respectivamente. Estas librerías -aportan una visualización más elegante, en algunos casos usando widgets nativos -del sistema operativo en el que funcionan. - -Debido a la complejidad del ecosistema nace el proyecto PySimpleGUI, que -pretende aunar las diferentes herramientas en una sola, sirviendo de interfaz a -cualquiera de las anteriores y alguna otra. Además, el proyecto aporta gran -cantidad de ejemplos de uso. PySimpleGUI aún está en desarrollo y el soporte de -algunos de los motores no está terminado, pero es una fuente interesante de -información y recursos. diff --git a/src/10_closing_words.md b/src/10_closing_words.md deleted file mode 100644 index 404a58d..0000000 --- a/src/10_closing_words.md +++ /dev/null @@ -1,123 +0,0 @@ -# Lo que has aprendido - -Rescatando la definición de la introducción: - -> Python es un lenguaje de programación de alto nivel orientado al uso general. -> Fue creado por Guido Van Rossum y publicado en 1991. La filosofía de python -> hace hincapié en la limpieza y la legibilidad del código fuente con una -> sintaxis que facilita expresar conceptos en menos líneas de código que en -> otros lenguajes. -> -> Python es un lenguaje de tipado dinámico y gestión de memoria automática. -> Soporta múltiples paradigmas de programación, incluyendo la programación -> orientada a objetos, imperativa, funcional y procedural e incluye una extensa -> librería estándar. - -Ahora sí que estás en condición de entenderla no sólo para python sino para -cualquier otro lenguaje que se te presente de este modo. Ahora tienes la -habilidad de poder comprender de un vistazo qué te aporta el lenguaje que -tienes delante únicamente leyendo su descripción. - -Desgranándola poco a poco, has conocido la sintaxis de python en bastante -detalle y has visto cómo hace uso de las sangrías para delimitar bloques de -código, cosa que otros lenguajes hacen con llaves (`{}`) u otros símbolos. - -La facilidad de expresar conceptos complejos en pocas líneas de código puede -verse en las *list comprehensions*, la sentencia `with` y muchas otras -estructuras del sistema. Python es un lenguaje elegante y directo, similar al -lenguaje natural. - -El tipado dinámico trata lo que estudiaste en el apartado sobre datos, donde se -te cuenta que las referencias pueden cambiar de tipo en cualquier momento ya -que son los propios valores los que son capaces de recordar qué tipo tienen. - -La gestión de memoria automática también se presenta en el mismo apartado, -contándote que python hace uso de un *garbage collector* o recolector de basura -para limpiar de la memoria los datos que ya no usa. - -Los diferentes paradigmas de programación no se han tratado de forma explícita -en este documento, más allá de la programación orientada a objetos, que inunda -python por completo. Sin embargo, el apartado sobre funciones adelanta varios -de los conceptos básicos del paradigma de programación funcional: que las -funciones sean ciudadanos de primera clase (*first-class citizens*), el uso de -funciones anónimas (*lambda*) y las *closures*. - -Los paradigmas procedural e imperativo son la base para los dos -paradigmas de los que hemos hablado. La programación imperativa implica que se -programa mediante órdenes (el caso de python, recuerda) en lugar de -declaraciones (como puede ser la programación lógica, donde se muestran un -conjunto de normas que el programa debe cumplir). La programación procedural es -un paradigma cuyo fundamento es el uso de bloques de código y su *scope*, -creando funciones, estructuras de datos y variables aunque, a diferencia de la -programación funcional, en la programación procedural no es necesario que las -funciones sean ciudadanos de primera clase y pueden tener restricciones. - -Estos dos últimos paradigmas, en realidad, se soportan casi por accidente al -habilitar los dos anteriores. - -En muchas ocasiones, te encontrarás escribiendo pequeñas herramientas y no -necesitarás mucho más que usar las estructuras básicas de python y varias -funciones para alterarlas, por lo que estarás pensando de forma procedural -accidentalmente. - -Los paradigmas no son más que patrones de diseño que nos permiten clasificar -los lenguajes y sus filosofías, pero son muy interesantes a la hora de diseñar -nuestras aplicaciones. - -Además de todo esto, has tenido ocasión de conocer de forma superficial la -librería estándar del lenguaje y un conjunto de librerías adicionales que te -aportan los puntos de los que la librería estándar carece. Ahora sabes instalar -dependencias y usarlas en entornos virtuales (*virtual environments*) para -mantener limpia tu instalación. - -A parte de lo mencionado en la definición del lenguaje, has aprendido a -ejecutar, cargar y distribuir módulos de python, algo primordial si pretendes -crear paquetes o nuevas librerías y usar las de terceros. - -Con todo esto, tienes una visión general pero bastante detallada a nivel -técnico de lo que python aporta y cómo. Lo que necesitas para compensarla es -trabajar con él, acostumbrarte a su ecosistema y leer mucho código de buena -calidad para acostumbrarte a seguir las convenciones y recetas habituales. - - -## El código pythónico - -A lo largo del documento se tratan temas que puede que no te esperases -encontrar al leer sobre programación, ya que tu interés principal es resolver -tus problemas de forma efectiva y construir aplicaciones. Hacer robots que te -hagan la vida más fácil, en definitiva. - -Sin embargo, quien se dedica a la programación tiene una vida muy ligada a la -vida de quien se dedica a la filosofía o al diseño y es por eso que esas dos -disciplinas aparecen de vez en cuando en cualquier conversación un poco seria -sobre el trabajo con software. - -Las tres disciplinas, en primer lugar, ocurren en la mente de las personas y no -en sus manos. Es por eso que los patrones mentales y los modos de pensamiento -son parte fundamental de ellas. Ninguno de ellos son trabajos para los que se -pueda entrenar una memoria muscular. Es necesario pensar. Y es necesario pensar -de forma consciente y premeditada. - -Ver cómo desarrollan otras personas su actividad es valioso para realizar tu -tarea con elegancia. - -Otro detalle que has debido de observar, sobre todo porque acaba de aparecer, -es la *elegancia*. La elegancia es, hasta cierto punto, subjetiva y depende del -gusto de quien la mira. Sin embargo, esto sólo es así hasta cierto punto, la -realidad es que alguien puede considerar algo elegante y aun así no gustarle. -Python es un ejemplo de algo así. Guste o no guste, python es un lenguaje de -programación elegante, cuya elegancia forma parte primordial de la filosofía -del lenguaje. - -El autor de este documento, por ejemplo, no es un entusiasta de python, pero a -lo largo de la travesía de escribir este documento ha podido reencontrarse, una -vez más, con su elegancia. - -El concepto del *código pythónico* (*pythonic code*) es un resultado de esto. -Cuando se habla de código pythónico, se habla de un código que sigue los -estándares de elegancia de python. Que es bonito, comprensible y claro. Un -código que la comunidad de desarrollo de python aprobaría. - -Cuando programes en python, trata de programar código pythónico, pues es esa la -verdadera razón por la que se creó el lenguaje y es la forma en la que el -lenguaje más fácil te lo va a poner. diff --git a/src/A_devtools.md b/src/A_devtools.md deleted file mode 100644 index e856373..0000000 --- a/src/A_devtools.md +++ /dev/null @@ -1,90 +0,0 @@ -# Anexo I: Herramientas de desarrollo {-} - -IDLE es una herramienta de desarrollo muy limitada, suficiente para seguir los -ejemplos que se recogen en este documento pero insuficiente para desarrollar -aplicaciones avanzadas. - -## Desarrollo de código fuente - -Existen gran cantidad de entornos de desarrollo (IDE) avanzados y editores que -pueden ser recomendables, aunque es cuestión de gusto personal decantarse por -uno de ellos o por otro. - -La diferencia entre un entorno de desarrollo integrado y un editor es la -siguiente: los entornos de desarrollo cumplen varias funciones adicionales, -como en el caso de IDLE, dar acceso a una REPL de python y la posibilidad de -analizar las variables en memoria. Los editores únicamente sirven para escribir -el código, aunque en muchos casos la línea que separa ambos conceptos es -bastante borrosa: existen editores con funcionalidades avanzadas y entornos -integrados muy sencillos que parecen un simple editor. Resumiendo, los entornos -integrados de desarrollo (IDE *integrated development environment*) tienen -editores entre sus herramientas. - -### Entornos de desarrollo integrados - -Quien no utiliza entornos de desarrollo avanzados (una cuestión de gusto -personal), por lo que se le hace difícil recomendar alguno en particular. Sin -embargo, el wiki de python recoge una larguísima lista de editores y entornos -de desarrollo integrado interesantes[^ides]. - -[^ides]: - -En el entorno del análisis de datos, la distribución *Anaconda* es muy usada. -Anaconda es más que un entorno de desarrollo integrado. Incluye un entorno de -desarrollo llamado Spyder, una shell propia, un gestor de paquetes y -dependencias propio llamado Conda, posibilidad de integración con el lenguaje -de programación R y gran cantidad de paquetes instalados por defecto. - -En otros entornos PyCharm es bastante común, aunque también son muy comunes los -entornos de desarrollo integrados pensados para otros lenguajes que han -comenzado a soportar python posteriormente como KDevelop, NetBeans y otros. - -Te recomiendo que, si usas un entorno de desarrollo integrado en otros -lenguajes, investigues si soporta python. De este modo no tendrás que aprender -una nueva herramienta. Al ser un lenguaje tan común, probablemente lo soporte. -Si no lo soporta prueba con un IDE que siga una filosofía similar al que uses. - -### Editores de código - -Quien te escribe usa Vim, un editor de texto muy antiguo con muchas -características que le hacen ser un editor muy eficiente. Existe gran variedad -de editores de código que recomendar: Emacs, gEdit, Kate, Sublime, Atom... Todo -dependerá de tus gustos personales. - -La ventaja principal de los editores de código es que conociendo uno en -profundidad es más que suficiente para cualquier lenguaje, ya que están -diseñados únicamente para escribir el contenido de tus programas, dejando las -peculiaridades de ejecución de cada lenguaje a parte. Esto les aporta una -ligereza difícilmente alcanzable por los IDEs. - -Sin embargo, esta virtud también es su mayor defecto. Al no integrar ninguna -herramienta adicional, es necesario trabajar todo manualmente. En el caso de -python, te fuerzan a usar una shell independiente y a interactuar con ella de -forma manual. Al principio puede ser tedioso, pero aprender a gestionar los -detalles manualmente es interesante ya que te permite obtener un gran -conocimiento del sistema y lenguaje en el que trabajas. - -## Herramientas de depuración - -El propio diseño de python permite que sea fácilmente depurable. La REPL -facilita que se pruebe la aplicación a medida que se va desarrollando, función -a función, para asegurar que su comportamiento es el correcto. Además, la -capacidad introspectiva del lenguaje es una buena herramienta, en conjunción -con la REPL para comprobar el comportamiento. - -Además de estas características, la instalación de python viene acompañada del -programa `pdb` (*Python Debugger*), un depurador de código similar al conocido -[GNU-Debugger](https://en.wikipedia.org/wiki/GNU_Debugger). Existen otros -depuradores de código más amigables que este, pero la realidad es que no suelen -ser necesarios. - -## Testeo de aplicaciones - -Hoy en día el testeo de software es muy común, en parte gracias al desarrollo -guiado por pruebas, o TDD (*Test Driven Development*). - -La librería estándar de python incluye un módulo para pruebas unitarias llamado -`unittest` en que la librería Nose, muy conocida y usada, se basa para -facilitar el trabajo de modo similar a lo que ocurre con Requests y `urllib`. - -Por supuesto, existen otras alternativas, pero estas son las principales. diff --git a/src/Z_license.md b/src/Z_license.md deleted file mode 100644 index 46d9792..0000000 --- a/src/Z_license.md +++ /dev/null @@ -1,337 +0,0 @@ -# Anexo II: Licencia CC BY-SA 4.0 {- #licencia-appendix} - -#### Creative Commons Atribución/Reconocimiento-CompartirIgual 4.0 Licencia Pública Internacional - -Al ejercer los Derechos Licenciados (definidos a continuación), Usted acepta y -acuerda estar obligado por los términos y condiciones de esta Licencia -Internacional Pública de Atribución/Reconocimiento-CompartirIgual 4.0 de -Creative Commons ("Licencia Pública"). En la medida en que esta Licencia -Pública pueda ser interpretada como un contrato, a Usted se le otorgan los -Derechos Licenciados en consideración a su aceptación de estos términos y -condiciones, y el Licenciante le concede a Usted tales derechos en -consideración a los beneficios que el Licenciante recibe por poner a -disposición el Material Licenciado bajo estos términos y condiciones. - -##### Sección 1 – Definiciones. - -a. **Material Adaptado** es aquel material protegido por Derechos de - Autor y Derechos Similares que se deriva o se crea en base al - Material Licenciado y en el cual el Material Licenciado se traduce, - altera, arregla, transforma o modifica de manera tal que dicho - resultado sea de aquellos que requieran autorización de acuerdo con - los Derechos de Autor y Derechos Similares que ostenta el - Licenciante. A los efectos de esta Licencia Pública, cuando el - Material Licenciado se trate de una obra musical, una interpretación - o una grabación sonora, la sincronización temporal de este material - con una imagen en movimiento siempre producirá Material Adaptado. - -b. **Licencia de adaptador** es aquella licencia que Usted aplica a Sus - Derechos de Autor y Derechos Similares en Sus contribuciones - consideradas como Material Adaptado de acuerdo con los términos y - condiciones de esta Licencia Pública. - -c. **Una Licencia Compatible con BY-SA** es aquella que aparece en la - lista disponible en - [creativecommons.org/compatiblelicenses](//creativecommons.org/compatiblelicenses), - aprobada por Creative Commons, como una licencia esencialmente - equivalente a esta Licencia Pública. -d. **Derechos de Autor y Derechos Similares** son todos aquellos - derechos estrechamente vinculados a los derechos de autor, - incluidos, de manera enunciativa y no taxativa, los derechos sobre - las interpretaciones, las emisiones, las grabaciones sonoras y los - Derechos "Sui Generis" sobre Bases de Datos, sin importar cómo estos - derechos se encuentren enunciados o categorizados. A los efectos de - esta Licencia Pública, los derechos especificados en las secciones - [2(b)(1)-(2)](#s2b) no se consideran Derechos de Autor y Derechos - Similares. -e. **Medidas Tecnológicas Efectivas** son aquellas medidas que, en - ausencia de la debida autorización, no pueden ser eludidas en virtud - de las leyes que cumplen las obligaciones del artículo 11 del - Tratado de la OMPI sobre Derecho de Autor adoptado el 20 de - diciembre de 1996, y/o acuerdos internacionales similares. -f. **Excepciones y Limitaciones** son el uso justo (fair use), el trato - justo (fair dealing) y/o cualquier otra excepción o limitación a los - Derechos de Autor y Derechos Similares que se apliquen al uso el - Material Licenciado. -g. **Elementos de la Licencia** son los atributos que figuran en el - nombre de la Licencia Pública de Creative Commons. Los Elementos de - la Licencia de esta Licencia Pública son Atribución/Reconocimiento y - CompartirIgual. -h. **Material Licenciado** es obra artística o literaria, base de datos - o cualquier otro material al cual el Licenciante aplicó esta - Licencia Pública. -i. **Derechos Licenciados** son derechos otorgados a Usted bajo los - términos y condiciones de esta Licencia Pública, los cuales se - limitan a todos los Derechos de Autor y Derechos Similares que - apliquen al uso del Material Licenciado y que el Licenciante tiene - potestad legal para licenciar. -j. **Licenciante** es el individuo(s) o la entidad(es) que concede - derechos bajo esta Licencia Pública. -k. **Compartir** significa proporcionar material al público por - cualquier medio o procedimiento que requiera permiso conforme a los - Derechos Licenciados, tales como la reproducción, exhibición - pública, presentación pública, distribución, difusión, comunicación - o importación, así como también su puesta a disposición, incluyendo - formas en que el público pueda acceder al material desde un lugar y - momento elegido individualmente por ellos. -l. **Derechos "Sui Generis" sobre Bases de Datos** son aquellos - derechos diferentes a los derechos de autor, resultantes de la - Directiva 96/9/EC del Parlamento Europeo y del Consejo, de 11 de - marzo de 1996 sobre la protección jurídica de las bases de datos, en - sus versiones modificadas y/o posteriores, así como otros derechos - esencialmente equivalentes en cualquier otra parte del mundo. -m. **Usted** es el individuo o la entidad que ejerce los Derechos - Licenciados en esta Licencia Pública. La palabra **Su** tiene un - significado equivalente. - -##### Sección 2 – Ámbito de Aplicación. - -a. **Otorgamiento de la licencia**. - 1. Sujeto a los términos y condiciones de esta Licencia Pública, el - Licenciante le otorga a Usted una licencia de carácter global, - gratuita, no transferible a terceros, no exclusiva e irrevocable - para ejercer los Derechos Licenciados sobre el Material - Licenciado para: - A. reproducir y Compartir el Material Licenciado, en su - totalidad o en parte; y - B. producir, reproducir y Compartir Material Adaptado. - - 2. [Excepciones y - Limitaciones]{style="text-decoration: underline;"}. Para evitar - cualquier duda, donde se apliquen Excepciones y Limitaciones al - uso del Material Licenciado, esta Licencia Pública no será - aplicable, y Usted no tendrá necesidad de cumplir con sus - términos y condiciones. - 3. [Vigencia]{style="text-decoration: underline;"}. La vigencia de - esta Licencia Pública está especificada en la sección - [6(a)](#s6a). - 4. [Medios y formatos; modificaciones técnicas - permitidas]{style="text-decoration: underline;"}. El Licenciante - le autoriza a Usted a ejercer los Derechos Licenciados en todos - los medios y formatos, actualmente conocidos o por crearse en el - futuro, y a realizar las modificaciones técnicas necesarias para - ello. El Licenciante renuncia y/o se compromete a no hacer valer - cualquier derecho o potestad para prohibirle a Usted realizar - las modificaciones técnicas necesarias para ejercer los Derechos - Licenciados, incluyendo las modificaciones técnicas necesarias - para eludir las Medidas Tecnológicas Efectivas. A los efectos de - esta Licencia Pública, la mera realización de modificaciones - autorizadas por esta sección [2(a)(4)](#s2a4) nunca produce - Material Adaptado. - 5. [Receptores posteriores]{style="text-decoration: underline;"}. - A. [Oferta del Licenciante – Material - Licenciado]{style="text-decoration: underline;"}. Cada - receptor de Material Licenciado recibe automáticamente una - oferta del Licenciante para ejercer los Derechos Licenciados - bajo los términos y condiciones de esta Licencia Pública. - B. [Oferta adicional por parte del Licenciante – Material - Adaptado]{style="text-decoration: underline;"}. Cada - receptor del Material Adaptado por Usted recibe - automáticamente una oferta del Licenciante para ejercer los - Derechos Licenciados en el Material Adaptado bajo las - condiciones de la Licencia del Adaptador que Usted aplique. - C. [Sin restricciones a receptores - posteriores]{style="text-decoration: underline;"}. Usted no - puede ofrecer o imponer ningún término ni condición - diferente o adicional, ni puede aplicar ninguna Medida - Tecnológica Efectiva al Material Licenciado si haciéndolo - restringe el ejercicio de los Derechos Licenciados a - cualquier receptor del Material Licenciado. - 6. [Sin endoso]{style="text-decoration: underline;"}. Nada de lo - contenido en esta Licencia Pública constituye o puede - interpretarse como un permiso para afirmar o implicar que Usted, - o que Su uso del Material Licenciado, está conectado, - patrocinado, respaldado o reconocido con estatus oficial por el - Licenciante u otros designados para recibir la - Atribución/Reconocimiento según lo dispuesto en la sección - [3(a)(1)(A)(i)](#s3a1Ai). - -b. **Otros derechos**. - - 1. Los derechos morales, tales como el derecho a la integridad, no - están comprendidos bajo esta Licencia Pública ni tampoco los - derechos de publicidad y privacidad ni otros derechos personales - similares. Sin embargo, en la medida de lo posible, el - Licenciante renuncia y/o se compromete a no hacer valer ninguno - de estos derechos que ostenta como Licenciante, limitándose a lo - necesario para que Usted pueda ejercer los Derechos Licenciados, - pero no de otra manera. - 2. Los derechos de patentes y marcas no son objeto de esta Licencia - Pública. - 3. En la medida de lo posible, el Licenciante renuncia al derecho - de cobrarle regalías a Usted por el ejercicio de los Derechos - Licenciados, ya sea directamente o a través de una entidad de - gestión colectiva bajo cualquier esquema de licenciamiento - voluntario, renunciable o no renunciable. En todos los demás - casos, el Licenciante se reserva expresamente cualquier derecho - de cobrar esas regalías. - -##### Sección 3 – Condiciones de la Licencia. - -Su ejercicio de los Derechos Licenciados está expresamente sujeto a las -condiciones siguientes. - -a. **Atribución/Reconocimiento**. - - 1. Si Usted comparte el Material Licenciado (incluyendo en forma - modificada), Usted debe: - - A. Conservar lo siguiente si es facilitado por el Licenciante - con el Material Licenciado: - i. identificación del creador o los creadores del Material - Licenciado y de cualquier otra persona designada para - recibir Atribución/Reconocimiento, de cualquier manera - razonable solicitada por el Licenciante (incluyendo por - seudónimo si este ha sido designado); - ii. un aviso sobre derecho de autor; - iii. un aviso que se refiera a esta Licencia Pública; - iv. un aviso que se refiera a la limitación de garantías; - v. un URI o un hipervínculo al Material Licenciado en la - medida razonablemente posible; - - B. Indicar si Usted modificó el Material Licenciado y conservar - una indicación de las modificaciones anteriores; e - C. Indicar que el Material Licenciado está bajo esta Licencia - Pública, e incluir el texto, el URI o el hipervínculo a esta - Licencia Pública. - - 2. Usted puede satisfacer las condiciones de la sección - [3(a)(1)](#s3a1) de cualquier forma razonable según el medio, - las maneras y el contexto en los cuales Usted Comparta el - Material Licenciado. Por ejemplo, puede ser razonable satisfacer - las condiciones facilitando un URI o un hipervínculo a un - recurso que incluya la información requerida. - 3. Bajo requerimiento del Licenciante, Usted debe eliminar - cualquier información requerida por la sección - [3(a)(1)(A)](#s3a1A) en la medida razonablemente posible. - -b. **CompartirIgual**. - - Además de las condiciones de la sección [3(a)](#s3a), si Usted - Comparte Material Adaptado producido por Usted, también aplican las - condiciones siguientes. - - 1. La Licencia del Adaptador que Usted aplique debe ser una - licencia de Creative Commons con los mismos Elementos de la - Licencia, ya sea de esta versión o una posterior, o una Licencia - Compatible con la BY-SA. - 2. Usted debe incluir el texto, el URI o el hipervínculo a la - Licencia del Adaptador que aplique. Usted puede satisfacer esta - condición de cualquier forma razonable según el medio, las - maneras y el contexto en los cuales Usted Comparta el Material - Adaptado. - 3. Usted no puede ofrecer o imponer ningún término o condición - adicional o diferente, o aplicar ninguna Medida Tecnológica - Efectiva al Material Adaptado que restrinja el ejercicio de los - derechos concedidos en virtud de la Licencia de Adaptador que - Usted aplique. - -##### Sección 4 – Derechos "Sui Generis" sobre Bases de Datos. - -Cuando los Derechos Licenciados incluyan Derechos "Sui Generis" sobre -Bases de Datos que apliquen a Su uso del Material Licenciado: - -a. para evitar cualquier duda, la sección [2(a)(1)](#s2a1) le concede a - Usted el derecho a extraer, reutilizar, reproducir y Compartir todo - o una parte sustancial de los contenidos de la base de datos; -b. si Usted incluye la totalidad o una parte sustancial del contenido - de una base de datos en otra sobre la cual Usted ostenta Derecho - "Sui Generis" sobre Bases de Datos, entonces ella (pero no sus - contenidos individuales) se entenderá como Material Adaptado para - efectos de la sección [3(b)](#s3b); y -c. Usted debe cumplir con las condiciones de la sección [3(a)](#s3a) si - Usted Comparte la totalidad o una parte sustancial de los contenidos - de la base de datos. - -Para evitar dudas, esta sección [4](#s4) complementa y no sustituye Sus -obligaciones bajo esta Licencia Pública cuando los Derechos Licenciados -incluyen otros Derechos de Autor y Derechos Similares. - -##### Sección 5 – Exención de Garantías y Limitación de Responsabilidad. - -a. **Salvo que el Licenciante se haya comprometido mediante un acuerdo - por separado, en la medida de lo posible el Licenciante ofrece el - Material Licenciado tal como es y tal como está disponible y no se - hace responsable ni ofrece garantías de ningún tipo respecto al - Material Licenciado, ya sea de manera expresa, implícita, legal u - otra. Esto incluye, de manera no taxativa, las garantías de título, - comerciabilidad, idoneidad para un propósito en particular, no - infracción, ausencia de vicios ocultos u otros defectos, la - exactitud, la presencia o la ausencia de errores, sean o no - conocidos o detectables. Cuando no se permita, totalmente o en - parte, la declaración de ausencia de garantías, a Usted puede no - aplicársele esta exclusión.** -b. **En la medida de lo posible, en ningún caso el Licenciante será - responsable ante Usted por ninguna teoría legal (incluyendo, de - manera no taxativa, la negligencia) o de otra manera por cualquier - pérdida, coste, gasto o daño directo, especial, indirecto, - incidental, consecuente, punitivo, ejemplar u otro que surja de esta - Licencia Pública o del uso del Material Licenciado, incluso cuando - el Licenciante haya sido advertido de la posibilidad de tales - pérdidas, costes, gastos o daños. Cuando no se permita la limitación - de responsabilidad, ya sea totalmente o en parte, a Usted puede no - aplicársele esta limitación.** -c. La renuncia de garantías y la limitación de responsabilidad - descritas anteriormente deberán ser interpretadas, en la medida de - lo posible, como lo más próximo a una exención y renuncia absoluta a - todo tipo de responsabilidad. - -##### Sección 6 – Vigencia y Terminación. - -a. Esta Licencia Pública tiene una vigencia de aplicación igual al - plazo de protección de los Derechos de Autor y Derechos Similares - licenciados aquí. Sin embargo, si Usted incumple las condiciones de - esta Licencia Pública, los derechos que se le conceden mediante esta - Licencia Pública terminan automáticamente. -b. En aquellos casos en que Su derecho a utilizar el Material - Licenciado se haya terminado conforme a la sección [6(a)](#s6a), - este será restablecido: - - 1. automáticamente a partir de la fecha en que la violación sea - subsanada, siempre y cuando esta se subsane dentro de los 30 - días siguientes a partir de Su descubrimiento de la violación; o - 2. tras el restablecimiento expreso por parte del Licenciante. - - Para evitar dudas, esta sección [6(b)](#s6b) no afecta ningún - derecho que pueda tener el Licenciante a buscar resarcimiento por - Sus violaciones de esta Licencia Pública. - -c. Para evitar dudas, el Licenciante también puede ofrecer el Material - Licenciado bajo términos o condiciones diferentes, o dejar de - distribuir el Material Licenciado en cualquier momento; sin embargo, - hacer esto no pondrá fin a esta Licencia Pública. -d. Las secciones [1](#s1), [5](#s5), [6](#s6), [7](#s7), y [8](#s8) - permanecerán vigentes a la terminación de esta Licencia Pública. - -##### Sección 7 – Otros Términos y Condiciones. - -a. El Licenciante no estará obligado por ningún término o condición - adicional o diferente que Usted le comunique a menos que se acuerde - expresamente. -b. Cualquier arreglo, convenio o acuerdo en relación con el Material - Licenciado que no se indique en este documento se considera separado - e independiente de los términos y condiciones de esta Licencia - Pública. - -##### Sección 8 – Interpretación. - -a. Para evitar dudas, esta Licencia Pública no es ni deberá - interpretarse como una reducción, limitación, restricción, o una - imposición de condiciones al uso de Material Licenciado que - legalmente pueda realizarse sin permiso del titular, más allá de lo - contemplado en esta Licencia Pública. -b. En la medida de lo posible, si alguna disposición de esta Licencia - Pública se considera inaplicable, esta será automáticamente - modificada en la medida mínima necesaria para hacerla aplicable. Si - la disposición no puede ser reformada, deberá ser eliminada de esta - Licencia Pública sin afectar la exigibilidad de los términos y - condiciones restantes. -c. No se podrá renunciar a ningún término o condición de esta Licencia - Pública, ni se consentirá ningún incumplimiento, a menos que se - acuerde expresamente con el Licenciante. -d. Nada en esta Licencia Pública constituye ni puede ser interpretado - como una limitación o una renuncia a los privilegios e inmunidades - que aplican al Licenciante o a Usted, incluyendo aquellos surgidos a - partir de procesos legales de cualquier jurisdicción o autoridad. - - -- cgit v1.2.3