A LITTLE CLARITY

Your Python handbook.

Every Python word in the game, and the ones that come next. Big ideas, in small words.

move(direction, steps)

Moves Noodle. The direction is "up", "down", "left" or "right", and the number of steps is optional (it's 1 if you leave it out). If something's in the way, Noodle bumps into it and stays put.

move("right")
move("down", 3)
Noodle's world

look(direction)

Gives back what's next to Noodle in that direction, as a string: "wall", "grass", "snack", "home", "water", "ice", "door", "open door", "bridge", "gap", "stone" or "sign".

if look("right") == "wall":
    move("down")
Noodle's world

say(...) and print(...)

Both make Noodle say something in a speech bubble. Give them as many values as you like, separated by commas.

say("hi!")
print("I have", 3, "snacks")
Noodle's world

read()

Reads the sign Noodle is standing on. Signs can hold numbers, text, lists or tuples. Walk onto the sign first!

move("right")
route = read()
Noodle's world

where()

Noodle's position as a tuple (x, y). x counts to the right and y counts down, starting from the top-left corner.

x, y = where()
Noodle's world

at_home() and snacks_left()

at_home() is True when Noodle is home. snacks_left() gives back how many snacks haven't been eaten yet.

while not at_home():
    move("right")
say(snacks_left())
Noodle's world

The world's rules

Some variables shape the world itself. door_open (True/False) opens doors, pond ("water"/"ice") freezes ponds, bridge_length (a whole number) builds bridge planks, and stones (a list) decides which stepping stones exist. They're checked live while your code runs.

door_open = True
pond = "ice"
bridge_length = 5
stones = ["stone", "stone"]
Noodle's world

print()

Shows values. Separate several values with commas, and print puts spaces between them.

print("Hello, world!")
print("2 + 2 is", 2 + 2)
Introduction

Comments

Everything after a # is a comment: a note for humans that Python skips. Handy for explaining code, or for switching a line off.

# this whole line is a comment
move("right")  # so is this part
Introduction

Running code: top to bottom

Python runs your program one line at a time, from top to bottom, unless a loop, if or function call tells it to jump somewhere else.

print("first")
print("second")
Introduction

Calling a function

A function is a named piece of code. You call it by writing its name and brackets. Values inside the brackets are arguments.

move("right", 3)   # function: move, arguments: "right" and 3
len("hello")       # gives back 5
Introduction

Reading error messages

When Python gets stuck it raises an error. The message tells you *what* went wrong and the line number tells you *where*. Read the last line first. Common ones: SyntaxError (Python can't read the line), NameError (unknown name, often a typo), TypeError (wrong kind of value), IndentationError.

mvoe("right")
# NameError: name 'mvoe' is not defined
Introduction

Variables

name = value stores a value under a name. Use the name later and Python fills in the value. Assigning again replaces the old value.

steps = 3
steps = steps + 1
print(steps)   # 4
Variables and types

Naming variables

Names can contain letters, digits and _, but can't start with a digit. Python cares about capitals (Age and age are different). The Python style is snake_case: lowercase words joined with underscores.

snack_count = 3
bridge_length = 5
Variables and types

int and float

An int is a whole number (3, -12). A float has a decimal point (3.0, 0.5). Dividing with / always gives a float.

a = 7        # int
b = 7.5      # float
print(10 / 2)   # 5.0
Variables and types

str (strings)

A str is text, in single or double quotes. Text and numbers are different things: "5" is not 5.

name = "Noodle"
food = 'peach'
Variables and types

bool, True and False

A bool is either True or False, always with a capital letter. Comparisons give back booleans.

door_open = True
print(3 > 5)   # False
Variables and types

None

None means "no value". Functions without a return give back None.

result = None
if result is None:
    print("nothing yet")
Variables and types

type()

Tells you the type of a value.

print(type(5))      # <class 'int'>
print(type("5"))    # <class 'str'>
Variables and types

Converting types: int(), float(), str()

Turn values from one type into another. int() also chops off decimals (it doesn't round).

int("5") + 5      # 10
str(5) + "5"      # "55"
int(3.9)          # 3
float("2.5")      # 2.5
Variables and types

+, -, *, /, //, %, **

Maths operators. + add, - subtract, * multiply, / divide (always gives a float), // divide and round down (a whole number), % the remainder, ** to the power of. Brackets work like in maths.

7 + 2     # 9
7 - 2     # 5
7 * 2     # 14
7 / 2     # 3.5
7 // 2    # 3
7 % 2     # 1
7 ** 2    # 49
(1 + 2) * 3   # 9
Variables and types

+= and friends

x += 1 is short for x = x + 1. There's also -=, *= and /=.

steps = 0
steps += 1
steps += 1
print(steps)   # 2
Variables and types

input()

In normal Python, input("question") asks the user to type something and gives it back as a string. (Noodle's world has no keyboard, so it doesn't work in the game.)

name = input("What's your name? ")
print("Hi", name)
Variables and types

==, !=, >=, <=, >, <

Comparisons. == is equal to, != is not equal to, >= is greater than or equal to, <= is less than or equal to, > is greater than, < is less than. They give back True or False. Don't mix up == (compare) and = (store)!

5 == 5      # True
5 != 3      # True
4 >= 4      # True
3 <= 2      # False
3 > 2       # True
3 < 2       # False
"a" == "A"  # False
Conditionals

if

Runs the indented block only if the condition is True. The line ends with a colon, and the block is indented 4 spaces.

if snacks_left() == 0:
    say("all done!")
Conditionals

else

Runs when the if condition was False.

if look("right") == "wall":
    move("down")
else:
    move("right")
Conditionals

elif

"else, if": checks another condition. Python runs the first block whose condition is True, and skips the rest.

if way == "left":
    move("left")
elif way == "down":
    move("down")
else:
    move("right")
Conditionals

and, or, not

and is True when both sides are. or is True when at least one side is. not flips True and False.

if hungry and snacks_left() > 0:
    say("snack time")
if not at_home():
    move("right")
Conditionals

Truthy and falsy

In an if, empty things (0, "", [], None) count as False, and everything else counts as True.

snacks = []
if snacks:
    print("snacks!")
else:
    print("no snacks")
Conditionals

for loops

Repeats the indented block once for each item in a sequence (a range, list, string, file…). The loop variable holds the current item.

for step in range(3):
    move("right")

for letter in "abc":
    print(letter)
Loops

range()

range(5) counts 0, 1, 2, 3, 4. range(1, 5) counts 1 to 4 (it stops *before* the end). range(0, 10, 2) counts in steps of 2.

list(range(5))         # [0, 1, 2, 3, 4]
list(range(1, 5))      # [1, 2, 3, 4]
list(range(0, 10, 3))  # [0, 3, 6, 9]
Loops

while loops

Repeats as long as the condition stays True. Make sure something inside the loop eventually makes it False, or it runs forever.

while not at_home():
    move("right")
Loops

break and continue

break leaves the loop straight away. continue skips the rest of this round and starts the next one.

for i in range(10):
    if i == 3:
        continue   # skip 3
    if i == 6:
        break      # stop at 6
    print(i)
Loops

Indentation

Python uses indentation (4 spaces) to know which lines belong inside a loop, if or function. The same line means something different inside a block than after it.

for i in range(3):
    print("inside, 3 times")
print("after, once")
Loops

Counting and adding up

Start at 0 before the loop, and add to it inside. This pattern is called an accumulator.

total = 0
for price in [3, 5, 2]:
    total += price
print(total)   # 10
Loops

Nested loops

A loop inside a loop. The inner loop runs completely for every round of the outer one.

for row in range(3):
    for col in range(3):
        print(row, col)
Loops

def

Defines a new function. The indented lines are its body, which runs every time the function is called.

def scoop():
    move("down", 2)
    move("up", 2)

scoop()
scoop()
Functions

pass

pass means "do nothing". Python needs at least one line inside a function, loop, if or class, so pass is a placeholder until you write the real code.

def later():
    pass   # fill this in later

class Tile:
    pass
Functions

Parameters and arguments

Parameters are variables in the def line. Arguments are the values you pass in when calling. Each call can use different values.

def zig(n):
    move("right", n)
    move("down", n)

zig(1)
zig(2)
Functions

return

Hands a value back to whoever called the function, and ends the function there.

def double(x):
    return x * 2

print(double(4))   # 8
Functions

Default values

Parameters can have a default value, used when that argument is left out.

def hop(direction, steps=1):
    move(direction, steps)

hop("right")      # 1 step
hop("right", 3)   # 3 steps
Functions

Scope: local and global

Variables created inside a function are local: they only exist while it runs. To change an outside variable from inside a function, declare it global (or better: return the new value).

count = 0

def bump():
    global count
    count += 1
Functions

Docstrings

A string right at the start of a function describes what it does. help(function) shows it.

def scoop():
    """Go down into a pocket and come back up."""
    move("down")
    move("up")
Functions

Lists

A list holds several values in order, in square brackets with commas. Lists can hold any mix of types and can change.

stones = ["stone", "water", "stone"]
numbers = [3, 1, 4, 1, 5]
empty = []
Lists

Indexing: list[0]

Get one item by its position, counting from 0. Negative indexes count from the end: [-1] is the last item.

colors = ["red", "green", "blue"]
colors[0]    # "red"
colors[-1]   # "blue"
colors[1] = "pink"
Lists

Slicing: list[a:b]

list[a:b] gives a new list from index a up to (not including) b. Leave one side out to go to the start or end.

n = [10, 20, 30, 40, 50]
n[1:3]    # [20, 30]
n[:2]     # [10, 20]
n[3:]     # [40, 50]
Lists

.append() and .insert()

.append(x) adds x to the end. .insert(i, x) puts x at position i.

stones = ["stone"]
stones.append("stone")
stones.insert(0, "moss")
Lists

.remove() and .pop()

.remove(x) removes the first x. .pop() removes and gives back the last item (or .pop(i) for position i).

snacks = ["peach", "plum", "fig"]
snacks.remove("plum")
last = snacks.pop()   # "fig"
Lists

len(), in, min(), max(), sum()

len() counts items. x in list checks if something's in it. min, max and sum do what they say.

n = [3, 1, 4]
len(n)     # 3
4 in n     # True
max(n)     # 4
sum(n)     # 8
Lists

Looping over a list

for item in list: goes through every item. enumerate() gives you the index too.

for d in ["up", "right"]:
    move(d)

for i, d in enumerate(["up", "right"]):
    print(i, d)
Lists

Sorting

sorted(list) gives back a new sorted list. list.sort() sorts the list itself. reversed(list) goes through it backwards.

sorted([3, 1, 2])         # [1, 2, 3]
for d in reversed(trip):
    print(d)
Lists

List comprehensions

A short way to build a list from a loop: [expression for item in sequence], optionally with if.

squares = [n * n for n in range(5)]      # [0, 1, 4, 9, 16]
evens = [n for n in range(10) if n % 2 == 0]
Lists

Strings are sequences

A string is a sequence of characters. You can index it, slice it, loop through it and measure it with len(). Strings can't be changed in place: methods give back new strings.

word = "noodle"
word[0]      # "n"
word[1:4]    # "ood"
len(word)    # 6
Strings

f-strings

Put an f before the quotes, and anything in {curly braces} gets filled in with its value.

eaten = 3
print(f"I ate {eaten} snacks!")
print(f"{eaten * 2} if I'm greedy")
Strings

.lower(), .upper(), .title()

Give back a copy in lowercase, UPPERCASE or Title Case.

"PLEASE".lower()   # "please"
"noodle".upper()   # "NOODLE"
Strings

.strip()

Removes spaces (and newlines) from both ends. .lstrip() and .rstrip() do one side.

"  hi  ".strip()     # "hi"
"right\n".strip()    # "right"
Strings

.split() and .join()

.split() breaks a string into a list of words (or split on something else: .split(",")). " ".join(list) glues a list of strings together.

"up up down".split()        # ["up", "up", "down"]
"-".join(["a", "b", "c"])  # "a-b-c"
Strings

.count(), .replace(), .find()

.count(x) counts how often x appears. .replace(old, new) swaps text. .find(x) gives the position of x (or -1). .startswith() and .endswith() check the ends, and x in s checks if x appears anywhere.

"banana".count("a")        # 3
"sss".replace("s", "z")    # "zzz"
"noodle".find("d")         # 3
"oo" in "noodle"           # True
Strings

Escape characters

A backslash starts a special character: \n new line, \t tab, \" a quote inside a string, \\ a backslash.

print("line one\nline two")
print("she said \"hi\"")
Strings

Dictionaries

A dictionary pairs keys with values, in curly braces. Look up a value by its key.

words = {"sss": "right", "hiss": "down"}
words["sss"]          # "right"
Dictionaries and tuples

Adding and changing keys

Assign to a key to add it or change it. del d[key] removes it.

words["ssh"] = "up"
words["sss"] = "RIGHT"
del words["hiss"]
Dictionaries and tuples

.get() and in

Looking up a missing key gives a KeyError. .get(key, default) gives the default instead, and key in d checks first.

words.get("zzz", "?")   # "?"
if "sss" in words:
    print("found it")
Dictionaries and tuples

Looping over a dictionary

for key in d: goes through the keys. .values() gives values, and .items() gives (key, value) pairs.

for key, value in words.items():
    print(key, "means", value)
Dictionaries and tuples

Tuples

A tuple is like a list that can't change, written with round brackets. Good for things that belong together, like (x, y).

home = (5, 3)
home[0]    # 5
Dictionaries and tuples

Unpacking

Split a tuple (or list) into separate variables in one go. Functions can return several values this way.

x, y = (5, 3)

def corners():
    return 0, 9

low, high = corners()
Dictionaries and tuples

open() and with

open(name) opens a file. Using it with with closes the file automatically when the block ends.

with open("route.txt") as f:
    text = f.read()
Files

Reading a file

.read() gives the whole file as one string. for line in f: goes line by line (each line still ends in \n, so .strip() it). .readlines() gives a list of lines.

with open("route.txt") as f:
    for line in f:
        move(line.strip())
Files

Writing a file

Open with "w" to write. This replaces whatever was in the file. .write() doesn't add newlines for you.

with open("diary.txt", "w") as f:
    f.write("dear diary\n")
    f.write("today was nice\n")
Files

Appending to a file

Open with "a" to add to the end of a file instead of replacing it.

with open("diary.txt", "a") as f:
    f.write("one more line\n")
Files

File not found

Opening a file that doesn't exist (for reading) raises FileNotFoundError. Check the name and the folder you're in.

import os
print(os.listdir("."))   # what files are here?
Files

class

A class is a blueprint for making objects. By convention, class names use CapitalWords.

class Door:
    def __init__(self):
        self.is_open = False
Classes I

Objects (instances)

Call the class like a function to build an object. Each object has its own data.

front = Door()
back = Door()
front.is_open = True   # back is still closed
Classes I

__init__ and self

__init__ runs whenever a new object is made. self is the new object; the other parameters are the values you passed in.

class Pond:
    def __init__(self, surface):
        self.surface = surface

pond = Pond("ice")
Classes I

Attributes

Data stored on an object, reached with a dot. Set them in __init__ with self.name = value.

pond.surface            # "ice"
pond.surface = "water"
Classes I

Methods

A function defined inside a class. Its first parameter is always self, the object it's called on.

class Bridge:
    def __init__(self):
        self.length = 0

    def build(self, planks):
        self.length += planks

b = Bridge()
b.build(3)
Classes I

Inheritance

class Child(Parent): makes a class that gets all of the parent's methods and attributes for free.

class Pond:
    def describe(self):
        return "a pond"

class FrozenPond(Pond):
    pass

FrozenPond().describe()   # "a pond"
Classes II

Overriding methods

A child class can define a method with the same name as the parent's to replace it.

class Tile:
    def is_solid(self):
        return False

class Stone(Tile):
    def is_solid(self):
        return True
Classes II

super()

Calls the parent's version of a method, so you can add to it instead of replacing it.

class FrozenPond(Pond):
    def __init__(self):
        super().__init__()
        self.surface = "ice"
Classes II

__str__

A special method that decides what print(obj) and str(obj) show. (__repr__ is similar, for the programmer's view.)

class Snack:
    def __init__(self, fruit):
        self.fruit = fruit

    def __str__(self):
        return "a tasty " + self.fruit
Classes II

__len__ and other special methods

Special methods plug your objects into Python's built-in features: __len__ makes len(obj) work, __eq__ makes == work, __add__ makes + work, and __str__ decides what print shows.

class Basket:
    def __init__(self):
        self.items = []

    def __len__(self):
        return len(self.items)

len(Basket())   # 0
Classes II

isinstance()

Checks whether an object is made from a class (or one of its children).

isinstance(Stone(), Tile)   # True
isinstance(5, str)          # False
Classes II

Polymorphism

Different classes can answer the same method call in their own way, so code can treat them all alike.

for tile in [Stone(), Tile(), Lilypad()]:
    print(tile.is_solid())
Classes II

import numpy as np

Numpy is the standard toolbox for numbers in Python. It's imported with the nickname np by tradition. (In the game, it downloads the first time you use it.)

import numpy as np
Numpy and Matplotlib

np.array

An array is like a list made for maths. Maths on an array happens to every item at once.

a = np.array([1, 2, 3])
a * 2        # array([2, 4, 6])
a + a        # array([2, 4, 6])
Numpy and Matplotlib

sum, mean, max, argmax

Whole-array calculations. argmax gives the index of the biggest value rather than the value itself.

a = np.array([3, 1, 7, 2])
a.sum()          # 13
a.mean()         # 3.25
a.max()          # 7
np.argmax(a)     # 2
Numpy and Matplotlib

np.arange and np.linspace

np.arange(0, 10, 2) works like range, but gives an array. np.linspace(0, 1, 5) gives 5 evenly spaced numbers from 0 to 1.

np.arange(0, 10, 2)      # [0 2 4 6 8]
np.linspace(0, 1, 5)     # [0. 0.25 0.5 0.75 1.]
Numpy and Matplotlib

Filtering with conditions

Comparing an array gives an array of True/False, which you can use to pick items.

a = np.array([3, 1, 7, 2])
a[a > 2]       # array([3, 7])
Numpy and Matplotlib

Line charts with plt.plot

import matplotlib.pyplot as plt, then plt.plot(ys) (or plt.plot(xs, ys)) draws a line. In the game, charts appear in Noodle's notes. In normal Python, call plt.show() to open them.

import matplotlib.pyplot as plt

plt.plot([2, 5, 3])
plt.title("Noodle's walk")
plt.xlabel("sign")
plt.ylabel("height")
Numpy and Matplotlib

Bar and scatter charts

plt.bar(labels, values) draws bars, plt.scatter(xs, ys) draws dots, and plt.hist(values) draws a histogram.

plt.bar(["peach", "plum"], [3, 5])
plt.scatter([1, 2, 3], [3, 1, 2])
Numpy and Matplotlib

import

A module is a file of ready-made code. import name loads it, and you use its contents with a dot.

import math
math.sqrt(16)    # 4.0
Modules

from ... import ...

Takes just the names you need out of a module, so you can use them without the module's name in front.

from math import sqrt, pi
sqrt(9)    # 3.0
Modules

import ... as ...

Gives a module a shorter nickname.

import numpy as np
import matplotlib.pyplot as plt
Modules

math

Maths tools: sqrt (square root), ceil (round up), floor (round down), pi, and more. The built-in round() rounds to the nearest whole number.

import math
math.ceil(4.2)     # 5
math.floor(4.8)    # 4
round(4.5)         # 4 (rounds to even!)
Modules

random

Random numbers and choices: randint(a, b) (a whole number from a to b), choice(list), shuffle(list).

import random
random.randint(1, 6)              # a dice roll
random.choice(["up", "down"])
Modules

datetime

Dates and times. date(year, month, day) makes a date, and .strftime() formats it: %A is the weekday name, %d the day, %B the month name, %Y the year.

from datetime import date

d = date(2024, 2, 29)
d.strftime("%A")          # "Thursday"
d.strftime("%d %B %Y")    # "29 February 2024"
date.today()
Modules

Your own modules

Any .py file is a module: put functions in tools.py and import tools from another file in the same folder. Code under if __name__ == "__main__": only runs when the file is run directly, not when imported.

# tools.py
def double(x):
    return x * 2

if __name__ == "__main__":
    print(double(21))
Modules