Alphabets & Secret Messages
Now the codes earn their keep. Because the alphabet is 26 consecutive numbers, a letter can be counted to, added to and moved along — and the last program on this page, the Caesar cipher, is the one where every idea in this chapter arrives at once: traversal, ord() and chr(), a range check, and % to wrap Z round to A.
1Program 1 — build the alphabet
Print the capital letters A to Z on one line, without typing them out.
# the alphabet, built from the codes
letters = ''
for code in range(65, 91):
letters = letters + chr(code)
print(letters)ABCDEFGHIJKLMNOPQRSTUVWXYZ
for code in range(65, 91):The loop counts NUMBERS, not characters — this is a range loop like any other, and the string only appears when chr() is called. 91 as the stop, because 90 is Z and range() leaves its stop out.
letters = letters + chr(code)The collector from every building program on this page, fed by chr() instead of by a character taken from another string.
print() moves to a new line every time, so printing inside the loop gives a vertical column of 26 letters. The row is collected and printed once — the same reason the triangle patterns build line before printing it.Backwards is the same loop with the range() turned round, and a space added after each letter so the output can be read:
# the alphabet backwards, with a space between the letters
letters = ''
for code in range(90, 64, -1):
letters = letters + chr(code) + ' '
print(letters)Z Y X W V U T S R Q P O N M L K J I H G F E D C B A
64, one step past 65. Going down, one step past the last value you want is one below it. Writing 65 as the stop drops the A — and it is the same rule as the backwards triangles: the stop is one step past the last value, whichever way the loop is going.2Program 2 — the total of a word's codes
Add up the code numbers of every character in a word.
# the total of the codes of a word
word = input('Enter a word: ')
total = 0
for ch in word:
total = total + ord(ch)
print('The codes of', word, 'add up to', total)Enter a word: CAT The codes of CAT add up to 216
67 + 65 + 84 = 216. A pointless-looking program that is set constantly, because it is the shortest thing that proves you can turn a character into a number and accumulate it. It is also the seed of a real idea: adding up a string's codes is the crudest possible checksum — change one letter and the total changes.
ord() goes inside the loop, not around the word. ord(word) raises TypeError: ord() expected a character, but string of length 3 found. One character at a time is the only way it works.3Program 3 — the letter n places along
Ask for a capital letter and a number, and print the letter that many places further along the alphabet — wrapping round from Z back to A.
# the letter that comes n places after another one
letter = input('Enter a capital letter: ')
jump = int(input('How many places along? '))
place = ord(letter) - 65
place = (place + jump) % 26
print(letter, 'plus', jump, 'places is', chr(place + 65))Enter a capital letter: Z How many places along? 3 Z plus 3 places is C
place = ord(letter) - 65Turn the code into a place in the alphabet: A becomes 0, B becomes 1, Z becomes 25. Everything is easier in 0–25 than in 65–90, and this is the line that gets you there.
place = (place + jump) % 26Move along, then wrap. % 26 is the whole trick: 25 + 3 is 28, and 28 % 26 is 2 — which is C. Without it, Z + 3 would give 93, a character that is not a letter at all.
chr(place + 65)Back the other way: add 65 to return from a place in the alphabet to a real code, then chr() for the character. Every cipher program is these three lines with something in the middle.
% 26 means “wrap round the alphabet”. Try to do the arithmetic on the raw codes and the wrapping becomes a mess of ifs.4Program 4 — the Caesar cipher
Shift every letter of a message three places along the alphabet, leaving spaces and punctuation alone. Capitals stay capitals and small letters stay small.
# shift every letter three places along the alphabet
message = input('Enter a message: ')
shift = 3
secret = ''
for ch in message:
if ch.isupper():
place = ord(ch) - 65
place = (place + shift) % 26
secret = secret + chr(place + 65)
elif ch.islower():
place = ord(ch) - 97
place = (place + shift) % 26
secret = secret + chr(place + 97)
else:
secret = secret + ch
print('Secret message:', secret)Enter a message: Attack at Dawn! Secret message: Dwwdfn dw Gdzq!
Three branches, and you have seen all three before. The capitals use 65 as their base and the small letters use 97 — the same sandwich twice, with a different number in it. The else keeps the space and the exclamation mark, which is why the secret message is still readable as a sentence.
else matters more than it looks. Without it, every space and every mark is dropped and Attack at Dawn! encrypts to DwwdfndwGdzq — which cannot be decrypted back to the original, because the information about where the words ended has been thrown away.5Program 5 — decrypt it
Take an encrypted message and shift it back.
# and shift them back again
secret = input('Enter the secret message: ')
shift = 3
message = ''
for ch in secret:
if ch.isupper():
place = ord(ch) - 65
place = (place - shift) % 26
message = message + chr(place + 65)
elif ch.islower():
place = ord(ch) - 97
place = (place - shift) % 26
message = message + chr(place + 97)
else:
message = message + ch
print('Message:', message)Enter the secret message: Dwwdfn dw Gdzq! Message: Attack at Dawn!
+ shift became - shift. And % does the wrapping in both directions, because Python works out (0 - 3) % 26 as 23 — a positive answer, which is exactly what is needed to land back on X. In many other languages that expression is negative and the program breaks; Python is being unusually helpful here, and it is worth knowing that.# what % does at the two ends
print((25 + 3) % 26)
print((0 - 3) % 26)2 23
6Program 6 — prove it works
Encrypt a message and decrypt the result in one program, and check that what comes out is what went in.
# encrypt, then decrypt, and compare with the original
message = 'Meet me at Ten'
shift = 5
secret = ''
for ch in message:
if ch.isupper():
secret = secret + chr((ord(ch) - 65 + shift) % 26 + 65)
elif ch.islower():
secret = secret + chr((ord(ch) - 97 + shift) % 26 + 97)
else:
secret = secret + ch
back = ''
for ch in secret:
if ch.isupper():
back = back + chr((ord(ch) - 65 - shift) % 26 + 65)
elif ch.islower():
back = back + chr((ord(ch) - 97 - shift) % 26 + 97)
else:
back = back + ch
print('Original: ', message)
print('Encrypted:', secret)
print('Decrypted:', back)
print('Same as the original?', back == message)Original: Meet me at Ten Encrypted: Rjjy rj fy Yjs Decrypted: Meet me at Ten Same as the original? True
The sandwich is written on one line here — chr((ord(ch) - 65 + shift) % 26 + 65) — which is the same four steps with the brackets doing the ordering. Read it from the inside out: ord(ch), take 65, shift and wrap, add 65, chr(). Write it in three lines while you are learning it, and in one when you can see all four steps at a glance.
back == message printing True is worth more than any amount of eyeballing the middle line, and it costs one print(). Any program that transforms something and can transform it back should end this way while you are testing it.Original: Meet me at Ten Encrypted: Zrrg zr ng Gra Decrypted: Meet me at Ten Same as the original? True
7Recap
range(65, 91) counts numbers; chr(code) makes each one a character. The alphabet needs no typing out.
The sandwich. Every cipher program is those three steps with something in the middle, and 97 in place of 65 for small letters.
(25 + 3) % 26 is 2. And (0 - 3) % 26 is 23 in Python, so the same expression decrypts as well as it encrypts.
Spaces and punctuation must be added unchanged, or the words run together and the message cannot be read back.
- 1
Print the small-letter alphabet, and then every second letter of it.
Hint ·
range(97, 123), and a step of 2 for the second part. - 2
Print each letter of the alphabet beside its code, five to a line.
Hint · Build a line, and print it whenever the count so far divides by 5.
- 3
Let the user choose the shift, instead of fixing it at 3.
Hint · One
int(input())— the rest of the program does not change at all, which is the point of having named itshift. - 4
Encrypt only the vowels of a message, leaving the consonants alone.
Hint · One more test inside the letter branches. Note it is no longer reversible unless the shifted vowel is still a vowel — think about why.
- 5
Write the reverse-alphabet cipher: A becomes Z, B becomes Y, and so on.
Hint · Inside the sandwich, the new place is
25 - place. No%needed — it cannot go out of range.
Why does the Caesar cipher use % 26 rather than just adding the shift?
In Python, what is (0 - 3) % 26?
An encryption program has no else branch. What happens to 'Attack at Dawn!'?