Skip to content

Temperature Converter

Converting between temperature scales is one of the cleanest beginner projects: the math is simple, the use case is real, and there is plenty of room to grow the program. In this tutorial you will build a command-line temperature converter that handles Celsius and Fahrenheit at first, then extend it to Kelvin and Rankine, validate user input, and finish with a refactor that uses a dictionary-driven design — the same pattern professional codebases use to keep menus maintainable.

By the end you will understand:

  • The four major temperature scales and how they relate.
  • Why float is preferred over int for measurements.
  • How to organize a script with small, single-purpose functions.
  • How to validate input so the program never crashes.
  • How a dispatch table replaces growing if/elif chains.
From → ToFormula
Celsius → FahrenheitF = C × 9/5 + 32
Fahrenheit → CelsiusC = (F − 32) × 5/9
Celsius → KelvinK = C + 273.15
Kelvin → CelsiusC = K − 273.15
Fahrenheit → KelvinK = (F − 32) × 5/9 + 273.15
Kelvin → RankineR = K × 9/5
Rankine → FahrenheitF = R − 459.67

Reference points:

  • Water freezes at 0 °C / 32 °F / 273.15 K.
  • Water boils at 100 °C / 212 °F / 373.15 K.
  • Absolute zero is 0 K / −273.15 °C / −459.67 °F / 0 °R.
  • Python 3.6 or above.
  • A text editor or IDE (VS Code recommended).
  • Comfort running a Python file from the terminal (see Hello World).
  • Basic understanding of functions (see Simple Calculator if you need a refresher).
  1. Create a folder named tempconverter.
  2. Inside, create tempconverter.py.
  3. Open the folder in your code editor.

Add this to tempconverter.py:

Temperature Converter pch.viewSource
Temperature Converter
"""Temperature Converter — six conversions behind one dispatch table.

The program runs three ways:

    python tempconverter.py           # menu; unattended it plays DEMO_ANSWERS
    python tempconverter.py --test    # the unit tests
    python tempconverter.py --gui     # the tkinter window, needs a display

Everything the docs page teaches lives here, so a reader who copies a snippet
out of the page finds the same function in the file.
"""

import sys
import unittest

# What the menu answers when nobody is at the keyboard. Scripting the demo,
# rather than letting every prompt fall back to the same default, is the only
# way an unattended run exercises more than one branch — with a single default
# the loop either repeats one conversion forever or quits on the first prompt.
DEMO_ANSWERS = iter(["1", "100", "3", "0", "5", "212", "q"])


def ask(prompt, default=""):
    """Read a line, or take the next scripted answer when nobody is there.

    Without this the script raises EOFError as soon as it runs unattended --
    in a test, a scheduled job, or the documentation build that captures this
    output. The substituted answer is printed, never silent, so the captured
    transcript cannot be mistaken for something a person typed.
    """
    try:
        answer = input(prompt)
    except EOFError:
        # `input` has already written the prompt to stdout by the time it
        # raises, so printing it again here would double every line.
        answer = next(DEMO_ANSWERS, default)
        print(f"{answer}   (scripted demo answer)")
        return answer
    return answer.strip() or default


# --- the two conversions everyone starts with -------------------------------

def celsius_to_fahrenheit(c):
    return c * 9 / 5 + 32


def fahrenheit_to_celsius(f):
    return (f - 32) * 5 / 9


# --- and the short names the dispatch table uses ----------------------------

def c_to_f(c):
    return c * 9 / 5 + 32


def f_to_c(f):
    return (f - 32) * 5 / 9


def c_to_k(c):
    return c + 273.15


def k_to_c(k):
    return k - 273.15


def f_to_k(f):
    return c_to_k(f_to_c(f))


def k_to_f(k):
    return c_to_f(k_to_c(k))


def celsius_to_kelvin(c):
    """Kelvin, but refusing the temperatures that cannot exist.

    ``c_to_k`` will happily return a negative Kelvin. This one will not: below
    -273.15 degC there is no colder, so an input under it is a bad reading
    rather than a cold day, and failing loudly beats propagating it.
    """
    if c < -273.15:
        raise ValueError("Temperature below absolute zero is impossible.")
    return c + 273.15


def ask_number(prompt, default="0"):
    """Keep asking until the answer parses as a number."""
    while True:
        raw = ask(prompt, default)
        try:
            return float(raw)
        except ValueError:
            print(f"'{raw}' is not a valid number. Try again.")


