Friday, February 5, 2010

Bad Decorators

The Python programming language has a mechanism called a decorator that allows for the transformation of a function or method. The decorator syntax was introduced into the language to help with awkward code that was required in certain situations. For instance, it wasn't exactly trivial to declare a static class method. The use of a decorator is intended to mesh with the declaration of the function or method it is transforming, hence the location of the @decoratorname syntax right above the method. When a decorator is viewed as part of a method declaration, it is obvious that decorator is going to affect the method in one way or another. As to how obvious the way in which the method is affected depends on the usage of the decorator. Decorators can hurt the design of the software if misused. This is true of any programming language construct but may be harder to notice with decorators.

Declaring a static method in Python was a traditionally cumbersome task before the advent of decorators. There is no static keyword to place in front of the method to make it a static method. The preferred way to do this now is to apply the classmethod decorator to the method in question, in order to make it static. This decorator can be thought of as a good example of how decorators can be used without compromising the design. One property of classmethod that makes it a good decorator is that the intentions behind it are quite obvious. If you see a given method with a classmethod decorator, you most likely wouldn't think twice about why the decorator is there. Another reason the classmethod decorator isn't harmful to object-oriented design is that it is fundamental idea in object-orientation. Static methods are necessary to the very essence that describe a particular type of object in a software system. Below is the classmethod decorator in use.
class MyClass:
"""Simple class with a static method and an instance method."""

@classmethod
def my_static_method(cls):
"""Static method created with the classmethod decorator."""

return "From static method."

def my_instance_method(self):
"""Regular instance method."""

return "From instance method."
This simple class, MyClass defines two methods: my_static_method and my_instance_method. It is clear which method is the static method without even looking at the names. The classmethod decorator is not only a fundamental piece of object-oriented software design, but also to the Python programming language. The fact that there is no decorator definition to look up is also indicative of its simplicity. So what about a more complex decorator?

The purpose of a decorator is to transform a function or method. The decorator takes the original, and returns a new, function or method. Before the modified method is returned, it needs to do something to it. Otherwise, the decorator is completely pointless. In the case of the classmethod decorator, it transforms an otherwise instance-level method into one that applies to every instance of the same type. This decorator doesn't take the method signature into account. The classmethod decorator will work regardless of the number of parameters in the decorated method. More complex decorators will require specific parameters in the decorated method's signature. Consider a method with functionality that requires an authenticated session be established before executing. An obvious domain here would be a web application. More specifically, an instance method that is exposed as a web controller. The logical use of a decorator with these methods would be to make sure that the invocation request is authenticated. The fact that a given web application is likely to contain several of these methods that serve as web controllers makes the use of an authentication decorator even more attractive. This is in fact a good use of a decorator. The responsibility of making sure the method is allowed to execute is taken away from the method. This is great if it meshes well with the design. Here is an example of an authentication controller.
def authenticate(method):
"""Simple authentication controller."""

def _authenticate(self, user, passwd):
if user!="user" or passwd!="passwd":
raise Exception("Unauthorized")
return method(self, user, passwd)

return _authenticate

class MyController:
"""Web controller component with an authenticated method."""

@authenticate
def index(self, user, passwd):
return "Hello World"
Here we have a purposefully simplistic authenticate decorator. The decorator does nothing more that match the supplied user and passwd parameters against a string. The decorator implementation of no concern here. We are more interested in the index method of the MyController class. It is this method that is decorated by the authenticate decorator. What is worth noting here is how the introduction of the decorator raises the complexity of the index method ever so subtly. Notice that the authenticate decorator requires a user and passwd parameter. Without them, the decorator, and any methods decorated by it, simply will not work. This is fine in our example since we can see the decorator implementation and the signature requirements it will impose on our method. Our decorator is acting like an implicit interface that MyController realizes. This can be dangerous if not treated with care because it is implicit. The interface that authenticate is dictating is associated with the index method, not the MyController class.

An alternate way to implement this behavior would be a base class. This base class would implement the authentication functionality as required by any subsequent children classes. The methods of the child class that require authentication would invoke the authentication functionality of the base class directly. Below is an example implementation of this approach.
class Authenticator:
"""Simple authenticating class."""

def authenticate(self, user, passwd):
"""Simple authentication method."""

if user!="user" or passwd!="passwd":
raise Exception("Unauthorized")

class MyController(Authenticator):
"""Web controller component with an authenticated method."""

def index(self, user, passwd):
"""Simple web controller."""

self.authenticate(user, passwd)
return "Hello World"
Not much has changed with the MyController class between the last two examples. The class now inherits authentication capabilities from the Authenticator class. The index method will now directly invoke the authentication behavior. Similar to the decorator, inheriting the authentication functionality moves the responsibility away from the MyController class so that it can be shared with other classes. The difference being that with the latter approach, we gain the inheritance mechanism. Not only can the MyController class inherit functionality, but so can Authenticator. Decorator declarations don't handle inheritance so well.

Any given function or method is subject to multiple decorations. If the developer chooses to do so, they could have a stack of five method decorators above the method declaration. This is possible but just plain ugly. Doing this would take all meaning that the bare method would have had on its own. Multiple decorators being applied to a single method might still make sense in some scenarios. Take the preceding authentication-decorated web controller example for instance. Most web controllers would want the ability to return more than a single content type. Below is an altered version of the authentication decorator example.
def authenticate(method):
"""Simple authentication controller."""

def _authenticate(self, user, passwd):
if user!="user" or passwd!="passwd":
raise Exception("Unauthorized")
return method(self, user, passwd)

return _authenticate

def xml(method):
"""XML content-type decorator."""

def _xml(*args, **kw):
return to_xml(method(*args, **kw))

return _xml

def to_xml(content):
"""Example XML formatting function."""
return "<xml>%s</xml>"%(content)

class MyController:
"""Web controller component with an XML, authenticated method."""

@authenticate
@xml
def index(self, user, passwd):
return "Hello World"
In this example, the index method of the MyController class now has two decorations, authenticate and xml. This xml decorator simple transforms the data returned by the method. Notice also how the level of complexity has been raised again. In this example, the decorators are straightforward enough to understand at first glance. With multiple decorators, the end result of invoking the decorated method is hard to understand. You can't look to the class hierarchy for help in most cases.

None of these examples have particularly bad designs. They are simple enough to understand. The intent here was to show that harm decorators can cause to design are often subtle and go unnoticed for quite some time. This could even lead to introducing more decorators to correct the problems of the initial decorators. Decorators are intended to add functionality to methods or conditionally take functionality away from them. This can be an elegant way to impose minor constraints on how methods operate. The biggest problem with decorators is relying too heavily on them. Using this approach in conjunction with a object-oriented inheritance can lead to a design that is nearly unmaintainable.

1 comment :

  1. You should be more careful with the terminology: the classmethod decorator is for creating ... class methods. Static methods are created using the staticmethod decorator. This is a distinction which is not present in certain other OO-languages.

    ReplyDelete