Python – How to create a delete function for user input?

Tags: , , ,



I’ve been trying to create a program which allows users to view a text file’s contents and delete some or all of a single entry block.

An example of the text’s file contents can be seen below:

Special Type A Sunflower
2016-10-12 18:10:40
Asteraceae
Ingredient in Sunflower Oil
Brought to North America by Europeans
Requires fertile and moist soil
Full sun

Pine Tree
2018-12-15 13:30:45
Pinaceae
Evergreen
Tall and long-lived
Temperate climate

Tropical Sealion
2019-01-20 12:10:05
Otariidae
Found in zoos
Likes fish
Likes balls
Likes zookeepers

Big Honey Badger
2015-06-06 10:10:25
Mustelidae
Eats anything
King of the desert

As such, the entry block refers to all lines without a horizontal space.

Currently, my progress is at:

import time
import os
global o
global dataset
global database
from datetime import datetime

MyFilePath = os.getcwd() 
ActualFile = "creatures.txt"
FinalFilePath = os.path.join(MyFilePath, ActualFile) 

def get_dataset():
    database = []
    shown_info = []
    with open(FinalFilePath, "r") as textfile:  
        sections = textfile.read().split("nn")
        for section in sections:                 
            lines = section.split("n")      
            database.append({                
              "Name": lines[0],
              "Date": lines[1],              
              "Information": lines[2:]          
            })
    return database

def delete_creature():
    dataset = get_dataset()
    
    delete_question = str(input("Would you like to 1) delete a creature or 2) only some of its information from the dataset or 3) return to main page? Enter 1, 2 or 3: "))
    
    if delete_question == "1":
        delete_answer = str(input("Enter the name of the creature: "))
        for line in textfile:
            if delete_answer in line:
                line.clear()
            
    elif delete_question == "2":
        delete_answer = str(input("Enter the relevant information of the creature: "))
        for line in textfile:
            if delete_answer in line:
                line.clear()
        
    elif delete_question == "3":
        break
        
    else:
        raise ValueError
    except ValueError:
        print("nPlease try again! Your entry is invalid!")
        
while True:
    try:
        option = str(input("nGood day, This is a program to save and view creature details.n" +
                       "1) View all creatures.n" +
                       "2) Delete a creature.n" +
                       "3) Close the program.n" +
                       "Please select from the above options: "))
        
        if option == "1":
            view_all()
            
        elif option == "2":
            delete()
                
        elif option == "3":
            break
            
        else:
            print("nPlease input one of the options 1, 2 or 3.")
        
    except:
        break

The delete_function() is meant to delete the creature by:

  1. Name, which deletes the entire text block associated with the name
  2. Information, which deletes only the line of information

I can’t seem to get the delete_creature() function to work, however, and I am unsure of how to get it to work.

Does anyone know how to get it to work?

Many thanks!

Answer

Your problem with removing lines from a section is that you specifically hardcoded which line represents what. Removing a section in your case will be easy, removing a line will, if you do not change your concept, involve setting the line in question to empty or to some character representing the empty string later. Another question here is, do you need your sections to remain ordered as they were entered, or you can have them sorted back in a file in some other order.

What I would do is to change the input file format to e.g. INI file format. Then you can use the configparser module to parse and edit them in an easy manner. The INI file would look like:

[plant1]
name="Some plant's English name"
species="The plant's Latin species part"
subspecies="The plant's subspecies in Latin ofcourse"
genus="etc."

[animal1]
# Same as above for the animal
# etc. etc. etc.

configparser.ConfigParser() will let you load it in an dictionarrish manner and edit sections and values. Sections you can name animal1, plant1, or use them as something else, but I prefer to keep the name inside the value, usually under the name key, then use configparser to create a normal dictionary from names, where its value is another dictionary containing key-value pairs as specified in the section. And I reverse the process when saving the results. Either manually, or using configparser again.

The other format you might consider is JSON, using the json module. Using its function dumps() with separators and indentation set correctly, you will get pretty human-readable and editable output format. The nice thing is that you save the data structure you are working with, e.g. dictionary, then you load it and it comes back as you saved it, and you do not need to perform some additional stuff to get it done, as with configparser. The thing is, that an INI file is a bit less confusing for an user not custom to JSON to construct, and results in less errors, while JSON must be strictly formatted, and any errors in opening and closing the scopes or with separators results in whole thing not working or incorrect input. And it easily happens when the file is big.

Both formats allows users to put empty lines wherever they want and they will not change the way the file will be loaded, while your method is strict in regard to empty lines.

If you are expecting your database to be edited only by your program, then use the pickle module to do it and save yourself the mess.

Otherwise you can:

def getdata (stringfromfile):
    end = {}
    l = [] # lines in a section
    for x in stringfromfile.strip().splitlines():
        x = x.strip()
        if not x: # New section encountered
            end[l[0].lower()] = l[1:]
            l = []
            continue
        end.append(x)
    end[l[0].lower()] = l[1:] # Add last section
    # Connect keys to numbers in the same dict(), so that users can choose by number too
    for n, key in enumerate(sorted(end)):
        end[n] = key
    return end

# You define some constants for which line is what in a dict():
values = {"species": 0, "subspecies": 1, "genus": 2}

# You load the file and parse the data
data = getdata(f.read())

def edit  (name_or_number, edit_what, new_value):
    if isinstance(name_or_number, int):
        key = data[name_or_number]
    else:
        key = name_or_number.lower().strip()
    if isinstance(edit_what, str):
        edit_what = values[edit_what.strip().lower()]
    data[key][edit_what] = new_value.strip()

def add (name, list_of_lines):
    n = len(data)/2 # Number for new entry for numeric getting
    name = name.strip().lower()
    data[name] = list_of_lines
    data[n] = name

def remove (name):
    name = name.lower().strip()
    del data[name]
    # Well, this part is bad and clumsy
    # It would make more sense to keep numeric mappings in separate list
    # which will do this automatically, especially if the database file is big-big-big...
    # But I started this way, so, keeping it simple and stupid, just remap everything after removing the item (inefficient as hell itself)
    for x in data.keys():
        if isinstance(x, int):
            del data[x]
    for n, key in enumerate(sorted(data)):
        data[n] = key

def getstring (d):
    # Serialize for saving
    end = []
    for l0, ls in d.items():
        if isinstance(l0, int):
            continue # Skip numeric mappings
        lines = l0+"n"+"n".join(ls)
        end.append(lines)
    return "nn".join(end)

I didn’t test the code. There might be bugs.

If you need no specific lines, you can modify my code easily to search in the lines using the list.index() method, or just use numbers for the lines if they exist when you need to get to them. For doing so with configparser, use generic keys in a section like: answer0, answer1…, or just 0, 1, 2…, Then ignore them and load answers as a list or however. If you are going to use configparser to work on the file, you will get sometimes answer0, answer3… when you remove.

And a warning. If you want to keep the order in which input file gives the creatures, use ordereddict instead of the normal dictionary.

Also, editing the opened file in place is, of course, possible, but complicated and inadvisable, so just don’t. Load and save back. There are very rare situations when you want to change the file directly. And for that you would use the mmap module. Just don’t!



Source: stackoverflow