Quick answer: A weighted GPA is the sum of each course’s grade points multiplied by its credits, divided by total credits. A reliable Python calculator separates the grade scale, validates every course, handles zero credits, and rounds only the final displayed result.

A Python GPA calculator is a weighted average. Each course contributes grade points multiplied by credits, and the final GPA is total quality points divided by total credits. The important part is not the loop itself; it is keeping the grade scale, credit values, and validation rules explicit.
Most school examples start with three inputs: a course name, a letter grade, and the credits for that course. A dictionary mapping letter grades to grade points keeps the scale readable. A list of course rows keeps the calculation separate from display, files, or user input.
The official Python references for this guide are the decimal module, the csv module, dataclasses, and the built-in round() function.
Map Grades To Points
The simplest calculator stores a grade scale in a dictionary. Each course row then uses the letter grade to find its numeric point value. Credits act as weights, so a four-credit course affects the result more than a one-credit course.
GRADE_POINTS = {
"A": 4.0,
"A-": 3.7,
"B+": 3.3,
"B": 3.0,
"C+": 2.3,
"C": 2.0,
}
courses = [
("English", "A", 3),
("Calculus", "B+", 4),
("History", "A-", 3),
]
quality_points = sum(GRADE_POINTS[grade] * credits for _, grade, credits in courses)
total_credits = sum(credits for _, _, credits in courses)
gpa = quality_points / total_credits
print(round(gpa, 2))
This is a weighted GPA calculation only in the credit sense: every course uses the same grade scale, but credits decide how much each row counts. The pattern is easy to audit because the scale, rows, numerator, denominator, and final division are visible.
Use direct dictionary lookup when every grade must be present in the scale. If a grade is missing, Python raises a KeyError, which is better than silently using the wrong point value.
Validate Rows Before Calculating
Real GPA input should reject bad grades, zero credits, negative credits, and empty course lists. Validation belongs close to the calculation so a caller gets a clear error before a misleading result is printed.
def calculate_gpa(rows, grade_points):
quality_points = 0.0
total_credits = 0.0
for name, grade, credits in rows:
if grade not in grade_points:
raise ValueError(f"Bad grade for {name}: {grade}")
if credits <= 0:
raise ValueError(f"Credits must be positive for {name}")
quality_points += grade_points[grade] * credits
total_credits += credits
if total_credits == 0:
raise ValueError("At least one credited course is required")
return quality_points / total_credits
scale = {"A": 4.0, "B": 3.0, "C": 2.0}
term = [("Biology", "A", 4), ("Art", "B", 2)]
print(round(calculate_gpa(term, scale), 2))
The function accepts rows and a scale, so it can be tested without files, prompts, or web forms. That makes it useful for a command-line script, a notebook, or a small web app.
The checks are intentionally strict. A pass or incomplete grade can be handled as a separate policy, but it should not accidentally enter a numeric GPA calculation.

