Re: [Python-ideas] Assignments in list/generator expressions

In a message of Sun, 10 Apr 2011 20:18:35 EDT, Eugene Toder writes: <snip>
The problem, as I see it is that you see it as ``missing''. You have some internal model of computer languages and not having it disagrees with your notion of 'completeness' or 'symmetry' or 'consistency' or something along those lines. But the reason I was drawn to python in the first place was that it was an elegant language, which in practice meant that it didn't have a lot of cruft I found in other languages. After all, if I had wanted to use those other languages, I know where to find them. And I actually enjoy using Haskell, on those very rare occasions when I have a problem that I find suited to the language. But turning Python more Haskell-like has no appeal to me. I need a set of very concrete use cases of 'I am trying to do this' and either 'I cannot' or 'I am doing it this way and oh, how slow/ugly/error prone/ it is!' before I am interested in a proposal. Change for the sake of completeness has no value to me. I may be blessed in that I live in Gothenburg, Sweden, where Chalmers University is one of the great centres of Haskell-admiration. So I have many friends who love the language so much they try to do everything in it. Debugging a failing webserver written in Haskell has been painful. There is real value in using whitespace and indentation to separate logical ideas, and jamming everything into a comprehension completely destroys this. The aesthetic gain in symmetry comes a very, very, distant second to me in this race. Laura

On Sun, Apr 10, 2011 at 8:39 PM, Laura Creighton <lac@openend.se> wrote:
I just like to have a reasonable explanation for everything I see. A good story is what often separates a feature from a bug. On Sun, Apr 10, 2011 at 8:39 PM, Laura Creighton <lac@openend.se> wrote:
There is real value in using whitespace and indentation to separate logical ideas, and jamming everything into a comprehension completely destroys this.
I don't think it's fair to judge a feature by trying to imagine the worst misuse one can do with it. If you would do this consistently, you'd never introduce arrays (think code using arrays instead of classes, with magic undocumented indexes instead of attribute names) or dynamically-typed scripting languages (think Perl). Also, as pointed above, local assignment doesn't allow any more code written as comprehension than one can write now -- there are multiple work-arounds: repetition, nested comprehensions, for name in [expr]. Local assignment simply provides a more direct way of doing it, and to me a more direct way is easier to read. Eugene

On Sun, Apr 10, 2011 at 6:42 PM, Eugene Toder <eltoder@gmail.com> wrote:
Local assignment simply provides a more direct way of doing it, and to me a more direct way is easier to read.
I don't think there's going to be consensus that sticking additional assignment in the middle of a generator expression makes it more readable. The expressions you're talking about really are conceptually nested and you're trying to eliminate/hide that. Let me throw out an alternative. In these expressions there's redundant boilerplate ("x for x in ...") when what we're really doing is a simple filter or map across the sequence. In these examples, {sequence} stands in for both the sequence being iterated and the value from the sequence being operated on. ( for {seq} + 1 ) => ( x + 1 for x in seq ) ( for f({seq}) ) => ( f(x) for x in seq ) ( if {seq} > 1 } => ( x for x in seq if x > 1 ) But here's the catch. While I would like a simpler way to express these idioms, I don't think these offer enough of an advantage. And I think that when you start to nest them it quickly becomes unreadable: (y for y in (f(x) for x in xs) if y) => (if {(for f({xs})}) And {} looks a bit too much like () for my taste -- I find that trailing })}) particularly hard to read. So why offer this proposal? I think there's a natural tendency to try to come up with terse expressions for common idioms. But they don't add power and they make the language less approachable. I think terseness is sometimes the opposite of readability. In C# the lambda syntax is terse and I find it unreadable if written without line breaks: f(x, y + 1, x => x + 1, y + 1) vs. f(x, y + 1, x => x + 1, y + 1) --- Bruce *New! *Puzzazz newsletter: http://j.mp/puzzazz-news-2011-04 including April Fools! *New!** *Blog post: http://www.vroospeak.com/2011/04/march-gets-more-madness-next-year.html April Fools!

Bruce Leban wrote:
I don't think there's anything wrong whatsoever with the current way of doing the first two of these. The third one could perhaps benefit from something like (all x in seq if ...) but I tend to agree that the gain is too small to be worth a change.
Seems to me the readability problem there is mainly due to it being mixed in with other comma-separated expressions. I'm not sure it's really all that much better with lambda: f(x, y + 1, lambda x: x + 1, y + 1) I'm not even sure offhand how that parses, so I'd probably put parens around the lambda anyway to be sure: f(x, y + 1, (lambda x: x + 1), y + 1) Now do that with the terse version: f(x, y + 1, (x => x + 1), y + 1) This doesn't look significantly worse to me than any other function call with operators in the argument expressions. If there's a problem spotting the fact that there's an => in there, it could be made a little more noticeable: f(x, y + 1, (x ==> x + 1), y + 1) -- Greg

On Mon, Apr 11, 2011 at 11:42 AM, Eugene Toder <eltoder@gmail.com> wrote:
And we already have one kind of expression that owes its existence almost entirely to the suboptimal workaround people were using to get around the fact it wasn't available* :) A "given/as" list comprehension subclause inspired by PEP 3150 might actually be a more interesting idea to explore than I first thought (considering that PEP 3150 itself is absolutely no help at all for this particular use case). That is, instead of having to choose one of the following 4 alternatives (and assorted variants of the explicit loop, such as invoking list() on a generator function): ys = [f(x) for x in xs if f(x)] ys = [y for y in (f(x) for x in xs) if y] ys = [y for x in xs for y in [f(x)] if y] def _build_sequence(xs): ys = [] for x in xs: y = f(x): if y: ys.append(y) return ys ys = _build_sequence(xs) The language could offer an "obvious" answer of the form: ys = [y for x in xs given f(x) as y if y] Multiple subexpressions would be handled gracefully via tuple unpacking, and the translation to "long-form" Python code for actual execution by the eval loop would follow the same style as is already used for comprehensions and generator expressions (i.e. the last alternative I listed above, with the details of the emitted code changing appropriately based on the kind of comprehension). So, despite my initial negative reaction, I'd now be interested in reading a fully fleshed out PEP for this proposal. Cheers, Nick. (*Read PEP 308 if the reference is unfamiliar) -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Mon, Apr 11, 2011 at 12:58 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Oops, random colon slipped in as typo. That should be: def _build_sequence(xs): ys = [] for x in xs: y = f(x) if y: ys.append(y) return ys ys = _build_sequence(xs) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Sun, Apr 10, 2011 at 10:58 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
And 5 years later there are still people out there who prefer using and/or trick. They tend to say something about the new ways being too new and the old ways being good enough :) On Sun, Apr 10, 2011 at 11:03 PM, Guido van Rossum <guido@python.org> wrote:
Sorry, I don't get the reference. On Sun, Apr 10, 2011 at 11:03 PM, Guido van Rossum <guido@python.org> wrote:
In my defense, I'm actually more interested in the history than in adding this feature, as I find the lack of it a pretty small niggle. The discussion was started with specific examples and suggestions, but it was not very interesting so far, because many (not all) responses are a generic 'new features are bad because they are new' and it's very hard to argue with that :-) And I do not dispute the fact that new features need a high level of scrutiny. Eugene

