Python fundamentals

Author

Eshin Jolly

Published

September 22, 2025

1. Meet the Python Notebook

Welcome to the absolute basics tutorial for Python!

This file is called a Python/Jupyter Notebook: an interactive way to write Python code and prose together in a single file.

Notebooks are one of the most popular ways to use Python for data analysis and data-science. A notebook consists of separate cells that demarcate blocks of text or code. The two main types of cells you create are:

  1. Code cell: contains Python you want to execute
  2. Markdown cell: contains prose that you want to render (like this cell)

You can move between cells using the arrow keys on your keyboard and you can execute a cell by using the keyboard-shortcut shift+enter or ctrl+enter

Try selecting the next cell by pressing down and then execute it by pressing shift+enter. You should see some output generated below the cell.

print("You ran me!")

To instead edit the contents of cell just press enter. You can press escape to exit editing mode.

Try that on the next cell by changing the text inside of quotes to something else and then execute it:

print("Change me!")

You can make new cells above or below an existing one using the “+” button in the toolbar, searching in the command palette, or using a keyboard shortcut a for above, b for below` when you’re not editing a cell.

Try making a new cell below this one and then copy and paste the contents of the previous cell into it and execute it

Recap

  • One of the primary ways to work with Python interactively is to use notebooks like this one’
  • Notebooks contain code and prose cells and their execution outputs all in a single file
  • We primary work with notebooks by
    • Make a new cell
    • Write Python (or markdown/text) inside of it
    • Execute the cell to get the output

2. Python basics

Let’s cover some Python basics to help you get going fast.

For more detailed tutorials we highly recommend checking out the links at the bottom of this notebook!

Python is a modern interpreted programming language that is incredibly popular because it looks more like English that most other programming languages.

Just like English, in Python we write statements that can do a variety of things:

  • create Variables which store different values
  • create Functions which are little recipes to turn inputs into outputs
  • create Logic that does different things depending on conditions we check
  • create Loops to repeat bits of logic over-and-over

Let’s look at each of these in turn.

In the examples below, you’ll notice that some lines are prefixed with # and appear in a different color.
These are called code comments, which are ignored by Python and useful for providing short explanations inline.
You can also “comment-out code” which just means to prefix the like with # effectively disabling that line during execution.

2.1 Variables

Variables are names we give to values so we can remember and use them later.

# Assign a value to a variable
name = "penguin"
age = 5
weight = 3.2

# Just typing the name of the variable with print it's value out
name
age
weight

You can see that variables can be of different types depending on the values we want to store. The most common ones are:

  • Numbers (integers or decimals/floats)
  • Text (strings of letters)
  • Boolean (true or false)
# Numbers
integer_number = 42  # whole numbers
decimal_number = 3.14  # decimal numbers (called 'float')
# Text (strings)
species = "human"  # use single or double quotes for text
letter = "H"
# Boolean (True/False)
is_cute = True
can_fly = False

Important

In this note book we’re separating our code into different cells, but any variables we define are avaialble to all cells.
In other words, code-cells provide visual separation while we work, but to Python all code in the notebook is part of the same session.

# We defined species above but can use it anywhere!
print(species)

Basic operations between variables

After we define variables we can use them in a variety of ways.

The simplest operations are just basic calculator-like-arithmetic:

# Math operations
a = 10
b = 3

a + b  # addition
a - b  # subtraction
a * b  # multiplication
a / b  # division
a**b  # power (10 to the power of 3)
a % b  # remainder (10 divided by 3 has remainder 1)

What’s neat is that Python can also use these operations on Text (strings)!

# String operations
first_name = "Emperor"
last_name = "Penguin"

# Combine strings (concatenation)
full_name = first_name + " " + last_name
full_name
# Repeat strings
cheer = "Go! " * 3
cheer

Trying making a few cells below this one and playing with some basic operations

# Your code here
# Your code here
# Your code here

2.2 Functions

Functions in Python are little recipes that we can reuse. Function do the following:

  • take some variables as input (arguments; optional)
  • perform some operations
  • return new variables as output (return values; optional)
# Create a variable
word = "Psychology"

# Check it's length using the len() function
number_of_chars = len(word)
number_of_chars

In the example above, we passed the variable word to the function len and it returned a value counting the number of letters in the word.

But functions don’t have to return anything. Sometimes they are just useful for producing some effect like saving a file or printing something to the screen.

In fact that’s exactly what the print we saw at the beginning does!

text_to_print = "Hello World"

output = print(text_to_print)

If we check the value of output we’ll see it’s empty because print doesn’t return anything.
It just produces an effect of displaying text.

output

Creating your own functions

You can make your own functions using the def keyword in the following way

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

Here we create a new function called add that accepts 2 arguments a and b and returns their sum

Then we can use it like any other function:

add(1, 2)

Try creating you own function called greet that accepts a name as input and returns “Hello” and the name.

You can edit the function definition the cell below and then execute the cell to see if it works

# Edit me!
def greet():
    pass


# Use the function
message = greet("Goofy")
print(message)

Functions also don’t have to take any input. You can simple use them to encapsulate some code you want to run over and over again.

# define the function
def print_hello_goodbye():
    print("hello")
    print("goodbye")


# Run the function no inputs or outputs!
print_hello_goodbye()

2.3 Logic

Python has special keywords that allow us to control our logic with conditional statements: if, elif, and else.

We indent our code to use them like this:

if condition:
  do_something
elif other_condition:
  do_something_else
else:
  otherwise_do_this

Each keyword expects condition or other_condition to be a boolean variable, i.e. True or False.

We can create these using comparison operations between variables.
These are just like the basic operations (e.g. +, -), but instead compare values and return True/False

# Define variables
a = 10
b = 20

# Compare them
a < b

Comparison operations

These are the comparison operators you’ll use most often:

a == b  # equals
a != b  # not equals
a < b  # less than
a <= b  # less than or equal
a > b  # greater than
a >= b  # greater than or equal

One special comparison operator is is which you can use to compare to the special None type.

Remember how print doesn’t return anything? Let’s check using is

output = print("hello")

output is None

Let’s see how to use these with the if/elif/else keywords.

Execute the cell below to see what it prints. Then try changing the name variable and rexecuting the cell to see what happens.

# Define a variable
name = "Eshin"

# Check it's value and do different stuff
if name == "Eshin":
    print("Your role is assistant professor")
else:
    print("I'm not sure what your role is...")

Above we use == to compare the value of the variable name to the string "Eshin" and change what we do if it’s True vs False

We can also combine comparisons using and, or, not for more complex conditional statements.

Below, try playing with guest_name and guest_age and rerunning the cell to understand what’s happening:

guest_name = "Goofy"
guest_age = 18

if guest_age >= 21 and guest_name != "Pluto":
    print("Welcome to Mickey's restaurant!")
else:
    print("Sorry you can't enter")

and, or, and not behave as your intuition might suggest: they combine multiple True/False statements into a single more complex statement

# Define some variables
a = 2
b = -1

a < 5
not a < 5  # negates the check

a < 5 and b > 0  # checks that BOTH are True
a < 5 or b > 0  # checks that EITHER is True

# We can use parentheses to group more complicated statements
(a < 5 and b > 0) or (a < 3)

Here’s an example where we combine what learned about defining our own functions with some conditional logic:

# We're just defining the function in this cell so it doesn't return or print anything
# it just makes subtract_bigger() available to the rest of the cells in the notebook


def subtract_bigger(a, b):
    """Always subtracts the smaller number from the bigger number"""
    if a > b:
        return a - b
    elif b > a:
        return b - a
# 4 - 3 = 1
subtract_bigger(4, 3)

# Same!
subtract_bigger(3, 4)

In the next cell we’ve passed 2 numbers that are the same to subtract_bigger, but our function silently fails… Nothing is returned from the output.

Can you update the logic in the function to handle this? The function should return 0

Remember to re-execute the cell containing the function definition after editing it!

subtract_bigger(4, 4)

Recap

Try using the next few cells to play the with concepts you’ve learned so far:

  • defining variables
  • using basic operations
  • using comparison operations
  • using functions
  • creating functions
  • using logic
# Your code here
# Your code here
# Your code here

3. Beyond strings and numbers

Most often you’ll be using Python multiple variables at once, e.g. a list of participant IDs, or stimuli names.

Python has a few built-in types of “containers” that make it easy to store multiple variables together

3.1 Lists

Lists are the simplest, and most flexible way to multiple variables based on their order.

We create lists using square brackets []

# Create a list
species = ["Adelie", "Gentoo", "Chinstrap"]

# Look at it's value
species

# Use the len function to check it's length
len(species)

To get specific value(s) from a list we index it using square brackets [] and the numerical position of the item we want.

Important
While it may not make much sense now, Python and many other programming languages start counting at 0 not at 1.
This is known as “zero-based-indexing” and actually turns out to be very convenient for many operations.
It will take a little getting used to, but just remember that the:

  • first-item, 0th position
  • second-item, 1st position
  • third-item, 2nd position…

Let’s get the first item in the list:

species[0]

Or the second item

species[1]

Python also let’s using negative positions which counts backwards from the last item in reverse order

species[-1]  # last item

This is much easier that trying to get the last item of a list by first counting it’s length to figure out how many items it has, e.g.

species[len(species) - 1]

We can also grab multiple values by slicing a list.
We provide a range of values we’d like and then get back a smaller list with those values.
We use the following format:

start:stop:step

  • start
    • where to begin
    • can leave out if you want to start at beginning (0th index)
  • stop
    • “up-to” this position
    • not included in the result
    • can leave out if you want to include “until the end”
  • step
    • optional, how to skip between items
    • defaults to 1

Play with start, stop, and step below to get a feel for how they behave

names = ["Eshin", "Emma", "Justin", "Aaron", "Jacob", "Jinwoo"]
start = 0
stop = 2
step = 1

names[start:stop:step]

Some common slicing patterns you’ll might look like:

# Get the first 3 items
# Leaving out start = start at 0
names[:3]
# 3rd name until the end
names[2:]
# Get the last 3 items
names[-3:]

# Take a moment to think/play around about how this works
# In this case:
# step = -3
# stop = len(names) because we left it out
# step = 1 because we left it out
# Reverse the list
names[::-1]

# This works because we're stepping through the list backwards!
# start = 0
# stop = len(names)
# step = -1

Methods

So far we’ve been playing with variables (e.g. names) and functions (e.g. len()) as separate “things” in Python.

However, many objects actually have special functions that come “pre-attached” to them letting them alter that object somehow.
We refer to these functions as an object’s methods and we refer to them using .methodname() syntax

Let’s say we want to add another item to our names list after creating it already. We can use the .append method to do just that!

# Before
names
# Call method
names.append("New Lab Member")

# After
names

Notice that we using .append() just like any other function by passing in 1 or more arguments.
Unlike a regular function, however it doesn’t return anything and just modifies names directly!

Let’s look at another example of similarities and differences between functions and methods

Let’s say we want to add multiple new items to names. We could call .append once for each item, but that’s annoying…

So lists have another method called .extend that expects the input to be list

names.extend(["new name 1", "new name 2", "new name 3"])
names

We could achieve the same functionality by writing a function instead

def extend(list_1, list_2):
    return list_1 + list_2  # adding lists in Python combines them!


combined = extend(["Eshin", "Emma", "Justin", "Aaron"], ["Jacob", "Jinwoo"])
combined

Notice the similarities and differences:

  • both .extend and extend take arguments and create a combined list
  • because .extend is a method, i.e. “belongs” to names we only need to pass in the second list we want and names gets modified in-place
  • because extend is a function, it doesn’t “keep track” of any lists and so we always need to pass in everything we want to combine

You can see all the various methods that an object has in Python by typing . and checking out the scrollable auto-complete or pressing cmd+. if it doesn’t show-up automatically

Try finding a method that let’s you sort the items in a list alphabetically and running it on combined

# You code here

Many of simpler variable types we met earlier also have a variety of helpful methods:

# String methods
species = "chinstrap penguin"
species.upper()  # convert to uppercase
species.capitalize()  # capitalize first letter
species.split()  # split into words

# Number methods
score = 100
score.is_integer()

3.2 Dictionaries

The other most common way to store multiple variables is to order them by a key rather than position.

In Python we can do this with a dictionary, which we can create and index using curly braces {}.

Dictionaries store their entries using using key : value pairs, so we can use a key to index a value

# Create a dictionary
character = {
    "name": "Mickey",
    "favorite_food": "cheese",
    "age": 391,
    "friends": ["Minny", "Goofy"],
}
character

Notice that each value, e.g. Mickey, cheese, etc is associated with a key, e.g. name, favorite_food

This allows to get specific values by referring to their key

character["name"]

character["favorite_food"]

This makes dictionaries different from lists, because values have no concept of “position”
The code below gives us an error because the character dictionary is searching for a key named 0, which doesn’t exist!

# Try to get the "first" value of the dictionary
character[0]

The only way to retrieve values from character is to use one of the valid keys
We can combine what we just learned about methods to checkout the keys and values of character

character.keys()
character.values()

Dictionaries are most useful when you want to store multiple variables together and be able to easily reference them by names that you control (keys).

In other cases, lists are all you need!

3.3 Looping

Now that we have a way to store multiple variables using a list or dictionary we can use looping as a way to step through each item and perform some operation

In Python we iterate or “loop” over a variable that contains other variables using for loops Let’s see an example combining what we learned about methods to grow an empty list with lowercase versions of the items in penguins

# Create list
penguins = ["Adelie", "Gentoo", "Chinstrap"]

# Setup an empty list to store our results
penguins_lowercase = []

# Loop through it
for this_penguin in species:
    # We indent code just like we do for if/elif/else

    # Use the .lower method that all strings have
    new_item = this_penguin.lower()

    # Use the .append method to add the item to the list
    penguins_lowercase.append(new_item)

# Show result
penguins_lowercase

In the code above, Python will repeat everything indented inside the for loop, setting the value of this_penguin to the next item in the list after it’s done.
When there are no more items, the for loop ends and the code below it runs.

To loop over dictionaries we can use the .items() method which returns key, value pairs

for key, value in character.items():
    print("The key is:", key)
    print("The value is: ", value)

Let’s take a look at character.items() more carefully to understand how we can loop over multiple things at the same time
Notice that it has a type called dict_items that looks just like a list [] where each item is ('key', 'value')

The pattern we used above: for key, value
separately sets key to the first variable in each pair, and value to the second variable

items = character.items()
items

While the .items() method is specific to dictionaries we can use this same pattern for item_list1, item_list2
along with the the built-in zip() function to generally loop over multiple things at the same time

Notice how zipped looks like items from above, organized as score, age pairs

subject_scores = [76, 88, 100]
subject_ages = [25, 18, 65]

# We pass the result of zip() to the list() function so we can view it
# for doesn't need us to do this as we'll see below
zipped = list(zip(subject_scores, subject_ages))
zipped

Meaning we can loop over them together:

# for can understand zip() without list()
for score, age in zip(subject_scores, subject_ages):
    print("This score: ", score)
    print("This age: ", age)

Of course we can do whatever we’d like inside a for loop. A common one is combining it with some logic:

# Loop with conditions
weights = [3750, 3800, 3250, 4500, 3650]

# Store how many heavy penguins we have
heavy_count = 0

# Loop over weights
for weight in weights:
    # Check this weight
    if weight > 4000:
        # Updated the value of heavy_count
        heavy_count = heavy_count + 1

# strings prefixed with f allow us to put Python code inside of {} when we print them
print(f"Found {heavy_count} heavy penguins")

Another one is using break and continue to _break_out of for-loop early without running through all the items or to skip processing an item and move to the next item

# Range is a function that creates a list from 0 to N
# in this case 0-9
for i in range(10):
    if i == 5:
        break  # stop at 5
    print(i)

print("---")

# list of 0-4
for i in range(5):
    if i == 2:
        continue  # skip 2
    print(i)

Recap

Try using the next few cells to play the with concepts you’ve learned so far:

  • creating and working with lists
  • crating and working with dictionaries
  • writing for loops
# You code here
# You code here
# You code here

4. Getting help, handling errors, and using libraries

4.1 Getting help

These easiest way to get help from write inside a notebook is to add the special ? operator to the end of any function or method
This will pull-up the “doc-string” telling you about arguments, outputs, and other information

# Look up a function
len?
# Look up the append method on our names list
names.append?

4.2 Understanding errors

Python tries to be pretty helpful about telling you what went wrong and where
But initially it can be challenging and intidimating to understand these messages or “stack-trace”

Let’s try to understand one we saw earlier: trying to get a value from a dictionary using a numeric position instead of a key

character[0]

In the output you can see a few important pieces:

  • the line number of the code the trigger the error (line 1)
  • the exact type of error triggered (KeyError)
  • the error message printed after the error type (0)

Here’s a more complicated example where we make the same error, but it’s buried within more code;

def get_value(key):
    return character[key]


for i in range(10):
    value = get_value(i)
    print(value)

No we see more output, but we can still identify the same important pieces:

  • line number we now have 2 locations:
    • line 5 inside the for loop because we call get_value(i)
    • line 2 inside get_value which because character[key] is using a number for key
  • type KeyError because 0 isn’t a valid key
  • message printed after the error type (0)

In Python we call error outputs a “stack-trace” because Python is tracing the error through the layers of code for us!
Line 5 is where Python crashed, but because that line calls a function, Python can point to the exact problematic line (2) within the function.

There are many other types of errors you can produce and you’ll likely see many of the following:

# TypeError
# Using + with mixed types doesn't make sense!
1 + "name"
# IndexError
letters = ["a", "b"]

# The 3rd value doesn't exist!
# The index/position is beyond the length of the list!
letters[2]
# SyntaxError
# We're missing a ":" after names
# These basically typos that are easiest to spot because you'll see squigglies showing you
if names
    print("We have names")
# NameError
# We haven't created an x variable!
x

If you can’t easily figure out what the issue is from the stack-trace that Python gives you, try looking it up on google or chatgpt.
Both the internet and LLMs are very good at giving you help for common errors because so many people have encountered them before!

You can also check out this brief guide on debugging errors for additional resources


Next Steps

Congratulations! You’ve covered the absolute essentials to start your Python journey.

Here are a few additional to continue learning:

Back to top