Use Decimal For Fixed GPA Rounding
Floating-point arithmetic is fine for many class examples, but some GPA policies require exact decimal rounding. The Decimal type lets you choose the rounding rule and keep inputs as decimal text.
from decimal import Decimal, ROUND_HALF_UP
GRADE_POINTS = {
"A": Decimal("4.0"),
"A-": Decimal("3.7"),
"B+": Decimal("3.3"),
"B": Decimal("3.0"),
}
courses = [
("Physics", "A-", Decimal("4")),
("Writing", "B+", Decimal("3")),
("Music", "A", Decimal("1")),
]
quality_points = sum(GRADE_POINTS[grade] * credits for _, grade, credits in courses)
total_credits = sum(credits for _, _, credits in courses)
gpa = quality_points / total_credits
print(gpa.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
Decimal("3.7") is created from a string so the decimal value is exact. The final quantize() call rounds the printed GPA to two decimal places with a named policy.
Keep the display rule separate from the calculation rule. A transcript might show two decimal places, while an internal eligibility check might use more precision.
Add Course Weights With Dataclasses
Some schools add extra points for honors, AP, or advanced classes. A dataclass keeps each course row readable while still allowing an extra weight per course.
from dataclasses import dataclass
@dataclass(frozen=True)
class Course:
name: str
grade: str
credits: float
bonus: float = 0.0
GRADE_POINTS = {"A": 4.0, "A-": 3.7, "B+": 3.3, "B": 3.0}
courses = [
Course("AP Biology", "A", 4, 1.0),
Course("English", "B+", 3, 0.0),
Course("Honors Algebra", "A-", 4, 0.5),
]
quality_points = 0.0
total_credits = 0.0
for course in courses:
points = min(GRADE_POINTS[course.grade] + course.bonus, 5.0)
quality_points += points * course.credits
total_credits += course.credits
print(round(quality_points / total_credits, 2))
This is a weighted GPA in the course-level sense: selected courses add a bonus before credits are applied. The min() call caps a course at 5.0, which is a common policy but not universal.
If your school uses a different rule, change the scale, bonus, or cap in one place. The calculation should reflect the published policy, not a hidden assumption.
Read CSV-Style Rows
Many GPA tools eventually read rows from a form export or spreadsheet. The standard csv.DictReader can parse CSV-style rows into dictionaries, then the calculator can convert credits and look up grade points.
import csv
from io import StringIO
text = """course,grade,credits
English,A,3
Chemistry,B,4
Economics,A-,3
"""
GRADE_POINTS = {"A": 4.0, "A-": 3.7, "B": 3.0}
reader = csv.DictReader(StringIO(text))
rows = []
for row in reader:
rows.append((row["course"], row["grade"], float(row["credits"])))
quality_points = sum(GRADE_POINTS[grade] * credits for _, grade, credits in rows)
total_credits = sum(credits for _, _, credits in rows)
print(round(quality_points / total_credits, 2))
The CSV layer should stay thin. It reads text, converts credits, and hands clean rows to the GPA logic. That separation makes it easier to replace the source later with a file upload, database query, or pasted table.
Check headers before processing a full file in production code. If the export says credit_hours instead of credits, a header check can fail early with a useful message.

Summarize Terms And Cumulative GPA
A transcript often needs both term GPA and cumulative GPA. Store each term as a list of course rows, summarize each list, and then flatten all rows for the cumulative result.
GRADE_POINTS = {"A": 4.0, "A-": 3.7, "B+": 3.3, "B": 3.0}
terms = {
"Fall": [("Writing", "A", 3), ("Math", "B+", 4)],
"Spring": [("History", "A-", 3), ("Lab", "B", 2)],
}
def summarize(label, rows):
quality_points = sum(GRADE_POINTS[grade] * credits for _, grade, credits in rows)
credits = sum(credits for _, _, credits in rows)
return label, credits, round(quality_points / credits, 2)
all_rows = [row for rows in terms.values() for row in rows]
for label, credits, gpa in [summarize(name, rows) for name, rows in terms.items()]:
print(label, credits, gpa)
print("Cumulative", *summarize("All", all_rows)[1:])
The same formula is used for each term and for the cumulative GPA. That matters because averaging the two term GPAs directly can be wrong when the terms have different credit totals.
For a complete application, keep a short checklist: define the grade scale, validate letter grades, validate positive credits, decide how pass or incomplete rows are handled, apply any course bonus, and round only for display. Those choices make a Python GPA calculator predictable and easier to test.
Represent Course Records
Use a clear record containing a grade and credit hours, such as a dictionary or dataclass. Keeping the fields explicit makes validation and later extensions easier than relying on positional magic numbers.

Map Grades To Points
Define the accepted grade symbols and their numeric points in one mapping. Decide how plus and minus grades, pass/fail courses, withdrawals, and unknown values should behave before calculating.
Apply Credit Weighting
For each valid course, add points multiplied by credits to the numerator and credits to the denominator. A simple average is a different calculation and should not be mixed into a weighted result.
Validate Numeric Input
Reject negative credits, non-numeric values, missing grades, and a total of zero credits. Report which course is invalid so the user can repair input instead of receiving a plausible wrong number.

Round At The Boundary
Keep full precision during the calculation and round only the final value for display. This avoids accumulating small rounding errors across courses.
Test Known Results
Test one course, equal credits, unequal credits, invalid grades, zero credits, and the maximum or minimum grade. Compare the final value with a hand-calculated fixture.
The official dataclasses documentation is useful for course records. Related Python Pool references include lists and tests.
For related input workflows, compare record collections, calculation tests, and validation diagnostics when building a GPA tool.
Frequently Asked Questions
How is GPA calculated in Python?
Multiply each course’s grade points by its credit hours, add those products, and divide by the total credit hours.
Why should credits be included?
A weighted GPA gives courses with more credit hours a proportionally larger effect than a simple average of course grades.
How should invalid grades be handled?
Validate the grade against an explicit scale, reject unknown values, and report the input problem instead of silently assigning zero.
How should a zero-credit semester be handled?
Return a clear validation error because division by zero has no meaningful GPA interpretation.
Hi there. The first code listing doesn’t appear to be wrong per se, but it’s really quite unpythonic and shouldn’t be held up as an example to new Python programmers. In the first place, the ‘for’ loop should not index into the ‘grades’ list but rather should iterate over the list directly, such as “for grade in grades:” or similar. Secondly, there is far too much repetition in the for loop. You could easily pre-populate a dictionary which maps letter grades to points (e.g. call it “point_map”) and access that dictionary on each iteration of the loop, giving you something like “points += point_map[grade]” and thereby collapsing twenty-something lines into one.
The first code just gives the idea of GPA Calculation. For a beginner who barely codes in python, it is easy for them to understand. To overcome the cons of the code, the 2nd code might help the beginner to understand the actual use of python.