Intro to Python Basics
Contents
Intro to Python Basics¶
This tutorial was inspired by and adapted from Whirlwind Tour of Python [CC0 1.0 License], DartBrains [CC BY-SA 4.0 License] and the NumPy Tutorials [BSD-3-Clause License].
Types of Variables¶
Numeric types:
int, float, long, complex
string
boolean
True / False
Python’s simple types are summarized in the following table:
Type |
Example |
Description |
---|---|---|
|
|
String: characters or text |
|
|
integers (i.e., whole numbers) |
|
|
floating-point numbers (i.e., real numbers) |
|
|
Complex numbers (i.e., numbers with real and imaginary part) |
|
|
Boolean: True/False values |
|
|
Special object indicating nulls |
Use the type()
function to find the type for a value or variable
# String
c = 'hello'
print(type(c))
<class 'str'>
# Integer
a = 1
print(type(a))
<class 'int'>
# Float
b = 1.0
print(type(b))
<class 'float'>
# Boolean
d = True
print(type(d))
<class 'bool'>
# None
e = None
print(type(e))
<class 'NoneType'>
# Cast integer to string
print(type(str(a)))
<class 'str'>
String Operators¶
Some of the arithmetic operators also have meaning for strings. E.g. for string concatenation use + sign
String repetition: Use * sign with a number of repetitions
# Assigning and combining strings
a = 'Hello'
b = 'World'
print(a + ' ' + b)
Hello World
# f-strings
firstname = 'Shawn'
print(f'My name is {firstname}')
My name is Shawn
# Combining f-strings
firstname = 'Shawn'
lastname = 'Rhoads'
print(f'My name is {firstname} {lastname}')
My name is Shawn Rhoads
# Repeat String without spaces
print(str(a)*5)
HelloHelloHelloHelloHello
# Repeat String with spaces
print(str(a+' ')*5)
Hello Hello Hello Hello Hello
Math Operators¶
+, -, *, and /
Exponentiation **
Modulo % (Remainder)
# Addition
a = 2 + 7
print(f'a = {a}')
a = 9
# Subtraction
b = a - 5
print(f'b = a-5 = {a}-5 = {b}')
b = a-5 = 9-5 = 4
# Multiplication
print(f'b*2 = {b}*2 = {b*2}')
b*2 = 4*2 = 8
# Division
print(f'a/3 = {a}/3 = {a/3}')
a/3 = 9/3 = 3.0
# Exponentiation
print(f'b^2 = b**2 = {b}**2 = {b**2}')
b^2 = b**2 = 4**2 = 16
# Modulo
print('(Modulo returns the remainder of dividing the left hand operand by right hand operand)')
print(f'remainder of b/9 = remainder of {b}/9 = {b%9}')
(Modulo returns the remainder of dividing the left hand operand by right hand operand)
remainder of b/9 = remainder of 4/9 = 4
Logical Operators¶
Perform logical comparison and return Boolean value
x == y # x is equal to y
x != y # x is not equal to y
x > y # x is greater than y
x < y # x is less than y
x >= y # x is greater than or equal to y
x <= y # x is less than or equal to y
# Works for strings
a = 'hello'
b = 'world'
c = 'Hello'
print('a==b: ' + str(a==b))
print('a==c: ' + str(a==c))
print('a!=b: ' + str(a!=b))
print()
a==b: False
a==c: False
a!=b: True
# Works for numeric
d = 5
e = 8
print('d < e: ' + str(d < e))
print('d >= e: ' + str(d >= e))
d < e: True
d >= e: False
Conditional Logic (if…)¶
Unlike most other languages, Python uses tab formatting rather than closing conditional statements (e.g., end)
Syntax (note spacing):
if condition:
do_something
elif condition:
do_alternative
else:
do_otherwise # often reserved to report an error
# after a long list of options
Implicit conversion of the value to bool()
happens if condition is of a different type than bool, thus all of the following should work:
n = 2
if n:
print("n is non-0 or not None")
n is non-0 or not None
if n is None:
print("n is None")
if n is not str:
print("n is not string")
n is not string
x = 20
y = 21
if y > x:
print('y > x')
elif y < x:
print('y < x')
y > x
if x==20 and y==21:
print('True')
else:
print('False')
True
if x==20 or y==100:
print('True')
else:
print('False')
True
if x==200 or y==100:
print('True')
else:
print('False')
False
It’s also possible to use a while loop to repeat statements while condition remains True:
while condition do:
do_statements
x = 0
end = 10
csum = 0
while x < end:
csum += x
print(x, csum)
x += 1
print("Exited with x==%d" % x )
0 0
1 1
2 3
3 6
4 10
5 15
6 21
7 28
8 36
9 45
Exited with x==10
Python Containers¶
There are 4 main types of builtin containers for storing data in Python:
list \(\checkmark\)
dict \(\checkmark\)
set
tuple
Lists¶
In Python, a list is a mutable sequence of values. Mutable means that we can change separate entries within a list.
Each value in the list is an element or item
Elements can be any Python data type
Lists can mix data types
Lists are initialized with [] or list()
l = [1,2,3]
Elements within a list are indexed (starting with 0)
l[0]
Elements can be nested lists
nested = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Lists can be sliced.
l[start:stop:stride]
Like all python containers, lists have many useful methods that can be applied
a.insert(index,new element)
a.append(element to add at end)
len(a)
list_a = [1,2,3]
print(list_a)
[1, 2, 3]
list_of_strings = ['hello', 'bye', 'hi']
print(list_of_strings)
['hello', 'bye', 'hi']
# Indexing and Slicing
list_b = ['list','of','things']
print(list_b[0])
print(list_b[1:3])
list
['of', 'things']
list = [1,2,3,4,5]
list[0::2]
[1, 3, 5]
# List methods (insert)
list_b.insert(2,'python')
print(list_b)
['list', 'of', 'python', 'things']
# List methods (append)
list_b.append('.')
print(list_b)
['list', 'of', 'python', 'things', '.']
# Methods could be applied to other objects (like strings)
example_string = 'PSYC 347 - Computational Models of Human Social Behavior and Neuroscience'
print(example_string)
print(example_string.upper())
PSYC 347 - Computational Models of Human Social Behavior and Neuroscience
PSYC 347 - COMPUTATIONAL MODELS OF HUMAN SOCIAL BEHAVIOR AND NEUROSCIENCE
Dictionaries¶
In Python, a dictionary (or dict) is mapping between a set of indices (keys) and a set of values
The items in a dictionary are key-value pairs
Keys can be any Python data type
Dictionaries are unordered
# Dictionaries
eng2sp = {}
eng2sp['one'] = 'uno'
print(eng2sp)
{'one': 'uno'}
eng2sp = {'one': 'uno', 'two': 'dos', 'three': 'tres'}
print(eng2sp)
{'one': 'uno', 'two': 'dos', 'three': 'tres'}
# keys vs values
print(eng2sp.keys())
print(eng2sp.values())
dict_keys(['one', 'two', 'three'])
dict_values(['uno', 'dos', 'tres'])
Loops¶
for loop is probably the most popular loop construct in Python:
for target in sequence:
do_statements
for i in [0, 1, 2, 3, 4, 5]:
print(i)
0
1
2
3
4
5
for i in range(6):
print(i)
0
1
2
3
4
5
for i in range(2,5):
print(i)
2
3
4
# indexing using for loops
list_a = ['P','S','Y','C','H']
for index, value in enumerate(list_a):
print(index, value)
0 P
1 S
2 Y
3 C
4 H
example_string = "Python is making science easier"
for c in example_string:
print(c)
P
y
t
h
o
n
i
s
m
a
k
i
n
g
s
c
i
e
n
c
e
e
a
s
i
e
r
# appending to lists in for loop
print(list_b)
print('\n...running for loop\n')
for value in ['a','b','c']:
list_b.append(value)
print(list_b)
['list', 'of', 'python', 'things', '.']
...running for loop
['list', 'of', 'python', 'things', '.', 'a', 'b', 'c']
# manipulating lists using for loops
for value in ['m','o','d','e','l','s']:
print(value.upper())
M
O
D
E
L
S
# only capitalize every other letter
for index, value in enumerate(['m','o','d','e','l','s']):
if index % 2:
print(value.upper())
else:
print(value)
m
O
d
E
l
S
As an alternative, list comprehension is a very powerful technique allowing for efficient construction of new lists.
[a for a in l]
# list comprehension
list_example = [10, 11, 12, 13, 14, 15]
print([x*9 for x in list_example])
[90, 99, 108, 117, 126, 135]
# List Comprehension with methods
list_example2 = ['words', 'to', 'change']
print([x.upper() for x in list_example2])
['WORDS', 'TO', 'CHANGE']
Functions¶
A function is a named sequence of statements that performs a computation. You define the function by giving it a name, specify a sequence of statements, and optionally values to return. Later, you can “call” the function by name.
def make_upper_case(text):
return (text.upper())
The expression in the parenthesis is the argument.
It is common to say that a function “takes” an argument and “returns” a result.
The result is called the return value.
The first line of the function definition is called the header; the rest is called the body.
The header has to end with a colon and the body has to be indented. It is a common practice to use 4 spaces for indentation, and to avoid mixing with tabs.
Function body in Python ends whenever statement begins at the original level of indentation. There is no end or fed or any other identify to signal the end of function. Indentation is part of the the language syntax in Python, making it more readable and less cluttered.
string = "Python is making science easier"
string_upper = string.upper()
print(string_upper)
PYTHON IS MAKING SCIENCE EASIER
make_upper_case(string)
'PYTHON IS MAKING SCIENCE EASIER'
Classes¶
(from: https://realpython.com/python3-object-oriented-programming/)
"The primitive data structures available in Python, like numbers, strings, and lists are designed to represent simple things like the cost of something, the name of a poem, and your favorite colors, respectively.What if you wanted to represent something much more complicated?
For example, let’s say you wanted to track a number of different animals. If you used a list, the first element could be the animal’s name while the second element could represent its age.
How would you know which element is supposed to be which? What if you had 100 different animals? Are you certain each animal has both a name and an age, and so forth? What if you wanted to add other properties to these animals? This lacks organization, and it’s the exact need for classes.
Classes are used to create new user-defined data structures that contain arbitrary information about something. In the case of an animal, we could create an Animal() class to track properties about the Animal like the name and age.
It’s important to note that a class just provides structure—it’s a blueprint for how something should be defined, but it doesn’t actually provide any real content itself. The Animal() class may specify that the name and age are necessary for defining an animal, but it will not actually state what a specific animal’s name or age is.
It may help to think of a class as an idea for how something should be defined.”
Let’s create a Cat
class! What are some attributes of a Cat
? It should have a name
and age
! It can also be tired
and hungry
. What are some actions (or methods) that the Cat
can do? It feed
and sleep
and meow
! Should feeding and sleeping update specific attributes of the Cat
? Now that we have our Cat
, let’s code it up!
class Cat:
# Initializer / Instance Attributes
def __init__(self, name, age):
self.name = name
self.age = age
self.hungry = True
self.tired = True
# instance method
def description(self):
__name_age__ = "{} is {} years old. ".format(self.name, self.age)
if self.tired == True:
__is_tired__ = "{} is tired. ".format(self.name)
else:
__is_tired__ = "{} is not tired. ".format(self.name)
if self.hungry == True:
__is_hungry__ = "{} is hungry. ".format(self.name)
else:
__is_hungry__ = "{} is not hungry. ".format(self.name)
return str(__name_age__+__is_tired__+__is_hungry__)
# instance method
def meow(self):
return "{} says {}".format(self.name, "Meow! Meow!")
def feed(self):
self.hungry = False
print(self.meow())
def sleep(self):
self.tired = False
# Instantiate the Cat object
kitty = Cat("Fat Cat", 6)
# call our instance methods
print(kitty.description())
print(kitty.meow())
Fat Cat is 6 years old. Fat Cat is tired. Fat Cat is hungry.
Fat Cat says Meow! Meow!
kitty.feed()
Fat Cat says Meow! Meow!
print(kitty.description())
Fat Cat is 6 years old. Fat Cat is tired. Fat Cat is not hungry.
kitty.sleep()
print(kitty.description())
Fat Cat is 6 years old. Fat Cat is not tired. Fat Cat is not hungry.
kitty.name = 'Shawn'
kitty.meow()
'Shawn says Meow! Meow!'
Modules¶
A Module is a python file that contains a collection of related definitions. Python has hundreds of standard modules. These are organized into what is known as the Python Standard Library. You can also create and use your own modules. To use functionality from a module, you first have to import the entire module or parts of it into your namespace
To import the entire module:
import module_name
You can also import a module using a specific name:
import module_name as new_module_name
To import specific definitions (e.g. functions, variables, etc) from the module into your local namespace:
from module_name import name1, name2
import os
from glob import glob
To get the curent directory, you can use: os.path.abspath(os.path.curdir)
Let’s use glob, a pattern matching function. We will use an if statement to print:
A list of directories if we are using Google Colab
A list of ipynb files in the current folder if we are using Jupyter Notebook locally
if 'google.colab' in str(get_ipython()):
print('Running on Colab... Printing list of directories')
data_file_list = glob('/*')
else:
print('Not running on Colab... Printing list of files')
data_file_list = glob(os.path.join(os.path.curdir,'*ipynb'))
print(data_file_list)
Not running on Colab... Printing list of files
['./module-01-03_Python-Exercises.ipynb', './module-01-02_Working-with-Data.ipynb', './module-04-01_Prosocial-RL-Exercises.ipynb', './module-01-00_Jupyter-Notebooks-Colab.ipynb', './module-03-00_Two-Armed-Bandit.ipynb', './module-01-00_Jupyter-Notebooks.ipynb', './module-04-00_Social-Learning.ipynb', './module-02-00_Linear-Modeling.ipynb', './module-01-01_Intro-to-Python.ipynb', './module-03-02_RL-Exercises.ipynb', './module-02-02_Modeling-Exercises.ipynb', './module-03-01_Models-of-Learning.ipynb', './module-02-01_Nonlinear-Modeling.ipynb']
This gives us a list of the files including the relative path from the current directory. What if we wanted just the filenames? There are several different ways to do this. First, we can use the the os.path.basename function. We loop over every file, grab the base file name and then append it to a new list.
file_list = []
for f in data_file_list:
file_list.append(os.path.basename(f))
print(file_list)
['module-01-03_Python-Exercises.ipynb', 'module-01-02_Working-with-Data.ipynb', 'module-04-01_Prosocial-RL-Exercises.ipynb', 'module-01-00_Jupyter-Notebooks-Colab.ipynb', 'module-03-00_Two-Armed-Bandit.ipynb', 'module-01-00_Jupyter-Notebooks.ipynb', 'module-04-00_Social-Learning.ipynb', 'module-02-00_Linear-Modeling.ipynb', 'module-01-01_Intro-to-Python.ipynb', 'module-03-02_RL-Exercises.ipynb', 'module-02-02_Modeling-Exercises.ipynb', 'module-03-01_Models-of-Learning.ipynb', 'module-02-01_Nonlinear-Modeling.ipynb']
It is also sometimes even cleaner to do this as a list comprehension
[os.path.basename(x) for x in data_file_list]
['module-01-03_Python-Exercises.ipynb',
'module-01-02_Working-with-Data.ipynb',
'module-04-01_Prosocial-RL-Exercises.ipynb',
'module-01-00_Jupyter-Notebooks-Colab.ipynb',
'module-03-00_Two-Armed-Bandit.ipynb',
'module-01-00_Jupyter-Notebooks.ipynb',
'module-04-00_Social-Learning.ipynb',
'module-02-00_Linear-Modeling.ipynb',
'module-01-01_Intro-to-Python.ipynb',
'module-03-02_RL-Exercises.ipynb',
'module-02-02_Modeling-Exercises.ipynb',
'module-03-01_Models-of-Learning.ipynb',
'module-02-01_Nonlinear-Modeling.ipynb']
NumPy¶
NumPy is the fundamental package for scientific computing with Python.
import numpy as np
NumPy’s main object is the homogeneous multidimensional array. It is a table of elements (usually numbers), all of the same type, indexed by a tuple of non-negative integers. In NumPy dimensions are called axes.
NumPy’s array class is called ndarray
. It is also known by the alias array
. The more important attributes of an ndarray object are:
ndarray.ndim: the number of axes (dimensions) of the array.
ndarray.shape: the dimensions of the array. This is a tuple of integers indicating the size of the array in each dimension. For a matrix with n rows and m columns, shape will be
(n,m)
. The length of theshape
tuple is therefore the number of axes,ndim
.ndarray.size: the total number of elements of the array. This is equal to the product of the elements of
shape
.ndarray.dtype: an object describing the type of the elements in the array. One can create or specify dtype’s using standard Python types. Additionally NumPy provides types of its own. numpy.int32, numpy.int16, and numpy.float64 are some examples.
ndarray.itemsize: the size in bytes of each element of the array. For example, an array of elements of type
float64
hasitemsize
8 (=64/8), while one of typecomplex32
hasitemsize
4 (=32/8). It is equivalent tondarray.dtype.itemsize
.ndarray.data: the buffer containing the actual elements of the array. Normally, we won’t need to use this attribute because we will access the elements in an array using indexing facilities.
a = np.arange(15) #array of numbers 0 to 14
a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
a.dtype
dtype('int64')
print(a)
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
print(a.shape)
(15,)
print(a.ndim)
1
print(a.dtype.name)
int64
print(a.itemsize)
8
print(a.size)
15
print(type(a))
<class 'numpy.ndarray'>
Creating arrays¶
You can create an array from a regular Python list or tuple using the array function. The type of the resulting array is deduced from the type of the elements in the sequences.
A frequent error consists in calling array with multiple numeric arguments, rather than providing a single list of numbers as an argument.
a = np.array(1,2,3,4) # WRONG
a = np.array([1,2,3,4]) # RIGHT
b = np.array([6, 7, 8])
print(b)
[6 7 8]
print(type(b))
<class 'numpy.ndarray'>
array
transforms sequences of sequences into two-dimensional arrays, sequences of sequences of sequences into three-dimensional arrays, and so on.
c = np.array([(1.5, 2 ,3), (4, 5, 6), (7.1, 7.2, 7.3)])
print(c)
[[1.5 2. 3. ]
[4. 5. 6. ]
[7.1 7.2 7.3]]
print(c.shape)
(3, 3)
The function zeros
creates an array full of zeros, the function ones
creates an array full of ones, the function random.rand
creates an array of random floats from a uniform distribution over [0, 1], and the function empty
creates an array whose initial content is random and depends on the state of the memory. By default, the dtype of the created array is float64
.
np.zeros((3,4))
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
test = np.ones((2,3,4), dtype=np.int16)
print(test)
[[[1 1 1 1]
[1 1 1 1]
[1 1 1 1]]
[[1 1 1 1]
[1 1 1 1]
[1 1 1 1]]]
np.random.rand(3,2)
array([[0.90508547, 0.71195178],
[0.72020388, 0.16838038],
[0.47611324, 0.5349403 ]])
np.empty((2,3)) # uninitialized, output may vary
array([[0.90508547, 0.71195178, 0.72020388],
[0.16838038, 0.47611324, 0.5349403 ]])
To create sequences of numbers, NumPy provides a function analogous to range
that returns arrays instead of lists.
np.arange( 10, 30, 5 ) # array from 10 to 30 in increments of 5
array([10, 15, 20, 25])
Shape Manipulation¶
Three main functions include:
ravel()
flattens an arrayreshape()
changes the shape of arraystranspose()
transposes the array
example = np.random.rand(4,4)
print(example)
[[0.14101142 0.52124336 0.04065392 0.36218001]
[0.2233788 0.0250243 0.12087429 0.96521804]
[0.75529428 0.98640117 0.64910287 0.74553599]
[0.23558827 0.09218231 0.80696487 0.45230181]]
example*10
array([[1.41011423, 5.21243359, 0.40653922, 3.62180012],
[2.23378805, 0.25024297, 1.20874287, 9.65218044],
[7.55294279, 9.86401174, 6.49102866, 7.45535994],
[2.35588273, 0.92182308, 8.06964873, 4.5230181 ]])
example_flat = example.ravel() # returns the array, flattened
print(example_flat)
[0.14101142 0.52124336 0.04065392 0.36218001 0.2233788 0.0250243
0.12087429 0.96521804 0.75529428 0.98640117 0.64910287 0.74553599
0.23558827 0.09218231 0.80696487 0.45230181]
example_flat.reshape(2,8) # returns the array with a modified shape 2x8
array([[0.14101142, 0.52124336, 0.04065392, 0.36218001, 0.2233788 ,
0.0250243 , 0.12087429, 0.96521804],
[0.75529428, 0.98640117, 0.64910287, 0.74553599, 0.23558827,
0.09218231, 0.80696487, 0.45230181]])
example_flat.reshape(4,4) # returns the array back to original shape
array([[0.14101142, 0.52124336, 0.04065392, 0.36218001],
[0.2233788 , 0.0250243 , 0.12087429, 0.96521804],
[0.75529428, 0.98640117, 0.64910287, 0.74553599],
[0.23558827, 0.09218231, 0.80696487, 0.45230181]])
test = np.random.rand(2,3,6)
print(test)
[[[0.47246735 0.93497331 0.6176843 0.24898456 0.03014371 0.97996762]
[0.63009135 0.45392435 0.9490089 0.58247366 0.74585935 0.36274748]
[0.22745285 0.93103471 0.46541396 0.43484719 0.65265412 0.65606734]]
[[0.41280335 0.13149874 0.1255207 0.39640958 0.40324838 0.97229183]
[0.33352982 0.74574888 0.98623868 0.26142712 0.08086071 0.36000286]
[0.71168076 0.93815648 0.77675183 0.86574354 0.21302474 0.78735483]]]
print(test.ravel())
[0.47246735 0.93497331 0.6176843 0.24898456 0.03014371 0.97996762
0.63009135 0.45392435 0.9490089 0.58247366 0.74585935 0.36274748
0.22745285 0.93103471 0.46541396 0.43484719 0.65265412 0.65606734
0.41280335 0.13149874 0.1255207 0.39640958 0.40324838 0.97229183
0.33352982 0.74574888 0.98623868 0.26142712 0.08086071 0.36000286
0.71168076 0.93815648 0.77675183 0.86574354 0.21302474 0.78735483]
print(example.transpose())
[[0.14101142 0.2233788 0.75529428 0.23558827]
[0.52124336 0.0250243 0.98640117 0.09218231]
[0.04065392 0.12087429 0.64910287 0.80696487]
[0.36218001 0.96521804 0.74553599 0.45230181]]
“The Zen of Python”¶
“Python aficionados are often quick to point out how “intuitive”, “beautiful”, or “fun” Python is. While I tend to agree, I also recognize that beauty, intuition, and fun often go hand in hand with familiarity, and so for those familiar with other languages such florid sentiments can come across as a bit smug. Nevertheless, I hope that if you give Python a chance, you’ll see where such impressions might come from. And if you really want to dig into the programming philosophy that drives much of the coding practice of Python power-users, a nice little Easter egg exists in the Python interpreter: simply close your eyes, meditate for a few minutes, and import this
”
import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!