Accessing the result of comprehension's expression from the conditional

In list/generator comprehension, currently we have no way to access the result of the expression and have to write something like this: [f(x) for x in l if f(x) > 0] if f() is heavy or non-pure (i.e. have side effects), calling f() twice might be undesirable. Even if f() is not a function such as [x + 1 for x in l if x + 1 > 0] it looks ugly since we're repeating ourself. We can work around it like this: [y for y in (f(x) for x in l) if y > 0] but then we have an unnecessary nested loop comprehension. I'm suggesting about something like: [f(x) for x in l if @ > 0] where @ is the result of the listcomp's expression (i.e. f(x)) Personally, I don't like the use of symbols like @, as python is not perl. I'm still thinking of a better syntax/approach and is open for suggestion. What do you guys think?

On Fri, Jun 19, 2009, Lie Ryan wrote:
Listcomps and genexps are like lambdas: run up against their limits and you should switch to a regular for loop or generator. -- Aahz (aahz@pythoncraft.com) <*> http://www.pythoncraft.com/ "as long as we like the same operating system, things are cool." --piranha

Aahz wrote:
I think of it not as limitation but as an odd gap in functionality. I think having the semantics that the filtering is done after the expression part would be much more useful than the current behavior (filtering before expression). If filtering is done after expression, we can access both the original and the transformed objects (it may also be possible to optimize the cases where the filter does not use the transformed objects, although this complex behavior wouldn't be pythonic) Try rewriting this: res = [x**x as F for x in nums if F < 100] (note: this is my new preferred syntax) Attempts: - Using a regular for-loop res = [] for x in nums: F = x**x if F < 100: res.append(F) remarks: five lines that's much more difficult to understand than a single, concise expression in standard form. - Using nested comprehension res = [F for F in (x**x for x in nums) if F < 100] remarks: using nested list comprehension is DRY (on a loose definition of DRY principle). I have to repeat the comprehension body twice, when I only need one expression and one filtering. - Using map() res = [F for F in map(lambda x: x**x, nums) if F < 100] remarks: when the expression part is just a simple expression, you have to use lambda and that's plain ugly. Not to mention when you want to filter based on both x and F. Advantages of the proposal: - shorter - faster, as looping is done in C - more readable. The main advantage of comprehension is that it have standardized form, which is easier to understand, unlike a for-loop which can have an infinite number of variations. - (unnecessary) nested comprehension is an abuse. - with `as` keyword, no new keyword and no ambiguity since currently `as` cannot exist inside comprehension. Disadvantages: - reverses the current semantic of filtering-then-expression. This shouldn't be too much problem since side-effect on the expression part is a cardinal sin and... - if the expression part is heavy, it might be possible to do optimization by filtering first when the filter part does not require the result (i.e. when there is no "as" clause). A good side effect of this optimization is codes that relies on filtering being done before expression will just work as they cannot contain an `as` keyword. (As "simple is better than complex", I actually don't really like `as` can change evaluation order; I much prefer to keep everything simple and consistent, i.e. always evaluate expression then filter or otherwise) possible syntaxes: - [x**x as F for x in nums if F < 100] the as keyword is already often used to rename things (in with, import, etc) I like this one much better than @. The as part, of course, is optional - [x**x for x in nums if @ < 100] the initial proposed syntax, ugly as hell.

On Fri, Jun 19, 2009 at 12:39 AM, Lie Ryan<lie.1296@gmail.com> wrote:
Depends on how complicated the "standard form" is; right now, it (list comps) is/are relatively simple. Perl syntax is concise and adheres to a standard, but is not easy to understand. Not to equate a minor syntax addition to Perl, but every addition is a step in that general direction. The road to hell was paved with good intentions. Cheers, Chris -- http://blog.rebertia.com

2009/6/19 Lie Ryan <lie.1296@gmail.com>:
OK, with this explanation (and the new syntax) I see what you're getting at better. However, changing the order of evaluate vs filter is a huge compatibility problem. There's no way this will be possible. Even with syntax triggering the change (so that it's one way with the "as", the other without), that's a disaster waiting to happen. You have at least 3 explicit ways of stating your intent (genexp inside listcomp, map inside listcomp, explicit loop). None is as clean-looking as your (amended) proposal, but they work now, and they don't have the semantic issues of your proposal. (For a more general, more radical, equally certain to be shot down, option, which at least doesn't introduce the change in semantics, you could try proposing "as" as an assignment-as-expression operator. So you could have [y for x in l if (f(x) as y) < 100] Hmm, on second thoughts - no, don't bother... :-)) Paul.

Paul Moore wrote:
How about this syntax which would solve your concern for the semantic change: [x**x as F for x in lst if F() < 100] it's similar to original `as` proposal, except that F is a callable instead of direct value. The advantage of F being callable is that it does not need semantic change, the filtering part will be done before expression just like it is right now. However, we can explicitly request for the expression to be evaluated by calling F(); the return value of F() will be saved and reused for the final result and other calls to F(). A diagrammatic explanation: +--------------------------- | this is the part that name | the expression's callable | --+- [x**x as F for x in lst if x and F() < 100 and isvalid(F())] -+-- ---+---------------------------- | | -+- -+- | the filtering part is | | | | evaluated before | | | | expression just like | | | | current behavior | | | | -----------------------+ | | | | | | then when F gets called; | | | expression is evaluated, | | | cached, and returned | | | ---------------------------+ | | | | F is called again, return cached result | | ----------------------------------------+ | | at the end of the day, if F is called, | return the cached result, else evaluate | the expression and use that +----------------------------------------- using the as-callable syntax, the semantic of this: [f(x) as F for x in lst if g(F())] would be similar to: result = [] for x in lst: # F() ensures f(x) will be only ever be called once def F(): nonlocal _cache if not _cache: _cache = f(x) return _cache _cache = None if g(F()): result.append(F()) the only disadvantage of this as-callable is if you forgot to call F.
It took me several minutes to understand that one... and no, that syntax as makes it way too easy to be too creative in list comprehension, devaluing the "standard form" which IMO is the strongest point of list comprehension. Not to mention that that syntax moved the expression part to be inside the filtering part... which is quite... disturbing...

On Sat, 20 Jun 2009 09:45:58 am Lie Ryan wrote:
Let's look at what that would be equivalent to. L = [] for x in lst: F = lambda x=x: x**x # Need to use default value in the # lambda otherwise all elements will have the same value. tmp = F() if tmp < 100: L.append(tmp) It's not clear why you think this is an improvement over: [x**x as F for x in lst if F < 100] # note the missing ()s which would be equivalent to: L = [] for x in lst: F = x**x if F < 100: L.append(F) Despite what you say here:
That's not true. It can't be true. If you want to filter on x**x being greater than 100, you need to calculate x**x first. In theory, a sufficiently clever compiler could recognise that, say, x**x < 100 implies 0 <= x < 3.59728 (approx), but in general, you can't predict the value of f(x) without actually calculating f(x). What happens if you accidentally forget to put brackets after the F expression? Do you get a syntax error? Undefined behaviour? A runtime exception? -- Steven D'Aprano

Steven D'Aprano wrote:
Did you read the middle part of the post and the diagram, which address the question you're asking and how it would be handled?
As F is just callable, F > 100 should result in comparison of number against callable. But as it is rare that you really actually wanted to do such thing, I think python can also be a little protective and issue a warning.

On Sun, 21 Jun 2009 04:08:05 am Lie Ryan wrote:
Yes. It made no sense to me at all. Let's make a practical example: c = 1246158315.0 # approximately a week from now L = filter(lambda t: t > c, [time.time() for x in range(20)]) becomes: L = [time.time() as F for x in range(20) if F() > c] How do you expect your proposed syntax to determine whether or not the current time is greater than c without actually checking the current time? Note also that your proposal requires list comps to become like lambda. Using your earlier example: [x**x as F for x in lst if F() < 100] This doesn't bind the value of x**x to the name F, but the expression x**x to F. That makes it like a lambda: lambda x: x**x except the name x is implied and some sort of magic takes place to ensure that by the time you call F(), the appropriate value of x is still around. If this is to apply to generator expressions as well, calling F() could occur some arbitrarily large time later. That means that [time.time() for x in lst] will create a list that looks something like: [1245555766.5681751, 1245555767.2609128, ...] but your proposed: [time.time() as F for x in lst] will create a list something like: [<function F at 0x88a879c>, <function F at 0x88a8d84>, ...] -- Steven D'Aprano

Steven D'Aprano wrote:
Of course it will call F(). F() will evaluate time.time() and cache it so future call to F() do not need to call time.time() twice (resulting in different time used for filter and expression).
F() could be called when the filtering takes place or not be called at all, when it does gets called, the expression would be evaluated. In effect this moves forward the expression evaluation to the middle of filtering process.
Where did you get that idea? The comprehension would call F before appending it to the list. The equivalent for-loop syntax also clearly stated that: result = [] for x in lst: # F() ensures f(x) will be only ever be called once def F(): nonlocal _cache if not _cache: _cache = f(x) return _cache _cache = None if g(F()): # here F is called before it's appended, # result is the same as calling f(x) # but prevents evaluating f(x) twice result.append(F())

On Fri, Jun 19, 2009 at 3:39 AM, Lie Ryan<lie.1296@gmail.com> wrote:
res = [x**x as F for x in nums if F < 100]
This, I have wanted. That doesn't resolve all the issues others raised, but (at least for me), you just moved it from "a little worse than the status quo" to "hmm... that would be nice if it could be done without too many side effect on the rest of the language." On the possibility that a more general problem sometimes spurs a more elegant solution, I'll point out that I have more often wanted access to the previous or following element, and that my desired filters on the results are often reliant on the results-so-far.
Disadvantages: - reverses the current semantic of filtering-then-expression.
For What Its Worth, I'm not sure how strong that argument should be. Is this just a bizarre corner case, or is there lots of code that relies on it? That ordering actually surprises me, because I expect python to evaluate left to right. On the other hand, now that we have conditional expressions, consistency with those is probably more important, so maybe the number of people surprised by the current situation will go down with time. -jJ

Jim Jewett <jimjjewett@gmail.com> writes:
You have it: res = [f for f in (x**x for x in nums) if f < 100] In addition to the fact that this works now in existing Python, I find it clearer than the above syntax you say you want. -- \ “It's my belief we developed language because of our deep inner | `\ need to complain.” —Jane Wagner, via Lily Tomlin | _o__) | Ben Finney

On Sat, Jun 20, 2009 at 9:22 AM, MRAB<python@mrabarnett.plus.com> wrote:
That toggles the first part of the comprehensions to be or not be an expression, depending on if there is a with clause later. You could miss this when you read it, and it opens the door to doing more strange things, like: res = [F/2 for x in nums with x**x as F if F < 100] This is basically a strangely syntaxed nested loop -- Read my blog! I depend on your acceptance of my opinion! I am interesting! http://techblog.ironfroggy.com/ Follow me if you're into that sort of thing: http://www.twitter.com/ironfroggy

Calvin Spealman wrote:
I don't know what you mean.
The test is done first, so it's equivalent to:
test 0 test 1 value 1 test 2 test 3 value 3 test 4 That means that: res = [x**x for x in nums if x**x < 100] is equivalent to: res = [] for x in nums : if x**x < 100: res.append(x**x) My suggestions turns: res = [] for x in nums : F = x**x # <== temporary variable if F < 100: res.append(F) into: res = [F for x in nums with x**x as F if F < 100] ^^^^^^^^^^^^^^ temporary variable

On Fri, Jun 19, 2009 at 2:39 AM, Lie Ryan<lie.1296@gmail.com> wrote:
res = [x**x as F for x in nums if F < 100]
In some languages, you can use "let" in a list comprehension: [f | x <- nums, let f = x**x, f < 100] which I guess might look something like this in python: [f for x in nums let f = x**x if f < 100] (JavaScript actually has a `let` keyword so I suspect we will eventually adopt something like that.) -j

On Fri, 19 Jun 2009 05:39:32 pm Lie Ryan wrote:
The point of the filtering is to avoid needlessly calculating a potentially expensive expression only to throw it away. If you want expression first, then filter, you can get that already in a one-liner: filter(lambda x: x > 0, [f(x) for x in seq]) Don't create new syntax when there are perfectly good functions that do the job already. -- Steven D'Aprano

On Sat, 20 Jun 2009 08:31:26 am Lie Ryan wrote:
You don't like lambda? Fine, define an external function first. Then you can write: filter(pred, (f(x) for x in seq)) There's no violation of DRY, there's no redundancy, there's no lambda, there's no "y" variable needed. What's ugly about it?
or nested comprehension: [y for y in (f(x) for x in seq) if y > 0]
You seem to be labouring under the misapprehension that anything that requires two steps instead of one is "ugly". I find the nested comprehension perfectly readable, although for more complicated cases I'd split it into two explicit steps. It is (almost) completely general, covering both filtering on *both* input args and output args: gen = (3*x**2-5*x+4 for x in seq if x % 3 != 2) result = [y for y in gen if -3 < y < 3] The only case it doesn't cover is where the second filter depends on the value of x, and even that can be covered with a *tiny* bit more work: gen = ((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2) result = [y[1] for y in gen if -y[0] < y[1] < y[0]] It requires no new syntax, no changes to the behaviour of list comps, no new meaning on "as", it's understandable and readable. Compare your suggestion: [3*x**2-5*x+4 as y for x in seq if (x % 3 != 2) and (-x < y < x)] Disadvantages: - It requires new syntax. - It requires new behaviour to list comps and generator expressions. - It creates yet another meaning to the keyword "as". Advantages: - It requires no intermediate tuple. But since intermediate tuples are only required for a tiny proportion of use-cases, this is not much of a advantage. - It loops over the data once rather than twice, but since it does twice as much work inside the loop the only saving is the setup and teardown costs of the second loop. Truly a micro-optimization, and premature at that. -- Steven D'Aprano

Steven D'Aprano wrote: <snip>
For me, this one is much clearer, understandable, and readable than y[0] and y[1]; and you still have the option to split them if you think y[0] and y[1] is better.
"as" is already used for renaming keyword in "with" and "import" statement, so I don't think it actually creates any more meaning that we don't already have.

Lie Ryan <lie.1296@gmail.com> writes:
I think it would be more readable without index references, but instead using tuple unpacking:: gen = ((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2) result = [b for (a, b) in gen if -a < b < a] It can even be done as a single expression without (IMO) significantly affecting readability:: result = [ b for (a, b) in ((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2) if -a < b < a]
I hope you'll agree that my above suggestion retains this, without needing any new syntax. -- \ “If you go to a costume party at your boss's house, wouldn't | `\ you think a good costume would be to dress up like the boss's | _o__) wife? Trust me, it's not.” —Jack Handey | Ben Finney

What about where/let expression? [(y, y) for x in some_list if y < 0 where y = f(x)] On Sun, Jun 21, 2009 at 10:36 AM, Lie Ryan<lie.1296@gmail.com> wrote:
-- С уважением, Андрей Попп. +7 911 740 24 91

(A) Please don't top-post. (http://en.wikipedia.org/wiki/Top-post) (B) That has the distinct disadvantage of adding a new keyword. I instead prefer the "as" version of the proposal for this reason. Cheers, Chris -- http://blog.rebertia.com

On Jun 21, 2009 1:06pm, Chris Rebert <pyideas@rebertia.com> wrote:
On Sun, Jun 21, 2009 at 10:36 AM, Lie Ryanlie.1296@gmail.com> wrote:
Ben Finney wrote:
Lie Ryan lie.1296@gmail.com> writes:
Steven D'Aprano wrote:
The only case it doesn't cover is where the second filter depends on
the value of x, and even that can be covered with a *tiny* bit more
work:
gen = ((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2)
result = [y[1] for y in gen if -y[0]
It requires no new syntax, no changes to the behaviour of list
comps, no new meaning on "as", it's understandable and readable.
I think it would be more readable without index references, but instead
using tuple unpacking::
gen = ((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2)
result = [b for (a, b) in gen if -a
It can even be done as a single expression without (IMO) significantly
affecting readability::
result = [
b for (a, b) in
((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2)
if -a
IMHO, when a comprehension requires more than a single line, it should
turn into explicit loop.
On Sun, Jun 21, 2009 at 1:54 AM, Andrey Popp8mayday@gmail.com> wrote:
What about where/let expression?
[(y, y) for x in some_list if y --
С уважением, Андрей Попп.
+7 911 740 24 91
(A) Please don't top-post. (http://en.wikipedia.org/wiki/Top-post)
(B) That has the distinct disadvantage of adding a new keyword. I
instead prefer the "as" version of the proposal for this reason.
Cheers,
Chris
--
(A) Sorry (B) There is no difference, except "where" is widely used in othe languages. Anyway, I think that "where" like functionality would be useful in list comprehensions, lambdas...

Lie Ryan schrieb:
Taking this further, using only map/filter style like this filter(pred, map(f, seq)) takes two steps. Why is it so bad that doing it in a listcomp (el for el in (f(y) for y in seq) if el > 2) takes two steps as well? Georg -- Thus spake the Lord: Thou shalt indent with four spaces. No more, no less. Four shall be the number of spaces thou shalt indent, and the number of thy indenting shall be four. Eight shalt thou not indent, nor either indent thou two, excepting that thou then proceed to four. Tabs are right out.

Georg Brandl wrote:
Comprehension, by its nature, is map+filter in a single expression. Nested comprehension is (map+filter)+(map+filter). (where + is some sort of function composition) The proposal enables comprehension to become filter+map+filter, map+filter, filter+map, or map-only; eliminating the redundant map in (map+filter)+(map+filter). The filter() and map() functions are two separate function in the first place, so there is no redundancy in it. Mixing the two styles is ugly since it means you have to think in two separate (though related) paradigms.

Lie Ryan writes:
The problem with the proposal is that that is *all* it does. I don't see how you plan to disambiguate in cases where the desired operation "really is" (map+filter)+(map+filter). So what you're stuck at is "I want this one operation to be a one-liner." The reply to that is "not every two-line function needs to have special syntax." Unless you can generalize this proposal to handle more general lambdas in a beautiful and Pythonic way, you've already been offered two obvious and nice ways to do it, and another obvious way that is an "ugly" mixed metaphor. All of the other idioms *do* generalize. We just spent several years de-cluttering Python 3, it's too soon to start cluttering it up again, no matter how beautiful it makes this special case. And I happen to think Ben's iterated generator comprehension is the most beautiful of the lot. YMMV of course but so does a that of a lot of other people; getting a consensus for new syntax is going to be impossible if you don't generalize the proposal.

Stephen J. Turnbull wrote:
The regular map+filter is still available by not using the `as` keyword (or by not calling F). Thus when what you really wanted is two maps, you need to use two comprehensions. The number of maps should == The number of comprehension; currently it is not always possible to do so, when the assumed map and filter ordering doesn't match our required ordering.

Lie Ryan writes:
I understand that; the first part is a trivial consequence of backward compatibility, and the second you've been at pains to explain already. I'm not trying to say you're *wrong*. The appeal of the proposed syntax in the particular case is obvious. Admittedly, I personally don't find it particularly useful. I don't have any problem at all with decomposing what you consider to be a single comprehension into a pipeline of generators. It's efficient and elegant, and it's not clear to me that your construct can generate better byte code than Ben's nested comprehensions. If not, your claim that "this is conceptually a single comprehension, why break it into two or more" seems to me to be founded on quicksand. But that's beside the point, I don't have to like all the constructs that other people find useful. Even Guido has allowed things into Python that he personally dislikes quite a bit. I'm saying it's my impression that it will be *insufficient*. It's like if somebody suggested introducing a unary operator "**" to denote "squared". That's just not useful enough, while a *binary* operator "**" for "power" is useful enough to have been added ages ago. In other words, that kind of logic hasn't been able to justify *proliferation* of syntax in the more than 5 years I've been following Python-Dev (and now Python-Ideas). It only works when the new syntax is sufficiently comprehensive to replace "all the old uglies" in some sense. Even if you can't clear the hurdle in one bound, you need to aim at something like "getting rid of *all* lambda/external function definitions in comprehensions".

Lie Ryan schrieb:
Let's stick with "+" for function composition. Then comprehension is, as you say, ``map + filter`` (map after filter). It is *not* ``filter + map`` (filter after map), which is what would be needed in the case discussed in this thread. In the functional waym you can of course combine map and filter in the order you like.
Nested comprehension is (map+filter)+(map+filter).
Yes, and in this case, we use it as ``(id+filter) + (map+id)``, which is the easiest way to build a ``filter + map`` with the ``map + filter`` building block.
Mixing the two styles is ugly since it means you have to think in two separate (though related) paradigms.
I mentally translate map and filter into comprehensions anyway (or maybe rather both translate into the same more abstract thing), so I don't see it as ugly. Georg

Lie Ryan schrieb:
Especially if f is already a handy callable of one argument, no need to use a lambda. Georg -- Thus spake the Lord: Thou shalt indent with four spaces. No more, no less. Four shall be the number of spaces thou shalt indent, and the number of thy indenting shall be four. Eight shalt thou not indent, nor either indent thou two, excepting that thou then proceed to four. Tabs are right out.

Lie Ryan wrote:
IMO this is premature optimization. (I know that's an annoying thing to say, but bear with me...) Just using [f(x) for x in nums if f(x) > 0] is the most readable and obvious option. I think the case where such a line of code actually needs to be optimized is rare, and on there rare occasions using a slightly less readable variant is reasonable (along with an insightful comment). In other words, from my experience all we would gain from the proposed new syntax is making such premature optimization easier, at the cost of less readable code and more complex syntax. - Tal

On Fri, Jun 19, 2009 at 7:34 AM, Tal Einat<taleinat@gmail.com> wrote:
Just using [f(x) for x in nums if f(x) > 0] is the most readable and obvious option.
I would often prefer to break it into two steps: temp = (f(x) for x in nums) results = [e for e in temp if e>0] Others will dislike the extra line and temp var, which is one reason it isn't among the several solutions previously suggested. Are these differences big enough (or the solutions obscure enough) that the variation is itself a cost of the current situation? -jJ

On Fri, Jun 19, 2009 at 4:45 PM, Jim Jewett<jimjjewett@gmail.com> wrote:
I sometimes find myself doing that too. That's probably more readable than what I wrote :)
Others will dislike the extra line and temp var, which is one reason it isn't among the several solutions previously suggested.
I sometimes avoid the temporary variable by using the same one twice, e.g.: results = (f(x) for x in nums) results = [res for res in results if res > 0] In the above example the second line is just filtering the results, and I feel this idiom conveys the idea pretty well. - Tal

Le Fri, 19 Jun 2009 09:45:08 -0400, Jim Jewett <jimjjewett@gmail.com> s'exprima ainsi:
Ditto for me. A list comp with both computation and filtering is for me two ideas, two steps, so I write two lines anyway. This also has the advantage to disambiguate the order issue (computation or filter first?) and there is no risk of double side-effect (which anyway I never use, but who knows...). Denis ------ la vita e estrany

You could write: [x|y <- l, x <- [f(y)], x > 0] Oh, wait. Thats Haskell. And even in haskell you would write: [x|x <- map f l, x > 0] In Python you can write: [x for x in map(f,l) if x > 0] In Python 2.x you may want to write: from itertools import imap [x for x in imap(f,l) if x > 0] A more SQL like approach that would fit somewhat with pythons syntax would be (as you can see its exactly the same lengths as the above but needs a new name): [f(x) as y for x in l if y > 0] Because in SQL you can write (IIRC): select f(x) as y from l where y > 0; Maybe something like .Nets LINQ would be a nice idea to integrate in python? -panzi Lie Ryan wrote:

On Fri, Jun 19, 2009 at 10:54 AM, Mathias Panzenböck<grosser.meister.morti@gmx.net> wrote:
Comprehensions and generator expressions already give us most of the LINQ functionality. Add in `list()` and the ability to `.sort()` lists with a `key` argument and you have the entire thing, except for the one corner case being discussed. Unless I've overlooked something... Cheers, Chris -- http://blog.rebertia.com

Chris Rebert wrote:
Yes: With LINQ its possible to build a query object out of an LINQ expression instead of evaluating it eagerly. This is used primarily to generate SQL code while still using syntax native to the host language (C#) and preserving type safety (ok the later cannot be done in python). -panzi

On Fri, Jun 19, 2009 at 12:56 PM, Mathias Panzenböck<grosser.meister.morti@gmx.net> wrote:
One could probably hack that part together with lambdas, the ast module, and some black magic though. And are there any use cases besides SQL? But point taken. Cheers, Chris -- http://blog.rebertia.com

Chris Rebert wrote:
Yes: Queries on XML data. So you have the exact same Syntax for queries on simple lists, SQL databases and XML files (but yes, using LINGQ for XML is still much more to write than using something like XPath). I think you can also add your own backends if you like (e.g. for yaml?). And it's all native syntax (no limits on expressiveness and no problems concerning string escaping etc.). -panzi

On Fri, Jun 19, 2009, Lie Ryan wrote:
Listcomps and genexps are like lambdas: run up against their limits and you should switch to a regular for loop or generator. -- Aahz (aahz@pythoncraft.com) <*> http://www.pythoncraft.com/ "as long as we like the same operating system, things are cool." --piranha

Aahz wrote:
I think of it not as limitation but as an odd gap in functionality. I think having the semantics that the filtering is done after the expression part would be much more useful than the current behavior (filtering before expression). If filtering is done after expression, we can access both the original and the transformed objects (it may also be possible to optimize the cases where the filter does not use the transformed objects, although this complex behavior wouldn't be pythonic) Try rewriting this: res = [x**x as F for x in nums if F < 100] (note: this is my new preferred syntax) Attempts: - Using a regular for-loop res = [] for x in nums: F = x**x if F < 100: res.append(F) remarks: five lines that's much more difficult to understand than a single, concise expression in standard form. - Using nested comprehension res = [F for F in (x**x for x in nums) if F < 100] remarks: using nested list comprehension is DRY (on a loose definition of DRY principle). I have to repeat the comprehension body twice, when I only need one expression and one filtering. - Using map() res = [F for F in map(lambda x: x**x, nums) if F < 100] remarks: when the expression part is just a simple expression, you have to use lambda and that's plain ugly. Not to mention when you want to filter based on both x and F. Advantages of the proposal: - shorter - faster, as looping is done in C - more readable. The main advantage of comprehension is that it have standardized form, which is easier to understand, unlike a for-loop which can have an infinite number of variations. - (unnecessary) nested comprehension is an abuse. - with `as` keyword, no new keyword and no ambiguity since currently `as` cannot exist inside comprehension. Disadvantages: - reverses the current semantic of filtering-then-expression. This shouldn't be too much problem since side-effect on the expression part is a cardinal sin and... - if the expression part is heavy, it might be possible to do optimization by filtering first when the filter part does not require the result (i.e. when there is no "as" clause). A good side effect of this optimization is codes that relies on filtering being done before expression will just work as they cannot contain an `as` keyword. (As "simple is better than complex", I actually don't really like `as` can change evaluation order; I much prefer to keep everything simple and consistent, i.e. always evaluate expression then filter or otherwise) possible syntaxes: - [x**x as F for x in nums if F < 100] the as keyword is already often used to rename things (in with, import, etc) I like this one much better than @. The as part, of course, is optional - [x**x for x in nums if @ < 100] the initial proposed syntax, ugly as hell.

On Fri, Jun 19, 2009 at 12:39 AM, Lie Ryan<lie.1296@gmail.com> wrote:
Depends on how complicated the "standard form" is; right now, it (list comps) is/are relatively simple. Perl syntax is concise and adheres to a standard, but is not easy to understand. Not to equate a minor syntax addition to Perl, but every addition is a step in that general direction. The road to hell was paved with good intentions. Cheers, Chris -- http://blog.rebertia.com

2009/6/19 Lie Ryan <lie.1296@gmail.com>:
OK, with this explanation (and the new syntax) I see what you're getting at better. However, changing the order of evaluate vs filter is a huge compatibility problem. There's no way this will be possible. Even with syntax triggering the change (so that it's one way with the "as", the other without), that's a disaster waiting to happen. You have at least 3 explicit ways of stating your intent (genexp inside listcomp, map inside listcomp, explicit loop). None is as clean-looking as your (amended) proposal, but they work now, and they don't have the semantic issues of your proposal. (For a more general, more radical, equally certain to be shot down, option, which at least doesn't introduce the change in semantics, you could try proposing "as" as an assignment-as-expression operator. So you could have [y for x in l if (f(x) as y) < 100] Hmm, on second thoughts - no, don't bother... :-)) Paul.

Paul Moore wrote:
How about this syntax which would solve your concern for the semantic change: [x**x as F for x in lst if F() < 100] it's similar to original `as` proposal, except that F is a callable instead of direct value. The advantage of F being callable is that it does not need semantic change, the filtering part will be done before expression just like it is right now. However, we can explicitly request for the expression to be evaluated by calling F(); the return value of F() will be saved and reused for the final result and other calls to F(). A diagrammatic explanation: +--------------------------- | this is the part that name | the expression's callable | --+- [x**x as F for x in lst if x and F() < 100 and isvalid(F())] -+-- ---+---------------------------- | | -+- -+- | the filtering part is | | | | evaluated before | | | | expression just like | | | | current behavior | | | | -----------------------+ | | | | | | then when F gets called; | | | expression is evaluated, | | | cached, and returned | | | ---------------------------+ | | | | F is called again, return cached result | | ----------------------------------------+ | | at the end of the day, if F is called, | return the cached result, else evaluate | the expression and use that +----------------------------------------- using the as-callable syntax, the semantic of this: [f(x) as F for x in lst if g(F())] would be similar to: result = [] for x in lst: # F() ensures f(x) will be only ever be called once def F(): nonlocal _cache if not _cache: _cache = f(x) return _cache _cache = None if g(F()): result.append(F()) the only disadvantage of this as-callable is if you forgot to call F.
It took me several minutes to understand that one... and no, that syntax as makes it way too easy to be too creative in list comprehension, devaluing the "standard form" which IMO is the strongest point of list comprehension. Not to mention that that syntax moved the expression part to be inside the filtering part... which is quite... disturbing...

On Sat, 20 Jun 2009 09:45:58 am Lie Ryan wrote:
Let's look at what that would be equivalent to. L = [] for x in lst: F = lambda x=x: x**x # Need to use default value in the # lambda otherwise all elements will have the same value. tmp = F() if tmp < 100: L.append(tmp) It's not clear why you think this is an improvement over: [x**x as F for x in lst if F < 100] # note the missing ()s which would be equivalent to: L = [] for x in lst: F = x**x if F < 100: L.append(F) Despite what you say here:
That's not true. It can't be true. If you want to filter on x**x being greater than 100, you need to calculate x**x first. In theory, a sufficiently clever compiler could recognise that, say, x**x < 100 implies 0 <= x < 3.59728 (approx), but in general, you can't predict the value of f(x) without actually calculating f(x). What happens if you accidentally forget to put brackets after the F expression? Do you get a syntax error? Undefined behaviour? A runtime exception? -- Steven D'Aprano

Steven D'Aprano wrote:
Did you read the middle part of the post and the diagram, which address the question you're asking and how it would be handled?
As F is just callable, F > 100 should result in comparison of number against callable. But as it is rare that you really actually wanted to do such thing, I think python can also be a little protective and issue a warning.

On Sun, 21 Jun 2009 04:08:05 am Lie Ryan wrote:
Yes. It made no sense to me at all. Let's make a practical example: c = 1246158315.0 # approximately a week from now L = filter(lambda t: t > c, [time.time() for x in range(20)]) becomes: L = [time.time() as F for x in range(20) if F() > c] How do you expect your proposed syntax to determine whether or not the current time is greater than c without actually checking the current time? Note also that your proposal requires list comps to become like lambda. Using your earlier example: [x**x as F for x in lst if F() < 100] This doesn't bind the value of x**x to the name F, but the expression x**x to F. That makes it like a lambda: lambda x: x**x except the name x is implied and some sort of magic takes place to ensure that by the time you call F(), the appropriate value of x is still around. If this is to apply to generator expressions as well, calling F() could occur some arbitrarily large time later. That means that [time.time() for x in lst] will create a list that looks something like: [1245555766.5681751, 1245555767.2609128, ...] but your proposed: [time.time() as F for x in lst] will create a list something like: [<function F at 0x88a879c>, <function F at 0x88a8d84>, ...] -- Steven D'Aprano

Steven D'Aprano wrote:
Of course it will call F(). F() will evaluate time.time() and cache it so future call to F() do not need to call time.time() twice (resulting in different time used for filter and expression).
F() could be called when the filtering takes place or not be called at all, when it does gets called, the expression would be evaluated. In effect this moves forward the expression evaluation to the middle of filtering process.
Where did you get that idea? The comprehension would call F before appending it to the list. The equivalent for-loop syntax also clearly stated that: result = [] for x in lst: # F() ensures f(x) will be only ever be called once def F(): nonlocal _cache if not _cache: _cache = f(x) return _cache _cache = None if g(F()): # here F is called before it's appended, # result is the same as calling f(x) # but prevents evaluating f(x) twice result.append(F())

On Fri, Jun 19, 2009 at 3:39 AM, Lie Ryan<lie.1296@gmail.com> wrote:
res = [x**x as F for x in nums if F < 100]
This, I have wanted. That doesn't resolve all the issues others raised, but (at least for me), you just moved it from "a little worse than the status quo" to "hmm... that would be nice if it could be done without too many side effect on the rest of the language." On the possibility that a more general problem sometimes spurs a more elegant solution, I'll point out that I have more often wanted access to the previous or following element, and that my desired filters on the results are often reliant on the results-so-far.
Disadvantages: - reverses the current semantic of filtering-then-expression.
For What Its Worth, I'm not sure how strong that argument should be. Is this just a bizarre corner case, or is there lots of code that relies on it? That ordering actually surprises me, because I expect python to evaluate left to right. On the other hand, now that we have conditional expressions, consistency with those is probably more important, so maybe the number of people surprised by the current situation will go down with time. -jJ

Jim Jewett <jimjjewett@gmail.com> writes:
You have it: res = [f for f in (x**x for x in nums) if f < 100] In addition to the fact that this works now in existing Python, I find it clearer than the above syntax you say you want. -- \ “It's my belief we developed language because of our deep inner | `\ need to complain.” —Jane Wagner, via Lily Tomlin | _o__) | Ben Finney

On Sat, Jun 20, 2009 at 9:22 AM, MRAB<python@mrabarnett.plus.com> wrote:
That toggles the first part of the comprehensions to be or not be an expression, depending on if there is a with clause later. You could miss this when you read it, and it opens the door to doing more strange things, like: res = [F/2 for x in nums with x**x as F if F < 100] This is basically a strangely syntaxed nested loop -- Read my blog! I depend on your acceptance of my opinion! I am interesting! http://techblog.ironfroggy.com/ Follow me if you're into that sort of thing: http://www.twitter.com/ironfroggy

Calvin Spealman wrote:
I don't know what you mean.
The test is done first, so it's equivalent to:
test 0 test 1 value 1 test 2 test 3 value 3 test 4 That means that: res = [x**x for x in nums if x**x < 100] is equivalent to: res = [] for x in nums : if x**x < 100: res.append(x**x) My suggestions turns: res = [] for x in nums : F = x**x # <== temporary variable if F < 100: res.append(F) into: res = [F for x in nums with x**x as F if F < 100] ^^^^^^^^^^^^^^ temporary variable

On Fri, Jun 19, 2009 at 2:39 AM, Lie Ryan<lie.1296@gmail.com> wrote:
res = [x**x as F for x in nums if F < 100]
In some languages, you can use "let" in a list comprehension: [f | x <- nums, let f = x**x, f < 100] which I guess might look something like this in python: [f for x in nums let f = x**x if f < 100] (JavaScript actually has a `let` keyword so I suspect we will eventually adopt something like that.) -j

On Fri, 19 Jun 2009 05:39:32 pm Lie Ryan wrote:
The point of the filtering is to avoid needlessly calculating a potentially expensive expression only to throw it away. If you want expression first, then filter, you can get that already in a one-liner: filter(lambda x: x > 0, [f(x) for x in seq]) Don't create new syntax when there are perfectly good functions that do the job already. -- Steven D'Aprano

On Sat, 20 Jun 2009 08:31:26 am Lie Ryan wrote:
You don't like lambda? Fine, define an external function first. Then you can write: filter(pred, (f(x) for x in seq)) There's no violation of DRY, there's no redundancy, there's no lambda, there's no "y" variable needed. What's ugly about it?
or nested comprehension: [y for y in (f(x) for x in seq) if y > 0]
You seem to be labouring under the misapprehension that anything that requires two steps instead of one is "ugly". I find the nested comprehension perfectly readable, although for more complicated cases I'd split it into two explicit steps. It is (almost) completely general, covering both filtering on *both* input args and output args: gen = (3*x**2-5*x+4 for x in seq if x % 3 != 2) result = [y for y in gen if -3 < y < 3] The only case it doesn't cover is where the second filter depends on the value of x, and even that can be covered with a *tiny* bit more work: gen = ((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2) result = [y[1] for y in gen if -y[0] < y[1] < y[0]] It requires no new syntax, no changes to the behaviour of list comps, no new meaning on "as", it's understandable and readable. Compare your suggestion: [3*x**2-5*x+4 as y for x in seq if (x % 3 != 2) and (-x < y < x)] Disadvantages: - It requires new syntax. - It requires new behaviour to list comps and generator expressions. - It creates yet another meaning to the keyword "as". Advantages: - It requires no intermediate tuple. But since intermediate tuples are only required for a tiny proportion of use-cases, this is not much of a advantage. - It loops over the data once rather than twice, but since it does twice as much work inside the loop the only saving is the setup and teardown costs of the second loop. Truly a micro-optimization, and premature at that. -- Steven D'Aprano

Steven D'Aprano wrote: <snip>
For me, this one is much clearer, understandable, and readable than y[0] and y[1]; and you still have the option to split them if you think y[0] and y[1] is better.
"as" is already used for renaming keyword in "with" and "import" statement, so I don't think it actually creates any more meaning that we don't already have.

Lie Ryan <lie.1296@gmail.com> writes:
I think it would be more readable without index references, but instead using tuple unpacking:: gen = ((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2) result = [b for (a, b) in gen if -a < b < a] It can even be done as a single expression without (IMO) significantly affecting readability:: result = [ b for (a, b) in ((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2) if -a < b < a]
I hope you'll agree that my above suggestion retains this, without needing any new syntax. -- \ “If you go to a costume party at your boss's house, wouldn't | `\ you think a good costume would be to dress up like the boss's | _o__) wife? Trust me, it's not.” —Jack Handey | Ben Finney

What about where/let expression? [(y, y) for x in some_list if y < 0 where y = f(x)] On Sun, Jun 21, 2009 at 10:36 AM, Lie Ryan<lie.1296@gmail.com> wrote:
-- С уважением, Андрей Попп. +7 911 740 24 91

(A) Please don't top-post. (http://en.wikipedia.org/wiki/Top-post) (B) That has the distinct disadvantage of adding a new keyword. I instead prefer the "as" version of the proposal for this reason. Cheers, Chris -- http://blog.rebertia.com

On Jun 21, 2009 1:06pm, Chris Rebert <pyideas@rebertia.com> wrote:
On Sun, Jun 21, 2009 at 10:36 AM, Lie Ryanlie.1296@gmail.com> wrote:
Ben Finney wrote:
Lie Ryan lie.1296@gmail.com> writes:
Steven D'Aprano wrote:
The only case it doesn't cover is where the second filter depends on
the value of x, and even that can be covered with a *tiny* bit more
work:
gen = ((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2)
result = [y[1] for y in gen if -y[0]
It requires no new syntax, no changes to the behaviour of list
comps, no new meaning on "as", it's understandable and readable.
I think it would be more readable without index references, but instead
using tuple unpacking::
gen = ((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2)
result = [b for (a, b) in gen if -a
It can even be done as a single expression without (IMO) significantly
affecting readability::
result = [
b for (a, b) in
((x, 3*x**2-5*x+4) for x in seq if x % 3 != 2)
if -a
IMHO, when a comprehension requires more than a single line, it should
turn into explicit loop.
On Sun, Jun 21, 2009 at 1:54 AM, Andrey Popp8mayday@gmail.com> wrote:
What about where/let expression?
[(y, y) for x in some_list if y --
С уважением, Андрей Попп.
+7 911 740 24 91
(A) Please don't top-post. (http://en.wikipedia.org/wiki/Top-post)
(B) That has the distinct disadvantage of adding a new keyword. I
instead prefer the "as" version of the proposal for this reason.
Cheers,
Chris
--
(A) Sorry (B) There is no difference, except "where" is widely used in othe languages. Anyway, I think that "where" like functionality would be useful in list comprehensions, lambdas...

Lie Ryan schrieb:
Taking this further, using only map/filter style like this filter(pred, map(f, seq)) takes two steps. Why is it so bad that doing it in a listcomp (el for el in (f(y) for y in seq) if el > 2) takes two steps as well? Georg -- Thus spake the Lord: Thou shalt indent with four spaces. No more, no less. Four shall be the number of spaces thou shalt indent, and the number of thy indenting shall be four. Eight shalt thou not indent, nor either indent thou two, excepting that thou then proceed to four. Tabs are right out.

Georg Brandl wrote:
Comprehension, by its nature, is map+filter in a single expression. Nested comprehension is (map+filter)+(map+filter). (where + is some sort of function composition) The proposal enables comprehension to become filter+map+filter, map+filter, filter+map, or map-only; eliminating the redundant map in (map+filter)+(map+filter). The filter() and map() functions are two separate function in the first place, so there is no redundancy in it. Mixing the two styles is ugly since it means you have to think in two separate (though related) paradigms.

Lie Ryan writes:
The problem with the proposal is that that is *all* it does. I don't see how you plan to disambiguate in cases where the desired operation "really is" (map+filter)+(map+filter). So what you're stuck at is "I want this one operation to be a one-liner." The reply to that is "not every two-line function needs to have special syntax." Unless you can generalize this proposal to handle more general lambdas in a beautiful and Pythonic way, you've already been offered two obvious and nice ways to do it, and another obvious way that is an "ugly" mixed metaphor. All of the other idioms *do* generalize. We just spent several years de-cluttering Python 3, it's too soon to start cluttering it up again, no matter how beautiful it makes this special case. And I happen to think Ben's iterated generator comprehension is the most beautiful of the lot. YMMV of course but so does a that of a lot of other people; getting a consensus for new syntax is going to be impossible if you don't generalize the proposal.

Stephen J. Turnbull wrote:
The regular map+filter is still available by not using the `as` keyword (or by not calling F). Thus when what you really wanted is two maps, you need to use two comprehensions. The number of maps should == The number of comprehension; currently it is not always possible to do so, when the assumed map and filter ordering doesn't match our required ordering.

Lie Ryan writes:
I understand that; the first part is a trivial consequence of backward compatibility, and the second you've been at pains to explain already. I'm not trying to say you're *wrong*. The appeal of the proposed syntax in the particular case is obvious. Admittedly, I personally don't find it particularly useful. I don't have any problem at all with decomposing what you consider to be a single comprehension into a pipeline of generators. It's efficient and elegant, and it's not clear to me that your construct can generate better byte code than Ben's nested comprehensions. If not, your claim that "this is conceptually a single comprehension, why break it into two or more" seems to me to be founded on quicksand. But that's beside the point, I don't have to like all the constructs that other people find useful. Even Guido has allowed things into Python that he personally dislikes quite a bit. I'm saying it's my impression that it will be *insufficient*. It's like if somebody suggested introducing a unary operator "**" to denote "squared". That's just not useful enough, while a *binary* operator "**" for "power" is useful enough to have been added ages ago. In other words, that kind of logic hasn't been able to justify *proliferation* of syntax in the more than 5 years I've been following Python-Dev (and now Python-Ideas). It only works when the new syntax is sufficiently comprehensive to replace "all the old uglies" in some sense. Even if you can't clear the hurdle in one bound, you need to aim at something like "getting rid of *all* lambda/external function definitions in comprehensions".

Lie Ryan schrieb:
Let's stick with "+" for function composition. Then comprehension is, as you say, ``map + filter`` (map after filter). It is *not* ``filter + map`` (filter after map), which is what would be needed in the case discussed in this thread. In the functional waym you can of course combine map and filter in the order you like.
Nested comprehension is (map+filter)+(map+filter).
Yes, and in this case, we use it as ``(id+filter) + (map+id)``, which is the easiest way to build a ``filter + map`` with the ``map + filter`` building block.
Mixing the two styles is ugly since it means you have to think in two separate (though related) paradigms.
I mentally translate map and filter into comprehensions anyway (or maybe rather both translate into the same more abstract thing), so I don't see it as ugly. Georg

Lie Ryan schrieb:
Especially if f is already a handy callable of one argument, no need to use a lambda. Georg -- Thus spake the Lord: Thou shalt indent with four spaces. No more, no less. Four shall be the number of spaces thou shalt indent, and the number of thy indenting shall be four. Eight shalt thou not indent, nor either indent thou two, excepting that thou then proceed to four. Tabs are right out.

Lie Ryan wrote:
IMO this is premature optimization. (I know that's an annoying thing to say, but bear with me...) Just using [f(x) for x in nums if f(x) > 0] is the most readable and obvious option. I think the case where such a line of code actually needs to be optimized is rare, and on there rare occasions using a slightly less readable variant is reasonable (along with an insightful comment). In other words, from my experience all we would gain from the proposed new syntax is making such premature optimization easier, at the cost of less readable code and more complex syntax. - Tal

On Fri, Jun 19, 2009 at 7:34 AM, Tal Einat<taleinat@gmail.com> wrote:
Just using [f(x) for x in nums if f(x) > 0] is the most readable and obvious option.
I would often prefer to break it into two steps: temp = (f(x) for x in nums) results = [e for e in temp if e>0] Others will dislike the extra line and temp var, which is one reason it isn't among the several solutions previously suggested. Are these differences big enough (or the solutions obscure enough) that the variation is itself a cost of the current situation? -jJ

On Fri, Jun 19, 2009 at 4:45 PM, Jim Jewett<jimjjewett@gmail.com> wrote:
I sometimes find myself doing that too. That's probably more readable than what I wrote :)
Others will dislike the extra line and temp var, which is one reason it isn't among the several solutions previously suggested.
I sometimes avoid the temporary variable by using the same one twice, e.g.: results = (f(x) for x in nums) results = [res for res in results if res > 0] In the above example the second line is just filtering the results, and I feel this idiom conveys the idea pretty well. - Tal

Le Fri, 19 Jun 2009 09:45:08 -0400, Jim Jewett <jimjjewett@gmail.com> s'exprima ainsi:
Ditto for me. A list comp with both computation and filtering is for me two ideas, two steps, so I write two lines anyway. This also has the advantage to disambiguate the order issue (computation or filter first?) and there is no risk of double side-effect (which anyway I never use, but who knows...). Denis ------ la vita e estrany

You could write: [x|y <- l, x <- [f(y)], x > 0] Oh, wait. Thats Haskell. And even in haskell you would write: [x|x <- map f l, x > 0] In Python you can write: [x for x in map(f,l) if x > 0] In Python 2.x you may want to write: from itertools import imap [x for x in imap(f,l) if x > 0] A more SQL like approach that would fit somewhat with pythons syntax would be (as you can see its exactly the same lengths as the above but needs a new name): [f(x) as y for x in l if y > 0] Because in SQL you can write (IIRC): select f(x) as y from l where y > 0; Maybe something like .Nets LINQ would be a nice idea to integrate in python? -panzi Lie Ryan wrote:

On Fri, Jun 19, 2009 at 10:54 AM, Mathias Panzenböck<grosser.meister.morti@gmx.net> wrote:
Comprehensions and generator expressions already give us most of the LINQ functionality. Add in `list()` and the ability to `.sort()` lists with a `key` argument and you have the entire thing, except for the one corner case being discussed. Unless I've overlooked something... Cheers, Chris -- http://blog.rebertia.com

Chris Rebert wrote:
Yes: With LINQ its possible to build a query object out of an LINQ expression instead of evaluating it eagerly. This is used primarily to generate SQL code while still using syntax native to the host language (C#) and preserving type safety (ok the later cannot be done in python). -panzi

On Fri, Jun 19, 2009 at 12:56 PM, Mathias Panzenböck<grosser.meister.morti@gmx.net> wrote:
One could probably hack that part together with lambdas, the ast module, and some black magic though. And are there any use cases besides SQL? But point taken. Cheers, Chris -- http://blog.rebertia.com

Chris Rebert wrote:
Yes: Queries on XML data. So you have the exact same Syntax for queries on simple lists, SQL databases and XML files (but yes, using LINGQ for XML is still much more to write than using something like XPath). I think you can also add your own backends if you like (e.g. for yaml?). And it's all native syntax (no limits on expressiveness and no problems concerning string escaping etc.). -panzi
participants (20)
-
8mayday@gmail.com
-
Aahz
-
Andrey Popp
-
Arnaud Delobelle
-
Ben Finney
-
Calvin Spealman
-
Chris Rebert
-
Georg Brandl
-
Jason Orendorff
-
Jim Jewett
-
Lie Ryan
-
Mathias Panzenböck
-
MRAB
-
Paul Moore
-
Raymond Hettinger
-
spir
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Tal Einat
-
Terry Reedy