Dr. Cho’s Website
Course Materials

# Functions and classes in Python

Institute for Environmental and Spatial Analysis...University of North Georgia

## 1   Functions

### 1.1   Defining a function

Define a simple function.

``````def f(x):
y = 2*x
return y
print(f(3))``````

Define it again.

``````def f(x): y = 2*x; return y
print(f(3))``````

### 1.2   Null function

Can you define a function with no body? Let’s try!

``````def nop():
nop()``````

Not working! Use the `pass` statement as a placeholder.

``````def nop(): pass # just do nothing
nop()``````

### 1.3   Functions are objects

A function is an instance of a class (object) with its name.

``````def foo():
""" function document
called doc string """
return 'foo';
print(dir())    # lists all names in the current scope
print(dir(foo)) # lists all valid attributed for the foo object``````

Can you see the `__class__` attribute? Try this.

``````def foo():
""" function document
called doc string """
return 'foo';
print(dir())
print(dir(foo))
print(foo.__name__)
print(foo.__doc__)
print(foo.__call__())``````

### 1.4   Functions can be nested inside a function

We can limit the scope of a function to its parent function.

``````def some_math(x, y):
z = x + y
print('{x}+{y}={z}'.format(x=x, y=y, z=z))
def mul(x, y):
z = x * y
print(f'{x}*{y}={z}') # shorter way of formatting strings
mul(x, y)
some_math(10, 2)``````

### 1.5   Closures

Normally, any local variables defined inside a function do not persist.

``````def accum(x):
total += x # well, there is even no way to initialize total
print(accum(1))
print(accum(2))``````

Use a nested function to make local variables persistent.

``````def accum_func():
total = 0          # initialize it
nonlocal total # do you remember nonlocal? total is the total above
total += x
return add         # return the add function itself
accum = accum_func()
print(accum(1))
print(accum(2))``````

The above programming pattern is called closure.

### 1.6   Attributes

We can define function attributes.

``````def accum(x):
accum.total += x
return accum.total
accum.total = 0 # create a new attribute
print(accum(1))
print(accum(2))
print(accum.total)``````

### 1.7   Decorators

Add pre-/psot-statements wrapping a function.

``````def decorator(func):
def wrapper():
print("before func")
func()
print("after    func")
return wrapper
def f():
print("f")
f = decorator(f)
f()

# the "pie" syntax
# equivalent to g = decorator(g)
@decorator
def g():
print("g")
g()``````

### 1.8   Revisit the persistent variable

We can use the decorator pattern to declare function attributes.

``````def static_decorator(name, val):
def wrapper(func):
setattr(func, name, val)
return func
return wrapper

# equivalent to accum = static_decorator('total', 0)(accum)
@static_decorator('total', 0)
def accum(x):
accum.total += x
return accum.total
print(accum(1))
print(accum(2))``````

### 1.9   Have you noticed this?

Functions can be variables.

``````def f(g):       # f takes a function and just returns it
return g
def h(x):       # h takes an argument and prints it
print(x)
a = f(h)        # a == h
a(12)           # calls h(12)``````

In C, we call them function pointers.

## 2   Classes

### 2.1   How are classes different from functions?

Classes create a new object type that provides both data and functionality while functions only provide the latter.

Classes can be instantiated to create a new object (an instance).

Classes create a new namespace.

### 2.2   Defining a class

A simple class:

``````class Accumulator:
# attribute variable
total = 0
# attribute function requires self (Accumulator in this case) as the first argument
# Accumulator.add() is a function object
self.total += x # access the total attribute
def print(self):
print(self.total)
accum = Accumulator()
# accum.add() is a method object
accum.add(1) # the first self argument is missing
accum.print()
Accumulator.print()``````

### 2.3   Class with arguments

Define a class with arguments.

``````class Accumulator2:
def __init__(self, start):  # automatically invoked when an instance is created
self.total = start
self.total += x
def print(self):
print(self.total)
accum = Accumulator2(3)
accum.print()``````

### 2.4   Class variables

All objects instantiated from the same class share class variables.

``````class Dog:
kind = 'canine'             # class variable shared by instances
def __init__(self, name):
self.name = name        # instance variable unique to each instance

fido = Dog('Fido')
print(fido.kind, fido.name)

Dog.kind = 'feline'
buddy = Dog('Buddy')            # inherits feline
print(buddy.kind, buddy.name)

Dog.kind = 'bovine'
print(buddy.kind, buddy.name)   # kind from the class

buddy.kind = 'canine'           # creates its own kind attribute
Dog.kind = 'bovine'
print(buddy.kind, buddy.name)   # no more from the class

print(Dog.kind)
print(Dog.name) # Oops!         # classes don't have instance variables``````

### 2.5   Derived classes

We can derive a class from a base class.

``````class Mammal:
hair = True
def __init__(self, name):
self.name = name
def walk(self):
print('{name} walks'.format(name=self.name))
class Dog(Mammal):
def bark(self):
print('{name} barks'.format(name=self.name))

dog = Mammal('A dog')
print(dog.hair)
dog.walk()

buddy = Dog('Buddy')
buddy.walk()
buddy.bark()``````

### 2.6   Defining a regular function using a class

Recall the `__call__` attribute? We can do the same

``````class Accumulator:
def __init__(self):
self.sum = 0
def __call__(self, x):
self.sum += x
def print(self):
print(self.sum)
accum = Accumulator()
accum(1)          # just like a regular function
accum(2)
accum.__call__(3) # alternative way
accum.print()``````

### 2.7   What about class pointers?

Classes also have their pointers.

``````class Class:
def __init__(self, val):
self.x = val
def print_x(self):
print(self.x)
def Return(c):
return c

a = Return(Class)(10) # indirect instantiation
a.print_x()

b = Class(20)
b.print_x()``````

## 3   Homework: Classes

### 3.1   TupleLister

Write a class that takes a tuple when creating an instance and provides a print method that lists all elements in the tuple one per line.

``````class TupleLister:
# your code here

tupls = TupleLister(('apple', 'orange', 'pear'))
tupls.print()
# apple
# orange
# pear``````

### 3.2   FactSum

Write a class that takes a positive integer and provides two methods for factorial (fact) and summation (sum).

``````class FactSum:
# your code here

factsum = FactSum(5)
print(factsum.fact())
print(factsum.sum())
# 120
# 15``````

### 3.3   SportsCar

Write a Car class with the name, has_engine, and max_passengers variables and the start function.

Write a SportsCar class derived from the Car class that provides an additional has_turbo variable and overrides the start function.

``````class Car:
# your code here
class SportsCar:
# your code here

boring_car = Car('My Car')
print(boring_car.name)           # My Car
print(boring_car.has_engine)     # True
print(boring_car.max_passengers) # 5
boring_car.start()               # vroom...

dream_car = SportsCar('Dream Car')
print(dream_car.name)             # Dream Car
print(dream_car.has_engine)      # True
print(dream_car.has_turbo)       # True
print(dream_car.max_passengers)  # 2
dream_car.start()                # Vrooooom!!!``````