<div dir="ltr"><br><br>On Tuesday, October 25, 2016 at 6:26:17 PM UTC-4, Nathaniel Smith wrote:<blockquote class="gmail_quote" style="margin: 0;margin-left: 0.8ex;border-left: 1px #ccc solid;padding-left: 1ex;">On Sat, Oct 22, 2016 at 9:02 AM, Nick Coghlan <<a href="javascript:" target="_blank" gdf-obfuscated-mailto="WEy-cmcmBgAJ" rel="nofollow" onmousedown="this.href='javascript:';return true;" onclick="this.href='javascript:';return true;">ncog...@gmail.com</a>> wrote:
<br>> On 20 October 2016 at 07:02, Nathaniel Smith <<a href="javascript:" target="_blank" gdf-obfuscated-mailto="WEy-cmcmBgAJ" rel="nofollow" onmousedown="this.href='javascript:';return true;" onclick="this.href='javascript:';return true;">n...@pobox.com</a>> wrote:
<br>>> The first change is to replace the outer for loop with a while/pop
<br>>> loop, so that if an exception occurs we'll know which iterables remain
<br>>> to be processed:
<br>>>
<br>>> def chain(*iterables):
<br>>> try:
<br>>> while iterables:
<br>>> for element in iterables.pop(0):
<br>>> yield element
<br>>> ...
<br>>>
<br>>> Now, what do we do if an exception does occur? We need to call
<br>>> iterclose on all of the remaining iterables, but the tricky bit is
<br>>> that this might itself raise new exceptions. If this happens, we don't
<br>>> want to abort early; instead, we want to continue until we've closed
<br>>> all the iterables, and then raise a chained exception. Basically what
<br>>> we want is:
<br>>>
<br>>> def chain(*iterables):
<br>>> try:
<br>>> while iterables:
<br>>> for element in iterables.pop(0):
<br>>> yield element
<br>>> finally:
<br>>> try:
<br>>> operators.iterclose(iter(<wbr>iterables[0]))
<br>>> finally:
<br>>> try:
<br>>> operators.iterclose(iter(<wbr>iterables[1]))
<br>>> finally:
<br>>> try:
<br>>> operators.iterclose(iter(<wbr>iterables[2]))
<br>>> finally:
<br>>> ...
<br>>>
<br>>> but of course that's not valid syntax. Fortunately, it's not too hard
<br>>> to rewrite that into real Python -- but it's a little dense:
<br>>>
<br>>> def chain(*iterables):
<br>>> try:
<br>>> while iterables:
<br>>> for element in iterables.pop(0):
<br>>> yield element
<br>>> # This is equivalent to the nested-finally chain above:
<br>>> except BaseException as last_exc:
<br>>> for iterable in iterables:
<br>>> try:
<br>>> operators.iterclose(iter(<wbr>iterable))
<br>>> except BaseException as new_exc:
<br>>> if new_exc.__context__ is None:
<br>>> new_exc.__context__ = last_exc
<br>>> last_exc = new_exc
<br>>> raise last_exc
<br>>>
<br>>> It's probably worth wrapping that bottom part into an iterclose_all()
<br>>> helper, since the pattern probably occurs in other cases as well.
<br>>> (Actually, now that I think about it, the map() example in the text
<br>>> should be doing this instead of what it's currently doing... I'll fix
<br>>> that.)
<br>>
<br>> At this point your code is starting to look a whole lot like the code
<br>> in contextlib.ExitStack.__exit__ :)
<br>
<br>One of the versions I tried but didn't include in my email used
<br>ExitStack :-). It turns out not to work here: the problem is that we
<br>effectively need to enter *all* the contexts before unwinding, even if
<br>trying to enter one of them fails. ExitStack is nested like (try (try
<br>(try ... finally) finally) finally), and we need (try finally (try
<br>finally (try finally ...))) But this is just a small side-point
<br>anyway, since most code is not implementing complicated
<br>meta-iterators; I'll address your real proposal below.
<br>
<br>> Accordingly, I'm going to suggest that while I agree the problem you
<br>> describe is one that genuinely emerges in large production
<br>> applications and other complex systems, this particular solution is
<br>> simply far too intrusive to be accepted as a language change for
<br>> Python - you're talking a fundamental change to the meaning of
<br>> iteration for the sake of the relatively small portion of the
<br>> community that either work on such complex services, or insist on
<br>> writing their code as if it might become part of such a service, even
<br>> when it currently isn't. Given that simple applications vastly
<br>> outnumber complex ones, and always will, I think making such a change
<br>> would be a bad trade-off that didn't come close to justifying the
<br>> costs imposed on the rest of the ecosystem to adjust to it.
<br>>
<br>> A potentially more fruitful direction of research to pursue for 3.7
<br>> would be the notion of "frame local resources", where each Python
<br>> level execution frame implicitly provided a lazily instantiated
<br>> ExitStack instance (or an equivalent) for resource management.
<br>> Assuming that it offered an "enter_frame_context" function that mapped
<br>> to "contextlib.ExitStack.enter_<wbr>context", such a system would let us do
<br>> things like:
<br>
<br>So basically a 'with expression', that gives up the block syntax --
<br>taking its scope from the current function instead -- in return for
<br>being usable in expression context? That's a really interesting, and I
<br>see the intuition that it might be less disruptive if our implicit
<br>iterclose calls are scoped to the function rather than the 'for' loop.
<br>
<br>But having thought about it and investigated some... I don't think
<br>function-scoping addresses my problem, and I don't see evidence that
<br>it's meaningfully less disruptive to existing code.
<br>
<br>First, "my problem":
<br>
<br>Obviously, Python's a language that should be usable for folks doing
<br>one-off scripts, and for paranoid folks trying to write robust complex
<br>systems, and for everyone in between -- these are all really important
<br>constituencies. And unfortunately, there is a trade-off here, where
<br>the changes we're discussing effect these constituencies differently.
<br>But it's not just a matter of shifting around a fixed amount of pain;
<br>the *quality* of the pain really changes under the different
<br>proposals.
<br>
<br>In the status quo:
<br>- for one-off scripts: you can just let the GC worry about generator
<br>and file handle cleanup, re-use iterators, whatever, it's cool
<br>- for robust systems: because it's the *caller's* responsibility to
<br>ensure that iterators are cleaned up, you... kinda can't really use
<br>generators without -- pick one -- (a) draconian style guides (like
<br>forbidding 'with' inside generators or forbidding bare 'for' loops
<br>entirely), (b) lots of auditing (every time you write a 'for' loop, go
<br>read the source to the generator you're iterating over -- no
<br>modularity for you and let's hope the answer doesn't change!), or (c)
<br>introducing really subtle bugs. Or all of the above. It's true that a
<br>lot of the time you can ignore this problem and get away with it one
<br>way or another, but if you're trying to write robust code then this
<br>doesn't really help -- it's like saying the footgun only has 1 bullet
<br>in the chamber. Not as reassuring as you'd think. It's like if every
<br>time you called a function, you had to explicitly say whether you
<br>wanted exception handling to be enabled inside that function, and if
<br>you forgot then the interpreter might just skip the 'finally' blocks
<br>while unwinding. There's just *isn't* a good solution available.
<br>
<br>In my proposal (for-scoped-iterclose):
<br>- for robust systems: life is great -- you're still stopping to think
<br>a little about cleanup every time you use an iterator (because that's
<br>what it means to write robust code!), but since the iterators now know
<br>when they need cleanup and regular 'for' loops know how to invoke it,
<br>then 99% of the time (i.e., whenever you don't intend to re-use an
<br>iterator) you can be confident that just writing 'for' will do exactly
<br>the right thing, and the other 1% of the time (when you do want to
<br>re-use an iterator), you already *know* you're doing something clever.
<br>So the cognitive overhead on each for-loop is really low.
<br>- for one-off scripts: ~99% of the time (actual measurement, see
<br>below) everything just works, except maybe a little bit better. 1% of
<br>the time, you deploy the clever trick of re-using an iterator with
<br>multiple for loops, and it breaks, so this is some pain. Here's what
<br>you see:
<br>
<br> gen_obj = ...
<br> for first_line in gen_obj:
<br> break
<br> for lines in gen_obj:
<br> ...
<br>
<br> Traceback (most recent call last):
<br> File "/tmp/foo.py", line 5, in <module>
<br> for lines in gen_obj:
<br> AlreadyClosedIteratorError: this iterator was already closed,
<br>possibly by a previous 'for' loop. (Maybe you want
<br>itertools.preserve?)
<br>
<br>(We could even have a PYTHONDEBUG flag that when enabled makes that
<br>error message include the file:line of the previous 'for' loop that
<br>called __iterclose__.)
<br>
<br>So this is pain! But the pain is (a) rare, not pervasive, (b)
<br>immediately obvious (an exception, the code doesn't work at all), not
<br>subtle and delayed, (c) easily googleable, (d) easy to fix and the fix
<br>is reliable. It's a totally different type of pain than the pain that
<br>we currently impose on folks who want to write robust code.
<br>
<br>Now compare to the new proposal (function-scoped-iterclose):
<br>
<br>- For those who want robust cleanup: Usually, I only need an iterator
<br>for as long as I'm iterating over it; that may or may not correspond
<br>to the end of the function (often won't). When these don't coincide,
<br>it can cause problems. E.g., consider the original example from my
<br>proposal:
<br>
<br> def read_newline_separated_json(<wbr>path):
<br> with open(path) as f:
<br> for line in f:
<br> yield json.loads(line)
<br>
<br>but now suppose that I'm a Data Scientist (tm) so instead of having 1
<br>file full of newline-separated JSON, I have a 100 gigabytes worth of
<br>the stuff stored in lots of files in a directory tree. Well, that's no
<br>problem, I'll just wrap that generator:
<br>
<br> def read_newline_separated_json_<wbr>tree(tree):
<br> for root, _, paths in os.walk(tree):
<br> for path in paths:
<br> for document in read_newline_separated_json(<wbr>join(root, path)):
<br> yield document
<br>
<br></blockquote><div> </div><blockquote class="gmail_quote" style="margin: 0;margin-left: 0.8ex;border-left: 1px #ccc solid;padding-left: 1ex;">And then I'll run it on PyPy, because that's what you do when you have
<br>100 GB of string processing, and... it'll crash, because the call to
<br>read_newline_separated_tree ends up doing thousands of calls to
<br>read_newline_separated_json, but never cleans up any of them up until
<br>the function exits, so eventually we run out of file descriptors.
<br></blockquote><div><br></div><div>I still don't understand why you can't write it like this:</div><div><br></div><div><div>def read_newline_separated_json_tree(tree):</div><div> for root, _, paths in os.walk(tree):</div><div> for path in paths:</div><div> with read_newline_separated_json(join(root, path)) as iterable:</div><div> yield from iterable</div></div><div><br></div><div>Zero extra lines. Works today. Does everything you want.</div><div> </div><blockquote class="gmail_quote" style="margin: 0;margin-left: 0.8ex;border-left: 1px #ccc solid;padding-left: 1ex;">
<br>A similar situation arises in the main loop of something like an HTTP server:
<br>
<br> while True:
<br> request = read_request(sock)
<br> for response_chunk in application_handler(request):
<br> send_response_chunk(sock)
<br></blockquote><div><br></div><div>Same thing:</div><div><br></div><div><div><br></div><div>while True:</div><div> request = read_request(sock)</div><div> with application_handler(request) as iterable:</div><div> for response_chunk in iterable:</div><div> send_response_chunk(sock)</div></div><div><br></div><div><br>I'll stop posting about this, but I don't see the motivation behind this proposals except replacing one explicit context management line with a hidden "line" of cognitive overhead. I think the solution is to stop returning an iterable when you have state needing a cleanup. Instead, return a context manager and force the caller to open it to get at the iterable.</div><div><br></div><div>Best,</div><div><br></div><div>Neil</div><div> </div><blockquote class="gmail_quote" style="margin: 0;margin-left: 0.8ex;border-left: 1px #ccc solid;padding-left: 1ex;">
<br>Here we'll accumulate arbitrary numbers of un-closed
<br>application_handler generators attached to the stack frame, which is
<br>no good at all. And this has the interesting failure mode that you'll
<br>probably miss it in testing, because most clients will only re-use a
<br>connection a small number of times. </blockquote><blockquote class="gmail_quote" style="margin: 0;margin-left: 0.8ex;border-left: 1px #ccc solid;padding-left: 1ex;">
<br>So what this means is that every time I write a for loop, I can't just
<br>do a quick "am I going to break out of the for-loop and then re-use
<br>this iterator?" check -- I have to stop and think about whether this
<br>for-loop is nested inside some other loop, etc. And, again, if I get
<br>it wrong, then it's a subtle bug that will bite me later. It's true
<br>that with the status quo, we need to wrap, X% of for-loops with 'with'
<br>blocks, and with this proposal that number would drop to, I don't
<br>know, (X/5)% or something. But that's not the most important cost: the
<br>most important cost is the cognitive overhead of figuring out which
<br>for-loops need the special treatment, and in this proposal that
<br>checking is actually *more* complicated than the status quo.
<br>
<br>- For those who just want to write a quick script and not think about
<br>it: here's a script that does repeated partial for-loops over a
<br>generator object:
<br>
<br> <a href="https://github.com/python/cpython/blob/553a84c4c9d6476518e2319acda6ba29b8588cb4/Tools/scripts/gprof2html.py#L40-L79" target="_blank" rel="nofollow" onmousedown="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fgithub.com%2Fpython%2Fcpython%2Fblob%2F553a84c4c9d6476518e2319acda6ba29b8588cb4%2FTools%2Fscripts%2Fgprof2html.py%23L40-L79\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNEK94co3oYYbZ5EjfmQMzm7kINQsg';return true;" onclick="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fgithub.com%2Fpython%2Fcpython%2Fblob%2F553a84c4c9d6476518e2319acda6ba29b8588cb4%2FTools%2Fscripts%2Fgprof2html.py%23L40-L79\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNEK94co3oYYbZ5EjfmQMzm7kINQsg';return true;">https://github.com/python/<wbr>cpython/blob/<wbr>553a84c4c9d6476518e2319acda6ba<wbr>29b8588cb4/Tools/scripts/<wbr>gprof2html.py#L40-L79</a>
<br>
<br>(and note that the generator object even has an ineffective 'with
<br>open(...)' block inside it!)
<br>
<br>With the function-scoped-iterclose, this script would continue to work
<br>as it does now. Excellent.
<br>
<br>But, suppose that I decide that that main() function is really
<br>complicated and that it would be better to refactor some of those
<br>loops out into helper functions. (Probably actually true in this
<br>example.) So I do that and... suddenly the code breaks. And in a
<br>rather confusing way, because it has to do with this complicated
<br>long-distance interaction between two different 'for' loops *and*
<br>where they're placed with respect to the original function versus the
<br>helper function.
<br>
<br>If I were an intermediate-level Python student (and I'm pretty sure
<br>anyone who is starting to get clever with re-using iterators counts as
<br>"intermediate level"), then I'm pretty sure I'd actually prefer the
<br>immediate obvious feedback from the for-scoped-iterclose. This would
<br>actually be a good time to teach folks about this aspect of resource
<br>handling, actually -- it's certainly an important thing to learn
<br>eventually on your way to Python mastery, even if it isn't needed for
<br>every script.
<br>
<br>In the pypy-dev thread about this proposal, there's some very
<br>distressed emails from someone who's been writing Python for a long
<br>time but only just realized that generator cleanup relies on the
<br>garbage collector:
<br>
<br> <a href="https://mail.python.org/pipermail/pypy-dev/2016-October/014709.html" target="_blank" rel="nofollow" onmousedown="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fmail.python.org%2Fpipermail%2Fpypy-dev%2F2016-October%2F014709.html\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNHedrr2LS8kAWYeH0q-YsIi-I54Fw';return true;" onclick="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fmail.python.org%2Fpipermail%2Fpypy-dev%2F2016-October%2F014709.html\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNHedrr2LS8kAWYeH0q-YsIi-I54Fw';return true;">https://mail.python.org/<wbr>pipermail/pypy-dev/2016-<wbr>October/014709.html</a>
<br> <a href="https://mail.python.org/pipermail/pypy-dev/2016-October/014720.html" target="_blank" rel="nofollow" onmousedown="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fmail.python.org%2Fpipermail%2Fpypy-dev%2F2016-October%2F014720.html\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNG8ANHiKY_QrVgO-liBBVfYfi7t9g';return true;" onclick="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fmail.python.org%2Fpipermail%2Fpypy-dev%2F2016-October%2F014720.html\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNG8ANHiKY_QrVgO-liBBVfYfi7t9g';return true;">https://mail.python.org/<wbr>pipermail/pypy-dev/2016-<wbr>October/014720.html</a>
<br>
<br>It's unpleasant to have the rug pulled out from under you like this
<br>and suddenly realize that you might have to go re-evaluate all the
<br>code you've ever written, and making for loops safe-by-default and
<br>fail-fast-when-unsafe avoids that.
<br>
<br>Anyway, in summary: function-scoped-iterclose doesn't seem to
<br>accomplish my goal of getting rid of the *type* of pain involved when
<br>you have to run a background thread in your brain that's doing
<br>constant paranoid checking every time you write a for loop. Instead it
<br>arguably takes that type of pain and spreads it around both the
<br>experts and the novices :-/.
<br>
<br>-------------
<br>
<br>Now, let's look at some evidence about how disruptive the two
<br>proposals are for real code:
<br>
<br>As mentioned else-thread, I wrote a stupid little CPython hack [1] to
<br>report when the same iterator object gets passed to multiple 'for'
<br>loops, and ran the CPython and Django testsuites with it [2]. Looking
<br>just at generator objects [3], across these two large codebases there
<br>are exactly 4 places where this happens. (Rough idea of prevalence:
<br>these 4 places together account for a total of 8 'for' loops; this is
<br>out of a total of 11,503 'for' loops total, of which 665 involve
<br>generator objects.) The 4 places are:
<br>
<br>1) CPython's Lib/test/test_collections.py:<wbr>1135, Lib/_collections_abc.py:378
<br>
<br>This appears to be a bug in the CPython test suite -- the little MySet
<br>class does 'def __init__(self, itr): self.contents = itr', which
<br>assumes that itr is a container that can be repeatedly iterated. But a
<br>bunch of the methods on collections.abc.Set like to pass in a
<br>generator object here instead, which breaks everything. If repeated
<br>'for' loops on generators raised an error then this bug would have
<br>been caught much sooner.
<br>
<br>2) CPython's Tools/scripts/gprof2html.py lines 45, 54, 59, 75
<br>
<br>Discussed above -- as written, for-scoped-iterclose would break this
<br>script, but function-scoped-iterclose would not, so here
<br>function-scoped-iterclose wins.
<br>
<br>3) Django django/utils/regex_helper.py:<wbr>236
<br>
<br>This code is very similar to the previous example in its general
<br>outline, except that the 'for' loops *have* been factored out into
<br>utility functions. So in this case for-scoped-iterclose and
<br>function-scoped-iterclose are equally disruptive.
<br>
<br>4) CPython's Lib/test/test_generators.py:<wbr>723
<br>
<br>I have to admit I cannot figure out what this code is doing, besides
<br>showing off :-). But the different 'for' loops are in different stack
<br>frames, so I'm pretty sure that for-scoped-iterclose and
<br>function-scoped-iterclose would be equally disruptive.
<br>
<br>Obviously there's a bias here in that these are still relatively
<br>"serious" libraries; I don't have a big corpus of one-off scripts that
<br>are just a big __main__, though gprof2html.py isn't far from that. (If
<br>anyone knows where to find such a thing let me know...) But still, the
<br>tally here is that out of 4 examples, we have 1 subtle bug that
<br>iterclose might have caught, 2 cases where for-scoped-iterclose and
<br>function-scoped-iterclose are equally disruptive, and only 1 where
<br>function-scoped-iterclose is less disruptive -- and in that case it's
<br>arguably just avoiding an obvious error now in favor of a more
<br>confusing error later.
<br>
<br>If this reduced the backwards-incompatible cases by a factor of, like,
<br>10x or 100x, then that would be a pretty strong argument in its favor.
<br>But it seems to be more like... 1.5x.
<br>
<br>-n
<br>
<br>[1] <a href="https://github.com/njsmith/cpython/commit/2b9d60e1c1b89f0f1ac30cbf0a5dceee835142c2" target="_blank" rel="nofollow" onmousedown="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fgithub.com%2Fnjsmith%2Fcpython%2Fcommit%2F2b9d60e1c1b89f0f1ac30cbf0a5dceee835142c2\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNFECAw8P9_7OmvAJZ00fyw7QHaojg';return true;" onclick="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fgithub.com%2Fnjsmith%2Fcpython%2Fcommit%2F2b9d60e1c1b89f0f1ac30cbf0a5dceee835142c2\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNFECAw8P9_7OmvAJZ00fyw7QHaojg';return true;">https://github.com/njsmith/<wbr>cpython/commit/<wbr>2b9d60e1c1b89f0f1ac30cbf0a5dce<wbr>ee835142c2</a>
<br>[2] CPython: revision b0a272709b from the github mirror; Django:
<br>revision 90c3b11e87
<br>[3] I also looked at "all iterators" and "all iterators with .close
<br>methods", but this email is long enough... basically the pattern is
<br>the same: there are another 13 'for' loops that involve repeated
<br>iteration over non-generator objects, and they're roughly equally
<br>split between spurious effects due to bugs in the CPython test-suite
<br>or my instrumentation, cases where for-scoped-iterclose and
<br>function-scoped-iterclose both cause the same problems, and cases
<br>where function-scoped-iterclose is less disruptive.
<br>
<br>-n
<br>
<br>--
<br>Nathaniel J. Smith -- <a href="https://vorpus.org" target="_blank" rel="nofollow" onmousedown="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fvorpus.org\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGu9BH-CNkjUnoU8qy5kxnhJP302A';return true;" onclick="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fvorpus.org\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGu9BH-CNkjUnoU8qy5kxnhJP302A';return true;">https://vorpus.org</a>
<br>______________________________<wbr>_________________
<br>Python-ideas mailing list
<br><a href="javascript:" target="_blank" gdf-obfuscated-mailto="WEy-cmcmBgAJ" rel="nofollow" onmousedown="this.href='javascript:';return true;" onclick="this.href='javascript:';return true;">Python...@python.org</a>
<br><a href="https://mail.python.org/mailman/listinfo/python-ideas" target="_blank" rel="nofollow" onmousedown="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fmail.python.org%2Fmailman%2Flistinfo%2Fpython-ideas\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNFj1EaNHnVmh20FnFPoUi4J-MpfQw';return true;" onclick="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fmail.python.org%2Fmailman%2Flistinfo%2Fpython-ideas\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNFj1EaNHnVmh20FnFPoUi4J-MpfQw';return true;">https://mail.python.org/<wbr>mailman/listinfo/python-ideas</a>
<br>Code of Conduct: <a href="http://python.org/psf/codeofconduct/" target="_blank" rel="nofollow" onmousedown="this.href='http://www.google.com/url?q\x3dhttp%3A%2F%2Fpython.org%2Fpsf%2Fcodeofconduct%2F\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNHJOrArSUDKkjrnthO6_CznMzkPsA';return true;" onclick="this.href='http://www.google.com/url?q\x3dhttp%3A%2F%2Fpython.org%2Fpsf%2Fcodeofconduct%2F\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNHJOrArSUDKkjrnthO6_CznMzkPsA';return true;">http://python.org/psf/<wbr>codeofconduct/</a>
<br></blockquote></div>