Some syntactic sugar proposals

J

John Ladasky

Only in Python 3.x, it's perfectly valid in Python 2.x. To achieve the
same in Python 3.x, try:

    if x in list(range(a, b,)): # BUT SEE MY COMMENT BELOW


This seems more like a pessimisation to me: your range version
constructs a list just to do a single container check. That's a _lot_
more cumbersome than two simple comparisons chained together.

Also: testing for the membership of x in a set is NOT the same thing
as testing using inequality operators. The inequality operators will
return True for any FLOATING-POINT value within the range (a...b), but
the set test will only return True for set members.
 
C

Christopher

? Of course we can write it as
    t = foo() if pred(foo()) else default_value
but here we have 2 foo() calls instead of one. Why can't we write just
something like this:
    t = foo() if pred(it) else default_value
where "it" means "foo() value"?

i don't like magic names. what about:

t = foo() as v if pred(v) else default_value
 
M

Mark Wooding

Christopher said:
i don't like magic names. what about:

t = foo() as v if pred(v) else default_value

This is an improvement on `it'; anaphorics are useful in their place,
but they don't seem to fit well with Python.

But I don't think that's the big problem with this proposal. The real
problem is that it completely changes the evaluation rule for the
conditional expression. (The evaluation rule is already pretty screwy:
Python is consistently left-to-right -- except here.)

Evaluating a conditional expression starts in the middle, by evaluating
the condition. If the condition is true, then it evaluates the
consequent (to the left); otherwise it evaluates the alternative (to the
right). Screwy, but tolerable.

The proposal is to evaluate the /consequent/, stash it somewhere,
evaluate the condition, and then either output the consequent which we
evaluated earlier or the alternative which we must evaluate now.

Of course, the implementation must be able to tell which of these
evaluation rules to apply. The marker to look for is either `it'
(original anaphoric proposal -- this is the real reason why `it' should
be a reserved word: its presence radically alters the evaluation rule,
so it ought to be a clear syntactic marker) or `as' (as suggested
above).

Elsewhere in the language, `as' is pretty consistent in what it does:
it provides a name for a thing described elsewhere (a `with' context
object, or an imported thing) -- but nothing else. It certainly doesn't
suggest a change in the way anything else is evaluated.

1/x if x != 0 else None

works for any numeric x; it'd be really surprising to me if

1/x as hunoz if x != 0 else None

didn't.

-1 on this one.

-- [mdw]
 
S

Steven D'Aprano

But I don't think that's the big problem with this proposal. The real
problem is that it completely changes the evaluation rule for the
conditional expression. (The evaluation rule is already pretty screwy:
Python is consistently left-to-right -- except here.)

Not quite...
9

But other than that, I agree with your analysis for why Python should not
be changed to allow:

t = foo() as v if pred(v) else default_value


Not everything needs to be a one liner. If you need this, do it the old-
fashioned way:

t = foo()
if not pred(t): t = default_value
 
M

Mark Wooding

Steven D'Aprano said:
Not quite...

9

You're wrong. Python evaluates these left-to-right, as I said.
Parentheses override operator associativity; they don't affect
evaluation order at all.

Consider:

def say(x):
print 'seen %s' % x
return x

print say(1) + say(2) * say(3)
print (say(1) + say(2)) * say(3)

Run this program and you get

seen 1
seen 2
seen 3
7
seen 1
seen 2
seen 3
9

So definitely left-to-right. Translating into reverse-Polish, say with
Dijkstra's shunting-yard algorithm, is enlightening: you get

1 2 3 * +

for the first and

1 2 + 3 *

for the second. This preserves evaluation order; indeed, this is a
general property of the shunting-yard algorithm.

Finally, I quote from the language reference (5.13 of the 2.5 version),
just to show that (this time, at least) I'm not trying to impose
unfamiliar terminology, and that Python is defined to behave like this
and I'm not relying on implementation-specific details. Alas, it also
highlights a genuine inconsistency, but one which might be considered
tolerable.