On 11 April 2011 05:10, Eugene Toder <eltoder@gmail.com> wrote:
The reference is to the "canonical" unanswerable question - "When did you stop beating your wife?" There is no good answer because the question is based on an incorrect assumption.
As far as history goes, the answer is basically "because no-one thought of it (or if they did, they didn't care about it enough to implement it)". You seem to be insisting that there has to be a deeper reason, though - which is why the discussion of history/motivation is becoming frustrating - there really isn't anything deeper.
The *only* interesting part of this thread to me is the discussion around something like "given" (PEP 3150, which I'd forgotten about). Oh, and some of the philosophical discussions on readability... Paul.

Nick Coghlan wrote:
ys = [y for x in xs given f(x) as y if y]
Hmmm. Here I find myself getting tripped up by the ordering when I try to read that smoothly. I think it's because words like 'given' or 'where', as used in mathematics, imply a definition of something that's been used *before*, whereas here it's defining something to be used *after* (i.e. in the following 'if' clause). Mathematicians use 'let' when they're defining something to be used subsequently. So it should really be either [y for x in xs letting y = f(x) if y] or [y for x in xs if y given y = f(x)] Incidentally, I think this also demonstrates that proposing a new syntax with a <new-keyword-goes-here> placeholder doesn't really work -- usually the choice of keyword matters, and it has an effect on how the rest of the syntax should be arranged. Or at least it does to someone with a strong sense of language design aesthetics. -- Greg

On Tue, Apr 12, 2011 at 9:17 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
This one is tricky, since the assignment is *after* the for loop, but *before* the filter condition. You could reorder it as below, but the translation to long-form Python code wouldn't be quite as clean (since the order of clauses wouldn't follow the order of nesting any more). ys = [y for x in xs if y given f(x) as y] Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan wrote:
This one is tricky, since the assignment is *after* the for loop, but *before* the filter condition.
That's why I like the 'letting' form, because it both reads correctly and matches the order of execution. ys = [y for x in xs letting y = f(x) if y] translates to ys = [] for x in xs: y = f(x) if y: ys.append(y) -- Greg

On Mon, Apr 11, 2011 at 2:14 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
ys = [y for x in xs letting y = f(x) if y]
Isn't that ambiguous? What if someone used the conditional expression: ys = [y for x in xs letting y = f(x) if y else g(x)] ? It seems like the letting/given has to go last in order to eliminate the possible ambiguity in the subsequent optional if clause. -- Carl Johnson

On Tue, Apr 12, 2011 at 1:53 PM, Carl M. Johnson <cmjohnson.mailinglist@gmail.com> wrote:
given/as resolves the parsing ambiguity problem by putting the names second: ys = [y for x in xs given f(x) if p(x) else g(x) as y] However, it does make it clear how limited such a clause still is if it exists only at the comprehension level and isn't an expression in its own right. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Tue, Apr 12, 2011 at 3:06 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
ys = [y for x in xs given f(x) if p(x) else g(x) as y]
You know, I may have spoken too soon in saying PEP 3150 couldn't help with the list comprehension use case. def remember(f): """Decorator that remembers result until arguments change""" # Essentially an LRU cache for exactly 1 entry last_args = object(), object() last_result = None @functools.wraps(f) def wrapped(*args, **kwds): nonlocal last_args, last_result if last_args != args, kwds: last_args = args, kwds last_value = f(*args, **kwds) return last_value return wrapped ys = [y(x) for x in xs if y(x)] given: y = remember(f) Or for the more complicated case: ys = [y(x) for x in xs if y(x)] given: @remember def y(x): fx = f(x) return fx if fx else g(x) Or even (using the subgenerator approach): ys = [y for y in all_y if y] given: all_y = (f(x) for x in x) There are all *sorts* of tricks that open up once you don't even need to think about possible impacts on the local namespace. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Mon, Apr 11, 2011 at 11:47 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Like Georg just said, the given clause seems hard to read. He was referring to the embedded clause in the list comprehension. I kind of feel the same way about the PEP 3150 syntax. I remember when I was first exposed to list comprehensions. At first it was hard because it was organized differently from any other Python code. However, I have adjusted. Maybe a given clause would be the same. I'm just not sure. That "given" after the expression keeps throwing me off. It just looks out of place. Would something like the following be feasible: c = sqrt(a*a + b*b) given: a = retrieve_a() b = retrieve_b() becomes: given: a = retrieve_a() b = retrieve_b() c = sqrt(a*a + b*b) where the given statement is effectively _decorating_ the simple statement? Unfortunately I am not familiar enough yet with the compiler to know what such decoration would entail, much less if it would be entirely too complicated. However, I wanted to throw out there an alternative that is maybe more readable. It's also one I would probably find more usable for plugging in given blocks or disabling them, much like with decorators. I know this throws off the approach the PEP takes, but I am trying to wrap my head around how I would use statement local namespaces. I like how it cleans up from the namespace those bits that only matter to the target statement. It does make it easier to follow to whom those statements in the given block belong. Anyway, this has certainly been an enlightening discussion. I hope it bears fruit. -eric Cheers,

On Tue, Apr 12, 2011 at 12:51 AM, Eric Snow <ericsnowcurrently@gmail.com>wrote:
Of course, a decorating syntax does not help with the original scenario, with embedded given statements in list comprehensions. But maybe that embedded syntax is too hard to read. -eric _______________________________________________

On Tue, Apr 12, 2011 at 4:51 PM, Eric Snow <ericsnowcurrently@gmail.com> wrote:
Yeah, in order variants have been part of the discussion since the beginning, usually in a 2-suite form like: let: # local namespace in: # binding here affect surrounding scope # but can also see names bound in "let" clause Dropping the second suite (and having the given clause implicit modify the following statement) isn't really feasible (and far more brain bending than adding a new clause to augment simple statements). I really don't see much point to the in-order variants, hence why discussing them is currently a TO-DO note in the PEP rather than a fully fleshed out description of the problems I have with them. If I had to come up with a concise rationale for when you would use them (with the post-fix syntax and anonymous function semantics proposed in the PEP): 1. You wish to avoid polluting the current namespace with working variables and helper functions (most relevant for module and class level code, but may also be relevant for functions in some closure related contexts) 2. You wish to provide greater prominence to the final statement in a series of operations, making it clear to the reader that the other statements are mere setup for that final step. This is similar to the principle of decorators, where the important information is the function name, parameters, annotations and applied decorators, while the precise implementation details will be uninteresting for many readers. 3. You want early binding for closure references without resorting to the default argument hack (including early binding of module globals and class variables) 4. You want to enable function-level optimisations in module or class level code 1 and 2 are the core of the rationale for even proposing the idea of statement local namespaces in the first place, but come with the downside of making it harder to test your setup code. 3 is one of the aspects of my most proposed implementation strategy that I most like (since the default-argument hack is a genuinely ugly wart). 4 is just a perk of my proposed implementation strategy rather than a compelling use case in its own right. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 4/12/2011 4:31 AM, Nick Coghlan wrote:
I am puzzled why namespace sanitation fans are so allergic to the namespace cleanup statement == 'del'.
Final position *is* a place of prominence.
And decorators put the def line *last* instead of first, whereas the proposed 'given' moves the punchline statement from last to first. A module or class doc string can list the important names, which should be readable and multiple chars. A module can reiterate with __all__. Private names get a leading _. Temporary names can be short and deleted when not needed. -- Terry Jan Reedy

