diff options
author | Ekaitz Zarraga <ekaitz@elenq.tech> | 2020-07-22 14:20:07 +0200 |
---|---|---|
committer | Ekaitz Zarraga <ekaitz@elenq.tech> | 2020-07-22 14:20:07 +0200 |
commit | c3a0a74c059ac7790008e7325567317435d0c7f8 (patch) | |
tree | d38dbf657c67bc2bc0e13e90ad754dac6b1ce3b1 | |
parent | f4f603af21ef8e747213b028f19d3fa489a00f72 (diff) |
Corrections in first chapters
-rw-r--r-- | es/01_intro.md | 15 | ||||
-rw-r--r-- | es/02_datos.md | 96 | ||||
-rw-r--r-- | es/03_estructura.md | 48 | ||||
-rw-r--r-- | es/04_funciones.md | 73 |
4 files changed, 116 insertions, 116 deletions
diff --git a/es/01_intro.md b/es/01_intro.md index b2a1752..84aa8a7 100644 --- a/es/01_intro.md +++ b/es/01_intro.md @@ -129,8 +129,10 @@ 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. +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. @@ -173,11 +175,10 @@ 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. +que IDLE nos brinda. IDLE sólo está facilitando nuestro trabajo de desarrollo, +como otros entornos 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 diff --git a/es/02_datos.md b/es/02_datos.md index 8400737..48c802c 100644 --- a/es/02_datos.md +++ b/es/02_datos.md @@ -16,11 +16,11 @@ 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 +constantemente. Los lenguajes de programación aportan a quien programa 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. +significado que su posición, la mayor parte de las veces permitiendo a quien +programa 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 @@ -197,11 +197,10 @@ 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> +Aunque realmente es algo más complejo, 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. Si necesitas saber más, debes investigar la +artimética de coma flotante, o *floating point arithmetic* en inglés. Declarar números de coma flotante es natural porque usa una sintaxis a la que estamos acostumbrados: @@ -263,14 +262,12 @@ SyntaxError: invalid syntax ``` > 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. +> de escape: `\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 @@ -301,7 +298,7 @@ En python los índices comienzan en `0`. 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 +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 @@ -319,7 +316,7 @@ los índices es idéntica. 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. +elementos usan claves arbitrarias definidas por quien programa. 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. @@ -379,8 +376,9 @@ intersecciones (*intersection*) y otros métodos descritos en esta teoría. ## 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. +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: @@ -427,9 +425,9 @@ cuenta la ayuda. 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. +Existen operadores básicos que te permiten transformar los datos. Algunos ya +los has visto antes, como el operador `=`, que sirve para nombrar cosas, la +suma (`+`) o la resta (`-`), pero hay otros. ``` python >>> 1 + 1 @@ -450,18 +448,18 @@ memorizar. 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 | +| operador | significado | +|----------|-------------------------------------------| +| `<` | menor que | +| `<=` | menor o igual que | +| `>` | mayor que | +| `>=` | mayor o igual que | +| `==` | igual que | +| `!=` | diferente de | +| `is` | identidad de objetos: «es» | +| `is not` | identidad negada: «no es» | +| `in` | comprobación de contenido: «en» | +| `not in` | comprobación de contenido negada: «no en» | ``` python >>> 1 > 1 @@ -486,11 +484,11 @@ True 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 | +| operador | significado | +|----------|----------------------------------------------------------| +| `and` | «Y» lógico, es `True` si todos sus operandos son `True` | +| `or` | «O» lógico, es `True` si algún operando es `True` | +| `not` | «No» lógico, invierte el operando | 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 @@ -514,16 +512,16 @@ True 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 | +| operador | significado | +|----------|----------| +| `+` | Suma | +| `-` | Negativo o resta| +| `*` | Multiplicación | +| `/` | División | +| `**` | Potencia | +| `%` | Resto | -### Ternary operator +### Operador ternario 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 @@ -536,7 +534,11 @@ así: 1 ``` -[^ternary]: <https://en.wikipedia.org/wiki/%3F:#Python> +[^ternary]: En realidad, el nombre de operador ternario no indica nada más que + el hecho de que use tres argumentos. Históricamente se ha usado este nombre + para este operador en concreto, que en otros lenguajes aparece con la forma + `condición ? resultado1 : resultado2`, porque no solía existir ningún otro + operador que recibiera tres argumentos. ### Operación en función del tipo @@ -604,7 +606,7 @@ 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. +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. diff --git a/es/03_estructura.md b/es/03_estructura.md index ba9abdb..4aa6570 100644 --- a/es/03_estructura.md +++ b/es/03_estructura.md @@ -25,9 +25,9 @@ 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*. +indentación si lo calcamos del inglés) 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 @@ -40,12 +40,10 @@ contexto. Sirven para delimitar zonas del programa, cuerpos de sentencias, etc. 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)> +desees, pero elijas lo que elijas debe ser coherente en todo el documento. Los +editores de código, como IDLE, pueden 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. ## Sintaxis general @@ -74,8 +72,9 @@ 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*. +en función de unas normas para evitar que el programa ejecute ciertas +sentencias o repita la ejecución de algunos bloques. A esto se le conoce como +*control de flujo*. #### Condicionales @@ -101,8 +100,8 @@ 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 +Si te preguntas qué condiciones debes usar, es tan simple como usar expresiones +de python cuyo resultado sea `True` o `False`. Cuando la expresión 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. @@ -200,7 +199,8 @@ 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> +[^truthy]: La documentación oficial de python describe estas conversiones en + detalle en la sección *Truth value testing*. ### List comprehensions @@ -208,7 +208,7 @@ normalmente los valores que representan un vacío se consideran `False`. 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]. +de construcción de sets de las matemáticas. 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 @@ -247,11 +247,9 @@ proceso que dejamos para el futuro. <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 +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. @@ -259,9 +257,9 @@ 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. +permisos de edición en el directorio, etc. Es nuestra responsabilidad 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. @@ -314,13 +312,11 @@ 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 +python 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 @@ -527,8 +523,8 @@ 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. +podido ver en detalle por no haber visitado, aún, la programación orientada a +objetos. Pero calma, pronto lo haremos. 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 diff --git a/es/04_funciones.md b/es/04_funciones.md index 618ccd6..2fd3c73 100644 --- a/es/04_funciones.md +++ b/es/04_funciones.md @@ -123,9 +123,10 @@ La sintaxis de una función lambda te la enseño con un ejemplo: 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`. +En el primer ejemplo se muestra la declaración de una función lambda. En el +segundo, colocando los paréntesis de precedencia y después de llamada a +función, se construye una función lambda y después 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. @@ -160,9 +161,7 @@ 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> +[^lambda]: Toman su nombre del *Lambda Calculus*. ## Scope avanzado @@ -189,11 +188,13 @@ 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. +- Las expresiones generadoras definidas en el PEP-289[^pep289], 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/> +[^pep289]: Los PEP son documentos donde se proponen mejoras para el lenguaje. + Puedes leer el contenido completo del PEP en: + <https://www.python.org/dev/peps/pep-0289/> ### Scope léxico, Closures @@ -212,9 +213,9 @@ def create_incrementer_function(increment): return incrementer increment10 = create_incrementer_function(10) -increment10(10) # Returns 20 +increment10(10) # 20 increment1 = create_incrementer_function(1) -increment1(10) # Returns 11 +increment1(10) # 11 ``` En este ejemplo hemos creado una función que construye funciones que sirven para incrementar valores. @@ -269,7 +270,7 @@ 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` +### Global y No-local 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 @@ -345,11 +346,10 @@ 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. +Las sentencias `global` y `nonlocal` son tramposas, ya que dificultan la +comprensión del programa. La mejor recomendación que puede hacerse es tratar de +evitarlas. Usarlas en exceso es, en general, un indicador de un mal diseño de +programa. ## Argumentos de entrada y llamadas @@ -388,9 +388,9 @@ TypeError: 'int' object is not callable #### 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. +estructura *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 @@ -425,8 +425,6 @@ except KeyError: ``` -[^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 @@ -435,9 +433,11 @@ 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. +resultados de la función serán distintos. + +Observa el siguiente ejemplo. 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 ): @@ -459,7 +459,7 @@ argumentos con nombre. No se han utilizado valores por defecto especiales, pero pueden usarse otros. ``` python -def move_file( source=None, target=None): +def move_file(source=None, target=None): "Mueve archivo de `source` a `target" pass @@ -479,7 +479,7 @@ 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 ) +def argument_catcher( *args, **kwargs ): "Función ejecutable con cualquier número de argumentos de entrada, tanto posicionales como con nombre." print( args ) @@ -497,7 +497,7 @@ 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 +#### 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 @@ -527,6 +527,9 @@ que es en la sentencia `def` donde aparecen. ## Decorators +> TODO: CAMBIAR EL EJEMPLO POR UNA DE CONTEO DE TIEMPO +> Así se verá la aplicación de los argumentos generales. + 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. @@ -634,17 +637,15 @@ 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. +(*first-class citizens*) ya era momento de 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 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 +analizarlo en el futuro. Lo que sí que has tratado en profundidad son los argumentos *positional* y *keyword*, y cómo se utilizan en todas sus posibles formas. |