210 likes | 549 Views
Useful Patterns & Idioms Trent Nelson ( tnelson@onresolve.com ). Python decorators. Overview. Initial experience learning decorators My own crude explanation/definition Some useful patterns/idioms. My Steps for Learning Decorators. Fired up Python Reference Manual
E N D
Useful Patterns & Idioms Trent Nelson (tnelson@onresolve.com) Python decorators
Overview • Initial experience learning decorators • My own crude explanation/definition • Some useful patterns/idioms
My Steps for Learning Decorators • Fired up Python Reference Manual • Look up ‘decorators’ in index • No hits • Search for ‘decorators’ • Found PEP 318: Decorators for Functions and Methods • Nice bit of history • Doesn’t provide much in the way of education
My Steps for Learning Decorators (cont.) • Stumbled upon ‘definition’ of decorators in section 7.6 of the Python Reference Manual: • “A function definition may be wrapped by one or more decorator expressions. Decorator expressions are evaluated when the function is defined, in the scope that contains the function definition. The result must be a callable, which is invoked with the function object as the only argument. The returned value is bound to the function name instead of the function object. Multiple decorators are applied in nested fashion.” • Well then...
My Steps for Learning Decorators • Wouldn’t recommend this approach! • Surprisingly very little examples of how to actually write decorators in Python documentation • Had to google around to grok the concept • Would have preferred an explanation along the lines of...
Decorators: My Crude Explanation by Example • Consider the following , which demonstrates two different types of decorators (one that doesn’t take any arguments, and one that does): class Foo(object): @cache def getExpensiveResource(self): ... @dll(c_char_p, returns=c_char_p) def readSetting(self, setting): ... • First important point: code body of your decorator will differ depending on whether or not you accept arguments
Decorator Without Arguments: @cache def cache(f): # f: function object of decorated method; has # useful info like f.func_name for the name of # the decorated method. def newf(*_args, **_kwds): # This code will be executed in lieu of the # method you've decorated. You can call the # decorated method via f(_args, _kwds). ... ... return newf
Decorator Without Arguments: @cache • Define your decorator to accept one parameter, ‘f’ • This will be the function object of the decorated method • Has useful info like f.func_name and f.f_frame • Define another method in the body, newf, that accepts the parameters *_args and **_kwds • Implement the body of your decorator in newf() • This will be called in lieu of the decorated method • Return newf at the end of your decorator def cache(f): # f: function object of decorated method; has # useful info like f.func_name for the name of # the decorated method. def newf(*_args, **_kwds): # This code will be executed in lieu of the # method you've decorated. You can call the # decorated method via f(_args, _kwds). ... ... return newf
Decorator With Arguments:@dll(c_char_p, returns=c_char_p) def dll(*args, **kwds): # args[0]: c_char_p # kwds['returns'] = c_char_p def decorator(f): # f: function object of decorated method; has # useful info like f.func_name for the name of # the decorated method. def newf(*_args, **_kwds): # This code will be executed in lieu of the # method you've decorated. You can call the # decorated method via f(_args, _kwds). ... ... return newf return decorator
Decorator With Arguments:@dll(c_char_p, returns=c_char_p) • Define your decorator as accepting two parameters, *args and **kwds • args[0]: c_char_p • kwds[‘returns’]: c_char_p • Define another method that takes a single parameter, ‘f’, which will be the function object of the decorated method • Define another method, newf, that accepts *_args, **_kwds • Implement the decorator body in newf • Return newf and decorator def dll(*args, **kwds): # args[0]: c_char_p # kwds['returns'] = c_char_p def decorator(f): # f: function object of decorated method; has # useful info like f.func_name for the name of # the decorated method. def newf(*_args, **_kwds): # This code will be executed in lieu of the # method you've decorated. You can call th # decorated method via f(_args, _kwds). ... ... return newf return decorator
Summary • If you don’t accept arguments: def cache(f): def newf(*_args, **kwds): ... return newf • If you do accept arguments: def dll(*args, **kwds): def decorator(f): def newf(*_args, **kwds): ... return newf return decorator • To call the original (decorated method) in newf(): result = f(_args, _kwds) • Next up: useful idioms
Useful Idioms • @cache: caching results of expensive operations • @returns: casting return types to other objects • @dll: simplifying interface to a C DLL via ctypes • @db.execute, @db.select, @db.selectAll: going too far?
Caching results of expensive operations: @cache • Definition: def cache(f): def newf(*_args, **_kwds): self = _args[0] cacheName = '_cache_' + f.func_name cache = self.__dict__.setdefault(cacheName, dict()) # Create a string representation of our decorated method's arguments to # use as the cache key. This ensures we only returned cached values for # method invocations with identical arguments. id = '%s,%s' % (repr(_args[1:]), repr(_kwds)) return cache.setdefault(id, f(*_args, **_kwds)) return newf • Sample usage: class Foo(object): @cache def expensiveOperation(foo, bar, *args, **kwds): ...
@dll/@db • See demo.
End of Presentation • Corresponding blog: • http://blogs.onresolve.com/?p=48 • Questions?