[Corrección Python + SQLite3 1] Libro de contactos.

B

La única intención de este hilo es aprender de ustedes mediante sus correcciones a mi código. Ustedes son mis profesores. Gustaría que hagan preguntas de por que he hecho esto y por que he hecho aquello, que me hagan pensar y ver otras alternativas a lo que he hecho. Las buenas practicas, lo que debería de evitar y los fallos garrafales también son bienvenidos, obviamente. Siéntete libre de ser el profesor mas duro de mediavida.

El código es totalmente funcional, otra cosa de la que soy muy consciente es que sea eficiente y este escrito de forma correcta.

Todo el código (mas abajo esta fragmentado en sus funciones):

spoiler

Para poner en practica la programación orientada a objetos he creado una pequeña 'aplicación' que funciona como un libro de contactos. He utilizado Python y la base de datos SQLite3.

La creación de la tabla que contiene dos columnas las cuales son PERSON_ID, NAME, PHONE_NUMBER.

import sqlite3

connection = sqlite3.connect('book.db')
pointer = connection.cursor()

#Creación de la tabla de CONTACTS. Si no existe.
pointer.execute("CREATE TABLE IF NOT EXISTS CONTACTS (PERSON_ID INTEGER PRIMARY KEY, NAME VARCHAR(50), PHONE_NUMBER INTEGER)")

Unicamente consta de una clase llamada book_contacts con 4 métodos en su interior que tienen las siguientes tareas.

  • Visualizar los contactos ya existentes:
class book_contacts():
    def show_contacts(self):

        connection.row_factory = sqlite3.Row
        rows = pointer.execute("SELECT * FROM CONTACTS")
    
        a = "ID:"
        b = "NAME:"
        c = "PHONE NUMBER:"
        for row in rows:
            print(f"{a} {row[0]}, {b} {row[1]}, {c} {row[2]}")
        print("\n")
  • Añadir nuevos contactos:
def add_contacts(self):

        name = input("\nName: ")
        phone_number = int(input("Phone number: "))

        pointer.execute("""INSERT INTO CONTACTS (NAME, PHONE_NUMBER) VALUES (?, ?)""", (name, phone_number))
        connection.commit()
        print("\n")
        print("New contact added!\n")
  • Borrar un contacto en especifico el cual se accede a el mediante su ID.
def delete_contacts(self):

        delete_id = int(input("ID to delete: "))
        pointer.execute("DELETE FROM CONTACTS WHERE PERSON_ID=?", (delete_id,))
        connection.commit()
        print("\nContact deleted!\n")
  • Y el ultimo de ellos es para borrar todos los contactos de la base de una vez.
def delete_all_contacts(self):

        print("\n")
        pointer.execute("DELETE FROM CONTACTS;",)
        connection.commit()
        print("All contacts deleted!")
        print("\n")

Al ejecutar el código se puede hacer uso de los métodos mediante números que van del 1 hasta el 4. Son simples condiciones que estan dentro de una función que a la vez siempre se esta ejecutando.

book = book_contacts()

print("Welcome to your contacts book!\n")

#Conjunto de opciones: condicionales que determinan el output segun el input del usuario.
def menu():

    user_options = [1, 2, 3, 4]

    print("1 - Show my contacts")
    print("2 - Add a new contact")
    print("3 - Delete an existing contact")
    print("4 - Delete all contacts")

    option_selected = None
    while option_selected not in user_options:
        option_selected = int(input("\nPlease select an option by number: "))
    

    if option_selected == 1:
        print("\n")
        book.show_contacts()
    elif option_selected == 2:
        # print("\n")
        book.add_contacts()
    elif option_selected == 3:
        print("\n")
        book.delete_contacts()
    elif option_selected == 4:
        print("\n")
        y_n = input("You are going to delete all your contacts, you sure? [Y/N] ").lower()
        if y_n == "y":
            book.delete_all_contacts()
        else:
            print("\n")
            print("Task cancelled!\n")
            menu()

