Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Decorators Demystified Presentation

Decorators Demystified

This is the material for SANS Webcast where we deep dive into the internals of how Python Decorators work. To understand this material you will want to watch the associated presentation.

The registration link is here: https://www.sans.org/webcasts/python-decorators-demystified-108900

(update) The talk has been archived here : https://www.youtube.com/watch?v=M4FrdJKGwX4&t=1981s

A quick review of concepts from 573:

1) Variables can hold functions:

x = print
x("Hello {}, what is your {}".format("sir robin", "quest"))

Watch it execute Click Here

2) The byte code and variable names are attributes of the variable that hold the function

def newfunc(input_arg=10):
    local1 = 100
    local2 = 5
    local3 = local1+local2
    return

newfunc.__code__.co_varnames, newfunc.__code__.co_consts
newfunc.__code__.co_code

Watch it execute Click Here

3) You can create functions to build and return new functions

def print_in_color(color):
    def colored_print(*args,**kwargs):
        print("Set Screen Color to {0}".format(color))
        print(*args,**kwargs)
        return None  
    return colored_print

red_print = print_in_color("RED")
blue_print = print_in_color("BLUE")
red_print("mark was here")
blue_print("mark was not here")

Watch it execute Click Here

Now we can dig into decorators!

1) The variables and values the functions used are stored in attributes of the variable "red_print"

red_print.__code__.co_varnames, red_print.__code__.co_consts

And a special attrubute named closure hold the contents of any function dependencies that have existed

red_print.__closure__[0].cell_contents
red_print.__code__.co_freevars

2) A Decorator is a function that does stuff, calls another function and does more stuff.

Watch this execute Click Here

def wrap_function_with_prints(function_to_call):
    def newfunc(*args, **kwargs):
        print("about to call {0}".format(str(function_to_call)))
        function_to_call(*args, **kwargs)
        print("called")
    return newfunc

def sayhi():
    print("HI!")

sayhi()
x = wrap_function_with_prints(sayhi)
x()

Lets try to wrap a function that takes arguments also..

def add(num1,num2):
   print("{}+{}={}".format(num1,num2,num1+num2))

x = wrap_function_with_prints(add)
x(10,35)

Most often you overwrite the original function with the new decorated function

sayhi = wrap_function_with_prints(sayhi)
sayhi()

This is an alternative syntax for doing the same thing

@wrap_function_with_prints
def sayhi():
    print("HI!")
    
@wrap_function_with_prints
def sayhi():
    print("HI!")

@wrap_function_with_prints
def saybye():
    print("BYE!")

@wrap_function_with_prints
def add(n1,n2):
    print("sum is", n1+n2)

@wrap_function_with_prints
def sayanything(anything):
    print(anything)

3) Your decorators can take arguments you just need a third function to consume and hold in closure

Watch it execute Click Here

def decorator_with_args(item1, item2):
    def decorator_factory(function_to_call):
        print("{0} {1} {2}".format(item1, item2, str(function_to_call)))
        def wrapped_f(*args,**kwargs):
            print("about to call")
            print("Decorator arguments:", item1, item2, str(args), str(kwargs))    	          
            function_to_call(*args,**kwargs)
            print("called")
        return wrapped_f
    return decorator_factory

def say(what):
    print(what)

x = decorator_with_args("SEC573","ROCKS")(say)
x("UH HUH")

@decorator_with_args("SEC573","ROCKS")
def say(what):
    print(what)
    
say("I've been decorated!!!")

4) A practical Example. A timer decorator

import functools

def time_this(func):
    @functools.wraps(func)
    def wrapper(*args,**kwargs):
        "This calculates run times"
        start_time= datetime.datetime.now()
        ret = func(*args,**kwargs)
        elapsed = datetime.datetime.now()- start_time
        print("The function {} took {}.{} seconds".format(func.__name__, elapsed.seconds, elapsed.microseconds))
        return ret
    return wrapper
       
from subprocess import Popen,PIPE
@time_this
def exec_cmd(cmd):
    "Run a command and capture the output"
    out,err = Popen(cmd,shell=True,stderr=PIPE,stdout=PIPE).communicate()
    return out+err
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment