Friday, July 24, 2009

python decorators and protected methods

Decorators is a nice tool to provide further flexibility for python applications, by annotating or "decorating" statements at code attributes.

Let us shortly review some basic concepts that we meet in python. Python has an interesting mix of OO and Module programming paradigms, besides all the rest. That is, module mechanism methods, attributes and classes mechanism (Old-style "class A():" and new style "class A(object):") with attributes (properties - do not confuse with @property decorator) . Classes are conceptually similar to modules, but not vise versa. Further, we will talk about how to decorate methods.

As your know, python has notation for private variables starting with underline symbol ("__" double underline is recommended ). All other (attributes, methods) are public. And this can be understand as a python way of seeing things - minimalistic programming.

So, for creating static methods we have @classmethod decorator.

We will build a similar decorator to imitate protected methods (for example, like in Java), in following manner:

##
# Decorator @protected
#
def protected(annotated_func):
'''Decorator @protected - function to be applied for "protected" property behavior emulation'''
import traceback, inspect
# important: class is does not actually exist at the moment we want to obtain a method,
# i.e. annotated_func is a function, because class is in a process of declaration
# (is not declared yet)
def protected_func(*args, **kwds):
self = args[0]
invoking_self = None
# work with stack frames to obtain source of invocation
frames = inspect.stack()
frame = frames[1][0]
try:
invoking_self = frame.f_locals['self']
if not self == invoking_self \
or not isinstance(invoking_self, self.__class__): # access only from heir or itself
raise Exception('Attempting to access an instancemethod \'%s\' in class \'%s\'\
that is protected' % (annotated_func.func_name, self.__class__.__name__))
finally:
del frames
del frame
del invoking_self
del self
return annotated_func(*args, **kwds)
protected_func.func_name = annotated_func.func_name
return protected_func

4 comments: