[Python-ideas] ScopeGuardStatement/Defer Proposal

Steven D'Aprano steve at pearwood.info
Fri Feb 17 02:25:41 CET 2012

Manuel Barkhau wrote:

> As "defer:" is currently invalid syntax, there shouldn't be any code
> breakage from adding the new keyword.

Of course there will be. Every new keyword will break code that uses that word 
as a regular name:

defer = True
instance.defer = None

Both of which will become a SyntaxError if defer becomes a keyword.

It's not even like "defer" is an uncommon word unlikely to be used anywhere. 
(Although I can't find any examples of it in the standard library.)

> Some rules:
>  - Deferred blocks are executed in the reverse lexical order in which
>    they appear.

Why in reverse order? This is unintuitive. If you write:

def func():
     defer: print(1)
     defer: print(2)
     defer: print(3)

the output will be


Is this a deliberate design choice, or an accident of implementation that D 
and Go have followed? If it is deliberate, what is the rationale for it?

> The nesting advantage becomes more apparent when more are required. Here
> is an example from

I disagree. Nesting is an advantage, the use of defer which eliminates that 
nesting is a MAJOR disadvantage of the concept. You seem to believe that 
nesting is a problem to be worked around. I call it a feature to be encouraged.

With try...except/finally, the structure of which blocks are called, and when, 
is directly reflected in the nesting and indentation.

With defer, that structure is gone. The reader has to try to recreate the 
execution order in their head. That is an enormous negative.

> http://www.doughellmann.com/articles/how-tos/python-exception-handling/index.html
>     #!/usr/bin/env python
>     import sys
>     import traceback
>     def throws():
>         raise RuntimeError('error from throws')
>     def cleanup():
>         raise RuntimeError('error from cleanup')
>     def nested():
>         try:
>             throws()
>         except Exception as original_error:
>             try:
>                 raise
>             finally:
>                 try:
>                     cleanup()
>                 except:
>                     pass # ignore errors in cleanup

I don't understand the point of that example. Wouldn't it be better written as 

def nested():

As far as I can tell, my version gives the same behaviour as yours:

py> main()
Traceback (most recent call last):
   File "<stdin>", line 3, in main
   File "<stdin>", line 3, in nested
   File "<stdin>", line 2, in throws
RuntimeError: error from throws

(Tested in Python 2.5 with the obvious syntax changes.)

> Here are the equivalent of main and nested functions using defer:
>     def nested():
>         defer RuntimeError: pass # ignore errors in cleanup
>         defer: cleanup()
>         throws()

How is the reader supposed to know that pass will ignore errors in cleanup, 
and nothing else, without the comment? Imagine that the first defer line and 
the second are separated by a bunch of code:

def nested():
     defer RuntimeError: pass
     if flag:
     if condition:
         defer: something()
     defer: cleanup()

What is there to connect the first defer to the cleanup now? It seems to me 
that defer would let you write spaghetti code in a way which is really 
difficult (if not impossible) with try blocks. When considering a proposal, we 
should consider how it will be abused as well as how it will be used.


More information about the Python-ideas mailing list