: 5.13 Evaluation order
: =====================
:
: Python evaluates expressions from left to right. Notice that while
: evaluating an assignment, the right-hand side is evaluated before the
: left-hand side.
:
: In the following lines, expressions will be evaluated in the
: arithmetic order of their suffixes:
:
: expr1, expr2, expr3, expr4
: (expr1, expr2, expr3, expr4)
: {expr1: expr2, expr3: expr4}
: expr1 + expr2 * (expr3 - expr4)
: func(expr1, expr2, *expr3, **expr4)
: expr3, expr4 = expr1, expr2

So the above example is /explicitly/ dealt with in the language
reference, if only you'd cared to look.
Not everything needs to be a one liner. If you need this, do it the old-
fashioned way:

t = foo()
if not pred(t): t = default_value

I already explained how to write it as a one-liner:

t = (lambda y: y if pred(y) else default_value)(foo())

-- [mdw]
 
S

Steven D'Aprano

On Thu, 18 Nov 2010 09:32:23 +0000, Mark Wooding wrote:

[...]
You're wrong. Python evaluates these left-to-right, as I said.
Parentheses override operator associativity; they don't affect
evaluation order at all.

Fair enough. I concede your point.


[...]
I already explained how to write it as a one-liner:

t = (lambda y: y if pred(y) else default_value)(foo())


I didn't say it couldn't be written as a one-liner. I suggested that it
was better not to.

The costs of the one-liner are:

* reduced readability;
* requires an increased level of knowledge of the reader ("what's lambda
do?");
* runtime inefficiency (you create a function object, only to use it once
then throw it away).

The advantages?

* one fewer line of code.

In my experience, the obsessiveness in which some people look for one-
liners is far from helpful, and goes against the spirit of Python. This
isn't Perl :)
 
M

Mark Wooding

Steven D'Aprano said:
I didn't say it couldn't be written as a one-liner. I suggested that it
was better not to.

Ahh. I misunderstood the first sentence above as dismissing the
possibility. Sorry. I agree that it's not a /nice/ one-liner. ;-)
The costs of the one-liner are:

* reduced readability;
* requires an increased level of knowledge of the reader ("what's lambda
do?");
* runtime inefficiency (you create a function object, only to use it once
then throw it away).

This last can be obviated by a clever compiler (which, in our case, we
have not got). The second could be considered an advantage: it's
educational!
The advantages?

* one fewer line of code.

In my experience, the obsessiveness in which some people look for one-
liners is far from helpful, and goes against the spirit of Python. This
isn't Perl :)

Oh, I agree completely. On the other hand, it's just /fun/. Python is
a fun language and using its features in playfully unusual ways is
enjoyable; like a good pun in a natural language. Just as puns aren't
always appropriate in written language, playful code isn't always
appropriate either; but that doesn't mean it's never appropriate.

(Java has no sense of humour. C++ does have, but it's a bit weird.)

-- [mdw]
 
M

Mark Dickinson

I've often thought this would make a nice O(1)-test lookup on an
xrange() generator.

An O(1) test for 'x in <range_object>' is implemented in Python 3.2,
at least provided that x has type 'int', 'long' or 'bool'. (If x is
an instance of a subclass of int or long, then there's a risk that the
semantics of the membership test have been changed by an explicitly
overridden __eq__, so Python wimps out and falls back to the O(n)
algorithm in that case.)

Python 3.2a4+ (py3k:86635:86636M, Nov 21 2010, 19:22:18)
[GCC 4.2.1 (Apple Inc. build 5664)] on darwin
Type "help", "copyright", "credits" or "license" for more information.True

IIRC, there wasn't sufficient interest to get it backported to Python
2.7 in time for its release. Though as a pure optimization, one could
argue that it would still be possible to get this into Python 2.7.2.

Mark
 
T

Tim Chase

An O(1) test for 'x in<range_object>' is implemented in Python 3.2,
at least provided that x has type 'int', 'long' or 'bool'. (If x is
an instance of a subclass of int or long, then there's a risk that the
semantics of the membership test have been changed by an explicitly
overridden __eq__, so Python wimps out and falls back to the O(n)
algorithm in that case.)

Drat, bested again by the python time-machine. Thanks for
bringing that to my attention.

-tkc
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,144
Latest member
KetoBaseReviews
Top