On Tue, Apr 12, 2011 at 12:35 PM, Terry Reedy <tjreedy@udel.edu> wrote: ..
I am puzzled why namespace sanitation fans are so allergic to the namespace cleanup statement == 'del'.
Because using cleanup may unexpectedly clobber the variables you want to keep. When using for var in [..]: .. del var idiom, I tend to maintain some naming convention for what var can be so that it does not conflict with other global variables. Naming conventions are usually considered suboptimal work-arounds for languages with poor namespace support.

On Tue, Apr 12, 2011 at 10:03 AM, Alexander Belopolsky <alexander.belopolsky@gmail.com> wrote:
Also note that if that loop happened to be an empty loop, var would not be set, and del var would fail. Another reason why del is suboptimal is that if you have multiple exits from your block you need a try/finally just to delete the variables, but then you definitely need to deal with the possibility of the variable not yet existing. Either way it's a mess. -- --Guido van Rossum (python.org/~guido)

On 4/12/11, Terry Reedy <tjreedy@udel.edu> wrote:
On 4/12/2011 4:31 AM, Nick Coghlan wrote:
I am puzzled why namespace sanitation fans are so allergic to the namespace cleanup statement == 'del'.
You have to do it explicitly, which calls attention to it precisely when you were ready to forget it. In other words, it has all the disadvantages that pre-decorator function wrapping did, and (if it were really needed) all the disadvantages of manual memory management, and the additional concern that -- because it is not idiomatic -- readers will expect that variable name (or at least that namespace) to be particularly important. -jJ

On Wed, Apr 13, 2011 at 2:35 AM, Terry Reedy <tjreedy@udel.edu> wrote:
For one of the key reasons decorators and context managers exist - it's hard to audit "at the end" stuff for correctness.
This whole discussion has been really useful to me in crystallising *why* I see value in PEP 3150, and it is directly related to the way function and class definitions work. I have the glimmerings of a rewrite of PEP 3150 kicking around in my skull, that may include restricting it to assignment statements (I'm not 100% decided on that point as yet - I'll make up my mind as the rest of the rewrite takes shape). The reason I am considering such a restriction is that the new Rationale section will likely be along the following lines: ========================= Function and class statements in Python have a unique property relative to ordinary assignment statements: to some degree, they are *declarative*. They present the reader of the code with some critical information about a name that is about to be defined, before proceeding on with the details of the actual definition in the function or class body. The *name* of the object being declared is the first thing stated after the keyword. Other important information is also given the honour of preceding the implementation details: - decorators (which can greatly affect the behaviour of the created object, and were placed ahead of even the keyword and name as a matter of practicality moreso than aesthetics) - the docstring (on the first line immediately following the header line) - parameters, default values and annotations for function definitions - parent classes, metaclass and optionally other details (depending on the metaclass) for class definitions This PEP proposes to make a similar declarative style available for arbitrary assignment operations, by permitting the inclusion of a "given" suite following any simple (non-augmented) assignment statement:: TARGET = [TARGET2 = ... TARGETN =] EXPR given: SUITE By convention, code in the body of the suite should be oriented solely towards correctly defining the assignment operation carried out in the header line. The header line operation should also be adequately descriptive (e.g. through appropriate choices of variable names) to give a reader a reasonable idea of the purpose of the operation without reading the body of the suite. ========================= Another addition I am considering is the idea of allowing the "given" suite to contain a docstring, thus providing a way to make it easy to attach a __doc__ attribute to arbitrary targets. This may require disallowing tuple unpacking and multiple assignment targets when using the given clause, or else simply raising a syntax error if a docstring is present for an assignment using either of those forms. Other use cases will of course still be possible, but that will be the driving force behind the revised PEP. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Tue, Apr 12, 2011 at 11:53 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Overall I like it. The idea of limiting to assignment is a good one. This gives room for custom namespaces without all the class machinery. It is certainly something I have seen brought up on several occasions here. And these namespaces would not be anonymous since they are tied to the assignment. One benefit is that we could deprecate module variables using this syntax. -eric

On Tue, Apr 12, 2011 at 10:53 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Hm. Most of the other simple statements currently mentioned in the PEP make sense to me as well, e.g. "return X given: ...".
I like the idea of allowing a docstring in the given-clause, but I'm not sure I care to enforce that the docstring is preserved somehow in the target expression. And then again maybe if it's not preserved it's not worth mentioning -- it can be considered a no-op just like putting a "docstring" (really just a comment) in the middle of a block of code, or e.g. at the top of a loop. (All in all I think you have mostly managed to confuse me in this message. At some point I even thought you meant that the body of the given clause should be limited to definitions...) -- --Guido van Rossum (python.org/~guido)

Nick Coghlan wrote:
I have the glimmerings of a rewrite of PEP 3150 kicking around in my skull, that may include restricting it to assignment statements
Please don't, as that would eliminate a potentially useful set of use cases. Or at least it would be trying to, but it wouldn't be entirely effective, because you would always be able to write dummy = something(foo) given: foo = ...
I'm guessing you're thinking that the docstring would be assigned to the __doc__ attribute of whatever object the RHS expression returns. I don't think there's any need to make a special case here regarding unpacking. If it's not possible to assign a __doc__ to the object you'll get an exception, and if it works but gets lost in the unpacking, too bad. Also I can't see why multiple assignment targets would be a problem at all -- the same object just gets assigned to all the targets, including the __doc__. -- Greg

On Thu, Apr 14, 2011 at 8:33 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Yep, that's why I'm considering just describing the motivation in terms of assignment, but then expanding it to other cases (including "pass") to avoid silly workarounds when people decide to use it more for the local namespace aspect than the "it's like 'def' for arbitrary assignments" aspect (and "return" and "yield" have a lot in common with assignment, anyway).
Ah, true, that would make a lot of sense. It also generalises nicely to other cases like "return" and "yield" as well. I'll see if I can thrash out something sensible along those lines. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 4/12/11, Nick Coghlan <ncoghlan@gmail.com> wrote:
Yeah, in order variants have been part of the discussion since the beginning, usually in a 2-suite form like:
...
If you're using two blocks (like the if statement), then why do they have to be in order? exec: # or "do" or "run" or ... ... # ordinary suite given: ... # suite limited to name bindings statements? normal suite?
The idea of a throw-away internal class does solve this, but I admit it looks very odd -- to the point that I would be looking for a keyword other than class.
The reverse-order double suite solves this, so long as you keep the suites reasonably sized.
Much as I like this, it sort of requires that the whole function be wrapped in a do ... given, which might start to look like pointless boilerplate if people start to do it too often. -jJ

On Tue, Apr 12, 2011 at 1:47 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
You know, I may have spoken too soon in saying PEP 3150 couldn't help with the list comprehension use case.
Alternatively, comprehension can be used instead of PEP 3150, e.g. desired_property = next( calc_value(temp, depth, purity, salinity, size, density) for sea in [water()] for temp in [get_temperature(sea)] for depth in [get_depth(sea)] for purity in [get_purity(sea)] for saltiness in [get_salinity(sea)] for size in [get_size(sea)] for density in [get_density(sea)] ) # Further operations using desired_property
In Haskell it's usually indented differently: c = sqrt(a*a + b*b) given: a = retrieve_a() b = retrieve_b() Eugene

On Tue, Apr 12, 2011 at 4:31 PM, Georg Brandl <g.brandl@gmx.net> wrote:
Is it just me, or do others find this "string of clauses" (no matter what the actual keywords are) not very readable?
Nope, it's not just you :) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Georg Brandl wrote:
Same here. If the "given:" blocks are only meant to hide away things in a separate namespace, I'm not sure why we need a new keyword. This is already possible using the "class" keyword and it even gives the namespace a name ;-) class myData: x = setup_x() y = setup_y() z = myFormula(myData.x, myData.y) That's a lot more readable, pythonic and intuitive than the proposed "given:" block syntax, which -to me at least- looks confusing. BTW, I don't find the comparison to the list comprehension syntax valid: List comprehensions only allow a very limited set of qualifiers which form the list definition. As such, it makes sense to have the definition behind the item expression (and it follows the rules used in math for similar definitions). The "given:" block, OTOH, makes no such restrictions and may well contain code that doesn't have anything to do with the statement it is supposed to configure. It has the potential of introducing top-posting to Python code: answer_to_question given: some_other_blurb original_question more_other_blurb And things get even more exciting if you start to nest these: answer_to_original_question given: comment_to_answer_of_other_question given: answer_to_some_other_question given: other_question new_angle_to_discussion other_blurb original_question more_other_blurb Following the flow of program execution and relevance of the different parts to the final result can be very confusing. Also note that it is rather unusual and unexpected for Python to execute the innermost block before the outer one as it would happen in the above example. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Apr 12 2011)
::: Try our new mxODBC.Connect Python Database Interface for free ! :::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/

M.-A. Lemburg wrote:
Only for certain values of "possible". It potentially confuses the reader by using a class in an unusual way. When one sees a class definition, one's first thought is that instances of it are going to be created at some point, but that's not the case here. -- Greg

On 4/12/2011 2:31 AM, Georg Brandl wrote:
On 12.04.2011 07:06, Nick Coghlan wrote:
ys = [y for x in xs given f(x) if p(x) else g(x) as y]
Is it just me,
No.
or do others find this "string of clauses" (no matter what the actual keywords are) not very readable?
I think people are trying to push an optional convenience feature way too far. I kind of wish comprehensions had been limited to one for clause (the source) and an optional if clause (the filter), as they pretty much are in math. Of course, I am one who switched to Python instead of lisp, perl, etc, *because* it read like pseudocode, with a nice mixture of expressions within statements, each in their own (logical) line. (I could have written this paragraph as a single sentence with a string of clauses, as I have seen in both older English and German, but I am not a fan of that either ;-). -- Terry Jan Reedy

Nick Coghlan wrote:
given/as resolves the parsing ambiguity problem by putting the names second:
And I don't like that. I would rather see an = sign. We're doing an assignment, after all, so we should make it look like one if we can. -- Greg

On Mon, Apr 11, 2011 at 11:53 PM, Carl M. Johnson <cmjohnson.mailinglist@gmail.com> wrote:
This ambiguity already exists -- you can write conditional expression in iteratee of for or in condition of if, but it won't parse, because the conflict is resolved in favour of list comprehension's 'if'. You have to put if/else in parenthesis. Eugene

Carl M. Johnson wrote:
Isn't that ambiguous? What if someone used the conditional expression: ys = [y for x in xs letting y = f(x) if y else g(x)] ?
No more ambiguous than 'if' already is in a comprehension, and that's resolved by requiring parens if one of the clauses in the comprehension contains an 'if' expression. -- Greg

Greg Ewing writes:
I don't have a problem with *reading* that, since you can't really win: "y" is used both before and after the "given" binding. I would expect you will need a restriction against using a "given"-bound variable in another "given" clause, so syntactically I don't think it matters where in the expression it appears.
I'm sorry, but I read that three times and it parsed as "y gets undefined if it is false" every time. This is way worse than "x if y else z", which awkward but I can't parse it any way other than "x (if y), else z". For some reason "given" binds the expression a lot more tightly than "letting" does in my dialect. I do prefer the "given" at the end, but putting it in the middle doesn't bother me. It's not that I couldn't get used to it, but I suspect I would never learn to like it. I hope others feel the same way.<wink>

Stephen J. Turnbull wrote:
I don't have a problem with *reading* that, since you can't really win: "y" is used both before and after the "given" binding.
But the "before" usage is just the usual comprehension quirk of putting the result expression before everything else, even though it's actually evaluated last. Once you allow for that, it's effectively after the given/letting/ whatever.
In that case, would you prefer this? ys = [y for x in xs if y given y = f(x)] -- Greg

Greg Ewing writes:
Turns out to be precisely why it's readable, I think. But the "but" is not the point. The point is that (in my dialect) it *is* readable.
Yes, very much. I would also prefer ys = [y for x in xs given y = f(x) if y] to the "letting" version, though that is harder to read (for me) than the version with the assignment at the end of the expression. I think the difference is that "let" is *local* to the comprehension and local context will bind to it. "given" is a meta-word, it refers to something *outside* the comprehension, in my dialect. So it is undisturbed by the local context. YMMV, that may be very specific to me. As I say, I could probably get used to "letting" if most people prefer it. But in my personal usage, "given" is clearly better.

On 4/12/11, Stephen J. Turnbull <stephen@xemacs.org> wrote:
Greg Ewing writes:
Stephen J. Turnbull wrote:
ys = [y for x in xs letting y = f(x) if y]
I'm sorry, but I read that three times and it parsed as "y gets undefined if it is false" every time.
Me too ... but then I try to figure out what to do with an undefined, so the eventual answer is either an error, or what you really wanted in the first place.
In that case, would you prefer this?
ys = [y for x in xs if y given y = f(x)]
I would ... but I don't actually *like* it, I just find it less problematic.
Yes, very much. I would also prefer
ys = [y for x in xs given y = f(x) if y]
to the "letting" version, though that is harder to read (for me) than the version with the assignment at the end of the expression.
I find this less objectionable still. It improves more if the "given" clause is on a separate line. I'm not sure there *is* a likable way to put the comprehension, the temporary assignment, and the filter into one unbroken thought. -jJ