CONVERSIONS = {
    "1": ("Celsius to Fahrenheit", c_to_f, "degC", "degF"),
    "2": ("Fahrenheit to Celsius", f_to_c, "degF", "degC"),
    "3": ("Celsius to Kelvin", c_to_k, "degC", "K"),
    "4": ("Kelvin to Celsius", k_to_c, "K", "degC"),
    "5": ("Fahrenheit to Kelvin", f_to_k, "degF", "K"),
    "6": ("Kelvin to Fahrenheit", k_to_f, "K", "degF"),
}


def main():
    """The menu loop. Six conversions, one table, no `elif` ladder."""
    while True:
        print("\nTemperature Converter")
        for key, (label, _, _, _) in CONVERSIONS.items():
            print(f"  {key}. {label}")
        print("  Q. Quit")
        choice = ask("Choice: ", "q").strip()
        if choice.lower() == "q":
            print("Bye.")
            break
        if choice not in CONVERSIONS:
            print("Invalid choice.")
            continue
        label, func, in_unit, out_unit = CONVERSIONS[choice]
        value = ask_number(f"Enter temperature ({in_unit}): ", "0")
        print(f"{value} {in_unit} = {func(value):.2f} {out_unit}")


def gui():
    """The same conversion behind a tkinter window.

    Imported inside the function on purpose: tkinter needs a display, and a
    module-level import would break every headless run of this file for the
    sake of a mode most readers never use.
    """
    import tkinter as tk

    root = tk.Tk()
    root.title("Temperature Converter")
    entry = tk.Entry(root)
    entry.pack()
    result = tk.Label(root)
    result.pack()

    def convert():
        try:
            c = float(entry.get())
        except ValueError:
            result.config(text="Enter a number.")
            return
        result.config(text=f"{c_to_f(c):.2f} degF")

    tk.Button(root, text="Convert", command=convert).pack()
    root.mainloop()


class TestConv(unittest.TestCase):
    """The two temperatures whose conversions everyone already knows."""

    def test_freezing(self):
        self.assertEqual(c_to_f(0), 32)

    def test_boiling(self):
        self.assertEqual(c_to_f(100), 212)

    def test_absolute_zero_rejected(self):
        with self.assertRaises(ValueError):
            celsius_to_kelvin(-300)

    def test_round_trip(self):
        for c in (-40, 0, 37, 100):
            self.assertAlmostEqual(f_to_c(c_to_f(c)), c)


if __name__ == "__main__":
    if "--test" in sys.argv:
        unittest.main(argv=sys.argv[:1], exit=False)
    elif "--gui" in sys.argv:
        gui()
    else:
        main()

Save the file, open a terminal, and run:

command
C:\Users\username\Documents\tempconverter> python tempconverter.py 
Temperature Converter
1. Celsius to Fahrenheit
2. Fahrenheit to Celsius
Enter your choice: 1
Enter temperature in Celsius: 35
Temperature in Fahrenheit: 95.0

Running the file exactly as it ships takes 0.2 s and prints:

python tempconverter.py
 
Temperature Converter
  1. Celsius to Fahrenheit
  2. Fahrenheit to Celsius
  3. Celsius to Kelvin
  4. Kelvin to Celsius
  5. Fahrenheit to Kelvin
  6. Kelvin to Fahrenheit
  Q. Quit
Choice: 1   (scripted demo answer)
Enter temperature (degC): 100   (scripted demo answer)
100.0 degC = 212.00 degF
 
Temperature Converter
  1. Celsius to Fahrenheit
  2. Fahrenheit to Celsius
  3. Celsius to Kelvin
  4. Kelvin to Celsius
  5. Fahrenheit to Kelvin
  6. Kelvin to Fahrenheit
...

The first 20 of 47 lines are shown; the run continues past this point.

Read from the top: this is what runs when you execute the file, and which function calls which. It is generated from the code, so it cannot drift from it.

diagram Diagram mermaid
tempconverter.py
def celsius_to_fahrenheit(c):
    return c * 9/5 + 32
 
def fahrenheit_to_celsius(f):
    return (f - 32) * 5/9

Each function does one thing. The body is the formula directly. Naming functions after what they do makes the program readable without comments.

tempconverter.py
def main():
    print("Temperature Converter")
    print("1. Celsius to Fahrenheit")
    print("2. Fahrenheit to Celsius")
    choice = input("Enter your choice: ")
    if choice == "1":
        c = float(input("Enter temperature in Celsius: "))
        print("Temperature in Fahrenheit:", celsius_to_fahrenheit(c))
    elif choice == "2":
        f = float(input("Enter temperature in Fahrenheit: "))
        print("Temperature in Celsius:", fahrenheit_to_celsius(f))
    else:
        print("Invalid choice.")
  • input() always returns a string.
  • float() turns that string into a number with a decimal — important because temperatures are rarely whole numbers.
tempconverter.py
main()

Calling main() at the bottom of the file kicks the program off when you run python tempconverter.py.

Try replacing float(...) with int(...):

why_float.py
int(input("Enter Celsius: "))   # user enters 36.6 → ValueError

int() refuses fractional input. Real temperatures (body temperature 36.6 °C, room temperature 22.5 °C) are not whole numbers. float() accepts both 36 and 36.6.

The naive version crashes when the user types "hot" instead of a number. Add a helper:

ask_number.py
def ask_number(prompt):
    while True:
        raw = input(prompt)
        try:
            return float(raw)
        except ValueError:
            print(f"'{raw}' is not a valid number. Try again.")

Use it everywhere you would have written float(input(...)):

usage.py
c = ask_number("Enter temperature in Celsius: ")

You can also reject obvious physics nonsense — no temperature below absolute zero:

phys_check.py
def celsius_to_kelvin(c):
    if c < -273.15:
        raise ValueError("Temperature below absolute zero is impossible.")
    return c + 273.15

When you support seven or eight conversions, a long if/elif chain becomes painful. Replace it with a dictionary that maps menu labels to conversion functions:

dispatch.py
def c_to_f(c): return c * 9/5 + 32
def f_to_c(f): return (f - 32) * 5/9
def c_to_k(c): return c + 273.15
def k_to_c(k): return k - 273.15
def f_to_k(f): return c_to_k(f_to_c(f))
def k_to_f(k): return c_to_f(k_to_c(k))
 
CONVERSIONS = {
    "1": ("Celsius → Fahrenheit",  c_to_f, "°C", "°F"),
    "2": ("Fahrenheit → Celsius",  f_to_c, "°F", "°C"),
    "3": ("Celsius → Kelvin",      c_to_k, "°C", "K"),
    "4": ("Kelvin → Celsius",      k_to_c, "K",  "°C"),
    "5": ("Fahrenheit → Kelvin",   f_to_k, "°F", "K"),
    "6": ("Kelvin → Fahrenheit",   k_to_f, "K",  "°F"),
}
 
def main():
    while True:
        print("\nTemperature Converter")
        for key, (label, _, _, _) in CONVERSIONS.items():
            print(f"  {key}. {label}")
        print("  Q. Quit")
        choice = input("Choice: ").strip()
        if choice.lower() == "q":
            break
        if choice not in CONVERSIONS:
            print("Invalid choice.")
            continue
        label, func, in_unit, out_unit = CONVERSIONS[choice]
        value = float(input(f"Enter temperature ({in_unit}): "))
        print(f"{value} {in_unit} = {func(value):.2f} {out_unit}")
 
main()

Notice how easy it is to add a new conversion — one entry in the dictionary, one helper function. No menu logic has to change.

print(c_to_f(35)) prints 95.0. For two decimal places:

fmt.py
print(f"{c_to_f(35):.2f}")   # 95.00

The :.2f inside the f-string means “format as a float with two digits after the decimal point.”

ProblemCauseFix
TypeError: unsupported operand type(s) for *: 'str' and 'float'Forgot to convert input() with float()Wrap input in float(input(...))
Wrong result like 0 + 32 = 32 for 0 °C → F (looks right but for 100 °C you get 132)Used c + 9/5 + 32 instead of c * 9/5 + 32Watch operator precedence; parenthesize when unsure
ValueError: could not convert string to float: ''User pressed Enter without typingValidate raw before converting
Off-by-273 for KelvinUsed 273 instead of 273.15Use the more accurate constant

Accept a comma-separated list:

batch.py
raw = input("Celsius values, comma separated: ")
for piece in raw.split(","):
    c = float(piece.strip())
    print(f"{c} °C = {c_to_f(c):.2f} °F")
