4. Functions and Classes#

Let us learn how to define your own functions, and further organize them into a class for neatness and extensibility.

References: Python Tutorial (https://docs.python.org/3/tutorial/)

  • Section 4.7-4.8: Functions

  • Chapter 6: Modules

  • Chapter 9: Classes

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

Defining functions#

If you find yourself running the same codes again and again with different inputs, it is time to define them as a function.

Here is a simple example:

def square(x):
    """Compute x*x"""
    # result returned
    return x*x
square(3)
9
a = np.array([1, 2, 3])
# input `x` can be anything for which `x*x` is valid
square(a)
array([1, 4, 9])

The line encosed by “”” “”” is called a Docstring, which is shown by help( ) command.

help(square)
Help on function square in module __main__:

square(x)
    Compute x*x
square?

A function does not need to return anything.

def print_square(x):
    """Print x*x"""
    print(x*x)
# the end of indentation is the end of definition
print_square(a)    
[1 4 9]

A function can return multiple values.

def square_cube(x):
    """Compute x**2 and x**3"""
    # return multiple values separated by comma
    return x**2, x**3
# results can be assigned to variables separated by comma
b, c = square_cube(a)
print(b, c)
[1 4 9] [ 1  8 27]
square_cube(3)
(9, 27)

Arguments and local variables#

A function can take single, multiple, or no arguments (inputs).
An argumet can be required, or optional with a default value.
An argument can be specified by the position, or a keyword.

def norm(x, p=2):
    """Give the L^p norm of a vector."""
    y = abs(x) ** p
    return np.sum(y) ** (1/p)
a = np.array([1, 2, -2])
norm(a)  # default p=2
3.0
norm(a, 1)  # specify by position
5.0
norm(p=1, x=a)  # specify by the keywords, in any oder
5.0

Local and global variables#

Arguments and variables assigned in a function are registered in a local namespace.

y = 0  # global variable
norm(a)  # this uses `y` as local variable, y=[1, 4, 9]
print(y)  # the global variable `y` is not affected
0

Any global variables can be referenced within a function.

a = 1  # global variable
def add_a(x):
    """Add x and a."""
    return a + x
print(add_a(1))  # 1 + 1
a = 2
print(add_a(1))  # 1 + 2
2
3

To modify a global variable from inside a function, it have to be declaired as global.

a = 1
def addto_a(x):
    """Add x into a."""
    global a
    a = a + x  # add x to a
addto_a(1)  # a = a + 1
print(a)
addto_a(1)  # a = a + 1
print(a)
2
3

You can modify an argument in a function.

def double(x):
    """Double x"""
    x = 2 * x
    return x
double(1)
2

Scripts, modules, and packages#

Before Jupyter (iPython) notebook was created, to reuse any code, you had to store it in a text file, with .py extension by convention. This is called a script.

%cat haisai.py
print('Haisai!')

The standard way of running a script is to type in a terminal:

$ python haisai.py

In a Jupyter notebook, you can use %run magic command.

%run haisai.py
Haisai!

You can edit a python script by any text editor.

In Jupyter notebook’s Files window, you can make a new script as a Text file by New menu, or edit an existing script by clicking the file name.

A script with function definitions is called a module.

%cat lp.py
"""L^p norm module"""

import numpy as np

def norm(x, p=2):
    """The L^p norm of a vector."""
    y = abs(x) ** p
    return np.sum(y) ** (1/p)

def normalize(x, p=2):
    """L^p normalization"""
    return x/norm(x, p)

You can import a module and use its function by module.function().

import lp
help(lp)
Help on module lp:

NAME
    lp - L^p norm module

FUNCTIONS
    norm(x, p=2)
        The L^p norm of a vector.

    normalize(x, p=2)
        L^p normalization

FILE
    /Users/doya/OIST Dropbox/kenji doya/Python/iSciComp/lp.py
a = np.array([-3, 4])
lp.norm(a)
5.0
lp.normalize(a, 1)
array([-0.42857143,  0.57142857])

Caution: Python reads in a module only upon the first import, as popular modules like numpy are imorted in many modules. If you modify your module, you need to restart your kernel or call importlib.reload().

import importlib
importlib.reload(lp)
<module 'lp' from '/Users/doya/OIST Dropbox/kenji doya/Python/iSciComp/lp.py'>

A collection of modules are put in a directory as a package.

# see how numpy is organized
%ls $CONDA_PREFIX/lib/python3.9/site-packages/numpy
ls: /opt/anaconda3/lib/python3.9/site-packages/numpy: No such file or directory

Object Oriented Programming#

Object Oriented Programming has been advocated since 1980’s in order to avoid naming coflicts and to allow incremental software development by promoting modularity.

Examples are: SmallTalk, Objective C, C++, Java,… and Python!

Major features of OOP is:

  • define data structure and functions together as a Class

  • an instance of a class is created as an object

  • the data (attributes) and functions (methods) are referenced as instance.attribute and instance.method().

  • a new class can be created as a subclass of existing classes to inherit their attributes and methods.

Defining a basic class#

Definition of a class starts with
class ClassName(BaseClass):
and include

  • definition of attributes

  • __init__() method called when a new instance is created

  • definition of other methods

The first argument of a method specifies the instance, which is named self by convention.

class Vector:
    """A class for vector calculation."""
    default_p = 2
    
    def __init__(self, arr):  # make a new instance
        self.vector = np.array(arr)     # array is registered as a vector
    
    def norm(self, p=None):
        """Give the L^p norm of a vector."""
        if p == None:
            p = self.default_p
        y = abs(self.vector) ** p
        return np.sum(y) ** (1/p)
    
    def normalize(self):
        """normalize the vector"""
        u = self.vector/self.norm()
        self.vector = u
    

A new instance is created by calling the class like a function.

x = Vector([0, 1, 2])

Attributes and methods are referenced by .

x.vector
array([0, 1, 2])
x.norm()
2.23606797749979
x.norm(3)
2.080083823051904
x.default_p = 1
x.norm()
3.0
x.normalize()
x.vector
array([0.        , 0.33333333, 0.66666667])
# another instance
y = Vector([0, 1, 2, 3])
y.norm()
3.7416573867739413

A subclass can inherit attributes and methods of base class.

class Vector2(Vector):
    """For more vector calculation."""
    
    def double(self):
        u = 2*self.vector
        self.vector = u
        
z = Vector2([1, 2, 3])
z.vector
array([1, 2, 3])
z.double()
z.vector
array([2, 4, 6])