Python Primer

Table of contents

1. Basics

1.1 Variable replacement

Python variable names start with a lower case letter, can contain letters, underscores, words, and numbers. There are many ways to use variables inside strings. The following examples show the different ways we can do it:

v = "sushi" 
u = 10
print(v) 
>>> sushi 
print("I like " + v) 
>>> I like sushi
print ('I like', v) 
>>> I like sushi
print(f'I like {v}') 
>>> I like sushi
print("I like %s" % (v)) 
>>> I like sushi
print("I ordered %s and I paid $%f" % (v, u)) 
>>> I ordered sushi and I paid $10.000000
print("I ordered %s and I paid $%i" % (v, u)) 
>>> I ordered sushi and I paid $10

We use input() to prompt users for input from the command line:

name = input('What is your name?\n')
print(f'Hello {name}.')

1.2 String methods

'q'.isupper()
>>> False
'Q'.isupper()
>>> True
'hello'.upper()
>>> 'HELLO'
'  abc '.strip()
>>> 'abc'
'abca'.strip('ac')
>>> 'b'
'aaabcaa'.count('a')
>>> 5
'aabcaa'.count('ab')
>>> 1

If occurrences of the argument overlap, only the first counts:

'ababa'.count('aba')
>>> 1

We can use this to count words:

'this is a string with many words'.count(' ')
>>> 6 # missing last word
'this is a string with many words'.count(' ') + 1
>>> 7 # correct answer

Although not a string method, but the in relational operator is useful with strings. Note that the letters in the string have to be consecutive to return True.

'ppl' in 'apple'
>>> True

'al' in 'apple'
>>> False

2. Lists

Lists in Python can hold multiple item types. Here’s how you can create a list:

items = ["sushi", 10, "sashimi", True]

You can check whether an item is in a list by using the in operator:

print("sashimi" in items) 
>>> True

You can use the index() method to find the index of an item in the list:

items.index("sushi") 
>>> 0
items.index(10) 
>>> 1

Using a negative index will access items from the end:

items[-1] 
>>> True

You can also extract a part of a list, using slices:

items[0:2] 
>>> ["sushi", 10]
items[2:] 
>>> ["sashimi", True]

You can get the number of items contained in a list using the len() function:

len(items) 
>>> 4

len() also works with strings:

len("items") 
>>> 5

You can add items to a list by using append(), extend(), or =+:

items.append("nigiri")
items.extend(["nigiri"])
items += ["nigiri"]

They will all have the same output below:

items = ["sushi", 10, "sashimi", True, "nigiri"]

You can remove an item from a list by using the remove() method with the name of the item you want to remove:

items.remove("nigiri")

To add an item in the middle of a list, at a specific index, use the insert() method:

items.insert(1, "nigiri") # adds "nigiri" at index 1

The list will now become:

items = ['sushi', 'nigiri', 10, 'sashimi', True]

2.1. List copying

You can copy a list in Python, by using the copy() method, or the list() method, or the slice operator [:]. All will create a new list that is a copy of the original list, so you can modify the new list without affecting the original list.

Using the copy() method:

original_list = [1, 2, 3]
new_list = original_list.copy()  

Using the list() method:

original_list = [1, 2, 3]
new_list = list(original_list)

Using the slice operator [:] method:

original_list = [1, 2, 3]
new_list = original_list[:]

2.2. List sorting

You can sort a list by using the sort() method:

items.sort(key=str.lower)

You use key=str.lower to normalize the sort because uppercase is ordered before lowercase.

Note: Sorting modifies the original list.

To avoid that, you can make a copy of the list first, like this:

items_copy = items[:]

You can also use the sorted() method to return a sorted copy of the original list:

items_copy = sorted(items, key=str.lower))

2.3. List iteration

In this section we discuss for loops and while loops.

2.3.1 Iteration using for loops

In Python, we can iterate over a list using a for loop. In this example, we first create a list called my_list with five elements. Then, we use a for loop to iterate over each item in the list, and print it to the console. Using in when iterating with for loops, the loop variable takes on the value of the list item or the string character in each iteration:

my_list = [1, 2, 3, 's', 't']

for item in my_list:
    print(item)

>>> 1
>>> 2
>>> 3
>>> s
>>> t

Similarly, we can iterate using a string:

for char in "olive":
    print(char)

>>> o
>>> l
>>> i
>>> v
>>> e

We can also use the range() function to specify the number of times we iterate. When using range() the loop variable is an integer and not the value of the list item or string character. If we provide one argument to range(), we get a range from 0 to 1 less that argument:

for num in range(4):
    print(num)

>>> 0
>>> 1
>>> 2
>>> 3

If we provide 2 arguments to range(), we get a sequence from the first argument up to but not including the 2nd argument:

for num in range(2, 6):
    print(num)

>>> 2
>>> 3
>>> 4
>>> 5

We can use the range() function to count down in a for loop. And we can also use a step size while looping. To count down in a for loop the step size needs to be negative:

for num in range(6, 2, -1):
    print(num)

>>> 6
>>> 5
>>> 4
>>> 3

To count down to 0 (including 0), the 2nd argument needs to be -1:

for num in range(6, -1, -1):
    print(num)

>>> 6
>>> 5
>>> 4
>>> 3
>>> 2
>>> 1
>>> 0

We can use a range for loop to process a string from right to left:

s = 'sashimi'

for i in range(len(s) -1, -1, -1):
    print(s[i])

>>> i
>>> m
>>> i
>>> h
>>> s
>>> a
>>> s

To access and print out the index and the item in a list, you should wrap the sequence into the enumerate() function.

What enumerate does is it takes an iterable (like a list) and returns a new iterable. This new iterable consists of tuples, and each tuple contains two values:

– The first value is the index of the item (starting from 0).
– The second value is the item itself.

Basically, we are converting a one-dimensional list to a two-dimensional list and then iterating over the inner lists:

items = ['a', 'b', 'c', 'd']
for index, item in enumerate(items):
    # enumerate returns: [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]
    print(index, item)

>>> 0 a
>>> 1 b
>>> 2 c
>>> 3 d

The above example works due to a concept called ‘Tuple Unpacking’. Python allows you to unpack, or assign, the elements of a tuple (or list) into multiple variables in a single operation, provided the number of variables on the left side of the assignment matches the number of elements in the tuple (or list). This is fancy stuff. Here’s an example:

list = [[1,'a'], [2, 'b'], [3, 'c']]

for first_item, second_item in list:
    print(first_item, second_item)

>>> 1 a
>>> 2 b
>>> 3 c

Let’s look at the more traditional way of iterating over a two-dimensional list in Python using for loops. In this example, we first create a two-dimensional list called my_list with three rows and three columns. Then, we use a nested for loop to iterate over each item in the list, and print it to the console. The outer for loop iterates over each row in the list, while the inner for loop iterates over each item in the current row.

my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

for row in my_list:
    for item in row:
        print(item)

Alternatively, you can use indexing i.e. range() to access the elements in a two-dimensional list. In this example, we use nested for loops to iterate over the list using indices. The outer loop iterates over each row in the list, while the inner loop iterates over each item in the current row using indices. We use the len() function to get the length of each row, and the range function to generate the indices for the loops.

my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

for i in range(len(my_list)):
    for j in range(len(my_list[i])):
        print(my_list[i][j])

2.3.2 Iteration using while loops

Let’s move on to while loops. We can also use a while loop to iterate over a list.

In this example, we use a while loop to iterate over each item in the list, and print it to the console. We use a counter variable i to keep track of the index we’re currently at in the list. We use the len() function to get the length of the list, and make sure we stop iterating when we reach the end of the list.

my_list = [1, 2, 3, 4, 5]

i = 0
while i < len(my_list):
    print(my_list[i])
    i += 1

>>> 1
>>> 2
>>> 3
>>> 4
>>> 5

We can process strings using while loops:

s = 'sashimi'

i = 0

while i < len(s):
    print('We have ' + s[i])
    i = i + 1

>>> We have i
>>> We have m
>>> We have i
>>> We have h
>>> We have s
>>> We have a
>>> We have s

To process a string from right to left using a while loop:

s = 'sashimi'

i = len(s) - 1

while i >= 0:
    print('We have ' + s[i])
    i = i - 1

>>> We have i
>>> We have m
>>> We have i
>>> We have h
>>> We have s
>>> We have a
>>> We have s

In the next example, we use nested while loops to iterate over a two-dimensional list using indices. The outer while loop iterates over each row in the list, while the inner while loop iterates over each item in the current row using indices. We use the len() function to get the length of each row, and the counter variables i and j to keep track of the current indices.

my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

i = 0
while i < len(my_list):
    j = 0
    while j < len(my_list[i]):
        print(my_list[i][j])
        j += 1
    i += 1

3. Tuples

Tuples are like lists, but immutable. Once created, you cannot change their contents (no append, remove, etc.). They are useful when you want a fixed collection of values.

You create tuples using parentheses:

t = (1, 2, 3)
print(t[0])
>>> 1

You can mix types, just like lists:

sushi_order = ("sashimi", 12.5, True)

A one-item tuple needs a trailing comma, otherwise Python thinks it’s just a value in parentheses:

not_a_tuple = (5)
type(not_a_tuple)
>>> 

one_item_tuple = (5,)
type(one_item_tuple)
>>> 

Tuples support indexing and slicing just like lists:

t = (10, 20, 30, 40)

t[1]
>>> 20

t[1:3]
>>> (20, 30)

You can also unpack tuples into variables (same idea as in the enumerate() example earlier):

coords = (45.5, -73.6)
lat, lon = coords
print(lat, lon)
>>> 45.5 -73.6

Because tuples are immutable, they can be used as dictionary keys or elements in a set, which lists cannot do.

4. Dictionaries

Dictionaries are key–value mappings. Instead of accessing items by position (index), you access them by a key.

You create a dictionary using curly braces:

menu = {
    "sushi": 10,
    "sashimi": 15,
    "ramen": 12
}

You access values using their keys:

menu["sushi"]
>>> 10

You can also add or update values by assigning to a key:

menu["udon"] = 11      # add new key
menu["sushi"] = 12     # update existing key

If you try to access a key that does not exist, you get a KeyError:

menu["pizza"]
>>> KeyError: 'pizza'

To avoid that, you can use get() and provide a default:

menu.get("pizza", "Not on the menu")
>>> 'Not on the menu'

You can inspect the keys, values, and items:

menu.keys()
>>> dict_keys(['sushi', 'sashimi', 'ramen', 'udon'])

menu.values()
>>> dict_values([12, 15, 12, 11])

menu.items()
>>> dict_items([('sushi', 12), ('sashimi', 15), ('ramen', 12), ('udon', 11)])

items() is often used when iterating:

for dish, price in menu.items():
    print(dish, price)

>>> sushi 12
>>> sashimi 15
>>> ramen 12
>>> udon 11

You can remove keys with del or pop():

del menu["ramen"]

price = menu.pop("sashimi")
print(price)
>>> 15

Dictionaries are extremely common in Python – configuration objects, JSON data, HTTP responses and many other things are usually dictionaries or dictionary-like objects.

5. Sets

Sets are unordered collections of unique items. If you add the same element twice, it will only appear once.

You create a set using curly braces (but with values only, no key–value pairs):

toppings = {"wasabi", "ginger", "soy sauce"}

Membership checks with sets are very fast:

"wasabi" in toppings
>>> True

"mayo" in toppings
>>> False

You can add and remove elements:

toppings.add("mayo")
toppings.remove("ginger")

print(toppings)
>>> {'wasabi', 'soy sauce', 'mayo'}

If you’re not sure an element exists and you don’t want an error, use discard() instead of remove():

toppings.discard("ginger")  # no error if missing

Sets are also great for basic math-style operations like union, intersection, and difference.

a = {1, 2, 3}
b = {3, 4, 5}

a | b      # union
>>> {1, 2, 3, 4, 5}

a & b      # intersection
>>> {3}

a - b      # difference
>>> {1, 2}

A very common use for sets is to remove duplicates from a list:

nums = [1, 2, 2, 3, 3, 3]
unique_nums = list(set(nums))
print(unique_nums)
>>> [1, 2, 3]    # order not guaranteed

6. Functions

Functions let you group reusable logic into a named block. You define them with def, and optionally return a value using return.

A simple function:

def greet(name):
    print(f"Hello {name}!")

greet("Abe")
>>> Hello Abe!

A function that returns a value:

def add(a, b):
    return a + b

result = add(3, 4)
print(result)
>>> 7

If you don’t use return, the function implicitly returns None.

You can provide default values for parameters:

def order_sushi(kind="sashimi", pieces=6):
    print(f"Ordering {pieces} pieces of {kind}")

order_sushi()
>>> Ordering 6 pieces of sashimi

order_sushi("nigiri", 10)
>>> Ordering 10 pieces of nigiri

order_sushi(pieces=12)
>>> Ordering 12 pieces of sashimi

You can also add a docstring (a triple-quoted string as the first line in the function body) to document what your function does:

def add(a, b):
    """Return the sum of a and b."""
    return a + b

help(add)
>>> Help on function add ...
>>> add(a, b)
>>>     Return the sum of a and b.

In Python, functions are “first-class citizens”: you can store them in variables, put them in lists, and pass them to other functions.

def shout(msg):
    print(msg.upper())

def whisper(msg):
    print(msg.lower())

actions = [shout, whisper]

for action in actions:
    action("Sushi time")

>>> SUSHI TIME
>>> sushi time

Understanding functions is key – you will see them everywhere, and you will write lots of them.

7. Classes

Classes are a way to define your own types. They group data (attributes) and behavior (methods) together.

You define a class using the class keyword. The __init__ method is the initializer that runs when you create a new instance. The first parameter of methods is usually called self and refers to the current instance.

class SushiOrder:
    def __init__(self, kind, pieces):
        self.kind = kind
        self.pieces = pieces

    def describe(self):
        print(f"{self.pieces} pieces of {self.kind}")

    def add_piece(self, n=1):
        self.pieces += n

You create (instantiate) an object by calling the class like a function:

order = SushiOrder("nigiri", 8)

order.describe()
>>> 8 pieces of nigiri

order.add_piece(2)
order.describe()
>>> 10 pieces of nigiri

Here:

kind and pieces live on the instance as self.kind and self.pieces.

describe and add_piece are methods that operate on that data.

You can also provide default values in __init__:

class Customer:
    def __init__(self, name, vip=False):
        self.name = name
        self.vip = vip

    def greet(self):
        if self.vip:
            print(f"Welcome back, VIP {self.name}!")
        else:
            print(f"Welcome {self.name}!")

c1 = Customer("Abe", vip=True)
c1.greet()
>>> Welcome back, VIP Abe!

A basic example of inheritance (a class that builds on another class):

class VIPCustomer(Customer):
    def __init__(self, name, level):
        super().__init__(name, vip=True)
        self.level = level

    def greet(self):
        print(f"VIP {self.name}, level {self.level} – your table is ready.")

v = VIPCustomer("Lili", level=3)
v.greet()
>>> VIP Lili, level 3 – your table is ready.

You don’t need to master classes to start using Python, but you’ll encounter them constantly (Django models, requests, responses, forms, etc.), so having this mental model helps:

A class is the blueprint.

An instance is a specific object created from that blueprint.

Methods are just functions that live on a class/instance.

8. Introspection

Introspection in Python means that you can analyze: Functions, variables and objects. This is mostly helpful for troubleshooting without using a debugger tool. In this section we will outline 4 different introspection tools commonly used in Python.

8.1. The dir() method

The dir() global function allows you to return all properties and methods of an object including the built in ones as a list. This is very useful especially when you are not sure what attributes or methods can be called on a specific object.

