[Python-Dev] Merging PEP 310 and PEP 340-redux?

Michael Hudson mwh at python.net
Fri May 13 15:12:34 CEST 2005


Guido van Rossum <gvanrossum at gmail.com> writes:

> I just read Raymond Chen's rant against control flow macros:
> http://blogs.msdn.com/oldnewthing/archive/2005/01/06/347666.aspx
>
> I think this pretty much kills PEP 340, as well as Nick Coghlan's
> alternative: both proposals let you write a "template" that can be
> used to hide exception-catching code, which is a form of control flow
> (and a pretty important one if you read Chen's rant against exceptions
> referenced by the former, even if you don't agree with everything he
> says in the latter).

Well, I'm not sure what the content of the latter article is, other
than "getting things right can be hard".

BTW, the "else:" on try statements is so very handy for getting this
sort of thing (more) corrent.

> Which leaves us, IMO, with the choice between PEP 310 and my own
> "PEP-340-redux" proposal; these *only* introduce a finally-clause,
> which does not affect the control flow. I'm not counting exceptions
> that might happen in the finally-clause; exceptions can happen
> anywhere anyway. But I am counting the *catching* of an exception as
> control flow, since that means that code past BLOCK (in the same
> function) is reachable even if BLOCK was not executed to completion;
> and this is the argument against PEP 340 and against Nick's
> alternative.
>
> Let's compare and contrast the two remaining competitors:
>
> PEP 310
> =======
>
> Syntax:
> with EXPR [= VAR]:
>     BLOCK
>
> Translation:
> [VAR =] abc = EXPR
> if hasattr(abc, "__enter__"):
>     abc.__enter__()
> try:
>     BLOCK
> finally:
>     abc.__exit__()
>
> Pros:
> - dead simple
>
> Cons:
> - can't use a decorated generator for EXPR

Sorry, why not?  [note: I work this out, below, but I still think the
code is worth posting]

import sys

class BlockTemplate(object):
      def __init__(self, g, args, kw):
          self.g = g
          self.args = args
          self.kw = kw
      def __enter__(self):
          self.giter = self.g(*self.args)
          self.giter.next()
      def __exit__(self):
          try:
              self.giter.next()
          except StopIteration:
              pass
          else:
              raise RuntimeError, "generator not exhausted"

def template(g):
    def _(*args, **kw):
        return BlockTemplate(g, args, kw)
    return _

@template
def redirected_stdout(out):
    print 'hi'
    save_stdout = sys.stdout
    sys.stdout = out
    yield None
    sys.stdout = save_stdout
    print 'ho'


## with redirected_stdout(fileobj):
##     print 1

output = open("foo", "w")

abc = redirected_stdout(output)
abc.__enter__()
try:
    print 1
finally:
    abc.__exit__()

output.close()

print repr(open("foo").read())

(this was a bit harder to get right than I expected, mind).

Oh, I guess the point is that with a decorated generator you can yield
a value to be used as VAR, rather than just discarding the value as
here.  Hmm.

> PEP 340 redux
> =============
>
> Syntax:
> do EXPR [as VAR]:
>     BLOCK
>
> Translation:
> abc = EXPR
> [VAR =] abc.__enter__()
> try:
>     BLOCK
> finally:
>     abc.__exit__(*"sys.exc_info()") # Not exactly

These two expansions look very similar to me.  What am I missing?

> Pros:
> - can use a decorated generator as EXPR
> - separation of EXPR and VAR (VAR gets what EXPR.__enter__() returns)

Oh!  Hmm.  This is a bit subtle.

I guess I should think about some examples.

> Cons:
> - slightly less simple (__enter__ must return something for VAR;
>   __exit__ takes optional args)

If things were fiddled such that sys.exc_info() return non-Nones when
a finally clause is being executed because of an exception, we don't
really need this wart, do we?

> Everything else is equal or can be made equal. We can make them more
> equal by treating the arguments passed to __exit__() as a separate
> decision, and waffling about whether __enter__() should be optional (I
> think it's a bad idea even for PEP 310; it *could* be made optional
> for PEP 340 redux).

I don't really recall why it's optional in PEP 310.

> Let's also not quibble about the keyword used; again, that can be a
> separate decision. Note that only PEP 310 can use the "VAR = EXPR"
> syntax; PEP 340 redux *must* use "EXPR as VAR" since it doesn't assign
> the value of EXPR to VAR; PEP 310 can be rewritten using this syntax
> as well.
>
> So then the all-important question I want to pose is: do we like the
> idea of using a (degenerate, decorated) generator as a "template" for
> the do-statement enough to accept the slightly increased complexity?

Looking at my above code, no (even though I think I've rendered the
point moot...).  Compare and contrast:

@template
def redirected_stdout(out):
    save_stdout = sys.stdout
    sys.stdout = out

    yield None

    sys.stdout = save_stdout

class redirected_stdout(object):

    def __init__(self, output):
        self.output = output

    def __enter__(self):
        self.save_stdout = sys.stdout
        sys.stdout = self.output

    def __exit__(self):
        sys.stdout = self.save_stdout

The former is shorter and contains less (well, no) 'self.'s, but I
think I find the latter somewhat clearer.

> The added complexity is caused by the need to separate VAR from EXPR
> so that a generator can be used. I personally like this separation; I
> actually like that the "anonymous block controller" is logically
> separate from the variable bound by the construct.

Nevertheless, I think I actually like this argument!

> From Greg Ewing's response to the proposal to endow file objects
> with __enter__ and __exit__ methods, I believe he thinks so too.
>
> Straight up-or-down votes in the full senate are appreciated at this point.

+1 for the PEP 340 variant.

> On to the secondary questions:
>
> - Today I like the 'do' keyword better; 'with' might confuse folks
> coming from Pascal or VB

No opinion.

> - I have a more elaborate proposal for __exit__'s arguments. Let the
> translation be as follows:
>
> abc = EXPR
> [VAR =] abc.__enter__()
> oke = False  # Pronounced "okay"
> exc = ()
> try:
>     try:
>         BLOCK
>         oke = True
>     except:
>         exc = sys.exc_info()
>         raise
> finally:
>     abc.__exit__(oke, *exc)

-"a bit"

> PS. I've come up with another interesting use case: block signals for
> the duration of a block. This could be a function in the signal
> module, e.g. signal.blocking([ist of signals to block]). The list
> would default to all signals. Similar signal.ignoring().

First you need to hit the authors of various libcs with big sticks.

Cheers,
mwh

-- 
  <shapr> ucking keyoar
                                                -- from Twisted.Quotes


More information about the Python-Dev mailing list