How the code works
The loop reads the text one character at a time. ord() turns a letter into its number: ord("A") is 65 and ord("a") is 97. Subtracting that base gives the letter’s place in the alphabet, from 0 to 25.
After the shift is added, % 26 wraps the result around, so with a shift of 3 the letter Z lands on C. chr() then turns the number back into a letter. If the cipher itself is new to you, the guide explains it without code.
Spaces, punctuation and digits take the else branch and stay as they are. The isascii() check is there for accented letters: without it, é counts as a letter and at a shift of 3 comes out as j, which you can’t decrypt back to é.
Decrypting in Python
You don’t need a second function. Decrypting is the same shift in the other direction, so pass a negative number:
print(caesar("Khoor Zruog!", -3))# Hello World!This works because Python’s % never returns a negative number here: (0 - 3) % 26 gives 23, which takes A back to X. C, Java and JavaScript would give -3, so the JavaScript version needs an extra step.
A program with user input
Homework tasks often ask for a program that reads the text and the shift from the keyboard. Add this under the function:
text = input("Text: ")shift = int(input("Shift: "))mode = input("Encrypt or decrypt? (e/d): ") if mode == "d": shift = -shift print(caesar(text, shift))input() always returns a string, so the shift has to go through int(). If someone types a word instead of a number, Python stops with a ValueError.
Breaking the cipher with brute force
An alphabet of 26 letters leaves only 25 shifts to try, so a short loop can read a message without the key. Look through the output for the line that makes sense:
def crack(text): for shift in range(1, 26): print(shift, caesar(text, -shift)) crack("Khoor Zruog!")# 1 Jgnnq Yqtnf!# 2 Ifmmp Xpsme!# 3 Hello World!# ...The article on brute force attacks goes deeper, and the online solver does the same in your browser.
A shorter version with str.translate
Once you understand the loop, you can replace it with a translation table. str.maketrans() pairs each letter with its shifted version, and translate() applies the table to the whole text. Accented letters stay untouched here as well.
from string import ascii_lowercase as lower, ascii_uppercase as upper def caesar(text, shift): shift %= 26 table = str.maketrans( lower + upper, lower[shift:] + lower[:shift] + upper[shift:] + upper[:shift], ) return text.translate(table)It gives the same results and is usually faster on long texts, because the table is built once. For a first assignment the loop is easier to explain.
Other alphabets and digits
To shift anything beyond A to Z, keep the alphabet in a string and find each character with index(). In this example the alphabet includes digits:
ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789" def caesar_alphabet(text, shift): result = "" for char in text: letter = char.lower() if letter in ALPHABET: index = (ALPHABET.index(letter) + shift) % len(ALPHABET) new = ALPHABET[index] result += new.upper() if char.isupper() else new else: result += char return result print(caesar_alphabet("Room 101", 3))# Urrp 434The wrap-around now uses len(ALPHABET), so you only have to change the string to support another language. Our Caesar cipher tool has ready alphabets for Polish, German, Spanish and French if you want to compare results.
Encrypting a text file
Read the file, pass its contents to the function, and write the result to a new file. Setting encoding="utf-8" keeps accented characters intact on every system.
with open("message.txt", encoding="utf-8") as source: text = source.read() with open("encrypted.txt", "w", encoding="utf-8") as target: target.write(caesar(text, 3))The same cipher as a class
A class is useful when one key is used many times, or when a course asks for object-oriented code. It stores the shift and uses the function from the top of this page:
class CaesarCipher: def __init__(self, shift=3): self.shift = shift def encrypt(self, text): return caesar(text, self.shift) def decrypt(self, text): return caesar(text, -self.shift) cipher = CaesarCipher(5)print(cipher.encrypt("Hello World!"))# Mjqqt Btwqi!Testing your code
A few assert lines catch most bugs: wrapping past Z, keeping upper and lower case, leaving punctuation alone, and getting the original text back. If nothing is wrong, the program prints OK.
assert caesar("ABC", 3) == "DEF"assert caesar("xyz", 3) == "abc"assert caesar("Hello, World!", 5) == "Mjqqt, Btwqi!"assert caesar(caesar("Python", 7), -7) == "Python"assert caesar("A", 26) == "A"assert caesar("é", 3) == "é"print("OK")Common mistakes
The classic one is writing chr(ord(char) + shift) without the modulo, which sends Z at a shift of 3 to the symbol ]. Another is using the base 65 for every letter, which breaks lower case. Beginners also often forget int() around input(), and Python then stops with a TypeError as soon as the shift is added.
Frequently asked questions
Which Python version do I need?
Python 3.7 or newer, because that is when isascii() was added. We ran the code on this page on Python 3.9. It needs no extra packages.
Can I use this to protect real data?
No. Anyone can break it in seconds with the loop shown above. Use it to learn and for puzzles, and use a maintained library with modern encryption for passwords or private data.
How can I check that my result is right?
Enter the same text and shift in our Caesar cipher tool and compare. You can also call the free REST API from your own tests.