On Sun, Apr 10, 2011 at 6:42 PM, Eugene Toder <eltoder@gmail.com> wrote:
Your continued insistence on hearing an explanation why it is missing is beginning to sound more and more like asking when Python stopped beating his wife. (This is not unusual. We get a lot of questions phrased as "why does Python not do X?" Do you think that is a good way to get a discussion started?) -- --Guido van Rossum (python.org/~guido)

On Sun, Apr 10, 2011 at 8:39 PM, Laura Creighton <lac@openend.se> wrote:
I just like to have a reasonable explanation for everything I see. A good story is what often separates a feature from a bug. On Sun, Apr 10, 2011 at 8:39 PM, Laura Creighton <lac@openend.se> wrote:
There is real value in using whitespace and indentation to separate logical ideas, and jamming everything into a comprehension completely destroys this.
I don't think it's fair to judge a feature by trying to imagine the worst misuse one can do with it. If you would do this consistently, you'd never introduce arrays (think code using arrays instead of classes, with magic undocumented indexes instead of attribute names) or dynamically-typed scripting languages (think Perl). Also, as pointed above, local assignment doesn't allow any more code written as comprehension than one can write now -- there are multiple work-arounds: repetition, nested comprehensions, for name in [expr]. Local assignment simply provides a more direct way of doing it, and to me a more direct way is easier to read. Eugene

On Sun, Apr 10, 2011 at 6:42 PM, Eugene Toder <eltoder@gmail.com> wrote:
Local assignment simply provides a more direct way of doing it, and to me a more direct way is easier to read.
I don't think there's going to be consensus that sticking additional assignment in the middle of a generator expression makes it more readable. The expressions you're talking about really are conceptually nested and you're trying to eliminate/hide that. Let me throw out an alternative. In these expressions there's redundant boilerplate ("x for x in ...") when what we're really doing is a simple filter or map across the sequence. In these examples, {sequence} stands in for both the sequence being iterated and the value from the sequence being operated on. ( for {seq} + 1 ) => ( x + 1 for x in seq ) ( for f({seq}) ) => ( f(x) for x in seq ) ( if {seq} > 1 } => ( x for x in seq if x > 1 ) But here's the catch. While I would like a simpler way to express these idioms, I don't think these offer enough of an advantage. And I think that when you start to nest them it quickly becomes unreadable: (y for y in (f(x) for x in xs) if y) => (if {(for f({xs})}) And {} looks a bit too much like () for my taste -- I find that trailing })}) particularly hard to read. So why offer this proposal? I think there's a natural tendency to try to come up with terse expressions for common idioms. But they don't add power and they make the language less approachable. I think terseness is sometimes the opposite of readability. In C# the lambda syntax is terse and I find it unreadable if written without line breaks: f(x, y + 1, x => x + 1, y + 1) vs. f(x, y + 1, x => x + 1, y + 1) --- Bruce *New! *Puzzazz newsletter: http://j.mp/puzzazz-news-2011-04 including April Fools! *New!** *Blog post: http://www.vroospeak.com/2011/04/march-gets-more-madness-next-year.html April Fools!

Bruce Leban wrote:
I don't think there's anything wrong whatsoever with the current way of doing the first two of these. The third one could perhaps benefit from something like (all x in seq if ...) but I tend to agree that the gain is too small to be worth a change.
Seems to me the readability problem there is mainly due to it being mixed in with other comma-separated expressions. I'm not sure it's really all that much better with lambda: f(x, y + 1, lambda x: x + 1, y + 1) I'm not even sure offhand how that parses, so I'd probably put parens around the lambda anyway to be sure: f(x, y + 1, (lambda x: x + 1), y + 1) Now do that with the terse version: f(x, y + 1, (x => x + 1), y + 1) This doesn't look significantly worse to me than any other function call with operators in the argument expressions. If there's a problem spotting the fact that there's an => in there, it could be made a little more noticeable: f(x, y + 1, (x ==> x + 1), y + 1) -- Greg

On Mon, Apr 11, 2011 at 11:42 AM, Eugene Toder <eltoder@gmail.com> wrote:
And we already have one kind of expression that owes its existence almost entirely to the suboptimal workaround people were using to get around the fact it wasn't available* :) A "given/as" list comprehension subclause inspired by PEP 3150 might actually be a more interesting idea to explore than I first thought (considering that PEP 3150 itself is absolutely no help at all for this particular use case). That is, instead of having to choose one of the following 4 alternatives (and assorted variants of the explicit loop, such as invoking list() on a generator function): ys = [f(x) for x in xs if f(x)] ys = [y for y in (f(x) for x in xs) if y] ys = [y for x in xs for y in [f(x)] if y] def _build_sequence(xs): ys = [] for x in xs: y = f(x): if y: ys.append(y) return ys ys = _build_sequence(xs) The language could offer an "obvious" answer of the form: ys = [y for x in xs given f(x) as y if y] Multiple subexpressions would be handled gracefully via tuple unpacking, and the translation to "long-form" Python code for actual execution by the eval loop would follow the same style as is already used for comprehensions and generator expressions (i.e. the last alternative I listed above, with the details of the emitted code changing appropriately based on the kind of comprehension). So, despite my initial negative reaction, I'd now be interested in reading a fully fleshed out PEP for this proposal. Cheers, Nick. (*Read PEP 308 if the reference is unfamiliar) -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Mon, Apr 11, 2011 at 12:58 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Oops, random colon slipped in as typo. That should be: def _build_sequence(xs): ys = [] for x in xs: y = f(x) if y: ys.append(y) return ys ys = _build_sequence(xs) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Sun, Apr 10, 2011 at 10:58 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
And 5 years later there are still people out there who prefer using and/or trick. They tend to say something about the new ways being too new and the old ways being good enough :) On Sun, Apr 10, 2011 at 11:03 PM, Guido van Rossum <guido@python.org> wrote:
Sorry, I don't get the reference. On Sun, Apr 10, 2011 at 11:03 PM, Guido van Rossum <guido@python.org> wrote:
In my defense, I'm actually more interested in the history than in adding this feature, as I find the lack of it a pretty small niggle. The discussion was started with specific examples and suggestions, but it was not very interesting so far, because many (not all) responses are a generic 'new features are bad because they are new' and it's very hard to argue with that :-) And I do not dispute the fact that new features need a high level of scrutiny. Eugene

