Python Forum
How to improve this Python 3 code?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to improve this Python 3 code?
#1
I have a .txt file with names and their school grades.

i wrote a program to output the name of a student + his/her average grade.
I find my approach doing this somehow unefficient and i fell like could be done better in a different way.

My Code:

f = open('python_ist_cool_tabelle.txt', 'r')

""" The .txt file: 
joe 10 15 20 30 40
bill 23 16 19 22
sue 8 22 17 14 32 17 24 21 2 9 11 17
grace 12 28 21 45 26 10
john 14 32 25 16 89
"""
a = f.readlines()

new_list = []
names_list = []
grade_averages = []

# creating seperate lists for names and grades
for i in a:
    items = i.split()
    grades = items[1:]
    names = items[0]
    new_list.append(grades)
    names_list.append(names)

# calculating the average of someones grades
for k in new_list:
    sum = 0
    for j in k[0:]:
        #print(int(j))
        sum = sum + int(j)
    average = sum / len(k)
    grade_averages.append(average)

# combining names and average grades
for q in range(len(names_list)):
    print(names_list[q] + ': your average score is ' + str(grade_averages[q]))
The Output:
joe: your average score is 23.0
bill: your average score is 20.0
sue: your average score is 16.166666666666668
grace: your average score is 23.666666666666668
john: your average score is 35.2
if you could read through my code and tell me what i could better would help me alot!

thanks! Heart
Reply
#2
  • when open a file, better use with context anager
    with open('python_ist_cool_tabelle.txt', 'r') as f:
        # work with file f
  • This file is short and there is no problem to read it in memory with readlines(), but you should know that you can iterate over f
    for line in f:
  • always use descriptive names. a is not such name. new_list is better as e.g. students_grades, names_list - just names, i and k is better as line or student

  • you can use extended iterable unpacking
    name, *grades = line.split()
  • don't use built-in functions, modules, etc. as names. In this case sum overrides the built-in function with same name.

  • No need to have separate lists. if you are going to use data further, you can have a better data structure - e.g. list of dicts or list of custom Student class, etc.

  • if you are going to just print average grade - you can do without any collection

  • when iterate over list, etc. you iteterate over elements, no need to use indexes. when you are iterating over multiple iterables simultaneously, use zip()

    for name, average in zip(name, grade_averages):
  • when print, try to use string formatting - e.g. f-strings or str.format() method. Concatenation works but you have to convert everything to str.

  • you can make your code better if you split in small chunks and define functions. this way the code is reusable, easy to test, etc. For example you may have a function that will parse the line, and return name and grades converted to int. Next step would be to make use of class/OOP - e.g. Student class.

with open('test.txt', 'r') as f:
    students = [] # only if you have to keep data for processing later
    for line in f:
        name, *grades = line.split() # line has new-line character at the end, but in this case we don't need to strip it
        grades = [int(grade) for grade in grades] # convert grades to int
        print(f'{name} your average score is {sum(grades)/len(grades):.2f}')
        students.append({'name':name, 'grades':grades}) # add student info as dict



# print students list
print('---------------')
print(students)
print('---------------')


for student in students:
    score = sum(student['grades'])/len(student['grades']) # calculate score on separate line
    print(f"{student['name']} your average score is {score:.2f}") # use calculated score in f-string
Output:
joe your average score is 23.00 bill your average score is 20.00 sue your average score is 16.17 grace your average score is 23.67 john your average score is 35.20 --------------- [{'name': 'joe', 'grades': [10, 15, 20, 30, 40]}, {'name': 'bill', 'grades': [23, 16, 19, 22]}, {'name': 'sue', 'grades': [8, 22, 17, 14, 32, 17, 24, 21, 2, 9, 11, 17]}, {'name': 'grace', 'grades': [12, 28, 21, 45, 26, 10]}, {'name': 'john', 'grades': [14, 32, 25, 16, 89]}] --------------- joe your average score is 23.00 bill your average score is 20.00 sue your average score is 16.17 grace your average score is 23.67 john your average score is 35.20
iamaghost and ndc85430 like this post
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply
#3
hey Buran,
thanks alot for your help.

so far i get everything besides one point:

how come that name, *grades = line.split() automatically detects what is a name and what is a grade?

when i print(grades) and print(name) they are seperated. Does it have something to do with *?
Reply
#4
(Jan-18-2021, 04:15 PM)iamaghost Wrote: Does it have something to do with *?

yes, exactly. I guess you are familiar with unpacking, i.e. when on the left side you have the exact number of names as the values you expect.
x, y = (1, 2) # so here x=1 and y=2
Now, when you use astersk itis called extended iterable unpacking

name will get the first value and *grades will be catch-all - grades will get all the rest values, regardless of their number.
iamaghost likes this post
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply
#5
Longer is always possible.

The typing stuff is not required.

