LSESU Applicable Maths Python Lesson 2

25/10/16

Today we will be learning about

  • Functions
  • import and modules

Recap from last week

  • Variables
    a = 1       # Integer variable
    b = 'hello' # String variable
  • Math operations
    c = 2*4
    d = 2**4
    e = 4%2
    f = 4/3 # What happens if I put // instead of / ?
  • Logic operations

    g = True and False
    h = True or False
    i = 'hello' in 'hello my name is tom'
    j = not True
  • Conditional blocks

if a == 1:
    print('True')
else:
    print('False')

while a < 5:
    print(a)
    a+=1

for i in range(0,5)
    print(i)

Functions

One of the principles of programming is Do Not Repeat Yourself. This means that if you find yourself writing something which you have written before in the same program, then something is wrong with how you are designing your program.

Functions are a way to "encapsulate" specific behaviour into a block which you can repeatedly use when only writing the code once. In fact you have already used 2 functions before when you used range() last week and random.choice() in the Give It a Go! session

A bad program which tests if a number if a multiple of 2


In [1]:
a = 4
b = 8
c = 9

if a%2 == 0:
    print('Multiple of 2')
else:
    print('Not a multiple of 2')
    
if b%2 == 0:
    print('Multiple of 2')
else:
    print('Not a multiple of 2')

if c%2 == 0:
    print('Multiple of 2')
else:
    print('Not a multiple of 2')


Multiple of 2
Multiple of 2
Not a multiple of 2

I am repeating myself here! Wouldn't it be better if we can write something once and use it multiple times?

A better program


In [2]:
# The def keyword is used to define a function

# Num is a "parameter" of the function, the input you provide to evaluate
def is_multiple_of_2(num):
    if num%2==0:                     # Remember this is the modulus operator!
        print('Multiple of 2')
    else:
        print('Not a multiple of 2')

Now I can write some code which does the same thing 3 times without repeating myself


In [3]:
# I can "call" the function I just wrote 3 times without repeating code
is_multiple_of_2(a)
is_multiple_of_2(b)
is_multiple_of_2(c)


Multiple of 2
Multiple of 2
Not a multiple of 2

This is good! But there is one more important feature of functions. The return statement is used with a function to give back something to the code which asked the function to run.

In summary:

  • You define a function with def to place code in a reusable block and use it anywhere you need it
  • You specify function parameters (like num) to use inside the function.
  • You call the function with "arguments" which are the variables you created. Each time you call is_multiple_of_2(), the argument (here it is a, then b, then c) becomes equal to the parameter to use.
  • You can then use the return statement to give something back to the code which called the function.

An even better program


In [4]:
def is_multiple_of_2_better(num):
    if num%2==0:
        return_string = 'Multiple of 2'
    else:
        return_string = 'Not a multiple of 2'
    return return_string

In [5]:
print(is_multiple_of_2_better(a))
print(is_multiple_of_2_better(b))
print(is_multiple_of_2_better(c))


Multiple of 2
Multiple of 2
Not a multiple of 2

Wouldn't it be better if we could ask the function to check if the argument was a multiple of anything?


In [6]:
def is_multiple(num,multiple):
    if num%multiple==0:
        return_string = 'Multiple'
    else:
        return_string = 'Not a multiple'
    return return_string

# Question - How could I make this even better?

In [7]:
multiple = 2 # What happens to the output if you change this to 3?

print(is_multiple(a,multiple))
print(is_multiple(b,multiple))
print(is_multiple(c,multiple))


Multiple
Multiple
Not a multiple

Functions can have many arguments or none at all, when they might to the same thing every time


In [8]:
def best_programming_language_ever():
    lang = 'Python'
    return lang

In [9]:
a = best_programming_language_ever() # a is equal to whatever the function returns
b = best_programming_language_ever()
c = best_programming_language_ever()
  • Question - print a, b and c. What do you see?

In [ ]:
# TO DO



# END TODO

You will see that this function does the same thing every time!

  • Question - Define a function which takes number a and number b, and returns True if a > b and False if a < b

In [10]:
# TO DO 

# I started you off with the first line (called the Function signature)
def my_function(a, b):

    
    

# END TODO


  File "<ipython-input-10-a5bde1a66374>", line 9
    # END TODO
              ^
SyntaxError: unexpected EOF while parsing

In [11]:
# Now write some code which uses this function called my_function with these variable pairs
a = 100
b = 1000

d = 'a'
e = 'b'




