Answered Sep 29, 2020 · 0 votes
Your issue is that you are reading the contents but not storing them again. But this code is yelling for some auxiliar functions so that everything stays clean. Lets start making some helper functions and solving the issue in the meanwhile.
We are requesting several integers from the user so lets create a function that handles this. It will return a special number (defaults to 0) in case we can't convert what the user provided to an integer.
def input_integer(request: str, *, error: int = 0) -> int: try: return int(input(request)) except ValueError: # If we can't convert to integer return error return error-
Note: if you are not used to the type annotations, they mean that the input_integer function expects a string argument (which is the message to request the integer) and a keyword integer argument that allows to configure which integer to return as an error, defaulting to 0. The function returns an integer. By making this annotations the reader will have better understanding of what the function does and IDEs will detect some type mismatch errors, but they will not be evaluated at runtime.
Okey so we are going to print a menu, lets also make a function for it. Actually, we are going to make a function that returns a function. The outter funtion will build the menu itself, and the returned function will print it and request for the user input.
def menu_factory(options: List[str], input_request: str) -> Callable[[], int]: # Number of options options_number = len(options)- # Menu text menu_text_lines = ["MENU:"] for i, option in enumerate(options): menu_text_lines.append(f"\t{i+1}. {option}") menu_text = "\n".join(menu_text_lines)- # Input request text input_request += f" (1..{options_number}): "- # Create the function that will be called to print the menu def menu_function() -> int: # Print the menu text we have previously built print(menu_text)- # Get the input from the user until he gives a valid one choice = input_integer(input_request) while not(0 < choice <= options_number): print(f"It must be a number between 1 and {options_number}.") choice = input_integer(input_request)- # Return the choice of the user return choice- return menu_function-
As you can see, functions can be defined inside other blocks and returned as any other object would have been. The function accepts a list of strings which are the options and an additional string to request the user for input. It will build the full messages adding the numbers and prepare the function that we will later call. In this case, the menu_factory function would be called like this:
menu = menu_factory( [ "Create Binary File.", "Display Binary File.", "Search for a given roll number.", "Input roll number and mark, Update mark of the student.", "Delete a record for a given roll number.", "Display the details of the students getting average marks " "more than 80.", "Append new records at the end of file.", "Exit", ], "Choose a command")-
And now, menu() will print the menu, request the user choice and return that choice after validating it is a proper number in the correct range.
The next thing we will have is a class to store the information of each student instead of a list. It will have 4 attributes (name, roll number, age and mark). The constructor (__init__) will just save them. I also added a __repr__ that tells python how to print Student objects. The __slots__ at the top of the class definition is a memory optimization. By setting __slots__ we tell the class which exact attributes it will have instead of having to store them as a generic dictionary. If you remove this line it will still work exactly the same, but each student you load will consume a little bit more RAM.
class Student: __slots__ = 'name', 'roll_no', 'age', 'mark'- def __init__(self, name: str, roll_no: int, age: int, mark: int) -> None: self.name = name self.roll_no = roll_no self.age = age self.mark = mark- def __repr__(self) -> str: return f"Student<name={self.name}, roll_no={self.roll_no}, " \ f"age={self.age}, mark={self.mark}>"-
We are pretty much done but there is another thing that we will do quite a bunch of times, reading and writting to the file. Lets create some auxiliar functions for that to. The read_file function will create a list of Stundets. The first try: ... except FileNotFoundError: pass block detects if the file can't be found (retunring an empty list of Students). The with open(...) as f: ... if a better approach than calling f.close() afterwards. Python itself will close the file when you get out of that with block, doesn't matter if it is due to an exception or any other reason. So basically we know for sure that the file will be closed no matter what. The inner try: ... except EOFError: break is to detect when have we ended reading the file and get out of the infinite while True: ... loop.
def read_file(path: str) -> List[Student]: content = [] try: with open(path, 'rb') as f: while True: try: content.append(pickle.load(f)) except EOFError: # Break the loop when reached end of the file break except FileNotFoundError: # Don't raise if the file doesn't exist pass return content-
The write function is pretty simple, open the file, write the content, done. Same goes for append.
def write_file(path: str, content: List[Student]) -> None: with open(path, 'wb') as f: for row in content: pickle.dump(row, f)--def append_file(path: str, content: List[Student]) -> None: with open(path, 'ab') as f: for row in content: pickle.dump(row, f)-
So now we have an input_integer function to read integers from the user, a menu_factory function that will build the menu, a Student class that will store each student's info, and a read_file, write_file and append_file functions. We can start implementing our logic. I'm going to create a dictionary of functions where the key is going to be the integer choice and the value are the functions that we will call to implemnent that option. You will see how choice_X functions will be super simple now that we have set up those auxiliary methods.
import picklefrom typing import Callable, List--FILENAME = 'student.bat'--def input_integer(request: str, *, error: int = 0) -> int: # ...--def menu_factory(options: List[str], input_request: str) -> Callable[[], int]: # ...--class Student: # ...--def read_file(path: str) -> List[Student]: # ...--def write_file(path: str, content: List[Student]) -> None: # ...--def append_file(path: str, content: List[Student]) -> None: # ...--if __name__ == '__main__' : # Create the menu function from the factory menu = menu_factory( [ "Create Binary File.", "Display Binary File.", "Search for a given roll number.", "Input roll number and mark, Update mark of the student.", "Delete a record for a given roll number.", "Display the details of the students getting average marks " "more than 80.", "Append new records at the end of file.", "Exit", ], "Choose a command" )- def choice_1() -> None: students = [] for i in range(1, input_integer("How many entries? ") + 1): print(f"Entry number {i}:") students.append(Student( input("\tName: "), input_integer("\tRoll number: "), input_integer("\tStudent age: "), input_integer("\tAverage mark: "), )) write_file(FILENAME, students)- def choice_2() -> None: print(read_file(FILENAME))- def choice_3() -> None: roll_no = input_integer("Roll number of the student to find: ") for student in read_file(FILENAME): if student.roll_no == roll_no: print(student) break else: # This will only be executed if no break was found print(f"Roll number {roll_no} not found in data file.")- def choice_4() -> None: roll_no = input_integer("Roll number of the student to update: ") students = read_file(FILENAME) for i, student in enumerate(students): if student.roll_no == roll_no: break else: # This will only be executed if no break was found print(f"Roll number {roll_no} not found in data file.") return- students[i].mark = input_integer("New average mark: ") write_file(FILENAME, students)- def choice_5() -> None: roll_no = input_integer("Roll number of the student to delete: ") students = read_file(FILENAME) for i, student in enumerate(students): if student.roll_no == roll_no: break else: # This will only be executed if no break was found print(f"Roll number {roll_no} not found in data file.") return- del students[i] write_file(FILENAME, students)- def choice_6() -> None: for i, student in enumerate(read_file(FILENAME)): if student.mark >= 80: print(student)- def choice_7() -> None: students = [] for i in range(1, input_integer("How many entries? ") + 1): print(f"Entry number {i}:") students.append(Student( input("\tName: "), input_integer("\tRoll number: "), input_integer("\tStudent age: "), input_integer("\tAverage mark: "), )) append_file(FILENAME, students)- menu_callbacks = { 1: choice_1, 2: choice_2, 3: choice_3, 4: choice_4, 5: choice_5, 6: choice_6, 7: choice_7, }- # Option number 8 is Exit while (choice := menu()) != 8: print() try: callback = menu_callbacks[choice] except KeyError: print("NOT IMPLEMENTED YET") else: callback() print("\n")-