On 11 April 2011 05:10, Eugene Toder <eltoder@gmail.com> wrote:
The reference is to the "canonical" unanswerable question - "When did you stop beating your wife?" There is no good answer because the question is based on an incorrect assumption.
As far as history goes, the answer is basically "because no-one thought of it (or if they did, they didn't care about it enough to implement it)". You seem to be insisting that there has to be a deeper reason, though - which is why the discussion of history/motivation is becoming frustrating - there really isn't anything deeper.
The *only* interesting part of this thread to me is the discussion around something like "given" (PEP 3150, which I'd forgotten about). Oh, and some of the philosophical discussions on readability... Paul.

Nick Coghlan wrote:
ys = [y for x in xs given f(x) as y if y]
Hmmm. Here I find myself getting tripped up by the ordering when I try to read that smoothly. I think it's because words like 'given' or 'where', as used in mathematics, imply a definition of something that's been used *before*, whereas here it's defining something to be used *after* (i.e. in the following 'if' clause). Mathematicians use 'let' when they're defining something to be used subsequently. So it should really be either [y for x in xs letting y = f(x) if y] or [y for x in xs if y given y = f(x)] Incidentally, I think this also demonstrates that proposing a new syntax with a <new-keyword-goes-here> placeholder doesn't really work -- usually the choice of keyword matters, and it has an effect on how the rest of the syntax should be arranged. Or at least it does to someone with a strong sense of language design aesthetics. -- Greg

On Tue, Apr 12, 2011 at 9:17 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
This one is tricky, since the assignment is *after* the for loop, but *before* the filter condition. You could reorder it as below, but the translation to long-form Python code wouldn't be quite as clean (since the order of clauses wouldn't follow the order of nesting any more). ys = [y for x in xs if y given f(x) as y] Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan wrote:
This one is tricky, since the assignment is *after* the for loop, but *before* the filter condition.
That's why I like the 'letting' form, because it both reads correctly and matches the order of execution. ys = [y for x in xs letting y = f(x) if y] translates to ys = [] for x in xs: y = f(x) if y: ys.append(y) -- Greg

On Mon, Apr 11, 2011 at 2:14 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
ys = [y for x in xs letting y = f(x) if y]
Isn't that ambiguous? What if someone used the conditional expression: ys = [y for x in xs letting y = f(x) if y else g(x)] ? It seems like the letting/given has to go last in order to eliminate the possible ambiguity in the subsequent optional if clause. -- Carl Johnson

On Tue, Apr 12, 2011 at 1:53 PM, Carl M. Johnson <cmjohnson.mailinglist@gmail.com> wrote:
given/as resolves the parsing ambiguity problem by putting the names second: ys = [y for x in xs given f(x) if p(x) else g(x) as y] However, it does make it clear how limited such a clause still is if it exists only at the comprehension level and isn't an expression in its own right. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Tue, Apr 12, 2011 at 3:06 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
ys = [y for x in xs given f(x) if p(x) else g(x) as y]
You know, I may have spoken too soon in saying PEP 3150 couldn't help with the list comprehension use case. def remember(f): """Decorator that remembers result until arguments change""" # Essentially an LRU cache for exactly 1 entry last_args = object(), object() last_result = None @functools.wraps(f) def wrapped(*args, **kwds): nonlocal last_args, last_result if last_args != args, kwds: last_args = args, kwds last_value = f(*args, **kwds) return last_value return wrapped ys = [y(x) for x in xs if y(x)] given: y = remember(f) Or for the more complicated case: ys = [y(x) for x in xs if y(x)] given: @remember def y(x): fx = f(x) return fx if fx else g(x) Or even (using the subgenerator approach): ys = [y for y in all_y if y] given: all_y = (f(x) for x in x) There are all *sorts* of tricks that open up once you don't even need to think about possible impacts on the local namespace. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Mon, Apr 11, 2011 at 11:47 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Like Georg just said, the given clause seems hard to read. He was referring to the embedded clause in the list comprehension. I kind of feel the same way about the PEP 3150 syntax. I remember when I was first exposed to list comprehensions. At first it was hard because it was organized differently from any other Python code. However, I have adjusted. Maybe a given clause would be the same. I'm just not sure. That "given" after the expression keeps throwing me off. It just looks out of place. Would something like the following be feasible: c = sqrt(a*a + b*b) given: a = retrieve_a() b = retrieve_b() becomes: given: a = retrieve_a() b = retrieve_b() c = sqrt(a*a + b*b) where the given statement is effectively _decorating_ the simple statement? Unfortunately I am not familiar enough yet with the compiler to know what such decoration would entail, much less if it would be entirely too complicated. However, I wanted to throw out there an alternative that is maybe more readable. It's also one I would probably find more usable for plugging in given blocks or disabling them, much like with decorators. I know this throws off the approach the PEP takes, but I am trying to wrap my head around how I would use statement local namespaces. I like how it cleans up from the namespace those bits that only matter to the target statement. It does make it easier to follow to whom those statements in the given block belong. Anyway, this has certainly been an enlightening discussion. I hope it bears fruit. -eric Cheers,

On Tue, Apr 12, 2011 at 12:51 AM, Eric Snow <ericsnowcurrently@gmail.com>wrote:
Of course, a decorating syntax does not help with the original scenario, with embedded given statements in list comprehensions. But maybe that embedded syntax is too hard to read. -eric _______________________________________________

On Tue, Apr 12, 2011 at 4:51 PM, Eric Snow <ericsnowcurrently@gmail.com> wrote:
Yeah, in order variants have been part of the discussion since the beginning, usually in a 2-suite form like: let: # local namespace in: # binding here affect surrounding scope # but can also see names bound in "let" clause Dropping the second suite (and having the given clause implicit modify the following statement) isn't really feasible (and far more brain bending than adding a new clause to augment simple statements). I really don't see much point to the in-order variants, hence why discussing them is currently a TO-DO note in the PEP rather than a fully fleshed out description of the problems I have with them. If I had to come up with a concise rationale for when you would use them (with the post-fix syntax and anonymous function semantics proposed in the PEP): 1. You wish to avoid polluting the current namespace with working variables and helper functions (most relevant for module and class level code, but may also be relevant for functions in some closure related contexts) 2. You wish to provide greater prominence to the final statement in a series of operations, making it clear to the reader that the other statements are mere setup for that final step. This is similar to the principle of decorators, where the important information is the function name, parameters, annotations and applied decorators, while the precise implementation details will be uninteresting for many readers. 3. You want early binding for closure references without resorting to the default argument hack (including early binding of module globals and class variables) 4. You want to enable function-level optimisations in module or class level code 1 and 2 are the core of the rationale for even proposing the idea of statement local namespaces in the first place, but come with the downside of making it harder to test your setup code. 3 is one of the aspects of my most proposed implementation strategy that I most like (since the default-argument hack is a genuinely ugly wart). 4 is just a perk of my proposed implementation strategy rather than a compelling use case in its own right. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 4/12/2011 4:31 AM, Nick Coghlan wrote:
I am puzzled why namespace sanitation fans are so allergic to the namespace cleanup statement == 'del'.
Final position *is* a place of prominence.
And decorators put the def line *last* instead of first, whereas the proposed 'given' moves the punchline statement from last to first. A module or class doc string can list the important names, which should be readable and multiple chars. A module can reiterate with __all__. Private names get a leading _. Temporary names can be short and deleted when not needed. -- Terry Jan Reedy