cli.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("value", type=float)
parser.add_argument("--from", dest="src", choices=["c","f","k"], required=True)
parser.add_argument("--to",   dest="dst", choices=["c","f","k"], required=True)
args = parser.parse_args()
# dispatch by (args.src, args.dst)

Now python tempconverter.py 100 --from c --to f returns 212.0.

Use Tkinter:

gui.py
import tkinter as tk
root = tk.Tk()
entry = tk.Entry(root); entry.pack()
result = tk.Label(root); result.pack()
def convert():
    c = float(entry.get())
    result.config(text=f"{c_to_f(c):.2f} °F")
tk.Button(root, text="Convert", command=convert).pack()
root.mainloop()

Use requests to fetch the current temperature for a city from an API like OpenWeatherMap, then convert and display.

test_conv.py
import unittest
class TestConv(unittest.TestCase):
    def test_freezing(self):
        self.assertEqual(c_to_f(0), 32)
    def test_boiling(self):
        self.assertEqual(c_to_f(100), 212)
unittest.main()

Run with python test_conv.py.

  • Weather and forecasting tools.
  • Cooking apps that swap between °C and °F for international recipes.
  • Industrial monitoring dashboards that use Kelvin internally and display in °C.
  • Scientific instruments that report in Kelvin or Rankine.
  • Education — building physical intuition for the scales.
  • Single-purpose functions — each conversion has its own function with a clear name.
  • Input validation at the boundary — convert and verify once, trust the value everywhere else.
  • Dispatch table over if/elif ladders — easier to maintain.
  • Format output explicitly:.2f produces predictable, readable numbers.
  • Add Rankine support so the converter handles all four standard scales.
  • Build a GUI with Tkinter for a nicer experience.
  • Add a “smart” mode that auto-detects the unit by suffix (100C, 212F, 300K).
  • Publish it as a PyPI package called tempconv.
  • Pair it with a weather API for live-temperature conversion of any city.

Temperature conversion is the perfect playground for practicing functions, math operators, user input, validation, and code refactoring. You started with a 10-line script and finished with a dispatch-table design that scales cleanly. The same dictionary-of-functions pattern shows up in unit converters, command-line tools, and event handlers throughout real-world Python projects. The full source is on GitHub. Find more beginner projects on Python Central Hub — and if you have questions, reach out at ravikishan.me/contact.

  • Losing the offset. Celsius and Fahrenheit do not share a zero, so the conversion is c * 9/5 + 32 — not a plain ratio. Multiplying by 1.8 alone turns 100 °C into 180 °F instead of 212 °F.
  • Integer division in older code. In Python 3 9/5 is 1.8, but 9//5 is 1, which silently converts 100 °C to 132 °F. The file uses / deliberately.
  • Trusting float(input()) with whatever arrives. Anything that is not a number raises ValueError and ends the program. The ask() wrapper here handles a missing answer, not a malformed one — those are different failures and only one of them is covered.
  • Forgetting that −40 is the fixed point. −40 °C is −40 °F. It is the single best test case, because a conversion that is wrong in either direction will usually still pass a test at 0.
  • celsius_to_fahrenheit(100) returns 212.0, measured by running the file with no input.
  • The formula carries both a scale and an offset: c * 9/5 + 32.
  • / is true division in Python 3; // would floor 9/5 to 1 and break the conversion silently.
  • ask() supplies a default when nothing is typed, so the program runs unattended — that is what makes the output above reproducible.
  • −40 is the temperature at which both scales agree, and the test case worth keeping.
pch.quizTag pch.quizDefaultTitle
  1. Why is `celsius * 1.8` not enough to convert to Fahrenheit?

    pch.quizShowAnswer

    B — The two scales have different zero points, so the conversion needs an offset as well as a ratio — without +32, 100 °C comes out as 180 °F

  2. What would change if the code used `9//5` instead of `9/5`?

    pch.quizShowAnswer

    B — `//` floors the result to 1, so every conversion would be wrong and none of them would raise an error

  3. `ask()` returns a default when nothing is typed. Which failure does that NOT cover?

    pch.quizShowAnswer

    B — A malformed answer — typing 'warm' still raises ValueError, because handling EOF and validating input are different jobs

  4. Why is −40 the most useful single test case for this program?

    pch.quizShowAnswer

    B — It is the fixed point where both scales read the same, so a conversion with a wrong sign or a swapped direction still fails it

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading