Python tutorial básico: Programando un registro.
Tags: python, beginners, tutorial, programming
Published: 2021-09-13 23:47:07


Escribiremos un programa que nos permita hacer un registro de animales, con su nombre científico
y su nombre común, estos enlazados por un ID único. Usando ese ID podremos eliminar al animal del registro,
o simplemente modificar los nombres ya escritos. Así como una opción de ver la lista de los animales ya
registrados y una opción que nos permita salir del programa. 

La meta es generar e imprimir una lista que se pueda modificar como esta:

|      | Animal Registry        |                   |
|------|------------------------|-------------------|
| **ID**   | **Scientific Name**        | **Common Name**       |
| 3Y99 | Canis Lupus Familiaris | Siberian Husky    |
| MT5R | Cebinae                | Capuchin Monkey   |
| 8D6U | Octopoda               | Octopus           |
| YG0E | Anthophila             | Bee               |
| JN14 | Dinastinae             | Rhinoceros Beetle |
| WVTA | Python Regius          | Ball Python       |
| HAZP | Gorilla                | Gorilla           |


Trabajaremos con:
- El modulo PrettyTable.
- Diccionarios anidados.
- Control del flujo.
- Funciones.

>Debes instalar PrettyTable mediante Pip.
>Intenta, en la consola, con:
>pip install prettytable

Primero, importaremos los módulos necesarios:
PrettyTable para la creación de la tabla en donde registraremos a los animales, random y string para 
generar un ID al azar con el cual identificaremos la fila en el registo y sys para salir del programa.


```python
from prettytable import PrettyTable
import random
import string
import sys
```

Ahora crearemos un diccionario vacío en donde almacenaremos los animales que vayamos a registrar.

``` python
animal_dic = {}
```

Escribiremos la condicional "if __name__ == "__main__"" para evitar errores de importación y por buenas practicas.
y llamamos a nuestra función *main()*. 

``` python
if __name__ == "__main__":
    main()
```
>La explicación de como *if* \__name__ == "\__main__" funciona está fuera del alcance de este articulo.

-----------------------------------------------------------------------------------------------------
## Funciones:

Ahora bien, escribamos las funciones que harán andar nuestro programa, estas, irán **ANTES** de la condicional "*if \__name__ == "\__main__"*" anteriormente descrito. De lo contrario Python intentaría ejecutar la función *main()* cuando esta **no** han sido definida, por lo tanto dispararía un error.


------------------------------------------------------------------------------------------------
## Función pricipal main:
Dentro de *main()* obligaremos a que se ejecute este  *loop* con un *while True*, 
imprimirá las instrucciones de añadir, borrar, actualizar, chequear y salir del programa.
También tenemos un *input()* que será la opción que elija el usuario y esta será almacenada en
la variable *user_input*. **Atento** al método *.lower()*, este convierte en minúsculas lo que ingrese el usuario, así evitamos errores de tipeo por parte de este. Finalmente, las condicionales. Dependiendo de lo que elija el usuario se llamará a una función.
``` python
# ~~~~~~~~~~~~~~~~~~ Functions(): ~~~~~~~~~~~~~~~~~~~
def main():
    # ~~~~~~~~~~~~~~ User's choise ~~~~~~~~~~~~~~~
    while True:
        instrucctions()
        user_input = input("\nWhat do you want to do? "
                           "(a, d, u, l, e): ").lower()
        if user_input == "a":
            add_animal()
        elif user_input == "d":
            delete_animal()
        elif user_input == "u":
            update_animal()
        elif user_input == "l":
            print_register()
        elif user_input == "e":
            exit_program()
        elif not user_input:
            print("please, enter something!")
```
---------------------------------------------------------------------------------------
### Instrucciones:
Crearemos una función para que el usuario vea las opciones de nuestro programa junto a una tecla a presionar  para que se ejecute esa opción, y para que el usuario sepa que hacer. Las opciones son: agregar animal, borrar animal, actualizar animal, mirar lista y salir del programa.
Por lo tanto, sería así:

```python
# ~~~~~~~~~~~~~ Functions ~~~~~~~~~~~~~~
def instrucctions():
    print('\nAnimal registry program:'
          '\n1: Enter A or a to add new animal.'
          '\n2: Enter D or d to delete a animal'
          '\n3: Enter U or u to update animal.'
          '\n4: Enter L or l to check list of animals. '
          '\n5: Enter E or e to exit the program.')
```

Imprimimos las opciones. *'/n'* significa un salto de linea.

-----------------------------------------------------------------------------------------------------
### Imprimir registro:

``` python
def print_registry():
    x = PrettyTable(["ID", "Scientific Name", "Common Name"])
    for animal_data in animal_dic:
        x.add_row([animal_data, animal_dic[animal_data]["scientific_name"],
                   animal_dic[animal_data]["common_name"]])

    print(x.get_string(title="Animal registry"))
```

Atento a lo que sucede aquí:
- Para darles nombres a los encabezados de las columnas los escribimos en formato de lista, y la pasamos 
como parámetro de *PrettyTable* y la almacenamos en la variable "*x*".
- Antes de hablar del *ciclo for*, debemos recordar y tener muy claro de que estamos intentando 
acceder a un diccionario **dentro** de otro diccionario. Solo para tenerlo claro, comparemos un 
diccionario *común*, a uno *anidado*.

#### Diccionario:
Donde tenemos una colección de elementos almacenados en pares de **key**/**value** (**clave**/**valor**). 
```py
  {'name': 'Adam'}
 ```
'*name*' es la **key** (**clave**) y '*Adam*' el **value** (**valor**).
```py
  {'name': 'Adam', 'last': 'Smith'}
  ```
Podemos tener **más** de un **key**/**value** en **UN** diccionario. '*name*' y '*last*' son las **keys**.

#### Diccionario anidado:
Los valores al exterior del diccionario, también son diccionarios.
```py
  {0: {'book': 'Wealth of Nations', 'author': 'Adam Smith'},
   1: {'book': 'Economic Sophisms',' author': 'Frédéric Bastiat'}}
   ```
**0** es la **key**/**clave** del diccionario externo, y '**book**' y '**author**' claves del diccionario interno.

Entendido esto, volvamos al código *anterior*. 
``` python
for animal_data in animal_dic:
```
Lo que ocurre aquí es que accedemos al **key externo** (Es decir, el *ID*), mediante *animal_date*.

``` python
x.add_row([animal_data, animal_dic[animal_data]["scientific_name"],
           animal_dic[animal_data]["common_name"]])
```

Ya que accedemos al **key** externo, tendremos acceso a los valores internos. Ocurre de 
la siguiente manera:
- animal_dic = ID del animal
- animal_dic[animal_data]["scientific_name"] = nombre científico
- animal_dic[animal_data]["common_name"] = nombre común

Entonces, pues:
``` python
x.add_row([animal_data, animal_dic[animal_data]["scientific_name"],
           animal_dic[animal_data]["common_name"]])
```
Ingresamos los valores de nuestro diccionario a la **fila** de PrettyTable como **una lista**, mediante el 
método *add_row()*.

Finalmente le damos un encabezado a nuestra tabla:

``` python
print(x.get_string(title="Animal registry"))
```

Ahora puedes imprimir la lista iniciando el programa y presionando *"l" o "L".* Obviamente, ahora nuestro diccionario está vacío. Más adelante veremos como ingresarle datos.

-----------------------------------------------------------------------------------------------------
### Random ID:
Necesitamos un ID para identificar la columna y al animal registrado, este ID servirá como *enlace*, siendo la **key** externa de nuestros diccionarios anidados. 
Es necesario para un futuro poder actualizar uno o ambos nombres del animal, o borrarlo definitivamente.
Para facilitarle la tarea al usuario, y evitar que cree duplicados, crearemos una función que retorne 
un string al azar. Lo haremos con el siguiente código:

``` python
def random_id():
    random_string = ''.join(random.choices(string.ascii_uppercase
                                           + string.digits, k=4))
    return random_string
```
Donde "*k*" es el largo del string.
>La explicación de como funciona esta función está fuera del alcance de este articulo.

-----------------------------------------------------------------------------------------------------
### Agregar:
Ahora hagamos la función que se encargará de tomar los valores que escriba el usuario e ingresarlos en el diccionario.

``` python
def add_animal():
    animal_id = random_id()
    scientific_name = input("\nPlease enter the scientific name: ").title()
    common_name = input("\nPlease enter the common name: ").title()
    data = {animal_id: {'scientific_name': scientific_name,
                        'common_name': common_name}}
    if not scientific_name and not common_name:
        print("You must write something!")
    else:
        animal_dic.update(data)
```