>>> from courses.models import Course
>>> courses = Course.objects.all()
>>> print(dir(courses[0]))

>>> ['check', 'clean', 'clean_fields', 'created', ... ]

We basically get a list of both private and public functions and attributes. Private functions and attributes are the ones that begin with an underscore.

8.2. The inspect module

Another good introspection method to know is inspect. It's a built-in python module that can also be used to get comprehensive information about a Django object. The getmembers() method return a list of tuple of all the attributes and functions of an object.

>>> from inspect import getmembers
>>> getmembers(user)

But a better way is to filter and return just the functions using the inspect.isfunction() method:

>>> from inspect import getmembers, isfunction
>>> from django.contrib.auth.models import User
>>> functions = [x[0] for x in getmembers(User) if isfunction(x[1])]
>>> print(functions)

8.3. The __dict__ attribute

You can use the __dict__ attribute of python objects which is used to store an object’s (writable) attributes. This allows you to see the readable attribute name as well as the values it contains. You use it by calling .__dict__ on the object like so: user.__dict__.

8.4. The type function

And finally, you can use the type() function to determine the type of any expression like this:

type(9.5)
>>> class 'float'

9. Mini-project: Simple Sushi Order Tracker (Console)

Let’s use lists, dictionaries, functions, and a tiny class to build a very small sushi order tracker you can run in the terminal.

Goal:

  • Let the user pick items from a menu.
  • Keep an “order” in memory.
  • Show a summary at the end.

Nothing fancy, just enough to tie the concepts together.

Step 1 – Define a menu (dictionary)

We’ll store the menu as a dictionary where the key is the item name and the value is the price.

MENU = {
    "sushi": 10,
    "sashimi": 15,
    "ramen": 12,
    "udon": 11,
}

Step 2 – A tiny OrderItem class

This is overkill for such a small script, but it’s a nice excuse to show a small class in action.

class OrderItem:
    def __init__(self, name, price, quantity=1):
        self.name = name
        self.price = price
        self.quantity = quantity

    def total(self):
        return self.price * self.quantity

Step 3 – Helper functions

We’ll write a few functions for:

  • showing the menu
  • asking for a choice
  • adding to the order
  • printing a summary
def show_menu():
    print("\n--- Menu ---")
    for dish, price in MENU.items():
        print(f"- {dish}: ${price}")
    print("Type 'done' when you’re finished.\n")


def ask_choice():
    choice = input("What would you like to order? ").strip().lower()
    return choice


def ask_quantity():
    while True:
        qty = input("How many? ")
        if qty.isdigit() and int(qty) > 0:
            return int(qty)
        print("Please enter a positive number.")

Step 4 – Main loop

This is where we keep asking the user for items until they type done.

def main():
    order = []  # list of OrderItem objects

    print("Welcome to Python Sushi Bar!\n")

    while True:
        show_menu()
        choice = ask_choice()

        if choice == "done":
            break

        if choice not in MENU:
            print("Sorry, that’s not on the menu.\n")
            continue

        qty = ask_quantity()
        item = OrderItem(choice, MENU[choice], quantity=qty)
        order.append(item)
        print(f"Added {qty} x {choice} to your order.\n")

    print_summary(order)

Now we just need a function to print the summary.

Step 5 – Print order summary

def print_summary(order):
    if not order:
        print("\nYou didn’t order anything.")
        return

    print("\n--- Your order ---")
    total = 0

    for item in order:
        line_total = item.total()
        total += line_total
        print(f"{item.quantity} x {item.name} = ${line_total}")

    print("-------------------")
    print(f"Total: ${total}")

Finally, run main() only when this file is executed directly:

if __name__ == "__main__":
    main()

You now have a tiny but complete command-line app that uses:

  • dictionaries for the menu
  • lists to store the order items
  • a small class for each line item
  • functions to keep the code organized

You can extend this later with:

  • discounts for VIP customers
  • saving orders to a file
  • adding/removing items from the current order

…but as a first mini-project it’s enough to give you a feel for “real” Python, not just isolated snippets.