I'm not sure if this has been asked / suggested before.

I'm wondering if there is any interest in conditional or loop-based `with`
statements. I think it could be done without a syntax change.

### Napkin proposal

Context managers could define `__if__` or `__for__`, and if those dunder
methods were defined, then the `with` statement would either behave like a
conditional or a loop.

If `__if__` was defined then

```
with Ctx():
    print('hi')
```

would only print `hi` if `__if__` returned True. This doesn't require a
syntax change.

The `__for__` variant would likely need a minor syntax change.

```
with item in Ctx():
    print(item)
```

The `__for__` method is a generator that generates arguments of a loop. The
item will be printed as many times as there are items generated by
`__for__`.


### Use Cases
This would simplify usage of my Timerit and ubelt library.

The timerit module defines ``timerit.Timerit``, which is an object that is
iterable. It has an ``__iter__`` method that generates
``timerit.TimerTimer``
objects, which are context managers.

    >>> import math
    >>> from timerit import Timerit
    >>> for timer in Timerit(num=200, verbose=2):
    >>>     with timer:
    >>>         math.factorial(10000)

The timer context manager measures how much time the body of it takes by
"tic"-ing ``__enter__`` and "toc"-ing on ``__exit__``. The underlying object
has access to the context manager, so it is able to read its measurement.
These
measurements are stored and then we compute some statistics on them. Notably
the minimum, mean, and standard-deviation of grouped (batched) running
times.

Unfortunately the syntax is one line and one indent bulker than I would
prefer.
However, a more consice version of the synax is available.

    >>> import math
    >>> from timerit import Timerit
    >>> for _ in Timerit(num=200, verbose=2):
    >>>     math.factorial(10000)

In this case the measurement is made in the `__iter__` method ``Timerit``
object itself, which I believe contains slightly more overhead than the
with-statement version. (I should test to determine if this is the case).

In the case where it does make a difference, a cool syntax might look like:
    >>> import math
    >>> from timerit import Timerit
    >>> with timer in Timerit(num=200, verbose=2):
    >>>     math.factorial(10000)

The other case is that my ``ubelt.Cacher`` library. Currently it requires 4
lines of boilerplate syntax.

    >>> import ubelt as ub
    >>> # Defines a cache name and dependencies, note the use of
`ub.hash_data`.
    >>> cacher = ub.Cacher('name', cfgstr=ub.hash_data('dependencies'))
# boilerplate:1
    >>> # Calling tryload will return your data on a hit and None on a miss
    >>> data = cacher.tryload()
# boilerplate:2
    >>> # Check if you need to recompute your data
    >>> if data is None:
 # boilerplate:3
    >>>     # Your code to recompute data goes here (this is not
boilerplate).
    >>>     data = 'mydata'
    >>>     # Cache the computation result (pickle is used by default)
    >>>     cacher.save(data)
# boilerplate:4

But a conditional ``with`` syntax would reduce boilerplate to 3 lines.

    >>> import ubelt as ub
    >>> with ub.Cacher('name', cfgstr=ub.hash_data('dependencies')) as
cacher:
    >>>     data = 'mydata'
    >>>     cacher.save(data)
    >>> data = cacher.data

I'm sure there are a lot of viable syntax variations, but does the idea of
a conditional or loop aware "with" statement seem like a reasonable
language proposal?

-- 
-Dr. Jon Crall (him)
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/OMFS77O4KRCJZBPMSEAMEKIQ67GTD5PH/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to