On Tue, Apr 12, 2011 at 12:35 PM, Terry Reedy <tjreedy@udel.edu> wrote: ..
I am puzzled why namespace sanitation fans are so allergic to the namespace cleanup statement == 'del'.
Because using cleanup may unexpectedly clobber the variables you want to keep. When using for var in [..]: .. del var idiom, I tend to maintain some naming convention for what var can be so that it does not conflict with other global variables. Naming conventions are usually considered suboptimal work-arounds for languages with poor namespace support.

On Tue, Apr 12, 2011 at 10:03 AM, Alexander Belopolsky <alexander.belopolsky@gmail.com> wrote:
Also note that if that loop happened to be an empty loop, var would not be set, and del var would fail. Another reason why del is suboptimal is that if you have multiple exits from your block you need a try/finally just to delete the variables, but then you definitely need to deal with the possibility of the variable not yet existing. Either way it's a mess. -- --Guido van Rossum (python.org/~guido)

On 4/12/11, Terry Reedy <tjreedy@udel.edu> wrote:
On 4/12/2011 4:31 AM, Nick Coghlan wrote:
I am puzzled why namespace sanitation fans are so allergic to the namespace cleanup statement == 'del'.
You have to do it explicitly, which calls attention to it precisely when you were ready to forget it. In other words, it has all the disadvantages that pre-decorator function wrapping did, and (if it were really needed) all the disadvantages of manual memory management, and the additional concern that -- because it is not idiomatic -- readers will expect that variable name (or at least that namespace) to be particularly important. -jJ

On Wed, Apr 13, 2011 at 2:35 AM, Terry Reedy <tjreedy@udel.edu> wrote:
For one of the key reasons decorators and context managers exist - it's hard to audit "at the end" stuff for correctness.
This whole discussion has been really useful to me in crystallising *why* I see value in PEP 3150, and it is directly related to the way function and class definitions work. I have the glimmerings of a rewrite of PEP 3150 kicking around in my skull, that may include restricting it to assignment statements (I'm not 100% decided on that point as yet - I'll make up my mind as the rest of the rewrite takes shape). The reason I am considering such a restriction is that the new Rationale section will likely be along the following lines: ========================= Function and class statements in Python have a unique property relative to ordinary assignment statements: to some degree, they are *declarative*. They present the reader of the code with some critical information about a name that is about to be defined, before proceeding on with the details of the actual definition in the function or class body. The *name* of the object being declared is the first thing stated after the keyword. Other important information is also given the honour of preceding the implementation details: - decorators (which can greatly affect the behaviour of the created object, and were placed ahead of even the keyword and name as a matter of practicality moreso than aesthetics) - the docstring (on the first line immediately following the header line) - parameters, default values and annotations for function definitions - parent classes, metaclass and optionally other details (depending on the metaclass) for class definitions This PEP proposes to make a similar declarative style available for arbitrary assignment operations, by permitting the inclusion of a "given" suite following any simple (non-augmented) assignment statement:: TARGET = [TARGET2 = ... TARGETN =] EXPR given: SUITE By convention, code in the body of the suite should be oriented solely towards correctly defining the assignment operation carried out in the header line. The header line operation should also be adequately descriptive (e.g. through appropriate choices of variable names) to give a reader a reasonable idea of the purpose of the operation without reading the body of the suite. ========================= Another addition I am considering is the idea of allowing the "given" suite to contain a docstring, thus providing a way to make it easy to attach a __doc__ attribute to arbitrary targets. This may require disallowing tuple unpacking and multiple assignment targets when using the given clause, or else simply raising a syntax error if a docstring is present for an assignment using either of those forms. Other use cases will of course still be possible, but that will be the driving force behind the revised PEP. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Tue, Apr 12, 2011 at 11:53 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Overall I like it. The idea of limiting to assignment is a good one. This gives room for custom namespaces without all the class machinery. It is certainly something I have seen brought up on several occasions here. And these namespaces would not be anonymous since they are tied to the assignment. One benefit is that we could deprecate module variables using this syntax. -eric

On Tue, Apr 12, 2011 at 10:53 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Hm. Most of the other simple statements currently mentioned in the PEP make sense to me as well, e.g. "return X given: ...".
I like the idea of allowing a docstring in the given-clause, but I'm not sure I care to enforce that the docstring is preserved somehow in the target expression. And then again maybe if it's not preserved it's not worth mentioning -- it can be considered a no-op just like putting a "docstring" (really just a comment) in the middle of a block of code, or e.g. at the top of a loop. (All in all I think you have mostly managed to confuse me in this message. At some point I even thought you meant that the body of the given clause should be limited to definitions...) -- --Guido van Rossum (python.org/~guido)

Nick Coghlan wrote:
I have the glimmerings of a rewrite of PEP 3150 kicking around in my skull, that may include restricting it to assignment statements
Please don't, as that would eliminate a potentially useful set of use cases. Or at least it would be trying to, but it wouldn't be entirely effective, because you would always be able to write dummy = something(foo) given: foo = ...
I'm guessing you're thinking that the docstring would be assigned to the __doc__ attribute of whatever object the RHS expression returns. I don't think there's any need to make a special case here regarding unpacking. If it's not possible to assign a __doc__ to the object you'll get an exception, and if it works but gets lost in the unpacking, too bad. Also I can't see why multiple assignment targets would be a problem at all -- the same object just gets assigned to all the targets, including the __doc__. -- Greg

On Thu, Apr 14, 2011 at 8:33 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Yep, that's why I'm considering just describing the motivation in terms of assignment, but then expanding it to other cases (including "pass") to avoid silly workarounds when people decide to use it more for the local namespace aspect than the "it's like 'def' for arbitrary assignments" aspect (and "return" and "yield" have a lot in common with assignment, anyway).
Ah, true, that would make a lot of sense. It also generalises nicely to other cases like "return" and "yield" as well. I'll see if I can thrash out something sensible along those lines. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 4/12/11, Nick Coghlan <ncoghlan@gmail.com> wrote:
Yeah, in order variants have been part of the discussion since the beginning, usually in a 2-suite form like:
...
If you're using two blocks (like the if statement), then why do they have to be in order? exec: # or "do" or "run" or ... ... # ordinary suite given: ... # suite limited to name bindings statements? normal suite?
The idea of a throw-away internal class does solve this, but I admit it looks very odd -- to the point that I would be looking for a keyword other than class.
The reverse-order double suite solves this, so long as you keep the suites reasonably sized.
Much as I like this, it sort of requires that the whole function be wrapped in a do ... given, which might start to look like pointless boilerplate if people start to do it too often. -jJ

On Tue, Apr 12, 2011 at 1:47 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
You know, I may have spoken too soon in saying PEP 3150 couldn't help with the list comprehension use case.
Alternatively, comprehension can be used instead of PEP 3150, e.g. desired_property = next( calc_value(temp, depth, purity, salinity, size, density) for sea in [water()] for temp in [get_temperature(sea)] for depth in [get_depth(sea)] for purity in [get_purity(sea)] for saltiness in [get_salinity(sea)] for size in [get_size(sea)] for density in [get_density(sea)] ) # Further operations using desired_property
In Haskell it's usually indented differently: c = sqrt(a*a + b*b) given: a = retrieve_a() b = retrieve_b() Eugene

