[Python-Dev] Re: Decorators: vertical bar syntax

Fernando Perez fperez528 at yahoo.com
Sun Aug 8 03:26:18 CEST 2004


Guido van Rossum wrote:

>> I'm including both of the following variants:

[Let's call this option #1]
>>      |classmethod
>>      |accepts(int, int)
>>      |returns(float)
>>      def foo(arg1, arg2):
>>          ...

[And this option #2]
>>      def foo(arg1, arg2):
>>          |classmethod
>>          |accepts(int, int)
>>          |returns(float)
>>          ...

> In the discussion on decorators months ago, solutions involving
> special syntax inside the block were ruled out early on.  Basically,
> you shouldn't have to peek inside the block to find out important
> external properties of the function.  (Yes, I know that docstrings
> violate this principle.  For the record, it would be better if they
> were prefix too; and a prefix decorator syntax will let us fix this in
> the future but introducing a "doc" decorator.)

Allow me to try and make a reasoned argument against your point, and in favor of
#2 (as labeled above), even for docstrings.  The reason why I find #1 more
difficult to mentally parse, is that as you read the code, you need to
accumulate information in your head which is not yet tied to anything in
particular (the def line hasn't arrived yet).  

In my brain, the process goes something like this (imagine this happening in
code with multiple decorators, each with multiple lines of comments detailing
its use):

      |classmethod

ok, a class method is coming, but I don't know yet what it's called.  Put this
on the 'mental stack', let's see where it goes...

      |accepts(int, int)

and it will take integer arguments; more for the stack...

      |returns(float)

and return a float; add to the stack...

      def foo(arg1, arg2):

Ah! It's function foo, with 2 args named arg1, arg2.  Now I go back (mentally)
and wrap together my knowledge about it, unwinding my stack, producing foo, a
classmethod with arg1,arg2 as integers, and which returns a float.

I find this unpleasant and inefficient from a cognitive viewpoint, because our
brain is best at processing information when it has 'buckets' where to put it:
if you read the def line first, the function name becomes the 'bucket' where
all further information goes.  That's why it's always harder to remember random
bits of information than things which connect to other things you already know.

For these reasons, I feel that option 2 is much more efficient.  A similar
outline of my mental process with option 2:

>>      def foo(arg1, arg2):

Ok, a new function foo() goes in, it takes 2 args, arg1 and arg2.  Make mental
bucket labeled 'foo'.  Anything that comes now, until we de-indent, goes into
box 'foo'.  No need to keep random bits of information up in the air until I
know where they're supposed to go.

>>          |classmethod

Store 'classmethod' in bucket 'foo'.

>>          |accepts(int, int)

Store 'accepts(int, int)' in bucket 'foo'.

>>          |returns(float)

Store 'returns(float)' in bucket 'foo'.

Done.  All information is stored in mental bucket 'foo', where it belongs.

Bucket 'foo' was created when I saw the def line, nicely separated by
indentation from all else, which is what my python mental processor is already
well trained to do.  Then, all the information that came afterwards (including
possibly the docstring) fell nicely into that mental bucket.

Option 1, which you seem to favor, reminds me of a problem I felt when studying
German for the first time (Spanish is my native language).  German has these
funny 'split-verbs', like anrufen, which are used in a sentence split in two,
and with the initial part of the verb only appearing at the very end of the
sentence.  A simple example (I don't recall the really painful ones anymore) is
"Ich rufe dich an".  You don't know that the verb is anrufen, until you see the
'an' at the end of the sentence.  With long sentences, I remember feeling very
frustrated, because I esentially had to buffer the whole thing in my head with
very little structure, waiting for the last bit to come in.  Then I needed to
quickly reconstruct the meaning, now that I knew which verb was being used, by
mentally reparsing the whole thing.  It's been many years, but I distinctly
remember this as being a significant hurdle in spoken conversation in German.  

I honestly feel adding this kind of process to python would be a significant
reduction to the fluidity with which the language 'parses mentally'.

And I like the fact that this syntax:

def foo(...):
    @dec1
    @dec2
    "docstring"

    # code begins...

sort of declares a 'header zone' for the function, which is visually well
defined by indentation and string highlighting, but allows the 'def' part to be
very easy to spot visually because it cuts the indentation cleanly.  It feels
like a quite natural convention for me to think that until I see the closing of
the docstring, I'm in the 'header' or 'interface' zone of the function.

I hope I've at least presented a clear argument in defense of this choice. 
Whether you agree or not, is up to you.  I deliberatly didn't mention the @ vs
| issue, since for this discussion it's completely irrelevant.

Best,

f



More information about the Python-Dev mailing list