- Lo primero que notarás es que llamamos a la función *random_id* que devuelve un string y lo 
almacenamos en la variable *animal_id*. Como su nombre lo indica, identifica al animal y su 
fila dentro del registro.
- Mediante el método *input()* le pedimos al usuario que escriba el nombre común y científico. 
y los almacenamos en variables. Usamos el método *.title()* por  estética.

Hagamos una pausa y miremos otra vez esta parte del código:

``` python
    data = {animal_id: {'scientific_name': scientific_name,
                        'common_name': common_name}}
```

- Actualizamos nuestro diccionario anidado y lo almacenamos en una variable llamada "*data*". De modo que:
*animal_id* = Es el *random ID* que retorna nuestra función. Es la **key**/**clave** externa de este 
diccionario anidado.
'*scientific_name*': que está entre comillas, es la key/llave interna.
*scientific_name*: que una variable, es la value/valor interno. Ingresado por el usuario.

- El siguiente bloque de código establece una condición donde si el usuario **no** ingresa 
ni un nombre científico ni un nombre común, imprima un mensaje de que debe hacerlo.

- Por ultimo, en el '*else*': si se han ingresados al menos uno de los dos *input()* 
actualizaremos el "*animal_dic*" con los datos escritos por el usuario.

-----------------------------------------------------------------------------------------------------
### Eliminar fila:
Ahora escribamos la función para borrar la fila que el usuario elija, mediante el *ID único*.

``` python
def delete_animal():
    animal_id = input("\nEnter the animal ID you want delete: ").upper()
    if animal_id in animal_dic:
        choice = input("Delete (y/n)").lower()
        if choice == "yes" or choice == "y":
            del animal_dic[animal_id]
            print(f"{animal_id} registry has been deleted!")
    else:
        print("ID not found. Check list pressing 'L'")
```

- Le pedimos al usuario que ingrese el ID de la fila. Usamos *upper()* para que este no tenga
que escribir mayúsculas.
- Creamos un '*if*': que chequea si el *ID ingresado* por el usuario está en el diccionario le de
la elección de querer borrarlo con un sí o no. Si escribe si se borrara con un 
"del animal_dic[animal_id]" que busca el ID (**key externo**) y borra todos sus **key**/**values** 
internos .
- Con el *else* declaramos de que, si no se ha encontrado el *ID*, imprime un "ID not found"


-----------------------------------------------------------------------------------------------------
### Actualización:
Tal vez el usuario ha cometido un error de tipeo, y desea cambiar los campos de una fila en especifico
Ahí la necesidad de una función que los actualice.

``` python
def update_animal():
    animal_id = input("\nEnter the animal ID you want update: ").upper()
    # If external key in dictionary, if key is equal to ID (animal_id)
    for animal in animal_dic:
        if animal == animal_id:
            choice = input(f"Update registry {animal_id}? (y/n): ").lower()
            if choice == "yes" or choice == "y":
                # Changing names
                scientific_name = input("Write a new scientific name: ").title()
                common_name = input("Write a new common name: ").title()
                if not scientific_name and not common_name:
                    print("You must write something!")
                else:
                # Updating
                    animal_dic[animal]['scientific_name'] = scientific_name
                    animal_dic[animal]['common_name'] = common_name
                    print("registry updated!")
                    print_registry()
        else:
            print("ID not found. Check list pressing 'L'")
```
- El usuario ingresa el ID mediante un *input()* y es almacenado en una *variable*.
- Sí el ID (**key externo**) existe en nuestro diccionario avanzará a recorrerlo mediante un *for loop*,
  de lo contrario, imprimirá un "*ID not found*".
- Preguntamos si el usuario realmente quiere borrar la **fila** y almacenamos su respuesta en una 
  *variable*. Si es afirmativo, procedemos. Contrario se detiene el flujo.
- Pedimos al usuario, mediante un *input()*, que escriba los nuevos nombres y los almacenamos en
  *variables*.
- Si el usuario ha dejado en blanco **ambos** campos se imprimirá "*You must write something!*" de lo 
  contrario accederemos al diccionario, a su llave externa y almacenaremos los nuevos valores. 

-----------------------------------------------------------------------------------------------------
### Salir del programa:
``` python
def exit_program():
    sys.exit("Goodbye!")
```
Usamos el método *exit()* del modulo *built-in* **sys** para salirnos del programa e imprimiremos un 
  mensaje.