On Tue, Apr 12, 2011 at 4:31 PM, Georg Brandl <g.brandl@gmx.net> wrote:
Is it just me, or do others find this "string of clauses" (no matter what the actual keywords are) not very readable?
Nope, it's not just you :) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Georg Brandl wrote:
Same here. If the "given:" blocks are only meant to hide away things in a separate namespace, I'm not sure why we need a new keyword. This is already possible using the "class" keyword and it even gives the namespace a name ;-) class myData: x = setup_x() y = setup_y() z = myFormula(myData.x, myData.y) That's a lot more readable, pythonic and intuitive than the proposed "given:" block syntax, which -to me at least- looks confusing. BTW, I don't find the comparison to the list comprehension syntax valid: List comprehensions only allow a very limited set of qualifiers which form the list definition. As such, it makes sense to have the definition behind the item expression (and it follows the rules used in math for similar definitions). The "given:" block, OTOH, makes no such restrictions and may well contain code that doesn't have anything to do with the statement it is supposed to configure. It has the potential of introducing top-posting to Python code: answer_to_question given: some_other_blurb original_question more_other_blurb And things get even more exciting if you start to nest these: answer_to_original_question given: comment_to_answer_of_other_question given: answer_to_some_other_question given: other_question new_angle_to_discussion other_blurb original_question more_other_blurb Following the flow of program execution and relevance of the different parts to the final result can be very confusing. Also note that it is rather unusual and unexpected for Python to execute the innermost block before the outer one as it would happen in the above example. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Apr 12 2011)
::: Try our new mxODBC.Connect Python Database Interface for free ! :::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/

M.-A. Lemburg wrote:
Only for certain values of "possible". It potentially confuses the reader by using a class in an unusual way. When one sees a class definition, one's first thought is that instances of it are going to be created at some point, but that's not the case here. -- Greg

On 4/12/2011 2:31 AM, Georg Brandl wrote:
On 12.04.2011 07:06, Nick Coghlan wrote:
ys = [y for x in xs given f(x) if p(x) else g(x) as y]
Is it just me,
No.
or do others find this "string of clauses" (no matter what the actual keywords are) not very readable?
I think people are trying to push an optional convenience feature way too far. I kind of wish comprehensions had been limited to one for clause (the source) and an optional if clause (the filter), as they pretty much are in math. Of course, I am one who switched to Python instead of lisp, perl, etc, *because* it read like pseudocode, with a nice mixture of expressions within statements, each in their own (logical) line. (I could have written this paragraph as a single sentence with a string of clauses, as I have seen in both older English and German, but I am not a fan of that either ;-). -- Terry Jan Reedy

Nick Coghlan wrote:
given/as resolves the parsing ambiguity problem by putting the names second:
And I don't like that. I would rather see an = sign. We're doing an assignment, after all, so we should make it look like one if we can. -- Greg

On Mon, Apr 11, 2011 at 11:53 PM, Carl M. Johnson <cmjohnson.mailinglist@gmail.com> wrote:
This ambiguity already exists -- you can write conditional expression in iteratee of for or in condition of if, but it won't parse, because the conflict is resolved in favour of list comprehension's 'if'. You have to put if/else in parenthesis. Eugene

Carl M. Johnson wrote:
Isn't that ambiguous? What if someone used the conditional expression: ys = [y for x in xs letting y = f(x) if y else g(x)] ?
No more ambiguous than 'if' already is in a comprehension, and that's resolved by requiring parens if one of the clauses in the comprehension contains an 'if' expression. -- Greg

Greg Ewing writes:
I don't have a problem with *reading* that, since you can't really win: "y" is used both before and after the "given" binding. I would expect you will need a restriction against using a "given"-bound variable in another "given" clause, so syntactically I don't think it matters where in the expression it appears.
I'm sorry, but I read that three times and it parsed as "y gets undefined if it is false" every time. This is way worse than "x if y else z", which awkward but I can't parse it any way other than "x (if y), else z". For some reason "given" binds the expression a lot more tightly than "letting" does in my dialect. I do prefer the "given" at the end, but putting it in the middle doesn't bother me. It's not that I couldn't get used to it, but I suspect I would never learn to like it. I hope others feel the same way.<wink>

Stephen J. Turnbull wrote:
I don't have a problem with *reading* that, since you can't really win: "y" is used both before and after the "given" binding.
But the "before" usage is just the usual comprehension quirk of putting the result expression before everything else, even though it's actually evaluated last. Once you allow for that, it's effectively after the given/letting/ whatever.
In that case, would you prefer this? ys = [y for x in xs if y given y = f(x)] -- Greg

Greg Ewing writes:
Turns out to be precisely why it's readable, I think. But the "but" is not the point. The point is that (in my dialect) it *is* readable.
Yes, very much. I would also prefer ys = [y for x in xs given y = f(x) if y] to the "letting" version, though that is harder to read (for me) than the version with the assignment at the end of the expression. I think the difference is that "let" is *local* to the comprehension and local context will bind to it. "given" is a meta-word, it refers to something *outside* the comprehension, in my dialect. So it is undisturbed by the local context. YMMV, that may be very specific to me. As I say, I could probably get used to "letting" if most people prefer it. But in my personal usage, "given" is clearly better.

On 4/12/11, Stephen J. Turnbull <stephen@xemacs.org> wrote:
Greg Ewing writes:
Stephen J. Turnbull wrote:
ys = [y for x in xs letting y = f(x) if y]
I'm sorry, but I read that three times and it parsed as "y gets undefined if it is false" every time.
Me too ... but then I try to figure out what to do with an undefined, so the eventual answer is either an error, or what you really wanted in the first place.
In that case, would you prefer this?
ys = [y for x in xs if y given y = f(x)]
I would ... but I don't actually *like* it, I just find it less problematic.
Yes, very much. I would also prefer
ys = [y for x in xs given y = f(x) if y]
to the "letting" version, though that is harder to read (for me) than the version with the assignment at the end of the expression.
I find this less objectionable still. It improves more if the "given" clause is on a separate line. I'm not sure there *is* a likable way to put the comprehension, the temporary assignment, and the filter into one unbroken thought. -jJ

On Sun, Apr 10, 2011 at 6:42 PM, Eugene Toder <eltoder@gmail.com> wrote:
Your continued insistence on hearing an explanation why it is missing is beginning to sound more and more like asking when Python stopped beating his wife. (This is not unusual. We get a lot of questions phrased as "why does Python not do X?" Do you think that is a good way to get a discussion started?) -- --Guido van Rossum (python.org/~guido)
participants (15)
-
Alexander Belopolsky
-
Bruce Leban
-
Carl M. Johnson
-
Eric Snow
-
Eugene Toder
-
Georg Brandl
-
Greg Ewing
-
Guido van Rossum
-
Jim Jewett
-
Laura Creighton
-
M.-A. Lemburg
-
Nick Coghlan
-
Paul Moore
-
Stephen J. Turnbull
-
Terry Reedy