COMP1531

7.6 - SDLC Design - SE Principles 2

 

Decorators

Decorators allow us to add functionality to a function without altering the function itself, by "decorating" (wrapping) around it.

 

But first... some background

Function Parameters

def foo1(zid, name, age, suburb):
    print(zid, name, age, suburb)

def foo2(zid=None, name=None, age=None, suburb=None):
    print(zid, name, age, suburb)

if __name__ == '__main__':
    
    foo1('z3418003', 'Hayden', '72', 'Kensington')
    
    foo2('z3418003', 'Hayden')
    foo2(name='Hayden', suburb='Kensington', age='72', zid='z3418003')
    foo2(age='72', zid='z3418003')
    
    foo2('z3418003', suburb='Kensington')

decor1.py

Function Parameters

def foo(zid=None, name=None, *args, **kwargs):
    print(zid, name)
    print(args) # A list
    print(kwargs) # A dictionary

if __name__ == '__main__':
    
    foo('z3418003', None, 'mercury', 'venus', planet1='earth', planet2='mars')

decor2.py

def foo(*args, **kwargs):
    print(args) # A list
    print(kwargs) # A dictionary

if __name__ == '__main__':
    foo('this', 'is', truly='dynamic')

decor3.py

A proper decorator

def make_uppercase(input):
	return input.upper()

def get_first_name():
	return "Hayden"

def get_last_name():
	return "Smith"

if __name__ == '__main__':
    print(make_uppercase(get_first_name()))
    print(make_uppercase(get_last_name()))

decor4.py

A proper decorator

def make_uppercase(function):
	def wrapper(*args, **kwargs):
		return function(*args, **kwargs).upper()
	return wrapper

@make_uppercase
def get_first_name():
	return "Hayden"

@make_uppercase
def get_last_name():
	return "Smith"

if __name__ == '__main__':
    print(get_first_name())
    print(get_last_name())

decor5.py

This code can be used as a template

Decorator, run twice

def run_twice(function):
	def wrapper(*args, **kwargs):
		return function(*args, **kwargs) \
		     + function(*args, **kwargs)
	return wrapper

@run_twice
def get_first_name():
	return "Hayden"

@run_twice
def get_last_name():
	return "Smith"

if __name__ == '__main__':
    print(get_first_name())
    print(get_last_name())

decor6.py

Decorator, more

class Message:
	def __init__(self, id, text):
		self.id = id
		self.text = text

messages = [
	Message(1, "Hello"),
	Message(2, "How are you?"),
]

def get_message_by_id(id):
	return [m for m in messages if m.id == id][0]

def message_id_to_obj(function):
	def wrapper(*args, **kwargs):
		argsList = list(args)
		argsList[0] = get_message_by_id(argsList[0])
		args = tuple(argsList)
		return function(*args, **kwargs)
	return wrapper

@message_id_to_obj
def printMessage(message):
	print(message.text)

if __name__ == '__main__':
	printMessage(1)

decor7.py

Single Responsibility Principle

Every module/function/class  in a program should have responsibility for just a single piece of that program's functionality

Single Responsibility Principle

Functions

 

We want to ensure that each function is only responsible for one task. If it's not, break it up into multiple functions.

 

This is often a good idea. The only instances where this might not be a good idea are if it complicates the caller substantially (i.e. makes the code calling your split up functions overly complex)

 

Primary purpose: Readability and modularity

Single Responsibility Principle

Classes

 

Three files:

  • srp2.py: Poor SRP
  • srp2_fixed.py: Fixed SRP, abstraction remains
  • srp2_fixed2.py: Fixed SRP, no abstraction

 

We can apply the same principles to classes, ensuring that a single class maintains a single broad responsibility, and each function within the class also has a more specific single responsibility

COMP1531 21T1 - 7.6 - SDLC Design - SE Principles 2

By haydensmith

COMP1531 21T1 - 7.6 - SDLC Design - SE Principles 2

  • 389