The more interesting part is the use of statistics (batteries included), the iterable unpacking, exception-handling, pathlib to have a high-level api for paths and the exception handling for crappy data.

You'll often receive invalid data from somewhere. Then it's good to know where which Exception could occour.
For example, if the encoding of the data is wrong, it could raise an UnicodeDecodeError.


import statistics
from io import TextIOWrapper
from pathlib import Path
from typing import Generator, Union


def reader(text_or_fd: Union[str, TextIOWrapper]) -> Generator[dict, None, None]:
    """
    Generator wich returns a list with dicts inside.
    Each result has the key name, which points to the first word.
    After this first word integers are expected.
    Each result has median, stdev, median_low, median_high
    """
    if isinstance(text_or_fd, str):
        iterator = text_or_fd.splitlines()
    elif isinstance(text_or_fd, TextIOWrapper):
        iterator = text_or_fd
    else:
        raise TypeError("Expecting str or TextIOWrapper as input")
    for line in iterator:
        if not line.strip():
            # strip whitespace from line
            # if len is 0, then continue
            # to next iteration
            # not [] -> evaluates to True
            continue
        # special condition, that line is empty could here not happen
        name, *values = line.split() # split by whitespace
        # the * in front of values unpacks the rest
        # items into a list, which is assigned to the name values
        # the first element is the name
        if not values:
            # happens if not enough values are there to unpack
            # then values is an empty list
            continue
        try:
            values = tuple(map(int, values))
        except ValueError:
            # happens if one of the values is not an integer
            # then the function int raises a ValueError
            # continue to next line if this happens (crappy data)
            continue
        # now using the statistics module to do statistics
        median = statistics.median(values)
        mean = statistics.mean(values)
        stdev = statistics.stdev(values)
        median_low = statistics.median_low(values)
        median_high = statistics.median_high(values)
        # yield each result
        yield {
            "name": name,
            "values": values,
            "median": median,
            "stdev": stdev,
            "median_low": median_low,
            "median_high": median_high,
        }


text = """

The .txt file:
joe 10 15 20 30 40
bill 23 16 19 22
sue 8 22 17 14 32 17 24 21 2 9 11 17
grace 12 28 21 45 26 10
john 14 32 25 16 89


"""

file = Path.home() / "Desktop/test.txt"
with file.open() as fd:
    results_from_file = list(reader(fd))

results_from_str = list(reader(text))

print("Results from fileobject:")
print(results_from_file)

print("\n\nResults from str:")
print(results_from_str)
Output:
Results from fileobject: [{'name': 'joe', 'values': (10, 15, 20, 30, 40), 'median': 20, 'stdev': 12.041594578792296, 'median_low': 20, 'median_high': 20}, {'name': 'bill', 'values': (23, 16, 19, 22), 'median': 20.5, 'stdev': 3.1622776601683795, 'median_low': 19, 'median_high': 22}, {'name': 'sue', 'values': (8, 22, 17, 14, 32, 17, 24, 21, 2, 9, 11, 17), 'median': 17.0, 'stdev': 8.09975682388432, 'median_low': 17, 'median_high': 17}, {'name': 'grace', 'values': (12, 28, 21, 45, 26, 10), 'median': 23.5, 'stdev': 12.722683155162935, 'median_low': 21, 'median_high': 26}, {'name': 'john', 'values': (14, 32, 25, 16, 89), 'median': 25, 'stdev': 30.930567405076808, 'median_low': 25, 'median_high': 25}] Results from str: [{'name': 'joe', 'values': (10, 15, 20, 30, 40), 'median': 20, 'stdev': 12.041594578792296, 'median_low': 20, 'median_high': 20}, {'name': 'bill', 'values': (23, 16, 19, 22), 'median': 20.5, 'stdev': 3.1622776601683795, 'median_low': 19, 'median_high': 22}, {'name': 'sue', 'values': (8, 22, 17, 14, 32, 17, 24, 21, 2, 9, 11, 17), 'median': 17.0, 'stdev': 8.09975682388432, 'median_low': 17, 'median_high': 17}, {'name': 'grace', 'values': (12, 28, 21, 45, 26, 10), 'median': 23.5, 'stdev': 12.722683155162935, 'median_low': 21, 'median_high': 26}, {'name': 'john', 'values': (14, 32, 25, 16, 89), 'median': 25, 'stdev': 30.930567405076808, 'median_low': 25, 'median_high': 25}]
Hopefully it was not too much.
By the way, a Generator simplifies your code.
iamaghost likes this post
My code examples are always for Python >=3.6.0
Almost dead, but too lazy to die: https://sourceserver.info
All humans together. We don't need politicians!
Reply
#6
wow thats very nice!! im learning python for 1 month know and theres a lot i didnt test out yet...i dont know anything about test, except, try etc. Wall

i'll check all that out to understand your reply more thanks for that
Reply
#7
no, i didnt know about it unfortunately. I only saw it on other peoples code online. I got it know thanks you for that!
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020