1. Bits and Bobs

First Class Functions

def dispatch(data):  # function object
    print(data)
    return list(data)

def subdispatch(func, seq):
    data = func(seq)
    print(data)

data = dispatch("ACGTGGC")
print(data)

func = dispatch  # function object can be assigned to a name
func("TTTTT")
data2 = func("CCCCC")
print(data2)

subdispatch(dispatch, "CATCATCAT")  # function object can be sent as an argument

Closures

def outer(msg):
    statement = msg

    def inner():
        print(statement)

    return inner

func1 = outer("something else")
func2 = outer("another thing")

func1()
func2()

Decorator Function

def original_function():
    print("running original code")

def decorator_function(func):
    def wrapper_function():
        print("do something before")
        func()
        print("do something after")

    return wrapper_function

new_function = decorator_function(original_function)
new_function()
def original_function(data1, data2):
    print(f"running w/ values {data1}, {data2}")

def decorator_function(func):
    def wrapper_function(*arg, **kwargs):
        print("do something before")
        func(*arg, **kwargs)
        print("do something after")

    return wrapper_function

new_function = decorator_function(original_function)
new_function("happy", 1)
def original_function(data1, data2):
    print(f"running w/ values {data1}, {data2}")

class decorator_class(object):

    def __init__(self, some_function):
        self.some_function = some_function

    def __call__(self, *args, **kwargs):
        print("do something before")
        self.some_function(*args, **kwargs)
        print("do something after")

new_function = decorator_class(original_function)
new_function("happy", 1)

debug_timer.py

import time

def debug_timer(some_function):
    def wrapper_function(*args, **kwargs):
        t1 = time.time()
        some_function(*args, **kwargs)
        t2 = time.time()
        print(f"elapsed time: {t2 - t1} s")

    return wrapper_function

@debug_timer  # @ is a symbol that allows access to the debug_timer
def original_function(data1, data2):
    print(f"orig func with ({data1},{data2})")

# t1 = time.time()
# original_function("happy", 1)
# t2 = time.time()
# print(f"elapsed time: {t2 - t1} s")

# test_function = debug_timer(original_function)
# test_function("happy", 1)

original_function("happy", 1)
original_function([row for row in range(0,100000)], 1)

@ is a decorator that extends the functionality of an existing function or class

Property Decorators

class Point:
    def __init__(self, x=0.0, y=0.0):
        self.x = x
        self.y = y

    @property
    def r(self):
        from math import sqrt
        return sqrt(self.x ** 2 + self.y ** 2)

    @property
    def theta(self):
        from math import atan, pi
        return atan(self.y / self.x) / pi * 180

    def print_euclid(self):
        print(f"({self.x}, {self.y})")

    def print_polar(self):
        print(f"({self.r},{self.theta})")

point1 = Point(2, 3)

point1.print_euclid()
point1.print_polar()

point1.x = 5

point1.print_euclid()
point1.print_polar()

Classmethod and Staticmethod

class Point:
    def __init__(self, x=0.0, y=0.0):
        self.x = x
        self.y = y

    @property
    def r(self):
        from math import sqrt
        return sqrt(self.x ** 2 + self.y ** 2)

    @property
    def theta(self):
        from math import atan, pi
        return atan(self.y / self.x) / pi * 180

    def print_euclid(self):
        print(f"({self.x}, {self.y})")

    def print_polar(self):
        print(f"({self.r}, {self.theta})")

    def print_origin(self):
        print(f"({self.x0}, {self.y0})")

    @classmethod
    def set_origin(cls, x=0.0, y=0.0):  # cls represents the class object
        cls.x0 = x
        cls.y0 = y

    @staticmethod
    def distance(point1, point2):
        from math import sqrt
        return sqrt((point2.x-point1.x)**2 + (point2.y-point1.y)**2)

point1 = Point(2, 3)
point2 = Point(4, 5)

point1.print_euclid()
point2.print_euclid()

# print(f"({point1.x},{point1.y})")
# print(f"({point2.x},{point2.y})")

point1.set_origin(5, 5)

point1.print_origin()
point2.print_origin()

point3 = Point(6, 6)

point3.print_origin()

print(Point.distance(point1, point2))