Skip to content

Instantly share code, notes, and snippets.

Last active Jan 4, 2016
What would you like to do?
#!/bin/env python
__author__ = 'Marco Massenzio <m.massenzio (at) gmail com'
""" Simple python decorator example.
from functools import wraps
def loggable(**dec_kwargs):
""" Outwardly an innocuos wrapper to log execution of a method, this turns nasty if
we pass the `snoop` keyword arg in the decorator: in which case, when invoked, not
only the passed-in value is pwned, but also whatever args were passed to the decorator
can be used against us
nsa_is_here = False
if dec_kwargs.pop('snoop', False):
print '>>> I can snoop here!'
nsa_is_here = True
nsa_data = dec_kwargs
def outer_wrapper(func):
""" The outer wrapper knows little about all this, but has access to the function
object - so it can log it and wrap it - and only indirect access to the args passed
to the decorator.
At this point, the wrapped ```func``` has not been invoked yet, so its arguments are,
obviously, unknown.
print 'Wrapping ', func.__name__
def log_func(*args, **kwargs):
""" This is the real-deal: the wrapper that will be invoked in place of ```func```:
now we have full access to the arguments passed in (both positional and keyword)
and we can inspect, and possibly modify, them before (or even after) passing them
to ```func```
if nsa_is_here:
print '>>> NSA sent us this: ', nsa_data
print '>>> We intercepted "val" = ', kwargs.get('val')
print 'Executing ', func.__name__
res = func(*args, **kwargs)
print 'done with ', func.__name__
if nsa_is_here:
print '>>> {} returned {}'.format(func.__name__, res)
return res
return log_func
return outer_wrapper
def foo(val, baz, quz=None):
print 'Value: ', val, ' - baz: ', baz, ' - quz: ', quz
print '+++'
@loggable(snoop=True, have_warrant='whocares', is_legal='barely')
def batter(name, **kwargs):
print 'name: ', name
print 'kwargs: ', kwargs
print '==='
def wont_work(val, name):
""" The lack of trailing parentheses ```()``` in the decorator causes this method's invocation
to fail with a::
TypeError: loggable() takes exactly 0 arguments (1 given)
print 'This will not work!'
def main():
""" Note that, by and large, invoking ```foo``` and ```batter`` does not require us to have
any knowledge about the internals of ```@loggable```: we just invoke those methods normally
kwargs = {'val': 100, 'baz': 2}
batter('ugo', decoy=1, noise=2, val=3)
if __name__ == '__main__':
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment