<div dir="ltr"> really like what Paul Moore wrote here as it 
matches a *LOT* of what I have been feeling as I have been reading this 
whole discussion; specifically:<br><ul><li>I find the example, and discussion, really hard to follow.</li></ul><ul><ul><li>I also, don't understand async, but I do understand generators very well (like Paul Moore)<br></li></ul></ul><ul><li>A lot of this doesn't seem natural (generators & context variable syntax)<br></li></ul><ul><li>And particular: " If the implementation is hard to explain, it's a bad idea."</li></ul><p>I've spend a lot of time thinking about this, and what the issues are.</p><p>I think they are multi-fold:</p><ul><li>I
 really use Generators a lot -- and find them wonderful & are one of
 the joy's of python.  They are super useful.  However, as I am going 
to, hopefully, demonstrate here, they are not initially intuitive (to a 
beginner).<br></li></ul><ul><li>Generators are not really functions; but
 they appear to be functions, this was very confusing to me when I 
started working with generators.</li><ul><li> Now, I'm used to it -- BUT, we really need to consider new people - and I suggest making this easier.</li></ul></ul><ul><li>I
 find the proposed context syntax very confusing (and slow).  I think 
contexts are super-important & instead need to be better integrated 
into the language (like nonlocal is)</li></ul><ul><li>People keep writing they want a real example -- so this is a very real example from real code I am writing (a python parser) and how I 
use contexts (obviously they are not part of the language yet, so I have
 emulated them) & how they interact with generators.<br></li></ul>The
 full example, which took me a few hours to write is available here (its
 a very very reduced example from a real parser of the python language 
written in python):<br><ul><li><a href="https://github.com/AmitGreen/Gem/blob/emerald_6/work/demo.py" target="_blank">https://github.com/AmitGreen/<wbr>Gem/blob/emerald_6/work/demo.<wbr>py</a></li></ul>Here is the result of running the code -- which reads & executes demo1.py (importing & executing demo2.py twice): [Not by executing, I mean the code is running its own parser to execute it & its own code to emulate an 'import' -- thus showing nested contexts):<br><br>It creates two input files for testing -- demo1.py:<br><br><div style="margin-left:40px">print 1<br>print 8 - 2 * 3<br>import demo2<br>print 9 - sqrt(16)<br>print 10 / (8 - 2 * 3)<br>import demo2<br>print 2 * 2 * 2 + 3 - 4</div><div style="margin-left:40px"><br></div>And it also creates demo2.py:<br><br><div style="margin-left:40px">print 3 * (2 - 1)<br>error<br>print 4<br></div><br>There are two syntax errors (on purpose) in the files, but since demo2.py is imported twice, this will show three syntax errors.<br><br>Running the code produces the following:<br><br><div style="margin-left:40px">demo1.py#1: expression '1' evaluates to 1<br>demo1.py#2: expression '8 - 2 * 3' evaluates to 2<br>demo1.py#3: importing module demo2<br>demo2.py#1: expression '3 * (3 - 2)' evaluates to 3<br>demo2.py#2: UNKNOWN STATEMENT: 'error'<br>demo2.py#3: expression '4' evaluates to 4<br>demo1.py#4: UNKNOWN ATOM: ' sqrt(16)'<br>demo1.py#5: expression '10 / (8 - 2 * 3)' evaluates to 5<br>demo1.py#6: importing module demo2<br>demo2.py#1: expression '3 * (3 - 2)' evaluates to 3<br>demo2.py#2: UNKNOWN STATEMENT: 'error'<br>demo2.py#3: expression '4' evaluates to 4<br>demo1.py#7: expression '2 * 2 * 2 + 3 - 4' evaluates to 7</div><br><div>This code demonstrates all of the following:</div><ul><li>Nested contexts</li><li>Using
 contexts 'naturally' -- i.e.: directly as variables; without a 
'context.' prefix -- which I would find too harder to read & also 
slower.</li><li>Using a generator that is deliberately broken up into three parts, start, next & stop.</li><li>Handling errors & how it interacts with both the generator & 'context'</li><li>Actually
 parsing the input -- which creates a deeply nested stack (due to 
recursive calls during expression parsing) -- thus a perfect example for
 contexts.</li></ul><p>So given all of the above, I'd first like to focus on the generator:</p><ul><li>Currently
 we can write generators as either: (1) functions; or (2) classes with a
 __next__ method.  However this is very confusing to a beginner.</li><li>Given a generator like the following (actually in the code):</li></ul><p style="margin-left:40px">        def __iter__(self):<br>            while not self.finished:<br>                self.loop += 1<br><br>                yield self<br></p><ul><li>What
 I found so surprising when I started working with generator, is that 
calling the generator does *NOT* actually start the function.</li><li>Instead, the actual code does not actually get called until the first __next__ method is called.</li><li>This is quite counter-intuitive.</li></ul><p>I therefore suggest the following:</p><ul><li>Give generators their own first class language syntax.</li><li>This syntax, would have good entry point's, to allow their interaction with context variables.</li></ul><p>Here is the generator in my code sample:</p><p>    #<br>    #   Here is our generator to walk over a file.<br>    #<br>    #   This generator has three sections:<br>    #<br>    #       generator_start     - Always run when the generator is started.<br>    #                             This opens the file & reads it.<br>    #<br>    #       generator_next      - Run each time the generator needs to retrieve<br>    #                             The next value.<br>    #<br>    #       generator_stop      - Called when the generator is going to stop.<br>    #<br>    def iterate_lines(path):<br>        data_lines = None<br><br><br>        def generator_startup(path):<br>            nonlocal current_path, data_lines<br><br>            with open(path) as f:<br>                current_path = path<br>                data         = f.read()<br><br>            data_lines = tuple(data.splitlines())<br><br><br>        def generator_next():<br>            nonlocal current_line, line_number<br><br>            for current_line in data_lines:<br>                line_number += 1<br>                line_position = 0<br><br>                yield current_line<br><br>            generator_stop()<br><br><br>        def generator_stop():<br>            current_path  = None<br>            line_number   = 0<br>            line_position = 0<br><br><br>        generator_startup(path)<br><br>        return generator_next()</p><p>This generator demonstrates the following:</p><ul><li>It
 immediately starts up when called (and in fact opens the file when 
called -- so if the file doesn't exist, an exception is thrown then, not
 later when the __next__ method is first called)</li><li>It's half way 
between a function generator & a class generator; thus (1) 
efficient; and (2) more understandable than a class generator.</li></ul><p>Here
 is (a first draft) proposal and how I would like to re-write the above 
generator, so it would have its own first class syntax:</p><p>    generator iterate_lines(path):<br>        local   data_lines = None<br>        context current_path, current_line, line_number, line_position<br><br>        start:<br>            with open(path) as f:<br>                current_path = path<br>                data         = f.read()<br><br>            data_lines = tuple(data.splitlines())<br><br>        next:<br>            for current_line in data_lines:<br>                line_number += 1<br>                line_position = 0<br><br>                yield current_line<br><br>        stop:<br>            current_path  = None<br>            line_number  = 0<br>            line_position  = 0</p><p>This:</p><ol><li>Adds a keyword 'generator' so its obvious this is a generator not a function.<br></li><li>Declares it variables (data_lines)</li><li>Declares which context variables it wants to use (current_path, currentline, line_number, & line_position)</li><li>Has a start section that immediately gets executed.</li><li>Has a next section that executes on each call to __next__ (and this is where the yield keyword must appear)</li><li>Has a stop section that executes when the generator receives a StopIteration.</li><li>The
 compiler could generate equally efficient code for generators as it 
does for current generators; while making the syntax clearer to the 
user.</li><li>The syntax is chosen so the user can edit it & convert it to a class generator.<br></li></ol><p>Given the above:</p><ul><li>I
 could now add special code to either the 'start' or 'next' section, 
saying which context I wanted to use (once we have that syntax 
implemented).</li></ul><p>The reason for its own syntax is to allow us 
to think more clearly about the different parts of a generator  & 
then makes it easier for the user to choose which part of the generator 
interacts with contexts & which context.  In particular the user 
could interact with multiple contexts (one in the start section & a 
different one in the next section).</p><p>[Also for other generators I think the syntax needs to be extended, to something like:</p><p>    next(context):</p><p>       use context:</p><p>           ....</p><p>Allowing
 two new features --- requesting that the __next__ receive the context 
of the caller & secondly being able to use that context itself.</p><p>Next, moving on to contexts:</p><ul><li>I love how non-local works & how you can access variables declared in your surrounding function.</li><li>I really think that contexts should work the same way</li><li>You would simply declare 'context' (like non-local) & just be able to use the variables directly.</li><li>Way easier to understand & use.</li></ul><p>The sample code I have actually emulates contexts using non-local, so as to demonstrate the idea I am explaining.<br></p><p>Thanks,</p><p>Amit</p>P.S.:
 As I'm very new to python ideas, I'm not sure if I should start a 
separate thread to discuss this or use the current thread.  Also I'm not
 sure if I should attached the sample code here or not ... So I just provided the link above.<br></div><div class="gmail_extra"><br><div class="gmail_quote">On Fri, Oct 13, 2017 at 4:29 PM, Paul Moore <span dir="ltr"><<a href="mailto:p.f.moore@gmail.com" target="_blank">p.f.moore@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">On 13 October 2017 at 19:32, Yury Selivanov <<a href="mailto:yselivanov.ml@gmail.com">yselivanov.ml@gmail.com</a>> wrote:<br>
>>> It seems simpler to have one specially named and specially called function<br>
>>> be special, rather than make the semantics<br>
>>> more complicated for all functions.<br>
> It's not possible to special case __aenter__ and __aexit__ reliably<br>
> (supporting wrappers, decorators, and possible side effects).<br>
>> +1.  I think that would make it much more usable by those of us who are not<br>
>> experts.<br>
> I still don't understand what Steve means by "more usable", to be honest.<br>
</span>I'd consider myself a "non-expert" in async. Essentially, I ignore it<br>
- I don't write the sort of applications that would benefit<br>
significantly from it, and I don't see any way to just do "a little<br>
bit" of async, so I never use it.<br>
But I *do* see value in the context variable proposals here - if only<br>
in terms of them being a way to write my code to respond to external<br>
settings in an async-friendly way. I don't follow the underlying<br>
justification (which is based in "we need this to let things work with<br>
async/coroutines) at all, but I'm completely OK with the basic idea<br>
(if I want to have a setting that behaves "naturally", like I'd expect<br>
decimal contexts to do, it needs a certain amount of language support,<br>
so the proposal is to add that). I'd expect to be able to write<br>
context variables that my code could respond to using a relatively<br>
simple pattern, and have things "just work". Much like I can write a<br>
context manager using @contextmanager and yield, and not need to<br>
understand all the intricacies of __enter__ and __exit__. (BTW,<br>
apologies if I'm mangling the terminology here - write it off as part<br>
of me being "not an expert" :-))<br>
What I'm getting from this discussion is that even if I *do* have a<br>
simple way of writing context variables, they'll still behave in ways<br>
that seem mildly weird to me (as a non-async user). Specifically, my<br>
head hurts when I try to understand what that decimal context example<br>
"should do". My instincts say that the current behaviour is wrong -<br>
but I'm not sure I can explain why. So on that example, I'd ask the<br>
following of any proposal:<br>
1. Users trying to write a context variable[1] shouldn't have to jump<br>
through hoops to get "natural" behaviour. That means that suggestions<br>
that the complexity be pushed onto decimal.context aren't OK unless<br>
it's also accepted that the current behaviour is wrong, and the only<br>
reason decimal.context needs to replicated is for backward<br>
compatibility (and new code can ignore the problem).<br>
2. The proposal should clearly establish what it views as "natural"<br>
behaviour, and why. I'm not happy with "it's how decimal.context has<br>
always behaved" as an explanation. Sure, people asking to break<br>
backward compatibility should have a good justification, but equally,<br>
people arguing to *preserve* an unintuitive current behaviour in new<br>
code should be prepared to explain why it's not a bug. To put it<br>
another way, context variables aren't required to be bug-compatible<br>
with thread local storage.<br>
[1] I'm assuming here that "settings that affect how a library behave"<br>
is a common requirement, and the PEP is intended as the "one obvious<br>
way" to implement them.<br>
Nick's other async refactoring example is different. If the two forms<br>
he showed don't behave identically in all contexts, then I'd consider<br>
that to be a major problem. Saying that "coroutines are special" just<br>
reads to me as "coroutines/async are sufficiently weird that I can't<br>
expect my normal patterns of reasoning to work with them". (Apologies<br>
if I'm conflating coroutines and async incorrectly - as a non-expert,<br>
they are essentially indistinguishable to me). I sincerely hope that<br>
isn't the message I should be getting - async is already more<br>
inaccessible than I'd like for the average user.<br>
The fact that Nick's async example immediately devolved into a<br>
discussion that I can't follow at all is fine - to an extent. I don't<br>
mind the experts debating implementation details that I don't need to<br>
know about. But if you make writing context variables harder, just to<br>
fix Nick's example, or if you make *using* async code like (either of)<br>
Nick's forms harder, then I do object, because that's affecting the<br>
end user experience.<br>
In that context, I take Steve's comment as meaning "fiddling about<br>
with how __aenter__ and __aexit__ work is fine, as that's internals<br>
that non-experts like me don't care about - but making context<br>
variables behave oddly because of this is *not* fine".<br>
Apologies if the above is unhelpful. I've been lurking but not<br>
commenting here, precisely because I *am* a non-expert, and I trust<br>
the experts to build something that works. But when non-experts were<br>
explicitly mentioned, I thought my input might be useful.<br>
The following quote from the Zen seems particularly relevant here:<br>
    If the implementation is hard to explain, it's a bad idea.<br>
(although the one about needing to be Dutch to understand why<br>
something is obvious might well trump it ;-))<br>
<span class="HOEnZb"><font color="#888888"><br>
</font></span><div class="HOEnZb"><div class="h5">______________________________<wbr>_________________<br>
Python-ideas mailing list<br>
<a href="mailto:Python-ideas@python.org">Python-ideas@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-ideas" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>mailman/listinfo/python-ideas</a><br>
Code of Conduct: <a href="http://python.org/psf/codeofconduct/" rel="noreferrer" target="_blank">http://python.org/psf/<wbr>codeofconduct/</a><br>