Dwight Hutto wrote at 2012-9-14 23:42 -0400: > ... >Reduce redundancy, is argumentative. > >To me, a decorator, is no more than a logging function. Correct me if >I'm wrong.
Well, it depends on how you are using decorators and how complex your decorators are. If what you are using as decorating function it really trivial, as trivial as "@<decoratorname>", then you do not gain much. But your decorator functions need not be trivial. An example: in a recent project, I have implemented a SOAP webservice where most services depend on a valid session and must return specified fields even when (as in the case of an error) there is no senseful value. Instead of putting into each of those function implementations the check "do I have a valid session?" and at the end "add required fields not specified", I opted for the following decorator: def valid_session(*fields): ! fields = ("errorcode",) + fields @decorator def valid_session(f, self, sessionkey, *args, **kw): ! s = get_session(sessionkey) ! if not s.get("authenticated", False): ! rd = {"errorcode": u"1000"} ! else: ! rd = f(self, sessionkey, *args, **kw) ! return tuple(rd.get(field, DEFAULTS.get(field, '')) for field in fields) return valid_session The lines starting with "!" represent the logic encapsulated by the decorator -- the logic, I would have to copy into each function implementation without it. I then use it this way: @valid_session() def logout(self, sessionkey): s = get_session(sessionkey) s["authenticated"] = False return {} @valid_session("amountavail") def getStock(self, sessionkey, customer, item, amount): info = self._get_article(item) return {u"amountavail":info["deliverability"] and u"0" or u"1"} @valid_session("item", "shortdescription", "pe", "me", "min", "price", "vpe", "stock", "linkpicture", "linkdetail", "linklist", "description", "tax") def fetchDetail(self, sessionkey, customer, item): return self._get_article(item) ... I hope you can see that at least in this example, the use of the decorator reduces redundancy and highly improves readability -- because boilerplate code (check valid session, add default values for unspecified fields) is not copied over and over again but isolated in a single place. The example uses a second decorator ("@decorator") -- in the decorator definition itself. This decorator comes from the "decorator" module, a module facilitating the definition of signature preserving decorators (important in my context): such a decorator ensures that the decoration result has the same parameters as the decorated function. To achieve this, complex Python implementation details and Python's introspection must be used. And I am very happy that I do not have to reproduce this logic in my decorator definitions but just say "@decorator" :-) Example 3: In another project, I had to implement a webservice where most of the functions should return "json" serialized data structures. As I like decorators, I chose a "@json" decorator. Its definition looks like this: @decorator def json(f, self, *args, **kw): r = f(self, *args, **kw) self.request.response.setHeader( 'content-type', # "application/json" made problems with the firewall, # try "text/json" instead #'application/json; charset=utf-8' 'text/json; charset=utf-8' ) return udumps(r) It calls the decorated function, then adds the correct "content-type" header and finally returns the "json" serialized return value. The webservice function definitions then look like: @json def f1(self, ....): .... @json def f2(self, ...): .... The function implementions can concentrate on their primary task. The "json" decorator" tells that the result is (by magic specified elsewhere) turned into a "json" serialized value. This example demontrates the improved maintainability (caused by the redundancy reduction): the "json rpc" specification stipulates the use of the "application/json" content type. Correspondingly, I used this content-type header initially. However, many enterprise firewalls try to protect against viruses by banning "application/*" responses -- and in those environments, my initial webservice implementation did not work. Thus, I changed the content type to "text/json". Thanks to the decorator encapsulation of the "json result logic", I could make my change at a single place -- not littered all over the webservice implementation. And a final example: Sometimes you are interested to cache (expensive) function results. Caching involves non-trivial logic (determine the cache, determine the key, check whether the cache contains a value for the key; if not, call the function, cache the result). The package "plone.memoize" defines a set of decorators (for different caching policies) which which caching can be as easy as: @memoize def f(....): .... The complete caching logic is encapsulated in the tiny "@memoize" prefix. It tells: calls to this function are cached. The function implementation can concentrate on its primary task and there is no need to obscure the implementation by the orthogonal aspect of caching. I hope I could convince you that while you may not have a serious need for decorators, there are cases where they can be really useful. Should I have not yet succeeded, I suggest you read some overview on aspect oriented programming. I am sure, you will find there losts of further examples why it is a good idea to separate general purpose aspects (logging, monitoring, persistency, resource management, caching, serialization, ...) from the primary task of a function. Decorators provide syntactic sugur to facilitate this separation in Python. -- Dieter -- http://mail.python.org/mailman/listinfo/python-list