[Python-Dev] PEP 318: More examples of decorator use

Jim Hugunin lists at hugunin.net
Tue Apr 6 18:08:54 EDT 2004


I'm a little scared of getting involved in the discussion of PEP 318.
Nevertheless, I think any language design discussion can benefit from more
examples.  Here's a list of ways that I would use decorators in my Python
code if I had simpler syntax than is available today.

*Interoperability with other languages and systems

jythonc parses method docstrings looking for a special directive to generate
Java-compatible method signatures, i.e.

def meth(self, a, b):
    "@sig public void meth(int a, int b)"
    pass

If Python had structured metadata, this would change to

[java_sig("public void meth(int a, int b)")]
def meth(self, a, b):
    pass

C# uses this mechanism to provide interoperability with C, COM, and web
services.  Here's a translation of a web services example from the .NET
framework docs.

[WebService(Description="Common Server Variables", 
            Namespace="http://www.contoso.com/")]
class ServerVariables:

    [WebMethod(Description="Obtains the Server Computer Name",
               EnableSession=false)]
    def GetMachineName(self):
       return self.Server.MachineName


    [SoapDocumentMethod(Action="http://www.contoso.com/Sample", 
           RequestNamespace="http://www.contoso.com/Request",
           RequestElementName="GetUserNameRequest",
           ResponseNamespace="http://www.contoso.com/Response",
           ResponseElementName="GetUserNameResponse"),
     WebMethod(Description="Obtains the User Name")]
    def GetUserName(self): 
        ...

This is the same functionality motivating Bob Ipolito's pleas for this
feature to support pyobjc, and it would obviously also help IronPython.


*Tracing and other global policies

Aspect-oriented programming has had great success capturing global policies
for things like tracing in a single module.  For a language like Python that
already has strong meta-programming support it's possible to program in an
AOP style without any language changes (see the tracing implementation in
parrotbench/b0.py for an example).

However, one lesson learned from actually applying AOP to large systems
(http://hugunin.net/papers/pra03-colyer.pdf) was that there was always a
need to capture a few local exceptions.  Current AOP systems can do this in
a clumsy way, but almost everyone agrees that C#/Java attributes are the
best way to specify these kinds of exceptions.

Here's an example of a tracing policy with a local exception.

from tracing import notrace, add_tracing

class C:
    def m1(self): ...
    def m2(self): ...

    [notrace]
    def m0(self): ...

if __DEBUG__: add_tracing(globals())

This code will recursively walk all the declarations in a module and wrap
all methods and functions with tracing code.  Attributes are used here to
indicate special handling for specific methods.  In this case, m0 should not
be traced.


*Unit testing

The nunit test framework uses attributes instead of naming conventions to
label test methods.  My favorite part of this is the additional attributes,
for example 'expected_exception' to indicate that the correct behavior for a
test is to throw a particular exception, i.e.

[test, expected_exception(InsufficientFundsError)]
def transfer2(self):
    self.source.transfer(100000000000)

vs.

def test_transfer2(self):
    self.assertRaises(InsufficientFundsError,
                      self.source.transfer, 1000000000)  

This example is extremely compelling for C# or Java code where functions
aren't first class and a complicated try/catch block is required instead of
assertRaises.  It's less compelling than I'd expected for Python; however, I
still find the version with attributes a little more clear.


*Parsing

There are many simple parser implementations for Python that embed grammar
rules in doc strings.  This would be nicer to have in explicit metadata.
Here's an example from Brian Sabbey's dparser for python.

[action("exp",  "exp '+' exp")]
def d_add(t):
    return t[0] + t[2]
    
[action("exp", '"[0-9]+"')]
def d_number(t):
    return int(t[0])


*Synchronization

I think a synchronized decorator would be useful, but more sophisticated
locking policies are even more interesting.  Here's an example of
reader/writer synchronization assuming a metaclass ReadWriteSynchro that
cooperates with attributes on specific methods.

class DataStore(ReadWriteSynchro):
    [reader]
    def read(self, key): ....

    [writer]
    def write(self, key, value): ...

    ...


*Other examples that I'd probably use

memoized from Michael Chermside to automatically cache the results of
idempotent functions

accepts and returns type checking wrappers from the PEP until/unless Python
gets a better answer for true optional type declarations

generic from Edward Loper to support generic functions.  I both really like
and really hate this decorator.  I'd love to have tolerable syntax for
generic functions and this gives me something tolerable.  On the other hand,
I really dislike code that uses sys._getframe() so that might be enough to
scare me off of this.


*Some examples that I'd like to see in an anti-patterns list

onexit from the PEP.  I don't think decorators should have side-effects
visible outside of the class/function itself.  This seems MUCH better
accomplished with the explicit call to the registration function.

singleton from the PEP.  Whenever possible, decorators should return the
same kind of thing that they are given.  This code seems much less clear
than explicitly creating a singleton instance.

propget, propset, propdel from Edward Loper via Guido.  To me, these violate
the principle of modularity.  I much prefer the explicit property
declaration that puts all of the parts together.

  class C(object):
     def getX(self): return self.__x
     def setX(self, x): self.__x = x
     x = property(getX, setX)


-Jim





More information about the Python-Dev mailing list