Variables and types in Python

But what is a variable?

A variable is way to store information. In the print_sum example z is a variable to the result of x + y

Variables are names for values. In Python the = symbol assigns the value on the right to the name on the left. The variable is created when a value is assigned to it. Here, Python assigns an age to a variable age and a name in quotation marks to a variable first_name.

  • Variables are names for values.
  • In Python the = symbol assigns the value on the right to the name on the left.
  • The variable is created when a value is assigned to it.
  • Here, Python assigns an age to a variable age and a name in quotation marks to a variable first_name.

In [1]:
age = 29
first_name = 'Christian'

In [2]:
first_name


Out[2]:
'Christian'

If we try to access a variable that we didn't define we get an error:


In [3]:
last_name


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-a60655368f93> in <module>()
----> 1 last_name

NameError: name 'last_name' is not defined

This is a Traceback, comes directly from the Python interpreter. It's the Python way to tell us where we made a mistake.

In this case we have an error called NameError and a message name 'last_name' is not defined

How can we make this code works?


In [ ]:
# Write your code here

Remember

  • Variables' names:
    • can't start with a digit
    • can't contain spaces, quotation marks, or other punctuation
    • may contain an underscore (typically used to separate words in long variable names)
  • Underscores at the start like __alistairs_real_age have a special meaning so we won't do that until we understand the convention.

We can reassign variables and mix them


In [ ]:
age = 18
first_name = first_name + " " + 'Calogero'
first_name

Remember variables' names are CASE SENSITIVE


In [ ]:
FIRST_NAME = FIRST_NAME + 'Calogero'

FIRST_NAME IS NOT first_name

Printing variables

Do you remember our first lines of code?


In [ ]:
def print_sum(x, y):
    z = x + y
    print(z)

print is the function we need to use when we want to print something


In [ ]:
print(first_name)

In [ ]:
print(age)

In [ ]:
print("Hello there")

In [ ]:
print(1)

As you can see we can print variables but also values

How can I print: My name is *first_name* and I am *age* old?


In [ ]:
print(f"My name is {first_name} and I am {age} years old")

let's change this a little bit


In [ ]:
first_name = "Christian"
age = 29

In [ ]:
print(f"My name is {first_name} and I am {age} years old")

We have 3 components here:

* print function
* f""
* variables inside {}

We already know the print function , what is new here is the f"" notation.

It's a new notation called f-strings (available from Python 3.6) to format strings in Python.

Basically we put our text inside f"" and the variables we want to be replaced by their values inside curly brackets, like {first_name}.

This is a real power notation and you can it also when you create variables like this:


In [ ]:
message = f"My name is {first_name} and I am {age} years old"

In [ ]:
print(message)
Challenges: take around 5 to resolve each of the following challenges

Challenge 1

What is the final value of position?


In [ ]:
initial = "left"
position = initial
initial = "right"

Challenge 2

What is this code doing?


In [ ]:
s = 60
m = s * 60
m

Recap

  • "Use variables to store values."
  • "Use print to display values."
  • "Variables must be created before they are used."
  • "Variables can be used in calculations."
  • "Python is case-sensitive."
  • "Use meaningful variable names."

Introduction to Python built-in data types

In Python there are mainly 4 data types:

  • Strings
  • Numbers
  • Lists
  • Dictionaries

Strings


In [ ]:
string_1 = 'This is a string'

In [ ]:
string_2 = "This is also a string"

In [ ]:
string_3 = '''This is a multi lines
            strings and it works fine'''

In [ ]:
string_4 = """This is a multi lines
            strings and it works fine"""

We can sum string together, we concatenate them


In [ ]:
first_name = 'Christian'
last_name = 'Barra'
name = first_name + last_name

In [ ]:
name

In [ ]:
# type

You can also multiply a string for a number


In [ ]:
name * 5

But not I want to multiply my name for 1.5!


In [ ]:
name * 2

Let's take about this error!

Numbers

So far we have seen 2 types of numbers:

* 5 -> integer
* 1.5 -> float

This are the main numberical types in Python

Integer is a number without decimal parts.

A float number is a rappresentation of a fraction, as a consequence is an aprozzimation!


In [ ]:
integer_1 = 1
integer_2 = 10
integer_1 + integer_2

