diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/00_meta.md | 12 | ||||
-rw-r--r-- | src/01_intro.md | 207 | ||||
-rw-r--r-- | src/02_datos.md | 662 | ||||
-rw-r--r-- | src/03_estructura.md | 550 | ||||
-rw-r--r-- | src/04_funciones.md | 654 | ||||
-rw-r--r-- | src/05_oop.md | 1093 | ||||
-rw-r--r-- | src/06_ejec_mod.md | 271 | ||||
-rw-r--r-- | src/07_install.md | 243 | ||||
-rw-r--r-- | src/08_stdlib.md | 372 | ||||
-rw-r--r-- | src/09_extralib.md | 162 | ||||
-rw-r--r-- | src/10_closing_words.md | 123 | ||||
-rw-r--r-- | src/A_devtools.md | 90 | ||||
-rw-r--r-- | src/Z_license.md | 337 |
13 files changed, 0 insertions, 4776 deletions
diff --git a/src/00_meta.md b/src/00_meta.md deleted file mode 100644 index 7dfbcd0..0000000 --- a/src/00_meta.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -links-as-notes: true -toc: true -title: Python -subtitle: -author: Ekaitz Zárraga -author-meta: Ekaitz Zárraga -lang: es-ES -polyglossia-lang: - name: spanish -license: CC-BY-SA ---- diff --git a/src/01_intro.md b/src/01_intro.md deleted file mode 100644 index b2a1752..0000000 --- a/src/01_intro.md +++ /dev/null @@ -1,207 +0,0 @@ -# Introducción - -Python es un lenguaje de programación de alto nivel orientado al uso general. -Fue creado por Guido Van Rossum y publicado en 1991. La filosofía de python -hace hincapié en la limpieza y la legibilidad del código fuente con una -sintaxis que facilita expresar conceptos en menos líneas de código que en otros -lenguajes. - -Python es un lenguaje de tipado dinámico y gestión de memoria automática. -Soporta múltiples paradigmas de programación, incluyendo la programación -orientada a objetos, imperativa, funcional y procedural e incluye una extensa -librería estándar. - -Pronto entenderás lo que todo esto significa, pero antes hay que instalar las -herramientas necesarias y trastear con ellas. - -## Instalación - -Para trabajar con python se necesita: - -- python3: el intérprete de python, en su versión 3. Verás que hay muchas - subversiones. Este documento cubre cualquiera de ellas. - -- pip: el gestor de paquetería de python. También se conoce como pip3 para - diferenciarlo del pip de python2. - -Nosotros añadiremos un par de amigos a la lista: - -- idle3: un editor de código python muy sencillo. Usaremos este porque - representa el ecosistema de forma muy simple. En el futuro, te recomiendo - usar algún otro editor más avanzado. - -- pipenv: el estándar de facto para gestionar entornos virtuales en python3. - Luego entenderás qué es eso. - - -### Instalación en distribuciones de Linux - -La instalación puede realizarse desde el gestor de paquetes habitual, ya que -python suele distribuirse en todos los repositorios de paquetes. - -En las distribuciones que usan el sistema de paquetes de Debian, puede -instalarse desde la terminal con el siguiente comando: - -``` bash -sudo apt-get install python3 python3-pip idle3 -``` - -### Instalación en otros sistemas - -Como siempre instalar en otros sistemas es más farragoso. Pero no es demasiado -difícil en este caso. La instalación puede realizarse con una descarga desde la -página web oficial de python: - -<https://python.org/downloads/> - -Una vez ahí seleccionar la versión necesaria, descargar el instalador y seguir -las instrucciones de éste. Recuerda seleccionar **instalar pip** entre las -opciones y activar la casilla de **añadir python al PATH**, que permitirá que -que ejecutes programas de python sin problemas. También puedes añadir -**IDLE**, el programa que sirve para editar el código, pero te recuerdo que -es un programa muy sencillo, que nos servirá para entender lo básico del -entorno sin ocultarnos el proceso, pero que más adelante podrás utilizar otros -editores que simplifiquen tareas. - - -## Admira el paisaje - -Una vez que has instalado python, es interesante ver lo que eso significa. -Python es un intérprete de código fuente del lenguaje del mismo nombre. -Concretamente, la que has instalado es una de las posibles implementaciones (la -implementación de referencia, en este caso) de este intérprete, conocida como -CPython, en su versión 3. Existen otras implementaciones, cada una con sus -peculiaridades, pero ésta es la principal y la más usada. - -Como intérprete que es, python es capaz de leer un archivo escrito en su -lenguaje y ejecutar sus órdenes en tu computadora. Ésta es principalmente su -labor. Sin embargo, también es capaz de realizar esta operación de forma -interactiva recibiendo las órdenes una por una y devolviendo el resultado de su -ejecución como respuesta. Este proceso se conoce como REPL, acrónimo de -read-eval-print-loop (lee-evalúa-imprime-repite), aunque en otros lugares se le -conoce como la shell de python. - -> NOTA: La shell de python (o REPL) y la shell del sistema son cosas -> diferentes. La shell de sistema también es un intérprete pero del lenguaje -> que el sistema ha definido (Bash, PowerShell...) y no suele ser capaz de -> entender python. - -Para acostumbrarte a la shell te propongo que abras IDLE. Lo primero que verás -será parecido a esto: - -``` python -Python 3.6.8 (default, Oct 7 2019, 12:59:55) -[GCC 8.3.0] on linux -Type "help", "copyright", "credits" or "license()" for more information. ->>> -``` - -Todo lo que escribas tras el símbolo `>>>` será interpretado como una orden y -cuando la termines pulsando la tecla `ENTER` de tu teclado, recibirás el -resultado de la ejecución de la orden insertada. El acrónimo REPL define el -comportamiento de este ciclo a las mil maravillas: - -> NOTA: En este documento, siempre que veas el símbolo `>>>` significa que se -> trata de un ejemplo ejecutado en la REPL. Si no lo ves, se tratará del -> contenido de un archivo de código python ejecutado de forma independiente. - -1. Lee lo que introduces. -2. Lo evalúa, obteniendo así un valor como resultado. -3. Lo imprime. -4. Repite el ciclo volviendo a leer. - -Por tanto, si introduces un valor directamente será devuelto: - -``` python ->>> 1 -1 -``` - -Y si lo alteras, por ejemplo, con una operación matemática sencilla, devuelve -el resultado correspondiente: - -``` python ->>> 2+2 -4 -``` - -Como ejercicio te propongo lo siguiente: - -1. Abre la shell de python (puedes hacerlo en IDLE o desde la shell de sistema - ejecutando `python` o `python3`). -2. Entra en la ayuda interactiva. PISTA: el mensaje que aparece al abrir la - REPL te dice cómo. -3. Sal de la ayuda (descubre tú mismo cómo se hace). -4. Ejecuta `import this` y lee el resultado. - - -### Tu primer archivo de código fuente - -La REPL es interesante para probar y depurar tus programas (o para usarla como -calculadora), pero es necesario grabar tus programas en ficheros si quieres -poder volver a ejecutarlos más adelante o compartirlos. - -En IDLE puedes abrir un nuevo documento de código en el menú de archivo. Una -vez lo tengas, como aún no sabes python puedes introducir lo siguiente: - -``` python -nombre = "Guido" -print("Hola, " + nombre) -``` - -Si guardas el fichero y pulsas `F5` (Ejecutar módulo), verás que en la pantalla -de la REPL aparece el resultado `Hola, Guido`. - -Como ves, el resultado de ejecutar los ficheros de código fuente aparece en la -shell, pero únicamente aparece lo que explícitamente le has pedido que imprima -con la orden `print`. - -Para entender el valor de la REPL, te sugiero que vayas a su ventana, y justo -después del resultado de la ejecución hagas lo siguiente: - -``` python -Hola, Guido ->>> nombre -'Guido' ->>> -``` - -La REPL conoce el valor `nombre` a pesar de que tu programa ha terminado de -ejecutarse. Esto es interesante a la hora de probar y analizar el programa. - - -### La vida real - -En realidad, los programas de producción no se ejecutan en una shell como la -que IDLE nos brinda. IDLE sólo está facilitando nuestro trabajo como -desarrolladores, como otros entornos de desarrollo hacen, cada uno a su manera. -En producción el código se levantará ejecutando el intérprete de python -directamente con nuestro programa como input. Por ejemplo en la shell **de -sistema** usando el siguiente comando. - -``` bash -python ejemplo.py -``` - -También es posible ejecutar los programas de python desde la interfaz gráfica, -pero internamente el resultado será el mismo. Siempre que todo esté bien -instalado y configurado, el sistema operativo despertará un intérprete de -python que ejecute las órdenes del fichero. - -Es importante ser consciente de lo que ocurre bajo la alfombra, para así ser -capaces de intervenir si encontramos errores. - -Más adelante, en la sección sobre módulos e importación volveremos aquí y -estudiaremos cómo se cargan y se interpretan los programas. - - -## Lo que has aprendido - -Has instalado python y te has acostumbrado a la herramienta (IDLE) que usarás -durante tu aprendizaje. Has ejecutado tu primer fichero y encontrado la -potencia de la REPL. - -Además, has abierto la ayuda y te has leído el Zen de Python, que pronto iremos -desgranando juntos. - -Para ser una introducción no está nada mal. diff --git a/src/02_datos.md b/src/02_datos.md deleted file mode 100644 index 1eb2f1a..0000000 --- a/src/02_datos.md +++ /dev/null @@ -1,662 +0,0 @@ -# Trabajando con datos - -Programar es, principalmente, tratar con datos y los datos no son más que -piezas de información que se estructura de una forma concreta. - -## Nombrando datos - -Los datos se almacenan en la memoria principal de la computadora. Puedes -imaginarte la memoria como un conjunto de cajas identificadas con direcciones, -como si fuesen los buzones de un portal con muchos pisos. Para utilizar datos, -éstos se guardan en los diferentes cajones y se van extrayendo y actualizando. -La importancia de nombrar las cosas es evidente, si no guardamos un listado de -dónde hemos guardado qué no podemos recuperar los datos que hemos creado. - -Al igual que en las matemáticas se utilizan los símbolos para representar -posibles valores y simplificar los procesos, en los lenguajes de programación -se utilizan símbolos para definir referencias a los datos y así poder referirse -a los mismos datos por su nombre sin tener que introducir su contenido -constantemente. Los lenguajes de programación aportan al programador la -facilidad de poder crear sus propios nombres para no tener que usar las -direcciones propias de la memoria, que normalmente son número sin más -significado que su posición, la mayor parte de las veces permitiendo al -programador abstraerse de estos detalles internos. - -En python existe un operador para declarar símbolos que hacen referencia a un -valor: el operador `=`. Este operador enlaza el valor de la derecha con el -nombre de la izquierda. Con un ejemplo es más fácil de comprender. Probando en -la REPL: - -``` python ->>> a = 10 ->>> a -10 ->>> a + 1 -11 -``` - -Como ves, en el primer paso se asocia el valor `10` al identificador `a` y más -adelante se puede utilizar `a` para hacer referencia al valor enlazado. - -Las referencias han de ser declaradas antes de usarse, si no, el intérprete no -las conoce y lanza una excepción: - -``` python ->>> b -Traceback (most recent call last): - File "<pyshell#6>", line 1, in <module> - 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 "<pyshell#27>", line 1, in <module> - 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]: <https://en.wikipedia.org/wiki/%3F:#Python> - - -### 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 "<pyshell#67>", line 1, in <module> - a[0] = 1 -TypeError: 'tuple' object does not support item assignment -``` - -Ten cuidado cuando trates con elementos mutables, sobre todo si tienen muchas -referencias, porque puede que estés alterando los valores en lugares que no te -interesa. Para evitar este tipo de problemas, puedes generar copias de los -objetos, pero el proceso es poco eficiente y tedioso. - -En este segundo caso, creamos una copia de `a` para que `b` sea independiente -de los cambios que ocurran en ésta. Aquí ya no estamos haciendo referencia -desde `b` a los datos que había en `a`, sino a una copia de éstos, almacenada -en otro lugar. - -``` python ->>> a = {} ->>> b = dict(a) ->>> a["cambio"] = "hola!" ->>> b -{} ->>> a -{'cambio': 'hola!'} -``` - -## Lo que has aprendido - -En este apartado has conocido los tipos fundamentales de python y cómo -convertir de uno a otro. Además, al conocer los operadores y su funcionamiento -ya eres más o menos capaz de usar python como una calculadora. - -Además, has tenido ocasión de entender de forma superficial varios conceptos -avanzados como el manejo automático de memoria, el tipado dinámico y los -números de coma flotante, que son muy interesantes a la hora de trabajar porque -te permiten comprender la realidad que tienes a tu alrededor. diff --git a/src/03_estructura.md b/src/03_estructura.md deleted file mode 100644 index 9d8cd5e..0000000 --- a/src/03_estructura.md +++ /dev/null @@ -1,550 +0,0 @@ -# Estructura del lenguaje - -Aunque ya sabes usar python de forma sencilla, aún no hemos tratado el -comportamiento del lenguaje y cómo se estructura su sintaxis más allá de varios -ejemplos sencillos y planos. En este apartado trataremos la estructura y las -diferentes formas de controlar el flujo del programa. - -Como en la mayor parte de lenguajes de programación conocidos, python ejecuta -las órdenes de arriba a abajo, línea por línea. - -Para demostrarlo, prueba a abrir un nuevo fichero, llenarlo con este contenido -y ejecutarlo (`F5`). - -``` python -print("Esta línea va primero") -print("Esta línea va segundo") -print("Esta línea va tercero") -print("Esta línea va cuarto") -``` - -Verás que el resultado del programa es siempre el mismo para todas las veces -que lo ejecutes y siempre salen los resultados en el mismo orden. - -Para poder alterar el orden de los comandos, o elegir en función de una -condición cuales se ejecutan, python dispone de unas estructuras. Pero, antes -de contarte cuales son, te adelanto su forma general. Normalmente se declaran -en una línea terminada en `:` y su cuerpo se sangra hacia dentro. La sangría (o -indentación si lo calcamos del inglés[^indent]) es lo que define dónde empieza -o termina un *bloque* en python. Las líneas consecutivas sangradas al mismo -nivel se consideran el mismo *bloque*. - -#### Bloques - -Los *bloques* de código son conjuntos de órdenes que pertenecen al mismo -contexto. Sirven para delimitar zonas del programa, cuerpos de sentencias, etc. - -- Puedes comenzar nuevos bloques incrementando el nivel de sangría. -- Los bloques pueden contener otros bloques. -- Los bloques terminan cuando el sangrado disminuye. - -Es **muy importante** sangrar los bloques correctamente, usando una sangría -coherente. Puedes usar dos espacios, el tabulador, cuatro espacios o lo que -desees, pero elijas lo que elijas debe ser coherente en todo el documento. -IDLE y los editores de código en general puede configurarse para usar una -anchura de indentación concreta, que se insertará cuando pulses la tecla -tabulador. El estándar de python es cuatro espacios. - -[^indent]: <https://en.wikipedia.org/wiki/Indentation_(typesetting)> - -## 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]: <https://docs.python.org/3/library/stdtypes.html#truth-value-testing> - - -### 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)) -<generator object <genexpr> at 0x7f779d9b2d58> -``` - -[^set-notation]: <https://en.wikipedia.org/wiki/Set-builder_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]: <https://docs.python.org/3/library/exceptions.html> - -### 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 "<stdin>", line 2 - - ^ -IndentationError: expected an indented block ->>> if True: -... pass -... -``` - -Suele utilizarse cuando no quiere tratarse una excepción o cuando se ha hecho -un boceto de una función que aún no quiere desarrollarse, para que el -intérprete no falle de forma inevitable. - -> NOTA: Las excepciones de sintaxis son las más graves, implican que el -> intérprete no es capaz de entender lo que le pedimos así que la ejecución del -> programa no llega a realizarse. La sintaxis se comprueba en una etapa previa -> a la ejecución. - -#### Continue - -`continue` sirve para terminar el bucle actual y volver a comprobar la -condición para decidir si volver a ejecutarlo. - -En el siguiente ejemplo salta la ejecución para el caso en el que `i` es `2`. - -``` python ->>> for i in [0, 1, 2, 3]: -... if i == 2: -... continue -... i -... -0 -1 -3 -``` - -#### Break - -`break` rompe el bucle actual. A diferencia del `continue`, no se intenta -ejecutar la siguiente sentencia, simplemente se rompe el bucle completo. En el -siguiente ejemplo se rompe el bucle cuando `i` es `2` y no se recupera. - -``` python ->>> for i in [0, 1, 2, 3]: -... if i == 2: -... break -... i -... -0 -1 -``` - -El `break` se usa mucho para romper bucles que son infinitos por definición. En -lugar de añadir una condición compleja a un bucle `while`, por ejemplo, puedes -usar un `True` en su condición e introducir un `if` más simple dentro de éste -con un `break` que termine el bucle en los casos que te interesen. O puedes -capturar una excepción y romper el bucle si ocurre. - -Además, es muy usado en búsquedas y habilita el uso del `else` en las -sentencias `for`. Te propongo como ejercicio que trates de ejecutar y -comprender el funcionamiento de esta pieza de código de python avanzado antes -de seguir leyendo: - -``` python -text = "texto de prueba" -pos = 0 - -for i in text: - if i == "b": - print("Found b in position: " + str(pos)) - break - pos = pos + 1 -else: - print("b not found :( ") -``` - -Si cambias el texto de la variable `text` por uno que no tenga letra `b` verás -que el bloque `else` se ejecuta. Esto se debe a que el `for` no termina de -forma abrupta, sino que itera por el string completo. - -Si quieres, puedes memorizar esta estructura para cuando quieras hacer -búsquedas. Es elegante y te será útil. - - -#### Return - -La sentencia `return` sólo tiene sentido dentro de las funciones. Sirve para -finalizar la ejecución de una función sustituyendo su llamada por el resultado -indicado en el `return`. Esta operación rompe todos los bucles por completo. - -En el apartado de las funciones profundizaremos en el uso del `return` pero es -importante mencionarlo aquí porque su funcionalidad puede sustituir al `break` -en muchas ocasiones. - -## Lo que has aprendido - -Es difícil resumir todo lo que has aprendido en este capítulo porque, la verdad -es que es mucha información. Pero no pasa nada porque este capítulo se ha -creado más como referencia que como otra cosa. No tengas miedo a volver a -leerlo todas las veces que necesites. A todos se nos olvida cómo hay que -declarar las funciones si llevamos mucho tiempo sin tocar python, o si era -`try-catch` o `try-except`. Este capítulo pretende, por un lado, darte una -pincelada de cómo se escribe en python y, por otro, servir como manual de -consulta posterior. - -Pero, por hacer la labor de resumen, has aprendido el orden de ejecución de las -órdenes y como alterarlo con bucles y condicionales. Para ello, has tenido que -aprender lo que es un *bloque* de código, una pieza fundamental para entender -la sintaxis. Tras ver varios ejemplos de bucles y condicionales, te has -sumergido en la verdad y la mentira mediante los valores *truthy* y *falsey*, -para después avanzar a las *list comprehensions*, cuyo nombre contiene *list* -pero valen para cualquier dato complejo. - -Has cambiado un poco de tema después, saltando a las excepciones, que no has -podido ver en detalle por no ser, aún, un experto en programación orientada a -objetos. Pero tranquilo, pronto lo serás. - -Una pincelada sobre funciones ha sido suficiente para que no les tengas miedo -nunca más y que podamos atacar el siguiente capítulo con energía, ya que ahora -toca jugar con funciones hasta entenderlas por completo. - -Las sentencias útiles que hemos recopilado al final permiten juguetear con -todas las estructuras que hemos definido en el capítulo de modo que puedas -usarlas a tu antojo de forma cómoda. Algunas de ellas como `continue` y `break` -no son realmente necesarias, puede programarse evitándolas y, de hecho, en -algunos lugares enseñan a no usarlas, como si de una buena práctica se tratara, -cambiando las condiciones de los bucles para que hagan esta labor. En este -documento se muestran porque, en primer lugar, si lees código escrito por otras -personas las encontrarás y tendrás que entender qué hacen y, en segundo, porque -son sentencias que simplifican el código haciéndolo más legible o más sencillo -por muy impuras que a algunos programadores les puedan parecer. diff --git a/src/04_funciones.md b/src/04_funciones.md deleted file mode 100644 index 618ccd6..0000000 --- a/src/04_funciones.md +++ /dev/null @@ -1,654 +0,0 @@ -# Funciones - -El objetivo de este capítulo es que te familiarices con el uso de las -funciones. Parece sencillo pero es una tarea un tanto complicada porque, visto -como nos gusta hacer las cosas, tenemos una gran cantidad de complejidad que -abordar. - -Antes de entrar, vamos a definir una función y a usarla un par de veces: - -``` python -def inc(a): - b = a + 1 - return b -``` - -Si la llamamos: - -``` python ->>> inc(1) -2 ->>> inc(10) -11 -``` - -Cuidado con las declaraciones internas en las funciones. Si preguntamos por -`b`: - -``` python ->>> b -Traceback (most recent call last): - File "<stdin>", line 1, in <module> -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 -<function <lambda> 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 -<function <lambda> 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: - <https://en.wikipedia.org/wiki/Deductive_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]: <https://www.python.org/dev/peps/pep-0289/> - - -### 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__ -(<cell at 0x7f04b4ebfa68: int object at 0xa68ac0>,) -``` - -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 "<stdin>", line 1, in <module> -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]: <https://en.wikipedia.org/wiki/Switch_statement> - -### 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: - <https://docs.python.org/3/library/functools.html#functools.wraps> - -La realidad es que los *decorators* son una forma muy elegante de añadir -funcionalidades a las funciones sin complicar demasiado el código. Permiten -añadir capacidad de depuración, *profiling* y todo tipo de funcionalidades que -se te ocurran. - -Este apartado se deja varias cosas en el tintero, como los decoradores con -parámetros de entrada, pero no pretende ser una referencia de cómo se usan, -sino una introducción a un concepto útil que resume perfectamente lo tratado -durante todo el capítulo. - -Te animo, como ejercicio, a que analices el decorador `@lru_cache` del módulo -`functools` y comprendas su interés y su funcionamiento. Para leerlo en la -ayuda debes importar el módulo `functools` primero. Como aún no sabes hacerlo, -aquí tienes la receta: - -``` python ->>> import functools ->>> help(functools.lru_cache) -``` - - -## Lo que has aprendido - -Este capítulo puede que sea el más complejo de todos los que te has encontrado -y te encontrarás. En él has aprendido a declarar y a usar funciones, cosa -sencilla, y todos los conceptos importantes relacionados con ellas. Un -conocimiento que es útil en python, pero que puede ser extendido a casi -cualquier lenguaje. - -Tras un sencillo acercamiento al *scope*, has comprendido que las funciones en -python son sólo un valor más, como puede ser un `int`, y que pueden declararse -en cualquier lugar, lo que te abre la puerta a querer declarar funciones -sencillas sin nombre, que se conocen como funciones *lambda*. - -Una vez has aclarado que las funciones son ciudadanos de primera clase -(*first-class citizens*) ya estabas preparado para afrontar la realidad del -*scope* donde has tratado los contextos y cómo funcionan definiendo el concepto -del *scope léxico* que, colateralmente, te ha enseñado lo que es una *closure*, -un método para implementarlo. Pero también has tenido ocasión de aprender que -en python es posible crear casos de *scope dinámico* mediante las sentencias -`global` y `nonlocal`, que pueden ser útiles, pero es mejor no abusar de ellas. - -Pero no había quedado claro en su momento cómo funcionaban los argumentos de -entrada y las llamadas a las funciones, así que has tenido ocasión de ver por -primera vez lo que es un *callable* en python, aunque se te ha prometido -analizarlo en el futuro. Lo que sí que has tenido ocasión de tratar son los -argumentos *positional* y *keyword*, y cómo se utilizan en todas sus posibles -formas. - -Finalmente, para agrupar todo esto en un único concepto, se te han mostrado los -*decorators*, aunque de forma muy general, con el fin de que vieras que todo lo -que se ha tratado en este capítulo aparece en conceptos avanzados y es -necesario entenderlo si quieren llegar a usarse de forma eficiente. diff --git a/src/05_oop.md b/src/05_oop.md deleted file mode 100644 index 6fdb9ce..0000000 --- a/src/05_oop.md +++ /dev/null @@ -1,1093 +0,0 @@ -# Programación Orientada a Objetos - -La *programación orientada a objetos* u *object oriented programming* (OOP) es -un paradigma de programación que envuelve python de pies a cabeza. A pesar de -que python se define como un lenguaje de programación multiparadigma, la -programación orientada a objetos es el paradigma principal de éste. A pesar de -que varias de las características que tratamos en el apartado anterior se -corresponden más con un lenguaje de programación funcional, en python **todo** -(o casi todo) es una clase. - -Python usa una programación orientada a objetos basada en clases[^class], a -diferencia de otros lenguajes como JavaScript, donde la orientación a objetos -está basada en prototipos[^proto]. No es el objetivo de este documento el de -contarte cuales son las diferencias entre ambas, pero es interesante que sepas -de su existencia, ya que es una de las pocas diferencias que existen entre -estos dos lenguajes de amplio uso en la actualidad. - -[^class]: <https://en.wikipedia.org/wiki/Class-based_programming> -[^proto]: <https://en.wikipedia.org/wiki/Prototype-based_programming> - -## 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 "<stdin>", line 1, in <module> -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) -<class 'function'> ->>> type ( bobby.bark ) -<class 'method'> -``` - -> 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]: <https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables> - -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]: <https://dle.rae.es/protocolo> - -### *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 "<stdin>", line 1, in <module> -TypeError: 'list' object is not an iterator ->>> it = iter(l) ->>> it -<list_iterator object at 0x7ff745723908> ->>> ->>> next(it) -1 ->>> next(it) -2 ->>> next(it) -3 ->>> next(it) -Traceback (most recent call last): - File "<stdin>", line 1, in <module> -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. -> <https://www.python.org/dev/peps/> - -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: -<https://www.python.org/dev/peps/pep-0343/> - -### *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 "<dateFileSystemIterator: <Current: " \ - + tostring(self.current) + ">" \ - + ",<Start: " + tostring(self.start) + ">" \ - + ",<End: " + tostring(self.end) + ">" \ - + ",<Step: " + str(self.step) + ">" - - -it = dateFileSystemIterator(start = date.today() - timedelta(days=30)) -print(it) -for i in it: - print(i) -``` - -#### Ejercicio libre: `yield` y los generadores - -La parte de la iteración del ejemplo previo puede realizarse forma más breve -mediante el uso de la sentencia `yield`. Aunque no la trataremos, `yield` -habilita muchos conceptos interesantes, entre ellos los *generadores*. - -A continuación tienes un ejemplo de cómo resolver el problema anterior mediante -el uso de esta sentencia. Te propongo como ejercicio que investigues cómo -funciona buscando información sobre los *generadores* (*generator*) y la -propia sentencia `yield`. - -``` python -from datetime import datetime, timedelta - -def iterate_dates( date_start, date_end=datetime.today(), - separator='/', step=timedelta(days=1)): - date = date_start - while date < date_end: - yield date.strftime('%Y'+separator+'%m'+separator+'%d') - date += step -``` - -`yield` tiene mucha relación con las *corrutinas* (*coroutine*) que, aunque no -se tratarán en este documento, son un concepto muy interesante que te animo a -investigar. Si lo haces, verás que los generadores son un caso simple de una -corrutina. - -## Lo que has aprendido - -Este capítulo también ha sido intenso como el anterior, pero te prometo que no -volverá a pasar. El interés principal de este capítulo es el de hacerte conocer -la programación orientada a objetos y enseñarte que en python lo inunda todo. -Todo son objetos. - -Para entenderlo has comenzado aprendiendo lo que es la programación orientada a -objetos, concretamente la orientada a clases, donde has visto por primera vez -los conceptos de identidad propia, comportamiento y estado. - -Desde ahí has saltado al fundamento teórico de la programación orientada a -objetos y has visitado la encapsulación, la herencia y el polimorfismo para -luego, una vez comprendidos, comenzar a definir clases en python. - -Esto te ha llevado a necesitar conocer qué es el argumento que suele llamarse -`self`, una excusa perfecta para definir qué son las variables y funciones de -clase y en qué se diferencian de las propiedades y métodos. - -Como la encapsulación no se había tratado en detalle aún, lo próximo que has -hecho ha sido zambullirte en los campos privados viendo cómo python los crea -mediante un truco llamado *name mangling* y su impacto en la herencia. - -Aunque en este punto conocías el comportamiento general de la herencia hacia -abajo, necesitabas conocerlo hacia arriba. Por eso, ha tocado visitar la -función `super` en este punto, función que te permite acceder a la superclase -de la clase en la que te encuentras. En lugar de contártela en detalle, se te -ha dado una pincelada sobre ella para que tú investigues cuando lo veas -necesario, pero que sepas por dónde empezar. - -Para describir más en detalle lo calado que está python de programación -orientada a objetos necesitabas un ejemplo mucho más agresivo: los protocolos. -A través de ellos has visto cómo python recoge las funcionalidades estándar y -te permite crear objetos que las cumplan. Además, te ha servido para ver que -**todo** en python es un objeto (hasta las clases lo son[^objects]) y para ver -formas elegantes de resolver problemas comunes, como los iteradores, `with` y -otros. - -También, te recuerdo que, aunque sea de forma colateral y sin prestarle -demasiada atención, se te ha sugerido que cuando programamos no lo hacemos -únicamente para nosotros mismos y que la facilidad de lectura del código y la -preparación de éste para que otros lo usen es primordial. Los próximos -capítulos tratan en parte de ésto: de hacer uso del patrimonio tecnológico de -la humanidad, y de ser parte de él. - -[^objects]: Puedes preguntárselo a python: - ``` python - >>> class C: pass - ... - - >>> isinstance(C, object) - True - ``` diff --git a/src/06_ejec_mod.md b/src/06_ejec_mod.md deleted file mode 100644 index 0c54f4f..0000000 --- a/src/06_ejec_mod.md +++ /dev/null @@ -1,271 +0,0 @@ -# Módulos y ejecución - -Hasta ahora, has ejecutado el código en la REPL y de vez en cuando has usado -`F5` en IDLE para ejecutar. Aunque te ha permitido salir del paso, necesitas -saber más en detalle cómo funciona la ejecución para empezar a hacer tus -programas. Además, es absurdo que te pelees contra todo, hay que saber qué -batallas librar, así que necesitarás aprender a importar código realizado por -otras personas para poder centrarte en lo que más te interesa: resolver tu -problema. - -Este capítulo trata ambas cosas, que están muy relacionadas, y sirve como -trampolín para el siguiente, la instalación de nuevos paquetes y la gestión de -dependencias, y los posteriores sobre librerías interesantes que te facilitarán -el desarrollo de tus proyectos. - -Este capítulo ya te capacita casi al cien por cien para la programación aunque -aún no hemos trabajado su utilidad, pero seguro que alguna idea se te habrá -ocurrido, si no no estarías leyendo este documento. - -## Terminología: módulos y paquetes - -En python a cualquier fichero de código (extensión `.py`) se le denomina -*módulo* (*module*). A cualquier directorio que contenga módulos de código -python y un fichero llamado `__init__.py`, que puede estar vacío, se le -denomina *paquete* (*package*). El uso del fichero `__init__.py` permite que -python busque módulos dentro del directorio. - -Piensa en los paquetes como grupos de módulos que incluso pueden anidarse con -subpaquetes. En la documentación oficial verás en más de una ocasión que se -trata a los paquetes como si fueran módulos, y tiene cierto sentido, porque, -normalmente, existe un módulo principal que permite el acceso a un subpaquete. -Así que, principalmente estás trabajando con un único módulo de un paquete, que -contiene subpaquetes a los que este módulo hace referencia. Aunque pueda sonar -algo enrevesado, no te preocupes por ahora: los módulos son ficheros únicos y -los paquetes conjuntos de ellos. - -## Ejecución - -Ya conoces un par de maneras de ejecutar tus módulos de python. Usar la REPL, -introduciéndole el código que quieres ejecutar, llamar a la función «ejecutar -módulo» de IDLE con la tecla `F5` e incluso llamar a la shell de sistema con un -comando similar a este: - -``` bash -python mi_archivo.py -``` - -Normalmente, los ficheros de python se ejecutan de este último modo en -producción, mientras que los dos anteriores son más usados a la hora de -desarrollar. Realmente son métodos similares, en todos ellos el intérprete -accede al fichero o contenido que recibe y ejecuta las líneas una a una. - -Existe también una opción adicional muy usada que sirve para ejecutar módulos -que el sistema sea capaz de encontrar por sí mismo, en lugar de indicarle la -ruta, se le puede indicar simplemente el nombre del módulo usando la opción -`-m`: - -``` bash -python -m nombre_de_modulo -``` - -## Importación y *namespaces* - -Anteriormente hemos pasado sobre la sintaxis de la importación de forma muy -superficial pero tampoco es mucho más compleja a lo que ha aparecido. La -sentencia `import` permite importar diferentes módulos a nuestro programa como -en el siguiente ejemplo: - -``` python ->>> import datetime ->>> datetime -<module 'datetime' from '/usr/lib/python3.6/datetime.py'> -``` - -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 -<module 'datetime' from '/usr/lib/python3.6/datetime.py'> -``` - -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]: <https://docs.python.org/3/library/__main__.html> - - -Siguiendo este concepto, también existe el un estándar de nomenclatura de -ficheros. El nombre `__main__.py` hace referencia al fichero que contiene el -código que se incluiría dentro del *guard* y será el fichero que python buscará -ejecutar siempre que se le pida ejecutar un paquete o un directorio sin -especificar qué módulo debe lanzar. Por ejemplo, ejecutar `python .`[^dot] en -la shell de sistema es equivalente a ejecutar `python __main__.py`. - -[^dot]: `.` significa directorio actual en cualquiera de los sistemas - operativos comunes. - - -## Lo que has aprendido - -En este capítulo corto has aprendido lo necesario sobre importación de módulos -y ejecución de código. Conocer en detalle el patrón de búsqueda de módulos de -python es primordial para evitar problemas en el futuro y organizar los -proyectos de forma elegante. - -Además, el `__main__` *guard* era una de las últimas convenciones de uso común -que quedaban por explicar y una vez vista ya eres capaz de leer proyectos de -código fuente que te encuentres por ahí sin demasiados problemas. - -Los próximos capítulos, basándose en lo aprendido en éste, te mostrarán cómo -instalar nuevas dependencias y cómo preparar tu propio código para que pueda -ser instalado de forma limpia y elegante. diff --git a/src/07_install.md b/src/07_install.md deleted file mode 100644 index 3da8ae0..0000000 --- a/src/07_install.md +++ /dev/null @@ -1,243 +0,0 @@ -# Instalación y dependencias - -Ahora que sabes lidiar con módulos, necesitas aprender a instalarlos en tu -propio sistema, porque es bastante tedioso que tengas que copiar el código de -todas tus dependencias en la carpeta de tu proyecto. - -En la introducción no aseguramos de instalar con python la herramienta `pip`. -Que sirve para instalar paquetes nuevos en el sistema de forma sencilla. - -## Funcionamiento de `pip` - -`pip` es una herramienta extremadamente flexible, capaz de instalar módulos de -python de diferentes fuentes: repositorios de git, carpetas de sistema o, la -más interesante quizás de todas, el *Python Package Index* (*PyPI*)[^pypi]. - -[^pypi]: https://pypi.org/ - -`pip` buscará la descripción del paquete en la fuente que se le indique y, de -esta descripción, obtendrá las dependencias necesarias y las indicaciones de -cómo debe instalarlo. Una vez procesadas las normas, procederá a instalar el -paquete en el directorio de sistema con todas las dependencias de éste para que -funcione correctamente. - -Una vez instalado el paquete en el directorio de sistema está listo para ser -importado. - -### PyPI - -El *Python Package Index* o *PyPI* es un repositorio que contiene software -programado en python. En él se listan miles de librerías creadas por -programadores de python para que cualquiera pueda descargarlas e instalarlas. -Más adelante veremos algunas de ellas y nos acostumbraremos a usarl PyPI como -recurso. - -Ahora que sabes programar en python tú también puedes publicar tus proyectos -ahí para que otras personas los usen para crear los suyos. - -### Reglas de instalación: `setuptools` y `setup.py` - -Para que `pip` pueda hacer su trabajo correctamente hay que indicarle cómo debe -hacerlo, ya que cada paquete es un mundo y tiene necesidades distintas. El -módulo `setuptools` permite crear un conjunto de reglas comprensible por `pip` -que facilita la distribución e instalación. - -Normalmente este conjunto de reglas suele almacenarse en un fichero de nombre -`setup.py`. Como ejemplo de `setup.py` puedes ver el de la librería -`BeautifulSoup4`[^bs4-setup] que se adjunta a continuación con ligeras -alteraciones para que encaje en la página: - -[^bs4-setup]: https://www.crummy.com/software/BeautifulSoup/bs4/doc/ - -``` {.python .numberLines} -from setuptools import ( - setup, - find_packages, -) - -with open("README.md", "r") as fh: - long_description = fh.read() - -setup( - name="beautifulsoup4", - # NOTE: We can't import __version__ from bs4 because bs4/__init__.py - # is Python 2 code, and converting it to Python 3 means going through - # this code to run 2to3. - # So we have to specify it twice for the time being. - version = '4.8.1', - author="Leonard Richardson", - author_email='leonardr@segfault.org', - url="http://www.crummy.com/software/BeautifulSoup/bs4/", - download_url = "http://www.crummy.com/software/BeautifulSoup/bs4/download/", - description="Screen-scraping library", - install_requires=["soupsieve>=1.2"], - long_description=long_description, - long_description_content_type="text/markdown", - license="MIT", - packages=find_packages(exclude=['tests*']), - extras_require = { - 'lxml' : [ 'lxml'], - 'html5lib' : ['html5lib'], - }, - use_2to3 = True, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 2.7", - 'Programming Language :: Python :: 3', - "Topic :: Text Processing :: Markup :: HTML", - "Topic :: Text Processing :: Markup :: XML", - "Topic :: Text Processing :: Markup :: SGML", - "Topic :: Software Development :: Libraries :: Python Modules", - ], -) -``` - -En el ejemplo se aprecia a la perfección el tipo de información que es -necesario aportarle a `setuptools`. Dependiendo del proyecto, esta -configuración puede ser más compleja o más sencilla, y tendrás que indagar a -través de la configuración de `setuptools` para ajustar la herramienta a tu -proyecto. - - -## Entornos virtuales y dependencias: `pipenv` - -La herramienta `pip` es interesante para instalar herramientas en el sistema -pero tiene ciertas carencias. La primera, que no es capaz de resolver las -dependencias a la hora de desinstalar paquetes, por lo que, si se instala un -paquete que dependa de otro paquete, `pip` instalará todas las dependencias -necesarias, pero por miedo a romper paquetes, no las desinstalará si se le pide -desinstalar el paquete que las arrastró. - -Por otro lado, si quieres trabajar en proyectos de desarrollo, probablemente -tengas que instalar sus dependencias. Si tienes varios proyectos en marcha -simultáneamente o si tus sistema necesita de alguna herramienta escrita en -python, es posible que tengas colisiones. - -Imagina que dos de los proyectos, por ejemplo, usan versiones diferentes de una -de sus librerías. Si instalas sus dependencias usando `pip`, se mezclaran en tu -sistema y no podrán coexistir. Además, cuando termines los proyectos o -abandones su desarrollo, te interesará limpiar sus dependencias de tu sistema, -cosa complicada si `pip` no gestiona la liberación de paquetes de forma -correcta. - -Para evitar estos problemas y algún otro adicional, existen herramientas -adicionales que alteran el comportamiento de `pip` y del propio python, creando -lo que se conoce como *entornos virtuales* (*virtual environments*) que quedan -aislados entre ellos y el sistema. - -El funcionamiento de los entornos virtuales es muy sencillo. Cuando se activan, -crean un nuevo contexto en el que alteran las variables de entorno que -describen dónde debe buscarse el intérprete de python, dónde busca éste los -módulos y dónde instala `pip` los paquetes. Cuando se desactivan, se restaura -el entorno por defecto. De este modo, cada entorno virtual queda perfectamente -aislado de otros o incluso de la instalación del sistema, permitiéndote hasta -tener diferentes versiones de python en tu sistema y que no colisionen entre -ellas. Cuando has terminado con tu entorno virtual puedes borrarlo de forma -segura sabiendo que no va a afectar a tu sistema y que va a llevarse todas las -dependencias con él. - -Históricamente se han utilizado varias herramientas para esta labor, como -`virtualenv`, que como era poco amigable se simplificaba con -`virtualenv-wrapper` u otras. Hoy en día `pipenv` es la herramienta -recomendada. - -`pipenv` es una combinación de `virtualenv` y `pip` creada para gestionar -entornos virtuales y dependencias de desarrollo. Puedes considerarla un gestor -de paquetes de desarrollo como `npm` en JavaScript, `composer` en PHP o -cualquier otro que conozcas. Aporta la mayor parte de funcionalidades -habituales como ficheros de dependencias, lockfiles etc. mientras que expone -una interfaz de comandos sencilla y bien documentada. - -### Instalación - -Para instalar pipenv, podemos usar `pip`, que instalamos en la introducción. -En la shell de sistema ejecutando: - -``` bash -pip install pipenv -``` - -> NOTA: en función del sistema que utilices, puede que `pip` se llame -> `pip3`. El funcionamiento es idéntico. - -### Uso - -Una vez instalado `pipenv`, puedes usarlo en el directorio que desees para -crear un conjunto de nuevas dependencias pidiéndole que instale un nuevo -paquete lanzando la orden `pipenv install` en la shell de sistema. - -Para ejecutar módulos en el entorno virtual recién creado dispones de dos -opciones: `pipenv shell` que prepara una shell de sistema en el entorno o -`pipenv run` que ejecuta el comando que se le envíe en el entorno. - -Puedes seguir añadiendo dependencias al proyecto con `pipenv install` y -eliminar las que no te gusten con `pipenv uninstall`. Además, dispones de -muchas opciones adicionales que te animo que ojees ejecutando `pipenv --help`. - - -### Usar IDLE desde un entorno virtual - -Si utilizas entornos virtuales deberás preparar IDLE para verlos y que su REPL -y su intérprete encuentren los paquetes del entorno virtual. - -Puedes lanzar IDLE desde el entorno virtual usando el siguiente truco en la -shell de sistema: - -``` bash -pipenv run python -m idlelib.idle -``` - -Recordando el capítulo anterior y lo descrito en este, ese comando ejecuta -`python -m idlelib.idle` en el entorno virtual en el que te encuentres. El -comando, por su parte le pide a python que ejecute el módulo `idle` del paquete -`idlelib`, que contiene el propio programa IDLE. - -Si trabajas con otros editores integrados de código tendrás que aprender a -hacer que sus intérpretes busquen en el entorno virtual actual, pero casi todos -los editores actuales soportarán esta opción de una forma u otra. - -## Otras herramientas - -La realidad es que las herramientas propuestas no son las únicas que existen -para estas tareas. Históricamente, python ha tenido otras herramientas con -comportamientos similares a `pip`, como `easy_install` y otras con parecidas a -`setuptools` como `distutils` y otras que asemejan a `pipenv`. Este capítulo -trata únicamente las herramientas que se consideran las recomendadas en el -momento en el que se está escribiendo[^recommended-tools], aunque los conceptos -descritos son extrapolables, no sólo a otras herramientas sino también a otros -lenguajes y entornos. - -[^recommended-tools]: Para un listado más extenso, visitar: -<https://packaging.python.org/guides/tool-recommendations/> - - -## Lo que has aprendido - -En este capítulo has aprendido lo necesario sobre las herramientas que rodean -a python y su uso. De este modo, no te vas a sentir perdido en un maremágnum de -nombres extraños y comandos cuando trabajes en proyectos que ya las usan. - -Más que convertirte en un experto de cómo trabajar con estas herramientas, cosa -que te dará la práctica, este episodio te ha dado las referencias que necesitas -para ir creciendo por tu cuenta en su uso, explicándote el interés de cada una -de ellas. - -Te encontrarás, probablemente, proyectos que utilicen las herramientas de un -modo peculiar, usen otras herramientas o hasta proyectos que no las usen -correctamente. Este es un mundo complejo, y la historia de python no facilita -la elección. Durante mucho tiempo la comunidad creó nuevas herramientas -para suplir las carencias de otras y el ecosistema se complicó. A pesar de que -hoy en día se recomiende el uso de unas herramientas sobre otras, no siempre -fue así y muchos proyectos se han quedado obsoletos en este sentido. - -Para evitar atarte a las herramientas que aquí se muestran, se ha preferido -darte una ligera noción, haciéndote recordar que las herramientas no son -realmente lo importante, sino el conocimiento subyacente. - -Con la herramienta principal que has conocido en este apartado, `pipenv`, -podrás instalar los paquetes que quieras de forma aislada y jugar con ellos -todo lo que desees sin manchar tu sistema por lo que podrás adentrarte en los -próximos capítulos sin miedo a estropear tu pulcra configuración inicial. diff --git a/src/08_stdlib.md b/src/08_stdlib.md deleted file mode 100644 index fbaa974..0000000 --- a/src/08_stdlib.md +++ /dev/null @@ -1,372 +0,0 @@ -# La librería estándar - -La librería estándar se refiere a todas las utilidades que un lenguaje de -programación trae consigo. Los lenguajes de programación, a parte de aportar la -propia funcionalidad del lenguaje en sí mismo que simplemente sería la -ejecución del código fuente que se le indique, suelen incluir funcionalidades -que no están necesariamente relacionadas con ese proceso. - -El motivo de esto es facilitar el uso del lenguaje para las labores más -comunes, incluyendo el código necesario para realizarlas sin necesitar añadidos -externos. - -En lenguajes de programación de dominio específico la librería estándar suele -contemplar muchos casos de ese dominio concreto. Por ejemplo, el lenguaje de -programación Julia, aunque esté diseñado con el objetivo de ser un lenguaje -válido para cualquier uso, su área de trabajo se centra en el entorno -científico. Es por eso que incluye en su librería estándar paquetes de álgebra -lineal, estadística y otros. - -Existen muchas diferentes aproximaciones a la librería estándar. Algunos -lenguajes las mantienen extremadamente reducidas con el fin de que la -implementación del lenguaje sea más liviana, con la contrapartida de forzar a -quien los use a tener que utilizar herramientas externas o a desarrollarlas. - -Python es un lenguaje de propósito general con una extensísima librería -estándar. Esto dificulta la selección de apartados a mostrar en este capítulo, -pero facilita la programación de cualquier aplicación en éste lenguaje. - -Los paquetes estándar de python facilitan la lectura de infinidad de tipos de -fichero, la conversión y el tratamiento de datos, el acceso a la red, la -ejecución concurrente, etc. por lo que librería estándar es más que suficiente -para muchas aplicaciones y no se requiere añadir módulos externos. - -Conocer la librería estándar y ser capaz de buscar en ella los paquetes que -necesites te facilitará mucho la tarea, evitando, por un lado, que dediques -tiempo a desarrollar funcionalidades que el propio lenguaje ya aporta y, por -otro, que instales paquetes externos que no necesitas. - -Todos los apartados aquí listados están extremadamente bien documentados en la -página oficial de la documentación de python y en la propia ayuda. Eso sí, -tendrás que importarlos para poder leer la ayuda, pero ya sabes cómo se hace. - -A continuación se recogen los módulos más interesantes, aunque en función del -proyecto puede que necesites algún otro. Puedes acceder al listado completo en -la página oficial de la documentación[^stdlibdoc]. - -[^stdlibdoc]: <https://docs.python.org/3/library/index.html#library-index> - - -## 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 "<stdin>", line 1, in \ -<module>\nNameError: name \'aasda\' is not defined\n' -``` - - -### Argumentos de entrada: `sys.argv` - -Es posible añadir argumentos de entrada a la ejecución de los programas. Piensa -en el propio programa llamado python, al ejecutarlo en la shell de sistema se -le pueden mandar diferentes opciones que, por ejemplo, digan qué módulo debe -ejecutar: - -``` bash -python test.py -``` - -Para la shell de sistema, todo lo que se escriba después de la primera palabra -es considerado un argumento de entrada. - -Cuando se ejecutan nuestros programas escritos en python, es posible también -enviarles argumentos de entrada: - -``` bash -python test.py argumento1 argumento2 ... -``` - -Lo que el python reciba después del nombre del módulo a ejecutar lo considerará -argumentos de entrada de nuestro módulo y nos lo ordenará y dejará disponible -en la variable `sys.argv`, una lista de todos los argumentos de entrada. - -Si muestras el contenido de la variable en la REPL, te responderá `['']` ya que -la REPL se ejecuta sin argumentos. Sin embargo, si creas un módulo y le añades -este contenido: - -``` python -import sys -print(sys.argv) -``` - -Verás que se imprime una lista con el nombre del archivo en su primer elemento. - -Si ejecutas el módulo desde la shell de sistema añadiéndole argumentos de -entrada: - -``` bash -python modulo.py arg1 arg2 arg3 -``` - -La lista `sys.argv` recibirá todos, siempre a modo de string. - -Los argumentos de entrada son extremadamente interesantes para permitir que los -programas sean configurables por quien los use sin necesidad de tener que -editar el código fuente, cosa que nunca debería ser necesaria a menos que -exista un error en éste o quiera añadirse una funcionalidad. - -## Procesamiento de argumentos de entrada: `argparse` - -Como la variable `sys.argv` entrega los argumentos de entrada tal y como los -recibe y no comprueba si son coherentes, python dispone de una librería -adicional para estas labores. El módulo `argparse` permite definir qué tipo de -argumentos de entrada y opciones tiene tu programa y qué reglas deben seguir. -Además, facilita la creación de ayudas como la que se muestra cuando ejecutas -`python -h` o `pip -h` en la shell de tu sistema. - -Es una librería bastante compleja con infinidad de opciones que es mejor que -leas en la propia documentación cuando necesites utilizarla. - -## Expresiones regulares: `re` - -Las expresiones regulares (*regular expression*, también conocidas como -*regexp* y *regex*) son secuencias de caracteres que describen un patrón de -búsqueda. - -En python se soportan mediante el módulo `re` de la librería estándar. - -## Matemáticas y estadística: `math` y `statistics` - -El módulo `math` soporta gran cantidad de operaciones matemáticas avanzadas -para coma flotante. En él puedes encontrar logaritmos, raíces, etc. - -El módulo `statistics` soporta estadística básica como medias, medianas, -desviación típica, etc. - -Ambos módulos tienen mucho interés ya que python se usa extensivamente en el -análisis de datos. Aunque tiene librerías de terceros mucho más adecuadas para -esta labor, para proyectos pequeños puede que sea suficiente con estos módulos. - -## Protocolos de internet: `urllib` - -`urllib` es un conjunto de paquetes que permiten seguir URLs o enlaces, -principalmente para HTTP y su versión segura HTTPs. Soporta cookies, -redirecciones, autenticación básica, etc. - -El siguiente ejemplo te muestra cómo descargar el primer boceto del estándar -del protocolo HTTP de la página web del IETF. Recordando los apartados previos, -fíjate en el uso de la sentencia `with` y en el uso de los corchetes para -obtener un número limitado de caracteres de la recién decodificada respuesta. - -``` python ->>> from urllib.request import urlopen ->>> with urlopen("https://tools.ietf.org/rfc/rfc2068.txt") as resp: -... print( resp.read().decode("utf-8")[:1750] ) -... -' - - - - - -Network Working Group R. Fielding -Request for Comments: 2068 UC Irvine -Category: Standards Track J. Gettys - J. Mogul - DEC - H. Frystyk - T. Berners-Lee - MIT/LCS - January 1997 - - - Hypertext Transfer Protocol -- HTTP/1.1 - -Status of this Memo - - This document specifies an Internet standards track protocol for the - Internet community, and requests discussion and suggestions for - improvements. Please refer to the current edition of the "Internet - Official Protocol Standards" (STD 1) for the standardization state - and status of this protocol. Distribution of this memo is unlimited. - -Abstract - - The Hypertext Transfer Protocol (HTTP) is an application-level - protocol for distributed, collaborative, hypermedia information - systems. It is a generic, stateless, object-oriented protocol which - can be used for many tasks, such as name servers and distributed - object management systems, through extension of its request methods. - A feature of HTTP is the typing and negotiation of data - representation, allowing systems to be built independently of the - data being transferred. - - HTTP has been in use by the World-Wide Web global information - initiative since 1990. This specification defines the protocol - referred to as "HTTP/1.1". - -' ->>> -``` - -## Fechas y horas: `datetime` - -`datetime`, que ya ha aparecido anteriormente, es un módulo para gestión de -fecha y hora. `datetime` soporta gran cantidad de operaciones y tipos de dato. -Entre ellos los `timedeltas`, diferencias temporales que te permiten sumar y -restar fechas con facilidad con los operadores habituales. - -Con las facilidades de `datetime`, es poco probable que necesites importar una -librería de gestión de fecha y hora independiente. - -## Procesamiento de ficheros: `json` y `sqlite3` - -Python habilita gran cantidad de procesadores de formatos de fichero, los dos -que se lista en este apartado tienen especial interés. - -El primer módulo, `json`, sirve para manipular datos en formato JSON. Sus dos -funciones principales `dumps` y `loads` vuelcan o cargan datos de JSON a -diccionarios o listas en una sola orden debido que la propia estructura de JSON -está formada por parejas clave valor o listas de valores ordenadas por índices. -Esta es la razón por la que en muchas ocasiones se utilizan ficheros de formato -JSON para intercambiar información entre aplicaciones de python: su lectura y -escritura es extremadamente sencilla. - -El segundo módulo, `sqlite3`, facilita el acceso a ficheros en formato SQLite3, -un formato binario con una interfaz de acceso que permite consultas SQL. El -módulo `sqlite3` es capaz de convertir las tablas que SQLite3 retorna a -estructuras de python de forma transparente y cómoda por lo que es un aliado -interesante para aplicaciones que requieren una base de datos pequeña y -resiliente. - -## Aritmética de coma flotante decimal: `decimal` - -En el apartado sobre datos tratamos la complejidad de los números de coma -flotante y que su representación binaria puede dar lugar a problemas. Este -módulo de aritmética decimal aporta una solución rigurosa a este problema con -el fin de facilitar el uso de python en entornos que requieren precisión -estricta que incluso puede requerir cumplir con normativas, como puede ser la -banca. - -La documentación del módulo muestra un par de ejemplos muy interesantes usando -la clase `Decimal` aportada por este. Se adjuntan a continuación para que los -estudies y los disecciones en busca de las diferencias con el uso de los -números de coma flotante normales: - -``` python ->>> from decimal import * ->>> round(Decimal('0.70') * Decimal('1.05'), 2) -Decimal('0.74') ->>> round(.70 * 1.05, 2) -0.73 - ->>> Decimal('1.00') % Decimal('.10') -Decimal('0.00') ->>> 1.00 % 0.10 -0.09999999999999995 - ->>> sum([Decimal('0.1')]*10) == Decimal('1.0') -True ->>> sum([0.1]*10) == 1.0 -False -``` - -## Lo que has aprendido - -Más que aprender, en este apartado has sobrevolado la librería estándar de -python desde la distancia y te has parado a observar qué problemas comunes ya -están resueltos en python. - -La realidad es que la librería estándar es mucho más extensa que lo listado en -este apartado, pero aquí se han mencionado los módulos que más frecuentemente -se suelen usar en entornos normales y algunos otros que tienen interés a la -hora de afianzar conocimiento que has obtenido en capítulos previos, como es el -caso del módulo `decimal`. - -Este capítulo empieza a mostrarte el interés de programar en python y las -posibilidades que tienes con él, únicamente con la librería estándar. En el -próximo verás la cantidad de funcionalidad adicional que le puedes añadir con -un par de librerías escritas por terceros. - -Sirva también este capítulo como recordatorio de lo extensa que es la librería -estándar de python. En el futuro, cuando tengas intención de buscar una -librería para resolver un problema que te despista de trabajar en la -funcionalidad principal de tu aplicación, empieza por la librería estándar. diff --git a/src/09_extralib.md b/src/09_extralib.md deleted file mode 100644 index 65ed418..0000000 --- a/src/09_extralib.md +++ /dev/null @@ -1,162 +0,0 @@ -# Librerías útiles - -Ahora que ya sabes cómo instalar librerías y que has visto que muchas -funcionalidades están contenidas en la librería estándar de python es un buen -momento para que visites varios proyectos que aportan recursos muy interesantes -a la hora de resolver problemas. Debido al carácter de uso general de python, -estas librerías aportan facilidades muy diversas. El criterio para escogerlas -parte de la experiencia personal del autor de este documento y añade algunas -librerías y herramientas que pueden ser interesantes debido a su amplio uso en -la industria. - -## Librerías científicas: ecosistema SciPy - -SciPy es un ecosistema de librerías de cálculo que tiene como objetivo -facilitar la tarea de ingenieros, científicos y matemáticos en sus -respectivos trabajos. Puedes considerarlo como un **Matlab** para python -separado por componentes independientes. - -Además de ser el nombre del ecosistema, comparte nombre con una de las -librerías fundamentales de éste. El ecosistema está formado por varias -librerías, entre ellas se encuentran: - -- Numpy: un paquete de computación científica para python. Soporta matrices - multidimensionales, funciones sofisticadas y álgebra lineal, entre otras. - -- SciPy: librería basada en Numpy que aporta rutinas numéricas eficientes para - interpolación, optimización, algebra lineal, estadística y otros. - -- SymPy: una librería de matemática simbólica para python que complementa el - resto de librerías del ecosistema, ya que casi todas están orientadas al - análisis numérico. - -- Matplotlib: librería para representar gráficos y figuras científicas en 2D. - -- Pandas: aporta unas estructuras de datos muy potentes basadas en tablas, su - objetivo es reforzar a python a la hora de tratar datos. El área de esta - librería es el del análisis de datos, pero puede combinarse con otras áreas - de estudio como la econometría (ver el proyecto statsmodels). Esta librería - dispone de un ecosistema muy poderoso a su alrededor debido al auge del - análisis de datos en la industria del software. Muchas librerías comparten la - interfaz de Pandas por lo que tener nociones de su comportamiento abre muchas - puertas en el sector del análisis de datos. Al igual que ocurre en SciPy, Las - estructuras de datos de Pandas también se basan en Numpy. - -- IPython: más que una librería, IPython es una herramienta. Se trata de una - REPL interactiva (su propio nombre viene de *Interactive Python*) que añade - diversas funcionalidades sobre la REPL de python habitual: histórico de - comandos, sugerencias, comandos mágicos (*magic*) que permiten alterar el - comportamiento de la propia interfaz y un larguísimo etcétera. IPython, - además, sirve como núcleo de los cuadernos Jupyter que integran una shell de - python con visualización de tablas y gráficos para generar documentos estilo - *literate programming*. - -## Machine Learning: ScikitLearn - -ScikitLearn es una librería de machine learning muy potente, implementa gran -cantidad de algoritmos y tiene una documentación extensa, sencilla y educativa. - -Encaja a la perfección con el ecosistema SciPy, ya que se basa en NumPy, SciPy -y Matplotlib. - -## Peticiones web: Requests - -Requests es una librería alternativa al módulo `urllib` aportado por la -librería estándar de python. Se describe a sí misma como «HTTP para seres -humanos», sugiriendo que `urllib` no es cómoda de usar. - -Requests gestiona automáticamente las URL-s de las peticiones a partir de los -datos que se le entreguen, sigue redirecciones, almacena cookies, descomprime -automáticamente, decodifica las respuestas de forma automática, etc. En general -es una ayuda cuando no se quiere dedicar tiempo a controlar cada detalle de la -conexión de modo manual. - -## Manipulación de HTML: Beautifulsoup - -Beautifulsoup es una librería de procesado de HTML extremadamente potente. -Simplifica el acceso a campos de ficheros HTML con una sintaxis similar a los -objetos de python, permitiendo acceder a la estructura anidada mediante el uso -del punto en lugar de tener que lidiar con consultas extrañas o expresiones -regulares. Esta funcionalidad puede combinarse con selectores CSS4 para un -acceso mucho más cómodo. - -Muy usada en conjunción con Requests, para el desarrollo de aplicaciones de -web-scraping. Aunque, si se desea usar algo más avanzado, se recomienda usar el -framework Scrapy. - -## Tratamiento de imagen: Pillow - -Pillow es un fork de la librería PIL (*Python Imaging Library*) cuyo desarrollo -fue abandonado. En sus diversos submódulos, Pillow permite acceder a datos de -imágenes, aplicarles transformaciones y filtros, dibujar sobre ellas, cambiar -su formato, etc. - -## Desarrollo web: Django, Flask - -Existen infinidad de frameworks y herramientas de desarrollo web en python. Así -como en el caso del análisis de datos la industria ha convergido en una -herramienta principal, Pandas, en el caso del desarrollo web hay muchas -opciones donde elegir. - -Los dos frameworks web más usados son Django y Flask, siendo el segundo menos -común que el primero pero digno de mención en conjunción con el otro por varias -razones. - -La primera es la diferencia en la filosofía: así como Django decide con qué -herramientas se debe trabajar, Flask, que se define a sí mismo como -microframework, deja en manos de quien lo usa la elección de qué herramientas -desea aplicarle. Cada una de las dos filosofías tiene ventajas y desventajas y -es tu responsabilidad elegir las que más te convengan para tu proyecto. - -La segunda razón para mencionar Flask es que su código fuente es uno de los más -interesantes a la hora de usar como referencia de cómo se debe programar en -python. Define su propia norma de estilo de programación, basada en la -sugerencia de estilo de python[^pep8] y su desarrollo es extremadamente -elegante. - -Django, por su parte, ha sido muy influyente y muchas de sus decisiones de -diseño han sido adoptadas por otros frameworks, tanto en python como en otros -lenguajes. Lo que sugiere que está extremadamente bien diseñado. - -A pesar de las diferencias filosóficas, existen muchas similitudes entre ambos -proyectos por lo que aprender a usar uno de ellos facilita mucho el uso del -otro y no es aprendizaje perdido. No tengas miedo en lanzarte a uno. - -[^pep8]: <https://www.python.org/dev/peps/pep-0008/> - - -## Protocolos de red: Twisted - -Twisted es motor de red asíncrono para python. Sobre él se han escrito -diferentes librerías para gestión de protocolos de Internet como DNS, via -Twisted-Names, IMAP y POP3, via Twisted-Mail, HTTP, via Twisted-Web, IRC y -XMPP, via Twisted-Words, etc. - -El diseño asíncrono del motor facilita sobremanera las comunicaciones -eficientes. Programar código asíncrono en python es relativamente sencillo, -pero ha preferido dejarse fuera de este documento por diversas razones. Te -animo a indagar en esta libreria para valorar el interés del código asíncrono. - -## Interfaces gráficas: PyQt, PyGTK, wxPython, PySimpleGUI - -A pesar de que python dispone de un módulo en su librería estándar para tratar -interfaces gráficas llamado TKinter, es recomendable utilizar librerías más -avanzadas para esto. TKinter es una interfaz a la herramienta Tk del lenguaje -de programación Tcl y acompaña a python desde hace años. - -En programas simples TKinter es más que suficiente (IDLE, por ejemplo, está -desarrollado con TKinter) pero a medida que se necesita complejidad o capacidad -del usuario para configurar detalles de su sistema suele quedarse pequeño. - -Para programas complejos se recomienda usar otro tipo de librerías más -avanzadas como PyQt, PyGTK o wxPython, todas ellas interfaces a librerías -escritas en C/C++ llamadas Qt, GTK y wxWidgets respectivamente. Estas librerías -aportan una visualización más elegante, en algunos casos usando widgets nativos -del sistema operativo en el que funcionan. - -Debido a la complejidad del ecosistema nace el proyecto PySimpleGUI, que -pretende aunar las diferentes herramientas en una sola, sirviendo de interfaz a -cualquiera de las anteriores y alguna otra. Además, el proyecto aporta gran -cantidad de ejemplos de uso. PySimpleGUI aún está en desarrollo y el soporte de -algunos de los motores no está terminado, pero es una fuente interesante de -información y recursos. diff --git a/src/10_closing_words.md b/src/10_closing_words.md deleted file mode 100644 index 404a58d..0000000 --- a/src/10_closing_words.md +++ /dev/null @@ -1,123 +0,0 @@ -# Lo que has aprendido - -Rescatando la definición de la introducción: - -> Python es un lenguaje de programación de alto nivel orientado al uso general. -> Fue creado por Guido Van Rossum y publicado en 1991. La filosofía de python -> hace hincapié en la limpieza y la legibilidad del código fuente con una -> sintaxis que facilita expresar conceptos en menos líneas de código que en -> otros lenguajes. -> -> Python es un lenguaje de tipado dinámico y gestión de memoria automática. -> Soporta múltiples paradigmas de programación, incluyendo la programación -> orientada a objetos, imperativa, funcional y procedural e incluye una extensa -> librería estándar. - -Ahora sí que estás en condición de entenderla no sólo para python sino para -cualquier otro lenguaje que se te presente de este modo. Ahora tienes la -habilidad de poder comprender de un vistazo qué te aporta el lenguaje que -tienes delante únicamente leyendo su descripción. - -Desgranándola poco a poco, has conocido la sintaxis de python en bastante -detalle y has visto cómo hace uso de las sangrías para delimitar bloques de -código, cosa que otros lenguajes hacen con llaves (`{}`) u otros símbolos. - -La facilidad de expresar conceptos complejos en pocas líneas de código puede -verse en las *list comprehensions*, la sentencia `with` y muchas otras -estructuras del sistema. Python es un lenguaje elegante y directo, similar al -lenguaje natural. - -El tipado dinámico trata lo que estudiaste en el apartado sobre datos, donde se -te cuenta que las referencias pueden cambiar de tipo en cualquier momento ya -que son los propios valores los que son capaces de recordar qué tipo tienen. - -La gestión de memoria automática también se presenta en el mismo apartado, -contándote que python hace uso de un *garbage collector* o recolector de basura -para limpiar de la memoria los datos que ya no usa. - -Los diferentes paradigmas de programación no se han tratado de forma explícita -en este documento, más allá de la programación orientada a objetos, que inunda -python por completo. Sin embargo, el apartado sobre funciones adelanta varios -de los conceptos básicos del paradigma de programación funcional: que las -funciones sean ciudadanos de primera clase (*first-class citizens*), el uso de -funciones anónimas (*lambda*) y las *closures*. - -Los paradigmas procedural e imperativo son la base para los dos -paradigmas de los que hemos hablado. La programación imperativa implica que se -programa mediante órdenes (el caso de python, recuerda) en lugar de -declaraciones (como puede ser la programación lógica, donde se muestran un -conjunto de normas que el programa debe cumplir). La programación procedural es -un paradigma cuyo fundamento es el uso de bloques de código y su *scope*, -creando funciones, estructuras de datos y variables aunque, a diferencia de la -programación funcional, en la programación procedural no es necesario que las -funciones sean ciudadanos de primera clase y pueden tener restricciones. - -Estos dos últimos paradigmas, en realidad, se soportan casi por accidente al -habilitar los dos anteriores. - -En muchas ocasiones, te encontrarás escribiendo pequeñas herramientas y no -necesitarás mucho más que usar las estructuras básicas de python y varias -funciones para alterarlas, por lo que estarás pensando de forma procedural -accidentalmente. - -Los paradigmas no son más que patrones de diseño que nos permiten clasificar -los lenguajes y sus filosofías, pero son muy interesantes a la hora de diseñar -nuestras aplicaciones. - -Además de todo esto, has tenido ocasión de conocer de forma superficial la -librería estándar del lenguaje y un conjunto de librerías adicionales que te -aportan los puntos de los que la librería estándar carece. Ahora sabes instalar -dependencias y usarlas en entornos virtuales (*virtual environments*) para -mantener limpia tu instalación. - -A parte de lo mencionado en la definición del lenguaje, has aprendido a -ejecutar, cargar y distribuir módulos de python, algo primordial si pretendes -crear paquetes o nuevas librerías y usar las de terceros. - -Con todo esto, tienes una visión general pero bastante detallada a nivel -técnico de lo que python aporta y cómo. Lo que necesitas para compensarla es -trabajar con él, acostumbrarte a su ecosistema y leer mucho código de buena -calidad para acostumbrarte a seguir las convenciones y recetas habituales. - - -## El código pythónico - -A lo largo del documento se tratan temas que puede que no te esperases -encontrar al leer sobre programación, ya que tu interés principal es resolver -tus problemas de forma efectiva y construir aplicaciones. Hacer robots que te -hagan la vida más fácil, en definitiva. - -Sin embargo, quien se dedica a la programación tiene una vida muy ligada a la -vida de quien se dedica a la filosofía o al diseño y es por eso que esas dos -disciplinas aparecen de vez en cuando en cualquier conversación un poco seria -sobre el trabajo con software. - -Las tres disciplinas, en primer lugar, ocurren en la mente de las personas y no -en sus manos. Es por eso que los patrones mentales y los modos de pensamiento -son parte fundamental de ellas. Ninguno de ellos son trabajos para los que se -pueda entrenar una memoria muscular. Es necesario pensar. Y es necesario pensar -de forma consciente y premeditada. - -Ver cómo desarrollan otras personas su actividad es valioso para realizar tu -tarea con elegancia. - -Otro detalle que has debido de observar, sobre todo porque acaba de aparecer, -es la *elegancia*. La elegancia es, hasta cierto punto, subjetiva y depende del -gusto de quien la mira. Sin embargo, esto sólo es así hasta cierto punto, la -realidad es que alguien puede considerar algo elegante y aun así no gustarle. -Python es un ejemplo de algo así. Guste o no guste, python es un lenguaje de -programación elegante, cuya elegancia forma parte primordial de la filosofía -del lenguaje. - -El autor de este documento, por ejemplo, no es un entusiasta de python, pero a -lo largo de la travesía de escribir este documento ha podido reencontrarse, una -vez más, con su elegancia. - -El concepto del *código pythónico* (*pythonic code*) es un resultado de esto. -Cuando se habla de código pythónico, se habla de un código que sigue los -estándares de elegancia de python. Que es bonito, comprensible y claro. Un -código que la comunidad de desarrollo de python aprobaría. - -Cuando programes en python, trata de programar código pythónico, pues es esa la -verdadera razón por la que se creó el lenguaje y es la forma en la que el -lenguaje más fácil te lo va a poner. diff --git a/src/A_devtools.md b/src/A_devtools.md deleted file mode 100644 index e856373..0000000 --- a/src/A_devtools.md +++ /dev/null @@ -1,90 +0,0 @@ -# Anexo I: Herramientas de desarrollo {-} - -IDLE es una herramienta de desarrollo muy limitada, suficiente para seguir los -ejemplos que se recogen en este documento pero insuficiente para desarrollar -aplicaciones avanzadas. - -## Desarrollo de código fuente - -Existen gran cantidad de entornos de desarrollo (IDE) avanzados y editores que -pueden ser recomendables, aunque es cuestión de gusto personal decantarse por -uno de ellos o por otro. - -La diferencia entre un entorno de desarrollo integrado y un editor es la -siguiente: los entornos de desarrollo cumplen varias funciones adicionales, -como en el caso de IDLE, dar acceso a una REPL de python y la posibilidad de -analizar las variables en memoria. Los editores únicamente sirven para escribir -el código, aunque en muchos casos la línea que separa ambos conceptos es -bastante borrosa: existen editores con funcionalidades avanzadas y entornos -integrados muy sencillos que parecen un simple editor. Resumiendo, los entornos -integrados de desarrollo (IDE *integrated development environment*) tienen -editores entre sus herramientas. - -### Entornos de desarrollo integrados - -Quien no utiliza entornos de desarrollo avanzados (una cuestión de gusto -personal), por lo que se le hace difícil recomendar alguno en particular. Sin -embargo, el wiki de python recoge una larguísima lista de editores y entornos -de desarrollo integrado interesantes[^ides]. - -[^ides]: <https://wiki.python.org/moin/PythonEditors> - -En el entorno del análisis de datos, la distribución *Anaconda* es muy usada. -Anaconda es más que un entorno de desarrollo integrado. Incluye un entorno de -desarrollo llamado Spyder, una shell propia, un gestor de paquetes y -dependencias propio llamado Conda, posibilidad de integración con el lenguaje -de programación R y gran cantidad de paquetes instalados por defecto. - -En otros entornos PyCharm es bastante común, aunque también son muy comunes los -entornos de desarrollo integrados pensados para otros lenguajes que han -comenzado a soportar python posteriormente como KDevelop, NetBeans y otros. - -Te recomiendo que, si usas un entorno de desarrollo integrado en otros -lenguajes, investigues si soporta python. De este modo no tendrás que aprender -una nueva herramienta. Al ser un lenguaje tan común, probablemente lo soporte. -Si no lo soporta prueba con un IDE que siga una filosofía similar al que uses. - -### Editores de código - -Quien te escribe usa Vim, un editor de texto muy antiguo con muchas -características que le hacen ser un editor muy eficiente. Existe gran variedad -de editores de código que recomendar: Emacs, gEdit, Kate, Sublime, Atom... Todo -dependerá de tus gustos personales. - -La ventaja principal de los editores de código es que conociendo uno en -profundidad es más que suficiente para cualquier lenguaje, ya que están -diseñados únicamente para escribir el contenido de tus programas, dejando las -peculiaridades de ejecución de cada lenguaje a parte. Esto les aporta una -ligereza difícilmente alcanzable por los IDEs. - -Sin embargo, esta virtud también es su mayor defecto. Al no integrar ninguna -herramienta adicional, es necesario trabajar todo manualmente. En el caso de -python, te fuerzan a usar una shell independiente y a interactuar con ella de -forma manual. Al principio puede ser tedioso, pero aprender a gestionar los -detalles manualmente es interesante ya que te permite obtener un gran -conocimiento del sistema y lenguaje en el que trabajas. - -## Herramientas de depuración - -El propio diseño de python permite que sea fácilmente depurable. La REPL -facilita que se pruebe la aplicación a medida que se va desarrollando, función -a función, para asegurar que su comportamiento es el correcto. Además, la -capacidad introspectiva del lenguaje es una buena herramienta, en conjunción -con la REPL para comprobar el comportamiento. - -Además de estas características, la instalación de python viene acompañada del -programa `pdb` (*Python Debugger*), un depurador de código similar al conocido -[GNU-Debugger](https://en.wikipedia.org/wiki/GNU_Debugger). Existen otros -depuradores de código más amigables que este, pero la realidad es que no suelen -ser necesarios. - -## Testeo de aplicaciones - -Hoy en día el testeo de software es muy común, en parte gracias al desarrollo -guiado por pruebas, o TDD (*Test Driven Development*). - -La librería estándar de python incluye un módulo para pruebas unitarias llamado -`unittest` en que la librería Nose, muy conocida y usada, se basa para -facilitar el trabajo de modo similar a lo que ocurre con Requests y `urllib`. - -Por supuesto, existen otras alternativas, pero estas son las principales. diff --git a/src/Z_license.md b/src/Z_license.md deleted file mode 100644 index 46d9792..0000000 --- a/src/Z_license.md +++ /dev/null @@ -1,337 +0,0 @@ -# Anexo II: Licencia CC BY-SA 4.0 {- #licencia-appendix} - -#### Creative Commons Atribución/Reconocimiento-CompartirIgual 4.0 Licencia Pública Internacional - -Al ejercer los Derechos Licenciados (definidos a continuación), Usted acepta y -acuerda estar obligado por los términos y condiciones de esta Licencia -Internacional Pública de Atribución/Reconocimiento-CompartirIgual 4.0 de -Creative Commons ("Licencia Pública"). En la medida en que esta Licencia -Pública pueda ser interpretada como un contrato, a Usted se le otorgan los -Derechos Licenciados en consideración a su aceptación de estos términos y -condiciones, y el Licenciante le concede a Usted tales derechos en -consideración a los beneficios que el Licenciante recibe por poner a -disposición el Material Licenciado bajo estos términos y condiciones. - -##### Sección 1 – Definiciones. - -a. **Material Adaptado** es aquel material protegido por Derechos de - Autor y Derechos Similares que se deriva o se crea en base al - Material Licenciado y en el cual el Material Licenciado se traduce, - altera, arregla, transforma o modifica de manera tal que dicho - resultado sea de aquellos que requieran autorización de acuerdo con - los Derechos de Autor y Derechos Similares que ostenta el - Licenciante. A los efectos de esta Licencia Pública, cuando el - Material Licenciado se trate de una obra musical, una interpretación - o una grabación sonora, la sincronización temporal de este material - con una imagen en movimiento siempre producirá Material Adaptado. - -b. **Licencia de adaptador** es aquella licencia que Usted aplica a Sus - Derechos de Autor y Derechos Similares en Sus contribuciones - consideradas como Material Adaptado de acuerdo con los términos y - condiciones de esta Licencia Pública. - -c. **Una Licencia Compatible con BY-SA** es aquella que aparece en la - lista disponible en - [creativecommons.org/compatiblelicenses](//creativecommons.org/compatiblelicenses), - aprobada por Creative Commons, como una licencia esencialmente - equivalente a esta Licencia Pública. -d. **Derechos de Autor y Derechos Similares** son todos aquellos - derechos estrechamente vinculados a los derechos de autor, - incluidos, de manera enunciativa y no taxativa, los derechos sobre - las interpretaciones, las emisiones, las grabaciones sonoras y los - Derechos "Sui Generis" sobre Bases de Datos, sin importar cómo estos - derechos se encuentren enunciados o categorizados. A los efectos de - esta Licencia Pública, los derechos especificados en las secciones - [2(b)(1)-(2)](#s2b) no se consideran Derechos de Autor y Derechos - Similares. -e. **Medidas Tecnológicas Efectivas** son aquellas medidas que, en - ausencia de la debida autorización, no pueden ser eludidas en virtud - de las leyes que cumplen las obligaciones del artículo 11 del - Tratado de la OMPI sobre Derecho de Autor adoptado el 20 de - diciembre de 1996, y/o acuerdos internacionales similares. -f. **Excepciones y Limitaciones** son el uso justo (fair use), el trato - justo (fair dealing) y/o cualquier otra excepción o limitación a los - Derechos de Autor y Derechos Similares que se apliquen al uso el - Material Licenciado. -g. **Elementos de la Licencia** son los atributos que figuran en el - nombre de la Licencia Pública de Creative Commons. Los Elementos de - la Licencia de esta Licencia Pública son Atribución/Reconocimiento y - CompartirIgual. -h. **Material Licenciado** es obra artística o literaria, base de datos - o cualquier otro material al cual el Licenciante aplicó esta - Licencia Pública. -i. **Derechos Licenciados** son derechos otorgados a Usted bajo los - términos y condiciones de esta Licencia Pública, los cuales se - limitan a todos los Derechos de Autor y Derechos Similares que - apliquen al uso del Material Licenciado y que el Licenciante tiene - potestad legal para licenciar. -j. **Licenciante** es el individuo(s) o la entidad(es) que concede - derechos bajo esta Licencia Pública. -k. **Compartir** significa proporcionar material al público por - cualquier medio o procedimiento que requiera permiso conforme a los - Derechos Licenciados, tales como la reproducción, exhibición - pública, presentación pública, distribución, difusión, comunicación - o importación, así como también su puesta a disposición, incluyendo - formas en que el público pueda acceder al material desde un lugar y - momento elegido individualmente por ellos. -l. **Derechos "Sui Generis" sobre Bases de Datos** son aquellos - derechos diferentes a los derechos de autor, resultantes de la - Directiva 96/9/EC del Parlamento Europeo y del Consejo, de 11 de - marzo de 1996 sobre la protección jurídica de las bases de datos, en - sus versiones modificadas y/o posteriores, así como otros derechos - esencialmente equivalentes en cualquier otra parte del mundo. -m. **Usted** es el individuo o la entidad que ejerce los Derechos - Licenciados en esta Licencia Pública. La palabra **Su** tiene un - significado equivalente. - -##### Sección 2 – Ámbito de Aplicación. - -a. **Otorgamiento de la licencia**. - 1. Sujeto a los términos y condiciones de esta Licencia Pública, el - Licenciante le otorga a Usted una licencia de carácter global, - gratuita, no transferible a terceros, no exclusiva e irrevocable - para ejercer los Derechos Licenciados sobre el Material - Licenciado para: - A. reproducir y Compartir el Material Licenciado, en su - totalidad o en parte; y - B. producir, reproducir y Compartir Material Adaptado. - - 2. [Excepciones y - Limitaciones]{style="text-decoration: underline;"}. Para evitar - cualquier duda, donde se apliquen Excepciones y Limitaciones al - uso del Material Licenciado, esta Licencia Pública no será - aplicable, y Usted no tendrá necesidad de cumplir con sus - términos y condiciones. - 3. [Vigencia]{style="text-decoration: underline;"}. La vigencia de - esta Licencia Pública está especificada en la sección - [6(a)](#s6a). - 4. [Medios y formatos; modificaciones técnicas - permitidas]{style="text-decoration: underline;"}. El Licenciante - le autoriza a Usted a ejercer los Derechos Licenciados en todos - los medios y formatos, actualmente conocidos o por crearse en el - futuro, y a realizar las modificaciones técnicas necesarias para - ello. El Licenciante renuncia y/o se compromete a no hacer valer - cualquier derecho o potestad para prohibirle a Usted realizar - las modificaciones técnicas necesarias para ejercer los Derechos - Licenciados, incluyendo las modificaciones técnicas necesarias - para eludir las Medidas Tecnológicas Efectivas. A los efectos de - esta Licencia Pública, la mera realización de modificaciones - autorizadas por esta sección [2(a)(4)](#s2a4) nunca produce - Material Adaptado. - 5. [Receptores posteriores]{style="text-decoration: underline;"}. - A. [Oferta del Licenciante – Material - Licenciado]{style="text-decoration: underline;"}. Cada - receptor de Material Licenciado recibe automáticamente una - oferta del Licenciante para ejercer los Derechos Licenciados - bajo los términos y condiciones de esta Licencia Pública. - B. [Oferta adicional por parte del Licenciante – Material - Adaptado]{style="text-decoration: underline;"}. Cada - receptor del Material Adaptado por Usted recibe - automáticamente una oferta del Licenciante para ejercer los - Derechos Licenciados en el Material Adaptado bajo las - condiciones de la Licencia del Adaptador que Usted aplique. - C. [Sin restricciones a receptores - posteriores]{style="text-decoration: underline;"}. Usted no - puede ofrecer o imponer ningún término ni condición - diferente o adicional, ni puede aplicar ninguna Medida - Tecnológica Efectiva al Material Licenciado si haciéndolo - restringe el ejercicio de los Derechos Licenciados a - cualquier receptor del Material Licenciado. - 6. [Sin endoso]{style="text-decoration: underline;"}. Nada de lo - contenido en esta Licencia Pública constituye o puede - interpretarse como un permiso para afirmar o implicar que Usted, - o que Su uso del Material Licenciado, está conectado, - patrocinado, respaldado o reconocido con estatus oficial por el - Licenciante u otros designados para recibir la - Atribución/Reconocimiento según lo dispuesto en la sección - [3(a)(1)(A)(i)](#s3a1Ai). - -b. **Otros derechos**. - - 1. Los derechos morales, tales como el derecho a la integridad, no - están comprendidos bajo esta Licencia Pública ni tampoco los - derechos de publicidad y privacidad ni otros derechos personales - similares. Sin embargo, en la medida de lo posible, el - Licenciante renuncia y/o se compromete a no hacer valer ninguno - de estos derechos que ostenta como Licenciante, limitándose a lo - necesario para que Usted pueda ejercer los Derechos Licenciados, - pero no de otra manera. - 2. Los derechos de patentes y marcas no son objeto de esta Licencia - Pública. - 3. En la medida de lo posible, el Licenciante renuncia al derecho - de cobrarle regalías a Usted por el ejercicio de los Derechos - Licenciados, ya sea directamente o a través de una entidad de - gestión colectiva bajo cualquier esquema de licenciamiento - voluntario, renunciable o no renunciable. En todos los demás - casos, el Licenciante se reserva expresamente cualquier derecho - de cobrar esas regalías. - -##### Sección 3 – Condiciones de la Licencia. - -Su ejercicio de los Derechos Licenciados está expresamente sujeto a las -condiciones siguientes. - -a. **Atribución/Reconocimiento**. - - 1. Si Usted comparte el Material Licenciado (incluyendo en forma - modificada), Usted debe: - - A. Conservar lo siguiente si es facilitado por el Licenciante - con el Material Licenciado: - i. identificación del creador o los creadores del Material - Licenciado y de cualquier otra persona designada para - recibir Atribución/Reconocimiento, de cualquier manera - razonable solicitada por el Licenciante (incluyendo por - seudónimo si este ha sido designado); - ii. un aviso sobre derecho de autor; - iii. un aviso que se refiera a esta Licencia Pública; - iv. un aviso que se refiera a la limitación de garantías; - v. un URI o un hipervínculo al Material Licenciado en la - medida razonablemente posible; - - B. Indicar si Usted modificó el Material Licenciado y conservar - una indicación de las modificaciones anteriores; e - C. Indicar que el Material Licenciado está bajo esta Licencia - Pública, e incluir el texto, el URI o el hipervínculo a esta - Licencia Pública. - - 2. Usted puede satisfacer las condiciones de la sección - [3(a)(1)](#s3a1) de cualquier forma razonable según el medio, - las maneras y el contexto en los cuales Usted Comparta el - Material Licenciado. Por ejemplo, puede ser razonable satisfacer - las condiciones facilitando un URI o un hipervínculo a un - recurso que incluya la información requerida. - 3. Bajo requerimiento del Licenciante, Usted debe eliminar - cualquier información requerida por la sección - [3(a)(1)(A)](#s3a1A) en la medida razonablemente posible. - -b. **CompartirIgual**. - - Además de las condiciones de la sección [3(a)](#s3a), si Usted - Comparte Material Adaptado producido por Usted, también aplican las - condiciones siguientes. - - 1. La Licencia del Adaptador que Usted aplique debe ser una - licencia de Creative Commons con los mismos Elementos de la - Licencia, ya sea de esta versión o una posterior, o una Licencia - Compatible con la BY-SA. - 2. Usted debe incluir el texto, el URI o el hipervínculo a la - Licencia del Adaptador que aplique. Usted puede satisfacer esta - condición de cualquier forma razonable según el medio, las - maneras y el contexto en los cuales Usted Comparta el Material - Adaptado. - 3. Usted no puede ofrecer o imponer ningún término o condición - adicional o diferente, o aplicar ninguna Medida Tecnológica - Efectiva al Material Adaptado que restrinja el ejercicio de los - derechos concedidos en virtud de la Licencia de Adaptador que - Usted aplique. - -##### Sección 4 – Derechos "Sui Generis" sobre Bases de Datos. - -Cuando los Derechos Licenciados incluyan Derechos "Sui Generis" sobre -Bases de Datos que apliquen a Su uso del Material Licenciado: - -a. para evitar cualquier duda, la sección [2(a)(1)](#s2a1) le concede a - Usted el derecho a extraer, reutilizar, reproducir y Compartir todo - o una parte sustancial de los contenidos de la base de datos; -b. si Usted incluye la totalidad o una parte sustancial del contenido - de una base de datos en otra sobre la cual Usted ostenta Derecho - "Sui Generis" sobre Bases de Datos, entonces ella (pero no sus - contenidos individuales) se entenderá como Material Adaptado para - efectos de la sección [3(b)](#s3b); y -c. Usted debe cumplir con las condiciones de la sección [3(a)](#s3a) si - Usted Comparte la totalidad o una parte sustancial de los contenidos - de la base de datos. - -Para evitar dudas, esta sección [4](#s4) complementa y no sustituye Sus -obligaciones bajo esta Licencia Pública cuando los Derechos Licenciados -incluyen otros Derechos de Autor y Derechos Similares. - -##### Sección 5 – Exención de Garantías y Limitación de Responsabilidad. - -a. **Salvo que el Licenciante se haya comprometido mediante un acuerdo - por separado, en la medida de lo posible el Licenciante ofrece el - Material Licenciado tal como es y tal como está disponible y no se - hace responsable ni ofrece garantías de ningún tipo respecto al - Material Licenciado, ya sea de manera expresa, implícita, legal u - otra. Esto incluye, de manera no taxativa, las garantías de título, - comerciabilidad, idoneidad para un propósito en particular, no - infracción, ausencia de vicios ocultos u otros defectos, la - exactitud, la presencia o la ausencia de errores, sean o no - conocidos o detectables. Cuando no se permita, totalmente o en - parte, la declaración de ausencia de garantías, a Usted puede no - aplicársele esta exclusión.** -b. **En la medida de lo posible, en ningún caso el Licenciante será - responsable ante Usted por ninguna teoría legal (incluyendo, de - manera no taxativa, la negligencia) o de otra manera por cualquier - pérdida, coste, gasto o daño directo, especial, indirecto, - incidental, consecuente, punitivo, ejemplar u otro que surja de esta - Licencia Pública o del uso del Material Licenciado, incluso cuando - el Licenciante haya sido advertido de la posibilidad de tales - pérdidas, costes, gastos o daños. Cuando no se permita la limitación - de responsabilidad, ya sea totalmente o en parte, a Usted puede no - aplicársele esta limitación.** -c. La renuncia de garantías y la limitación de responsabilidad - descritas anteriormente deberán ser interpretadas, en la medida de - lo posible, como lo más próximo a una exención y renuncia absoluta a - todo tipo de responsabilidad. - -##### Sección 6 – Vigencia y Terminación. - -a. Esta Licencia Pública tiene una vigencia de aplicación igual al - plazo de protección de los Derechos de Autor y Derechos Similares - licenciados aquí. Sin embargo, si Usted incumple las condiciones de - esta Licencia Pública, los derechos que se le conceden mediante esta - Licencia Pública terminan automáticamente. -b. En aquellos casos en que Su derecho a utilizar el Material - Licenciado se haya terminado conforme a la sección [6(a)](#s6a), - este será restablecido: - - 1. automáticamente a partir de la fecha en que la violación sea - subsanada, siempre y cuando esta se subsane dentro de los 30 - días siguientes a partir de Su descubrimiento de la violación; o - 2. tras el restablecimiento expreso por parte del Licenciante. - - Para evitar dudas, esta sección [6(b)](#s6b) no afecta ningún - derecho que pueda tener el Licenciante a buscar resarcimiento por - Sus violaciones de esta Licencia Pública. - -c. Para evitar dudas, el Licenciante también puede ofrecer el Material - Licenciado bajo términos o condiciones diferentes, o dejar de - distribuir el Material Licenciado en cualquier momento; sin embargo, - hacer esto no pondrá fin a esta Licencia Pública. -d. Las secciones [1](#s1), [5](#s5), [6](#s6), [7](#s7), y [8](#s8) - permanecerán vigentes a la terminación de esta Licencia Pública. - -##### Sección 7 – Otros Términos y Condiciones. - -a. El Licenciante no estará obligado por ningún término o condición - adicional o diferente que Usted le comunique a menos que se acuerde - expresamente. -b. Cualquier arreglo, convenio o acuerdo en relación con el Material - Licenciado que no se indique en este documento se considera separado - e independiente de los términos y condiciones de esta Licencia - Pública. - -##### Sección 8 – Interpretación. - -a. Para evitar dudas, esta Licencia Pública no es ni deberá - interpretarse como una reducción, limitación, restricción, o una - imposición de condiciones al uso de Material Licenciado que - legalmente pueda realizarse sin permiso del titular, más allá de lo - contemplado en esta Licencia Pública. -b. En la medida de lo posible, si alguna disposición de esta Licencia - Pública se considera inaplicable, esta será automáticamente - modificada en la medida mínima necesaria para hacerla aplicable. Si - la disposición no puede ser reformada, deberá ser eliminada de esta - Licencia Pública sin afectar la exigibilidad de los términos y - condiciones restantes. -c. No se podrá renunciar a ningún término o condición de esta Licencia - Pública, ni se consentirá ningún incumplimiento, a menos que se - acuerde expresamente con el Licenciante. -d. Nada en esta Licencia Pública constituye ni puede ser interpretado - como una limitación o una renuncia a los privilegios e inmunidades - que aplican al Licenciante o a Usted, incluyendo aquellos surgidos a - partir de procesos legales de cualquier jurisdicción o autoridad. - - |