Functions and Classes#
Challenge: DNA to RNA#
If you’ve taken a Biology class, you know that DNA is essentially a long string comprised of 4 nucleotides:
Cytosine (C)
Thymine (T)
Adenine (A)
Guanine (G)
Example:
dna = 'ACGTAAAACGTGGTGGATTTGACGTGTTTG'
RNA is similar to DNA with one exception: all instances of Thymine (T) are replaced with Uracil (U). Our DNA from above would look like this:
rna = 'ACGUAAAACGUGGUGGAUUUGACGUGUUUG'
In the cell below, create a function called dna_to_rna that accepts a string of DNA and converts it to RNA.
dna = 'ACGTAAAACGTGGTGGATTTGACGTGTTTG'
def dna_to_rna():
pass
Challenge: Hamming Distance#
The DNA strand 'AAAA' is similar to the strand 'AAAT' with one exception: the 4th nucleotide is different. In other words, the two strands have a hamming distance of 1, where hamming distance is the number of nucleotides that differ between two strands.
In the cell below, create a function called hamming_distance that accepts two parameters (dna1 and dna2) and calculates the hamming distance between the two strands.
NOTE: You can assume the two strands will have the same length.
def hamming_distance():
pass
dna1 = 'ACGTAAAACGTGGTGGATTTGACGTGTTTG'
dna2 = 'ATGTAAACCTGGTGGATTTCACGTGTTTG'
hamming_distance(dna1, dna2)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[5], line 1
----> 1 hamming_distance(dna1, dna2)
TypeError: hamming_distance() takes 0 positional arguments but 2 were given
args#
*args is a parameter that allows any number of parameters to be passed to a function. The * at the beginning specifies the variable number of arguments.
def multiply(x, y):
return x*y
multiply(2, 3)
multiply(2, 3, 4)
#use *args to pass variable length arguments
def multiply(*args):
product = 1
for num in args:
product = product * num
return product
multiply(2, 3)
multiply(2, 3, 4)
Unpacking Argument Lists#
You may find a situation where you have a collection that you want to unpack into a list. This is another situation where you can encounter the * operation.
list(range(3, 6, 2)) # normal call with separate arguments
args = [3, 6, 2]
list(range(*args))
**kwargs
Using kwargs allows unpacking of variable length dictionary like arguments.
def cheeseshop(kind, **kwargs):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
print("-" * 40)
for kw in kwargs:
print(kw, ":", kwargs[kw])
cheeseshop("Limburger",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
cheeseshop("Limburger",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch",
year = 1979)
Similar to *args you can unpack dictionary items as variable keyword arguments.
data = {'shopkeeper':"Michael Palin",
'client':"John Cleese",
'sketch':"Cheese Shop Sketch",
'year': 1979}
cheeseshop("Limburger",
**data)
#putting *args and **kwargs together
#note that args and kwargs are not mandatory names
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
Exercise: Write a function, sum_everything that takes any numbers of arguments and adds them together.
Decorators#
A decorator wraps a function and adds functionality to a function based on the decorator function.
def a_decorator(f):
def wrapper():
print("Before function call")
f()
print("After function call")
return wrapper
def howdy():
print("Howdy!")
howdy()
@a_decorator
def howdy():
print("Howdy!")
howdy()
Recursion#
![]()
A concept \(x\) is recursive if it is used in its own definition.
Example:
Suppose you are to write a function that takes in a list of playing card values and returns the sum of these cards. A loop may be an obvious solution, but you might also use recursion to solve this problem.
Here, you can think of it as taking the first card from the deck and giving the rest of the deck to someone else to add them. This person continues this pattern, taking the first card and passing the remaining deck elsewhere.
def add_cards(deck):
smaller_deck = deck[1:]
partial_total = add_cards(smaller_deck)
extra_card = deck[0]
return extra_card + partial_total
The function above would continue on forever, thus we need a way to stop the handing over of the summing. Here, we can understand the stopping place as when the list is empty – deck == 0. This is our base case.
def add_cards(deck):
#base case
if deck == []:
return 0
#recursion
else:
smaller_deck = deck[1:]
partial_total = add_cards(smaller_deck)
extra_card = deck[0]
return extra_card + partial_total
print(add_cards([5, 2, 7, 3]))
Exercise: Define a function sum_from_m_to_n which returns the sum of all values from m to n. Write this function recursively.
To help you get started:
What is a smaller version of the problem? Summing from m+1 to n
What do we do with the solved version of the smaller problem? Add m to it
What is the smallest version of the problem (base case)? When m equals n
def sum_from_m_to_n(m, n):
pass
sum_from_m_to_n(1, 5)
Use Case: Memoization#
In mathematics, the Fibonacci numbers, commonly denoted Fn, form a sequence, called the Fibonacci sequence, such that each number is the sum of the two preceding ones, starting from 0 and 1. That is
and
for n > 1.
The beginning of the sequence is thus:
def fib(n):
if n <= 1:
return n
else:
return fib(n-1) + fib(n - 2)
fib(10)
fib(100)
Visualizing the function calls for fib(5):

from functools import lru_cache
@lru_cache
def fib(n):
if n <= 1:
return n
else:
return fib(n-1) + fib(n - 2)
fib(100)
Object Oriented Programming with Python#

type('Lenny')
'Lenny'.__len__()
Example of Basic Class#

One finds similar hierarchical organization in biology with trees of life.

Making an Account Class#
Define class
Create instance
Assign attributes
class Account:
pass
lennys_account = Account()
type(lennys_account)
jacobs_account = Account()
lennys_account.balance = 100
lennys_account.balance
jacobs_account.balance
Defining Methods on the Account#
Define class methods
Use
__init__as constructor in class
class Account:
def __init__(self, name, balance):
self.name = name
self.balance = balance
def withdraw(self, amount):
self.balance -= amount
lennys_account = Account('Lenny', 100)
lennys_account.balance
lennys_account.withdraw(20)
lennys_account.balance
hardys_account = Account('Hardy', 1000)
hardys_account.
__init__(): a special type of method that gives access to the attributes of the class.
Exercise:
We would like to avoid the
self.amount = self.amount - howmuch
sytax and instead refactor this method to increment the attribute inside the withdraw method. Add two more methods to the class, deposit and statement. The deposit method should allow to make deposits similar to the withdrawl, and the statement method should print who the owner is and what amount of money is in the account.
class Account:
def __init__(self, name, balance):
pass
def withdraw(self, amount):
pass
def deposit(self, amount):
pass
def statement(self):
pass
lennys_account = Account('Lenny', 100)
lennys_account.statement()
lennys_account.deposit(1_000_000)
lennys_account.statement()
Class variables#
It is important to understand the consequence of using general variables rather than attaching the variable to each instance of a class.
class Dog:
tricks = [] # mistaken use of a class variable
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
d.tricks
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
d.tricks
Inheritence#
Create class that inherits from parent class
Override methods of parent class

class SavingsAccount(Account):
def __init__(self, name, balance, rate = 0.05):
super().__init__(name = name, balance = balance)
self.rate = rate
def add_interest(self):
self.balance *= (1 + self.rate)
lennys_account = SavingsAccount('Lenny', 100, .07)
lennys_account.balance
lennys_account.add_interest()
lennys_account.balance
print(lennys_account)
lennys_account
The __str__ and __repr__ methods#
Define string representations of class
class SavingsAccount(Account):
def __init__(self, name, balance, rate = 0.05):
super().__init__(name = name, balance = balance)
self.rate = rate
def add_interest(self):
self.balance *= (1 + self.rate)
lennys_account = SavingsAccount('Lenny', 100)
lennys_account
print(lennys_account)
class SavingsAccount(Account):
def __init__(self, name, balance, rate = 0.05):
super().__init__(name = name, balance = balance)
self.rate = rate
def add_interest(self):
self.balance *= (1 + self.rate)
def __str__(self):
return f'This is a savings account belonging to {self.name}'
def __repr__(self):
return f'{self.name} owns this account'
lennys_account = SavingsAccount('Lenny', 100)
lennys_account
print(lennys_account)
Exercise: Create a Band class. A Band should have the following properties and methods:
name: Stringmembers: a list of Strings, defaults to an empty listintroduce_lineup(): a method that prints all of the strings in membersadd_member(new_member): a method that adds a new member to the members then invokes introduce_lineupkick_out(old_member): a method that removes the given member from the members list. If the members list is empty, add a disbanded property equal to True. Otherwise, invoke introduce_lineup.
Exercise:
Create the following subclasses that extend the Band class functionality:
Punkbands have astreet_credproperty set toTrueandearningsproperty set to 0sell_out(amount): a method that changes street_cred to False and earnings increase by amount
destroy_hotel_room(): a method that changes street_cred to True and earnings decrease by 5000
Jazzbands have a songbook property set to an empty listadd_song(song_title): a method that adds the given song_title to the songbooksolo(): a method that prints a message saying “____ is cooking!” fill in the blank with the first string in the members list, then move that member to the end of the list.
class Punk:
pass
class Jazz:
pass