In [ ]:
integer_1 = 1
integer_2 = "10"
integer_1 + integer_2

You would probably see this error very often, when you are trying to do operation between types that are not compatible


In [ ]:
## But we can do this
integer_1 = 1
integer_2 = "10"
integer_1 + int(integer_2)

This is operation is called casting, we are forcing a conversion to another type, in this case from string to int.

We can also cast to str (string) and we saw the sum (+) operation before.

integer_1 = 1
integer_2 = "10"
str(integer_1) + integer_2

In [ ]:
integer_1 = 1
integer_2 = "10"
str(integer_1) + integer_2

What will be the result of this operation?

We can sum integers and floats together


In [ ]:
int_1 = 5
float_1 = 5.5

In [ ]:
int_1 + float_1

Internally Python does a casting conversion from int to float, basically


In [ ]:
float(5) + 5.5

List

I love lists!

Lists are a indexed collection of (also mixed) elements. With mixed I mean you can put different types of variables.

This is how you create a list


In [ ]:
my_first_list = [1,2,3,4,5]

In [ ]:
my_first_list

In [ ]:
a_mixed_list = [1,2,"Ciao"]

In [ ]:
a_mixed_list

In [ ]:
# this is useful when you can to cast something to a list
my_first_list = list("christian")

In [ ]:
my_first_list

In [ ]:
my_letters = list("Christian")

In [ ]:
my_letters

Lists are very flexible and they allow many operations. For example you can sum 2 lists


In [ ]:
a = [1,2,3,4,5]
b = [6,7,8,9,10]

In [ ]:
a + b

As with the strings more than summing we are concatenating our lists.

Same goes with multiplication, a * 3 is equal to a + a + a


In [ ]:
a*3

In [ ]:
a + a + a

If you want to add a new element to a list you can use append.


In [ ]:
l = [] # this is an empty list

In [ ]:
l.append(1)

In [ ]:
l

HINT

Did you notice # inside the code? It's how you put comments in Python

Back to adding elements you can also use the sum operator (+) but is not really Pythonic...


In [ ]:
l = []
l = l + [1]
l

So, generally, just avoid this practise.

What about deleting an element?


In [ ]:
l = [1,2,3,4,5]

In [ ]:
l

In [ ]:
l.pop()

In [ ]:
l

pop removes and returns the last element when you don't specify and index


In [ ]:
l = [1,2,3,4,5]

If you specificy and index betweenn () it will remove that element.


In [ ]:
l.pop(1)

In [ ]:
l

I deleted the first element, index 1, why 2 has been removed?

First, lists are an indexed collection of elements, so they have and index and an order.

The index starts from 0 not from 1

So index n. 1 is the second element, while index 0 is the first element.

With this concept we can introduce the brackets notation.


In [ ]:
l = [1,2,3,4,5]

We can access an element of a list using the brackets notation with an index inside:


In [ ]:
l[0]

In [ ]:
l[1]

The element should exist:


In [ ]:
l[100000]

Otherwise we would get an error.

We can also slice lists:


In [ ]:
l

In [ ]:
l[0:2]

This is what we called slicing.

Slicing is very powerful Python, is builty by, for now, 2 parts:

* the first index of the element, **this element will be included**
* the last index of element we want to consider, but **it won't be included**

Slicing returns a new list

2 important concepts:

* slicing is not like accessing an element
* slicing returns a list
* slicing don't raise a `list index out of range` but returns an empty list

In [ ]:
l[-50:-20]

In [ ]:
l[-50:20]

In [ ]:
l[0:10000000000]

Time for cool tricks:


In [ ]:
l[:]
  • If you avoid putting the first index it means from the beginning
  • If you avoid putting the last index it means till the end of the list You can also mix the concepts together

In [ ]:
# From element with index 1 till the end
l[1:]

In [ ]:
# from the beginning of the list till element with index 4, not included
l[:4]

In [ ]:
# Element with index 4 is:
l[4]

Small disgression: back to strings


In [ ]:
a_string = "Hello there, how is going?"

In [ ]:
a_string[0]

In [ ]:
a_string[4:10000]

In [ ]:
a_string[:]

Same slicing concepts can be applied to strings

Before moving to the next type


In [ ]:
l = [1,2,3,4,5]

In [ ]:
l[0] = "Ciao"

In [ ]:
l

This is how you change a value inside the list.

Nitty great details and best practices:

* avoid popping with an index
* avoid **adding** elements at the beggining/in the middle of your list.

Dictionary

Dictionaries are a key=value collection, they are not ordered (opposed to lists that are ordered) and you can put mixed types of variables inside (as for the lists.

This is how you create a dictionary:


In [ ]:
info_class = {
    "number_of_students": 10,
    "details": []
}

In [ ]:
info_class

In [ ]:
my_first_dictionary = dict(my_key="my_value")

In [ ]:
my_first_dictionary

How can we add a new element to a dictionary?

We already know the brackets notation, but with dicts we use the key instead of the index


In [ ]:
my_first_dictionary['new_key'] = "My new key"

In [ ]:
my_first_dictionary

If we want to access the element of a dict we use the same notation


In [ ]:
my_first_dictionary['new_key']

If the element does not exist we get an error


In [ ]:
my_first_dictionary['an_element']

One way to avoid this is using get


In [ ]:
my_first_dictionary.get("an_element")

If the element with key an_element does not exist we the default value, None if something is not specified


In [ ]:
my_first_dictionary.get("an_element", "My default value")

How do you delete an element?


In [ ]:
my_first_dictionary

In [ ]:
del my_first_dictionary["my_key"]

In [ ]:
my_first_dictionary

In [ ]:
# Remeber to check if the key exists when deleting an element
del my_first_dictionary["my_key"]

In [ ]:
# Otherwise you can use pop with a default value
my_first_dictionary.pop("my_key", "Does not exist")

You can't add dicts but you can update one using values from another one, but pay attention to elements with the same keys.


In [ ]:
my_first_dictionary = {"my_key":"my_value", "1": []}

In [ ]:
my_second_dictionary = {"my_key":"my_2_value", "2":"my_second_value"}

In [ ]:
my_first_dictionary.update(my_second_dictionary)

In [ ]:
my_first_dictionary

That's all for Python data types for now!

Recap

  • We have 4 main data types:
    • Strings
    • Numbers (Integers and floats)
    • Lists
    • Dictionaries
  • We can slice:
    • strings
    • lists
  • We can append new elements to a list
  • We can add new keys to a dictionary
  • Something that exists but we didn't talk about: tuple and set
Challenges: take around 5 to resolve each of the following challenges

In [ ]:
# Now I want to add the students that I miss from the list inside the dictionary
# student n. 4 is Bartosz and number 5 is Wioleta
# Use the way that you prefer, at the end you should obtain a dict with they keys that you don't have (4,5)
list_of_student_id = [1,2,3,4,5]
student_names = {1: "Christian", 2:"Monika", 3: "Lucasz"}

In [ ]:
# We have the dict of new students and we want to add the add this new elements to the old one
# How can we do this?
old_students = {1: "Christian", 2:"Monika", 3: "Lucasz"}
new_students = {6: "Anna", 7:"Jacek", 8: "Arnold"}

In [ ]:
# I'd like 2 new variables, first_name and last_name
# How can you extract first_name and last_name from my_name?
my_name = "christian Calogero Barra"

Loops and flow controls

Loops and flow controls serve the need to control how your code is going to be executed by Python.

More or less they translate to computers this kind of actions:

* If the fridge is empty go to the supermarket
* For each ingredient inside the recipe cook them
* While the house is not clean, keep cleaning

Flow controls

Flow control means to change the behaviour of your code depending from some conditions (True, False, minor, major or equal of something). In Python we have:

  • if
  • else
  • elif

In [ ]:
counter = 5
if counter > 1:
    print("Greater than 1")
else:
    print("Minor of 1")

Let's break this down:

1. we create a new variable: counter = 5
2. we check if counter is greater than 1
3. we print something
4. else is executed when counter > 1 is False
5. we print something else

Let's look at 2 exmaples with a subtle difference

elif stands for else if


In [ ]:
# Example 2
gender = "m"

if gender == "f":
    print("oman")
elif gender == "v":
    print("man")
elif gender == "a":
    print("man")
elif gender == "f":
    print("man")

In the first example the second if is something separated from the first one.

Let's explain this a little bit more.

Key points

  • with a chain of if elif and else only the code under the first satisfied condition is executed
  • == is to compare elements
  • != to check if elements are not the same
  • >, >=, <, <= to verify if something is minor/greater/equal
  • Do you see that we don't have { } inside if/elif/else? Python uses indentation

For and while

For

for iterates over something, we can call this something an object


In [ ]:
for element in [1,2,3,4,5]:
    print(element)

In [ ]:
my_string = "Christian"
for letter in my_string:
    print(letter)

In [ ]:
my_dict = {"first_name": "Christian", "last_name": "Barra"}
for key in my_dict:
    print(key)

Note

When I normally iterate over a dictionary I get the its keys

To get also the elements I need to use items()


In [ ]:
my_dict = {"first_name": "Christian", "last_name": "Barra"}

In [ ]:
for item in my_dict:
    print(item)

In [ ]:
for key in my_dict:
    print(my_dict[key])

Do you see what is happening here?

While

while is repeated till the expression results "True"


In [ ]:
i = 8
while i < 10:
    i = i + 1 
    print(i)

In [ ]:
x = 0
while x:
    print("Hello")
Challenges: take around 5 to resolve each of the following challenges

In the next challenges you will have to fill some empty spaces (__). Feel free to scroll back to each part of our lesson

# Concatenate all words: ["red", "green", "blue"] => "redgreenblue"
words = ["red", "green", "blue"]
result = ____
for ____ in ____:
    ____
print(result)

In [ ]:
# Put your code here
words = ["red", "green", "blue", "yellow"]
result = ""
for word in words:
    result = result + word
print(result)

In [ ]:
words[0] + words[1] + words[2]

In [ ]:
empty = "-"

In [ ]:
empty.join(words)
# Create acronym: ["red", "green", "blue"] => "RGB"
acronym = ___
for word in ["red", "green", "blue"]:
    if ___ == 'r':
        acronym = ___
    elif ___ == 'g':
        acronym = ___
    elif ___ == 'b':
        acronym = ___

print(acronym)

In [ ]:
# Put your code here
acronym = ""
for word in ["red", "green", "blue"]:
    if word[0] == 'r':
        acronym = acronym + word[0]
    elif word[0] == 'g':
        acronym = acronym + word[0]
    elif word[0] == 'b':
        acronym = acronym + word[0]
    
print(acronym.upper())
# Count the number of `a` inside the sentence

sentence = """From a very early age, perhaps the age of five or six, 
I knew that when I grew up I should be a writer. Between the ages of about seventeen 
and twenty-four I tried to abandon this idea, 
but I did so with the consciousness that I was outraging my true nature 
and that sooner or later I should have to settle down and write books."""

counter = ___

for letter in sentence:
    ___ ___ == ___:
        counter = ___

print(counter)

In [ ]:
# Put your code here
sentence = """From a very early age, perhaps the age of five or six, 
I knew that when I grew up I should be a writer. Between the ages of about seventeen 
and twenty-four I tried to abandon this idea, 
but I did so with the consciousness that I was outraging my true nature 
and that sooner or later I should have to settle down and write books."""

counter = 0

for letter in sentence:
    
    if letter == "a":
        counter += 1
        
print(counter)
# Separate odd and even numbers

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

odd_numbers = []
even_numbers = []

for number in numbers:
    if number % 2 == 0:
        ___
    else:
        ___

print(odd_numbers)
print(even_numbers)

In [ ]:
numbers = [1,2,3,4,5,6,7,8,9,10,12]

odd_numbers = []
even_numbers = []

for number in numbers:
    if number % 2 == 0:
        even_numbers = even_numbers + [number]
    else:
        odd_numbers = odd_numbers + [number]

print(even_numbers)
print(odd_numbers)

Functions and modules

Functions

Functions are a way to write reusable code, you write function using the def word followed my the name of the function.


In [ ]:
def my_Function():
    print("This is my function")

In [ ]:
# This is how you calla function
my_Function()

A function generally returns something.


In [ ]:
def is_even(number):
    '''
    Return True if the number is even.
    
    False is all the other cases.
    
    This text is called docstring.
    '''

    if number % 2 == 0:
        return True
    
    return False

In [ ]:
is_even(1)

In [ ]:
is_even(10)

Docstrings help you documenting your code.

The docstring of is_even is used by help function to provide more information about you function.


In [ ]:
help(is_even)

In [ ]:
def who_is_bigger(x, y):
    """
    Return the greater number between x and y
    """
    if x >= y:
        return x
    
    return y

In [ ]:
who_is_bigger(1,10)

In [ ]:
who_is_bigger(1, 10)

You can specifiy the arguments in 2 ways:

* like arguments
* key=value notation

In [ ]:
# this is like arguments
who_is_bigger(1,10)

In [ ]:
# with key=value notation
who_is_bigger(x=1,y=10)

In [ ]:
# with key=value the order is no longer important
who_is_bigger(y=10,x=1)

Our function can accept many arguments, you can also specify default arguments:


In [ ]:
def say_something(message="Ciao"):
    """
    Print a message, "Ciao" by default
    """
    print(message)

In [ ]:
say_something()

If we don't put a default for our argument and we call the function we get an error:


In [ ]:
def say_something(message="Ciao"):
    """
    Print a message, "Ciao" by default
    """
    print(message)

In [ ]:
say_something()

Another important concept in namespace. You can think of a namespace as a personal shelves where Python put things. Functions have a reserved namespace that is called local.


In [ ]:
x = 10

def tell_me_the_value():
    x = 1
    print(x)

In [ ]:
tell_me_the_value()

In [ ]:
x = 10

def tell_me_the_value():
    print(x)

In [ ]:
tell_me_the_value()

This is how Python will look for variables:

Local is what we have seen with our tell_me_the_value function. Python will first look inside the function and then outside.


In [ ]:
# Example 1
x = 10

def tell_me_the_value():
    x = "Ciao"
    def something_else(x):
        print(x)
    something_else(x)

In [ ]:
tell_me_the_value()
x

In [ ]:
# Example 2
x = 10

def tell_me_the_value():
    def something_else(x):
        x = 11
    something_else(x)

In [ ]:
tell_me_the_value()
x

In the example 1 python is accessing the enclosed scope, wheras in the example 2 it follow this rule:

1. look inside the local scope -> empty
2. look inside the enclosed scope (body of tell_me_the_value) -> empty
3. look inside the global scope -> found (x=10)

In [ ]:
# Example 1
x = 10

def tell_me_the_value():
    global x
    x = "Ciao"
    def something_else(x):
        print(x)
    something_else(x)

In [ ]:
tell_me_the_value()
x

global tells Python to consider a variable like a global and not as a local (as you can see x has a different value now)

HINT

The order of the parameters is important if you don't use the key=value notation.

Challenges: take around 5 to resolve each of the following challenges
# challenge n.1
input_list = [1,2,3, None, 5]

def api_call(input_list):
    '''
    Take a list of values and then return an integer 
    with the number of numbers in that list
    '''
    counter = 0

    for element in input_list:

        if type(element) is int:

            counter = counter + 1

    return counter

In [ ]:
# Put your code here
# challenge n.2
import random
input_list = [random.randint(-1,1) for i in range(100)]

def api_call(input_list):
    '''
    Take a list of values and then it return a list with the
    same number of elements [True, False,...] where
    True if element >= 0
    False if element < 0
    '''

    list_true_false = []
    for element in input_list:
        if element ____ ____:
            ____
        else:
            ____
    return ____

In [ ]:
# Copy your function here
# challenge n.3
import random
input_list = [random.randint(-10,10) for i in range(100)]

def api_call(input_list):
    '''
    This function take a list of values and then return a dictionary.

    The dictionary has 2 keys:
    bigger_0: number of numbers inside the given list > 0
    lower_0: number of numbers inside the given list < 0
    '''

In [ ]:
# Copy your function here

Modules

A module is a way for organize Python code together.

Let's switch to visual studio studio and create 2 files inside the same directory.

# utils.py

first_name = "YourName"
last_name = "YourLastName"

def return_my_name():
    return f"{first_name} {last_name}"
# app.py
from utils import return_my_name

return_my_name()

Now if we execute the code, with python app.py you should see something. Inside app.py we imported the module utils.

utils.py is a Python script that we can import.