Python Functions, Classes, and Modules¶
For longer and more complex tasks, it is important to organize your code into reuseable elements. For example, if you find yourself cutting and pasting the same or similar lines of code over and over, you probably need to define a function to encapsulate that code and make it reusable. An important principle in programming in DRY: "don't repeat yourself". Repetition is tedious and opens you up to errors. Strive for elegance and simplicity in your programs.
Functions¶
Functions are a central part of advanced python programming. Functions take some inputs ("arguments") and do something in response. Usually functions return something, but not always.
# define a function
def say_hello():
"""Return the word hello."""
return 'Hello'
# functions are also objects
type(say_hello)
# this doesnt call
say_hello?
# this does
say_hello()
# assign the result to something
res = say_hello()
res
# take some arguments
def say_hello_to(name):
"""Return a greeting to `name`"""
return 'Hello ' + name
# intended usage
say_hello_to('World')
say_hello_to(10)
# redefine the function
def say_hello_to(name):
"""Return a greeting to `name`"""
return 'Hello ' + str(name)
say_hello_to(10)
# take an optional keyword argument
def say_hello_or_hola(name, spanish=False):
"""Say hello in multiple languages."""
if spanish:
greeting = 'Hola '
else:
greeting = 'Hello '
return greeting + name
print(say_hello_or_hola('Ryan'))
print(say_hello_or_hola('Juan', spanish=True))
# flexible number of arguments
def say_hello_to_everyone(*args):
return ['hello ' + str(a) for a in args]
say_hello_to_everyone('Ryan', 'Juan', 'Xiaomeng')
def remove_last_from_list(input_list):
input_list.pop()
names = ['Ryan', 'Juan', 'Xiaomeng']
remove_last_from_list(names)
print(names)
remove_last_from_list(names)
print(names)
We can do something similar with a pure function.
In general, pure functions are safer and more reliable.
def remove_last_from_list_pure(input_list):
new_list = input_list.copy()
new_list.pop()
return new_list
names = ['Ryan', 'Juan', 'Xiaomeng']
new_names = remove_last_from_list_pure(names)
print(names)
print(new_names)
We could spend the rest of the day talking about functions, but we have to move on.
Namespaces¶
In python, a namespace is a mapping between variable names and python object. You can think of it like a dictionary.
The namespace can change depending on where you are in your program. Functions can "see" the variables in the parent namespace, but they can also redefine them in a private scope.
name = 'Ryan'
def _name(name):
A more complex function: Fibonacci Sequence¶
The Fibonacci sequence is the 1,1,2,3,5,8..., the sum of each number with the preceding one. Write a function to compute the Fibonacci sequence of length n. (Hint, use some list methods.)
def fib(n):
l = [1,1]
for i in range(n-2):
l.append(l[-1] + l[-2])
return l
fib(10)
Classes¶
We have worked with many different types of python objects so far: strings, lists, dictionaries, etc. These objects have different attributes and respond in different ways to the built-in functions (len
, etc.)
How can we make our own, custom objects? Answer: by defining classes.
A class to represent a hurricane¶
class Hurricane:
def __init__(self, name):
self.name = name
h = Hurricane('florence')
h
Our class only has a single attribute so far:
h.name
Let's add more, along with some input validation:
class Hurricane:
def __init__(self, name, category, lon):
self.name = name.upper()
self.category = int(category)
if lon > 180 or lon < -180:
raise ValueError(f'Invalid lon {lon}')
self.lon = lon
h = Hurricane('florence', 4, -46)
h
h.name
h = Hurricane('ryan', 5, 300)
Now let's add a custom method:
class Hurricane:
def __init__(self, name, category, lon):
self.name = name.upper()
self.category = int(category)
if lon > 180 or lon < -180:
raise ValueError(f'Invalid lon {lon}')
self.lon = lon
def is_dangerous(self):
return self.category > 1
f = Hurricane('florence', 4, -46)
f.is_dangerous()
Magic / dunder methods¶
We can implement special methods that begin with double-underscores (i.e. "dunder" methods), which allow us to customize the behavior of our classes. (Read more here). We have already learned one: __init__
. Let's implement the __repr__
method to make our class display something pretty.
class Hurricane:
def __init__(self, name, category, lon):
self.name = name.upper()
self.category = int(category)
if lon > 180 or lon < -180:
raise ValueError(f'Invalid lon {lon}')
self.lon = lon
def __repr__(self):
return f"{self.name} (cat {self.category})>"
def is_dangerous(self):
return self.category > 1
f = Hurricane('florence', 4, -46)
f