[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)
do_stuff()
return
the output will be
3
2
1
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
this?
def nested():
try:
throws()
finally:
try:
cleanup()
except:
pass
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
1
(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
do_this()
do_that()
do_something_else()
if flag:
return
if condition:
defer: something()
defer: cleanup()
throws()
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.
--
Steven
More information about the Python-ideas
mailing list