while True:
    menu()

Una observación con la que lidie durante estos 2 dias y no logré resolver es referido a las IDs. Al eliminar un contacto que tenga digamos ID=1 esta no queda liberada a uno nuevo que haya añadido por lo tanto queda inutilizada.

¡Gracias por vuestra atención!

B

Felicidades! como todo, se puede mejorar :P

  • Estas mezclando responsabilidades, "book contacts" se encarga de todo... y eso a la larga es complicado de mantener.
  • El tema del "id" es por mantener la integridad de los datos. Igual con una tabla no lo veas claro, pero en el momento que empieces a trabajar con relaciones lo verás xD
    Ten en cuenta que para que agotes la cantidad de id's que se pueden generar deberías de estar insertando miles de registros cada segundo por un periodo de unos miles de millones de años ;)

Te dejo un ejemplo de como lo dejaría yo... no dudes en preguntar cualquier duda:

spoiler

** No es perfecto, pero verás que es más sencillo de mantener y extender
** Te muestro "como lo haría yo" ajustándome a lo que tenías porque revisar código en un foro como este es una tarea complicada. Mejor si lo subes a github.
** OJO! Esto es código compartido en un post de un foro... en un "escenario real" la estructura del proyecto importaría. Aquí es un mejunje entre como me gustaría hacerlo y lo que el medio me permite compartir.

-- Edit 1: Editada la llamada a los comandos.
-- Edit 2: Aplicados cambios sugeridos por desu

1 respuesta
desu

#2 Felicidades! como todo, se puede mejorar :P

Mucha sobre enginieria, para que tienes database si solo quieres leer usando sqlite? igualmente deberias utilizar dataclasses y abc:
https://docs.python.org/3/library/abc.html

aunque mejor si no usas OOP:
YAGNI, KISS, DRY

Si haces oop y tienes una helper... lo siento pero es code smell, seguramente es una extension que en el caso de python deberia ser un metodo estatico para resolverse bien en runtime en caso de tener alguna dependencia implicita.

Sobre los commands, horrible... una de las conferencias de python mas famosas de la historia

what is a class with one method? a function (o algo asi, te recomiendo verlo todo, te hace falta la verdad)

el code smell es obvio... tienes un campo estatico menu_option.. tu lo que quieres es un sum type sobre este campo. wow enums, wow. en este caso sin usar classes (queda claro que a class with one metohd is a function!!!!!!!!!!!111!!!!!!!!! so much wowwwwww) puedes hacerlo de varias maneras, un switch que hace dispatch al run seria lo suyo, te permito el enum si quieres la sobre enginieria.

* no es perfecto pero es mas sencillo de mantener
** escribiras 250 lineas menos
*** tu codigo ira x2 mas rapido y sera mas eficiente

1 respuesta
B

#3 "Mucha" lo que se dice mucha... no la verdad. Mi intención es la de que el libro de contactos se pueda usar en la base de datos que te de la gana usando el dialecto que te se salga del nardo. Tu presupones que solo se quiere usar sqlite... está claro que ciñéndonos al ejemplo se podría considerar sobreingenieria.
Respecto al uso de ABC, totalmente de acuerdo...

Respecto al helper, bueno... no es perfecto como he dicho.

El numero de 250 lineas te lo sacas de un huevo y lo de x2 te lo sacas del otro.

Los videos de youtube están muy chulos, pero me estás diciendo cosas sin conocer que es lo que quiero hacer. La idea detrás del Commands es tener un sistema modular. Usando introspección podría cargar los archivos e inspeccionar los comandos definidos.
Respecto al miembro estático, es algo forzado para el ejemplo, la idea original es usar el nombre de la clase como nombre del comando.

Vamos, que toda tu verborrea la ventaja que aporta es la de no poder instanciar "Database".

1 respuesta
2 comentarios moderados

Usuarios habituales