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 +++++++++++++++ 13 files changed, 4774 insertions(+) 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 (limited to 'es') 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. + + -- cgit v1.2.3