[Python-ideas] with expression

Andrew Barnert abarnert at yahoo.com
Thu Feb 20 20:54:51 CET 2014


On Feb 20, 2014, at 10:50, Yann Kaiser <kaiser.yann at gmail.com> wrote:

> As an alternative to the recently-proposed "except expression", I
> suggested this in its thread. I was recommended to post this
> separately, as, while it is related, it is different enough from the
> original idea.
> 
> The general idea is to extend the context manager protocol so that it
> can produce a return value in alternative to simply letting or denying
> an exception from propagating, and introduce an inline form of "with".
> 
> The motivation behind is, in all honesty, that I find the suggested
> except-expressions from PEP 463 to be too verbose to inline, and that
> context managers have a much broader reach. The motivation from the
> aforementioned PEP(inline evaluation to a "default" expression upon
> catching an exception) also applies here.
> 
> It could look a bit like this:
> 
>    contexted_expr with context_manager as c
> 
> Once more, the rationale from PEP 463 also applies here, in that we
> can shift "easy defaulting" from being a potential concern for the
> callee to being always available to the caller through new syntax.
> Likewise, currently in-lining the use of a context manager can be
> done, but only through manually encapsulating what would be the
> context-ed code through lambdas or eval.
> 
> A full example could be as such:
> 
>    class Default(object):
>        """Context manager that returns a given default value when an exception
>        is caught."""
> 
>        def __init__(self, value, *exception_types):
>            self.value = value
>            self.exception_types = exception_types or BaseException
> 
>        def __enter__(self):
>            pass
> 
>        def __exit__(self, typ, val, tb):
>            if typ and issubclass(typ, self.exception_types):
>                return True, self.value
> 
> 
>    lst = [1, 2]
>    # found is assigned 2
>    found = lst[1] with Default(0, IndexError)
>    # not found is assigned 0
>    not_found = lst[2] with Default(0, IndexError)

This case is a nice example.

> The different interpretation of __exit__'s return value is probably
> something that needs to be discussed. In this form, it is potentially
> backwards-incompatible, so a preferable alternative would be a
> different special method, perhaps:
> 
>    def __return__(self, typ, val, tb, ret):
>        if typ is None:
>            return False, ret * 3
>        elif isinstance(typ, IndexError):
>            return True, 10
> 
> The alternatively-named special method would take priority over
> __exit__ and take over its augmented function:
> If no exception has occurred, typ, val, and tb are given None, as with
> the regular __exit__ incarnation, but ret, or whatever is the fourth
> positional parameter, is supplied with what "expr" evaluated to in
> "expr with cmgr". When an exception has occurred or propagated, typ,
> val and tb are set to the appropriate values(Since exceptions now keep
> their traceback as an attribute, maybe only supply the exception
> object?), and ret is given None.
> If the return value of this special method is None, the exception or
> return value is propagated as is. If it is a sequence, the first
> element is used like the return value of __exit__ would be, and the
> second element is used to (re-)place the return value of the whole
> with expression. When multiple context managers are being chained, the
> return value/exception is forwarded much like it is with twisted's
> Deferreds.
> 
> In the use case of providing a default value, if the default value is
> the product of an expensive operation, an alternative context manager
> can be designed to compute the value only when needed, for instance:
> 
>    fa = factorials[n] with SetDefault(factorials, n, lambda: math.factorial(n))

This one is less useful. That SetDefault has to repeat the factorials and n references, and it makes that fact explicit to the reader, and writes the same thing in two different ways.

But, more importantly, a simple "setdefault" function that did the same thing as your SetDefault class without the context management would be easier to write, and both nicer and easier to use:

    fa = setdefault(factorials, n, lambda: math.factorial(n))

> Other examples using existing context managers:
> 
>    contents = f.read() with open('file') as f

This probably buys you more in a context where a statement doesn't work just as well, like a function parameter, or a lambda callback:

    self.read = Button('Read', command=lambda: dostuff(f) with open(path) as f)

But either way, it's something I've wanted before.

>    with open('file') as f:
>        contents = f.read()
> 
> 
> 
>    d = Decimal(1) / Decimal(7) with Context(prec=5)
> 
>    with Context(prec=5):
>        d = Decimal(1) / Decimal(7)
> 
> I think that's all I can think of so far. Sorry as this might be a
> little too detailed to start with, so I will remind you there is no
> offense in rethinking any of what I posted here.
> 
> -yk
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/


More information about the Python-ideas mailing list