# END TO DO

Functions can also have default parameters, meaning you can leave an argument blank when you call the function if you need to


In [12]:
def is_multiple_with_default(num,multiple=2):
    if num%multiple==0:
        return 'Multiple'
    else:
        return 'Not a multiple'

In [13]:
# Now if I dont specify what multiple is then it automatically checks for multiple of 2

is_multiple_with_default(1)

# Question - Can you break this function?


Out[13]:
'Not a multiple'

In [14]:
# We can provide an additional condition to make sure that the function doesn't break

def is_multiple_with_default_better(num,multiple=2):
    if multiple == 0:
        return 'Zero argument for multiple not allowed'
    elif num%multiple==0:
        return 'Multiple'
    else:
        return 'Not a multiple'

In [15]:
# Now the function is safe to use with 0 as multiple.

is_multiple_with_default_better(2,0)


Out[15]:
'Zero argument for multiple not allowed'
  • Question - Rewrite my_function(a,b) to check if a==b first then evaluate the same as before

In [ ]:
# TODO 
def my_function(a,b):

    

# END TODO

In [16]:
# Run this block to check if my_function() is working correctly

print(test(1,2))
print(test(2,1))
print(test('a','b'))
print(test(50,50))


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-16-8bd6c2479ee5> in <module>()
      1 # Run this block to check if my_function() is working correctly
      2 
----> 3 print(test(1,2))
      4 print(test(2,1))
      5 print(test('a','b'))

NameError: name 'test' is not defined

Using modules and import


In [17]:
# Remember this from the Give It a Go Session?

import random
  • random is a library for random number generation and other randomness based tools. To use the random library you have to tell Python that you need it with the import statement.

  • random is one of the default libraries that comes with Python when you install it. To download more libraries from the web you can use the pip tool on the command line.

  • When you import any libraries, the import statement must be at the top of your file of code before you type anything else, otherwise the Python program doesn't know which libraries you are using.

Python organises all external code blocks in the following way:

-> package - Top level, contains modules

↓
- module - .py files with functions

    ↓
    - functions - functions inside modules which you call using the . operator

  • Sometimes you can import modules directly, like import random, and with larger packages written by the open source community it is typical to download the whole package and import only the modules inside you want. Using too many packages you don't need slows down your code!

Example:

import matplotlib.pyplot as plt
  • matplotlib is the package
  • pyplot is the module you want
  • plt is the shorthand name you have given it

In [18]:
# To import random but call it something shorter (like rand) you can run

import random as rand

Remember when I said that everything is an object in Python? Python modules have an ID and a type just like variables.


In [19]:
# Everything is a first class object!

print(id(rand))
print(type(rand))


4333975016
<class 'module'>
How do I use modules?

A module itself is not a function, using the . operator you can access the functions inside the module. We did this when we used random.choice() during the info session. choice() is the function, inside the random module.

A full list of the functions inside random can be found here. Learning to understand documentation for modules and packages is a good skill to develop when learning Python. I would recommend reading the random module documentation as practice.

  • The most basic random function

In [20]:
# Random() is a function. You can tell by the parentheses ()

rand.random()


Out[20]:
0.0872341167892815
  • Random module functions with parameters

In [21]:
# randint(a,b) is a function in the random which selects a random integer x from a <= x <= b

rand.randint(1,10)


Out[21]:
2

In [ ]:
# Can you call randint() without using the rand. before it?

# randint(1,10)

# Question - Is there a way to make this line work?
  • You can use functions from modules inside other functions

In [22]:
def weather():
    num = rand.random()
    if num > 0.5:
        return "It's going to be sunny today!"
    else:
        return "It's going to rain today :("

In [23]:
# Now the weather function uses the random module to guess the weather

print(weather())


It's going to rain today :(

In [24]:
# We used this function before

uni = rand.choice(['LSE','UCL','Imperial'])
print(uni)
# We can use a different function to select 2 or more random universities

multi_uni = rand.sample(['LSE','UCL','Imperial','Kings','Queen Mary','SOAS','UAL'],2)
print(multi_uni)

# The [] notation denotes a list, a data structure we will look at next week


LSE
['UCL', 'UAL']
  • Final task - Write a function which randomly selects at least 3 integers, and returns a different string for each integer. You can select the range of random integers to choose between, but it will be more work if you choose a lot!

In [ ]:
# TODO
def random_string():

In [ ]:
# Now test your random_string function in this block


# END TODO