Temperature Converter
Abstract
Section titled “Abstract”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
floatis preferred overintfor 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/elifchains.
The Math
Section titled “The Math”| From → To | Formula |
|---|---|
| Celsius → Fahrenheit | F = C × 9/5 + 32 |
| Fahrenheit → Celsius | C = (F − 32) × 5/9 |
| Celsius → Kelvin | K = C + 273.15 |
| Kelvin → Celsius | C = K − 273.15 |
| Fahrenheit → Kelvin | K = (F − 32) × 5/9 + 273.15 |
| Kelvin → Rankine | R = K × 9/5 |
| Rankine → Fahrenheit | F = 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.
Prerequisites
Section titled “Prerequisites”- 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).
Getting Started
Section titled “Getting Started”Create the project
Section titled “Create the project”- Create a folder named
tempconverter. - Inside, create
tempconverter.py. - Open the folder in your code editor.
Write the code
Section titled “Write the code”Add this to tempconverter.py:
Temperature Converter
pch.viewSource"""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:
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.0What it produces
Section titled “What it produces”Running the file exactly as it ships takes 0.2 s and prints:
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.
How it fits together
Section titled “How it fits together”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.
flowchart TD
RUN(["python tempconverter.py"])
ask("ask")
celsius_to_fahrenheit("celsius_to_fahrenheit")
fahrenheit_to_celsius("fahrenheit_to_celsius")
main("main")
RUN --> main
main --> ask
main --> celsius_to_fahrenheit
main --> fahrenheit_to_celsius
Step-by-Step Explanation
Section titled “Step-by-Step Explanation”1. Define each conversion as a function
Section titled “1. Define each conversion as a function”def celsius_to_fahrenheit(c):
return c * 9/5 + 32
def fahrenheit_to_celsius(f):
return (f - 32) * 5/9Each function does one thing. The body is the formula directly. Naming functions after what they do makes the program readable without comments.
2. The main() menu
Section titled “2. The main() menu”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.
3. Run the program
Section titled “3. Run the program”main()Calling main() at the bottom of the file kicks the program off when you run python tempconverter.py.
Why float, Not int?
Section titled “Why float, Not int?”Try replacing float(...) with int(...):
int(input("Enter Celsius: ")) # user enters 36.6 → ValueErrorint() 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.
Robust Input
Section titled “Robust Input”The naive version crashes when the user types "hot" instead of a number. Add a helper:
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(...)):
c = ask_number("Enter temperature in Celsius: ")You can also reject obvious physics nonsense — no temperature below absolute zero:
def celsius_to_kelvin(c):
if c < -273.15:
raise ValueError("Temperature below absolute zero is impossible.")
return c + 273.15Refactor: Dispatch Table
Section titled “Refactor: Dispatch Table”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:
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.
Output Formatting
Section titled “Output Formatting”print(c_to_f(35)) prints 95.0. For two decimal places:
print(f"{c_to_f(35):.2f}") # 95.00The :.2f inside the f-string means “format as a float with two digits after the decimal point.”
Common Mistakes
Section titled “Common Mistakes”| Problem | Cause | Fix |
|---|---|---|
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 + 32 | Watch operator precedence; parenthesize when unsure |
ValueError: could not convert string to float: '' | User pressed Enter without typing | Validate raw before converting |
| Off-by-273 for Kelvin | Used 273 instead of 273.15 | Use the more accurate constant |
Variations to Try
Section titled “Variations to Try”1. Convert multiple values at once
Section titled “1. Convert multiple values at once”Accept a comma-separated list:
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")2. CLI flags with argparse
Section titled “2. CLI flags with argparse”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.
3. GUI version
Section titled “3. GUI version”Use Tkinter:
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()4. Weather-API integration
Section titled “4. Weather-API integration”Use requests to fetch the current temperature for a city from an API like OpenWeatherMap, then convert and display.
5. Unit tests
Section titled “5. Unit tests”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.
Real-World Applications
Section titled “Real-World Applications”- 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.
Best Practices Demonstrated
Section titled “Best Practices Demonstrated”- 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/elifladders — easier to maintain. - Format output explicitly —
:.2fproduces predictable, readable numbers.
Next Steps
Section titled “Next Steps”- 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.
Conclusion
Section titled “Conclusion”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.
Pitfalls
Section titled “Pitfalls”- 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/5is 1.8, but9//5is 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 raisesValueErrorand ends the program. Theask()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.
Try it yourself
Section titled “Try it yourself”-
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
-
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
-
`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
-
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.coffeeCtapch.feedbackHeading
pch.feedbackSubheading