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."
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"
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"
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"
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.
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