I have been trying to convert a simple execution such as:
for x in xrange(10):
if x % 2 == 0:
print x, 'is even'
to a one liner version:
for x in xrange(10): if x % 2 == 0: print x, 'is even'
which gives me:
File "foo.py", line 1
for x in xrange(10): if x % 2 == 0: print x, 'is even'
^
SyntaxError: invalid syntax
I don't see any ambiguity in here. Is there a particular reason why this fails?
It's simply not allowed by the grammar. The relevant productions are:
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
As you can see, after a for you can either put a simple statement or a "suite", i.e. an indented block. An if statement is a compound statement, not a simple one.
Two lines are the minimum to express this program:
for x in xrange(10):
if x % 2 == 0: print x, 'is even'
(Of course, you can write equivalent programs that take only one line, such as
for x in xrange(0, 10, 2): print x, "is even"
or any of the other one-liners posted in response to this question.)
From the formal grammar for 2.7:
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
small_stmt: (expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt |
import_stmt | global_stmt | exec_stmt | assert_stmt)
If the suite had allowed a compound_stmt then what you suggest would be accepted. But that would also allow something like this:
if True: try:
# do something
except:
# handle
foo()
Is that except outside the enclosing if? Is the call to foo outside the enclosing if? I think this shows that we really don't want in-lining compound statements to be allowed by the formal grammar. Simply adding suite: compound_stmt makes the grammar ambiguous as I read it, where the same code can be interpreted with two or more different meanings, neither disprovable.
Basically, it's by design that what you ask is a parse error. Reworking the formal grammar could allow the code in your example to work without other funny stuff, but it requires careful attention to ambiguity and other problems.
See also Dangling Else, a grammar problem that afflicted the standard Algol-60 language. It's not always easy to find these kinds of problems, so a healthy fear of changing a working grammar is a good thing.
try something like :
In [14]: from __future__ import print_function
In [17]: for x in xrange(10): print (x,'is even') if x%2==0 else None
....:
0 is even
2 is even
4 is even
6 is even
8 is even
If you want something similar, use a list comprehension:
print '\n'.join('{0} is even'.format(x) for x in xrange(10) if x % 2 == 0)
Prints:
0 is even
2 is even
4 is even
6 is even
8 is even
You can try this:
for y in (x for x in xrange(10) if x % 2 == 0): print y
The Python docs about compound statements states a reason for the decision on why does the grammar disallow this:
A suite can be one or more semicolon-separated simple statements on
the same line as the header, following the header’s colon, or it can
be one or more indented statements on subsequent lines. Only the
latter form of suite can contain nested compound statements; the
following is illegal, mostly because it wouldn’t be clear to which if
clause a following else clause would belong:
if test1: if test2: print x
So in wberry's answer it was right about the Dangling Else.
Related
The following line of code outputs SyntaxError: invalid syntax
for (i in range(-WIDTH,WIDTH)):
The next one works without errors. I have no idea what the syntax error is supposed to be here. So I am just asking out of curiosity. My guess is that the brackets prevent the expression from being evaluated.
for i in range(-WIDTH,WIDTH):
Your parentheses are essentially just confusing the parser.
There are a couple of reasons you could have an open paren after a for, most notably using tuple unpacking:
>>> for (x, y) in zip(range(5), range(6, 11)):
... print(x, '->', y)
...
0 -> 6
1 -> 7
2 -> 8
3 -> 9
4 -> 10
Additionally, parens can be used in loads of places in Python for simple grouping, such as when breaking up long lines:
>>> s = ("This is "
... "a really awkward way "
... "to write a "
... "long string "
... "over several lines")
>>>
>>> s
'This is a really awkward way to write a long string over several lines'
So the parser won't really complain about it.
However, as you know, for is supposed to read like this:
for_stmt ::= "for" target_list "in" expression_list ":" suite
["else" ":" suite]
Which means that by grouping this way, you're constructing an invalid loop. Essentially, yours reads that there is no in because it's grouped into the target_list by your parentheses. Hope this makes sense.
A way to see more clearly what's happening: write the rest of your for loop (in expression_list) after your close paren. Then you will get a clearer error about how it is interpreting this statement.
>>> for (i in range(-WIDTH, WIDTH)) in range(-WIDTH, WIDTH):
... print(i)
...
File "<stdin>", line 1
SyntaxError: can't assign to comparison
So it will let you do it, but the result of x in y will be a boolean, which cannot be the target of an assignment. The original error you got is because it got to your : before it found your in, which is plain old invalid syntax, as if you just wrote for x:.
I know that semicolons are unnecessary in Python, but they can be used to cram multiple statements onto a single line, e.g.
>>> x = 42; y = 54
I always thought that a semicolon was equivalent to a line break. So I was a bit surprised to learn (h/t Ned Batchelder on Twitter) that a double semicolon is a SyntaxError:
>>> x = 42
>>> x = 42;
>>> x = 42;;
File "<stdin>", line 1
x = 42;;
^
SyntaxError: invalid syntax
I assumed the last program was equivalent to x = 42\n\n. I’d have thought the statement between the semicolons was treated as an empty line, a no-op. Apparently not.
Why is this an error?
From the Python grammar, we can see that ; is not defined as \n. The parser expects another statement after a ;, except if there's a newline after it:
Semicolon w/ statement Maybe a semicolon Newline
\/ \/ \/ \/
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
That's why x=42;; doesn't work; because there isn't a statement between the two semicolons, as "nothing" isn't a statement. If there was any complete statement between them, like a pass or even just a 0, the code would work.
x = 42;0; # Fine
x = 42;pass; # Fine
x = 42;; # Syntax error
if x == 42:; print("Yes") # Syntax error - "if x == 42:" isn't a complete statement
An empty statement still needs pass, even if you have a semicolon.
>>> x = 42;pass;
>>> x
42
While shortening my code I was cutting down a few variable declarations onto one line-
##For example- going from-
Var1 =15
Var2 = 26
Var3 = 922
##To-
Var1, Var2, Var3 = 15, 26, 922
However, when I tried doing the same thing to this code-
User_Input += Master_Key[Input_ref]
Key += Master_Key[Key_ref]
Key2 += Master_Key[Key_2_Ref]
##Which looks like-
User_Input, Key, Key2 += Master_Key[Input_Ref], Master_Key[Key_Ref], Master_Key[Key_2_Ref]
This throws the error
SyntaxError: illegal expression for augmented assignment
I have read the relevant Python documentation, but I still can't find a way to shorten this particular bit of code.
No, you cannot. You cannot use augmented assignment together with multiple targets.
You can see this in the Augmented assignment statements section you linked to:
augmented_assignment_stmt ::= augtarget augop (expression_list | yield_expression)
augtarget ::= identifier | attributeref | subscription | slicing
The augtarget rule only allows for one target. Compare this with the Assignment statements rules:
assignment_stmt ::= (target_list "=")+ (expression_list | yield_expression)
target_list ::= target ("," target)* [","]
target ::= identifier
| "(" target_list ")"
| "[" target_list "]"
| attributeref
| subscription
| slicing
where you have a target_list rule to assign to.
I'd not try and shorten this at all; trying to squeeze augmented assignments onto one line does not improve readability or comprehension of what is happening.
This question already has answers here:
When are parentheses required around a tuple?
(3 answers)
Closed 8 years ago.
So I stumbled into a particular behaviour of tuples in python that I was wondering if there is a particular reason for it happening.
While we are perfectly capable of assigning a tuple to a variable without
explicitely enclosing it in parentheses:
>>> foo_bar_tuple = "foo","bar"
>>>
we are not able to print or check in a conditional if statement the variable containing
the tuple in the previous fashion (without explicitely typing the parentheses):
>>> print foo_bar_tuple == "foo","bar"
False bar
>>> if foo_bar_tuple == "foo","bar": pass
SyntaxError: invalid syntax
>>>
>>> print foo_bar_tuple == ("foo","bar")
True
>>>
>>> if foo_bar_tuple == ("foo","bar"): pass
>>>
Does anyone why?
Thanks in advance and although I didn't find any similar topic please inform me if you think it is a possible dublicate.
Cheers,
Alex
It's because the expressions separated by commas are evaluated before the whole comma-separated tuple (which is an "expression list" in the terminology of the Python grammar). So when you do foo_bar_tuple=="foo", "bar", that is interpreted as (foo_bar_tuple=="foo"), "bar". This behavior is described in the documentation.
You can see this if you just write such an expression by itself:
>>> 1, 2 == 1, 2 # interpreted as "1, (2==1), 2"
(1, False, 2)
The SyntaxError for the unparenthesized tuple is because an unparenthesized tuple is not an "atom" in the Python grammar, which means it's not valid as the sole content of an if condition. (You can verify this for yourself by tracing around the grammar.)
Considering an example of if 1 == 1,2: which should cause SyntaxError, following the full grammar:
if 1 == 1,2:
Using the if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite], we get to shift the if keyword and start parsing 1 == 1,2:
For the test rule, only first production matches:
test: or_test ['if' or_test 'else' test] | lambdef
Then we get:
or_test: and_test ('or' and_test)*
And step down into and_test:
and_test: not_test ('and' not_test)*
Here we just step into not_test at the moment:
not_test: 'not' not_test | comparison
Notice, our input is 1 == 1,2:, thus the first production doesn't match and we check the other one: (1)
comparison: expr (comp_op expr)*
Continuing on stepping down (we take the only the first non-terminal as the zero-or-more star requires a terminal we don't have at all in our input):
expr: xor_expr ('|' xor_expr)*
xor_expr: and_expr ('^' and_expr)*
and_expr: shift_expr ('&' shift_expr)*
shift_expr: arith_expr (('<<'|'>>') arith_expr)*
arith_expr: term (('+'|'-') term)*
term: factor (('*'|'/'|'%'|'//') factor)*
factor: ('+'|'-'|'~') factor | power
Now we use the power production:
power: atom trailer* ['**' factor]
atom: ('(' [yield_expr|testlist_comp] ')' |
'[' [testlist_comp] ']' |
'{' [dictorsetmaker] '}' |
NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False')
And shift NUMBER (1 in our input) and reduce. Now we are back at (1) with input ==1,2: to parse. == matches comp_op:
comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not'
So we shift it and reduce, leaving us with input 1,2: (current parsing output is NUMBER comp_op, we need to match expr now). We repeat the process for the left-hand side, going straight to the atom nonterminal and selecting the NUMBER production. Shift and reduce.
Since , does not match any comp_op we reduce the test non-terminal and receive 'if' NUMBER comp_op NUMBER. We need to match else, elif or : now, but we have , so we fail with SyntaxError.
I think the operator precedence table summarizes this nicely:
You'll see that comparisons come before expressions, which are actually dead last.
in, not in, is, is not, Comparisons, including membership tests
<, <=, >, >=, <>, !=, == and identity tests
...
(expressions...), [expressions...], Binding or tuple display, list display,
{key: value...}, `expressions...` dictionary display, string conversion
I have a boolean expression string, that I would like to take apart:
condition = "a and (b or (c and d))"
Or let's say:
I want to be able to access the string contents between two parenthesis.
I want following outcome:
"(b or (c and d))"
"(c and d)"
I've tried the following with regular expressions (not really working)
x = re.match(".*(\(.*\))", condition)
print x.group(1)
Question:
What is the nicest way to take a boolean expression string apart?
This is the sort of thing you can't do with a simple regex. You need to actually parse the text. pyparsing is apparently excellent for doing that.
Like everyone said, you need a parser.
If you don't want to install one, you can start from this simple top-down parser (take the last code sample here)
Remove everything not related to your need (+, -, *, /, is, lambda, if, else, ...). Just keep parenthesis, and, or.
You will get a binary tree structure generated from your expression.
The tokenizer use the build-in tokenize (import tokenize), which is a lexical scanner for Python source code but works just fine for simple cases like yours.
If your requirements are fairly simple, you don't really need a parser.
Matching parentheses can easily be achieved using a stack.
You could do something like the following:
condition = "a and (b or (c and d))"
stack = []
for c in condition:
if c != ')':
stack.append(c)
else:
d = c
contents = []
while d != '(':
contents.insert(0, d)
d = stack.pop()
contents.insert(0, d)
s = ''.join(contents)
print(s)
stack.append(s)
produces:
(c and d)
(b or (c and d))
Build a parser:
Condition ::= Term Condition'
Condition' ::= epsilon | OR Term Condition'
Term ::= Factor Term'
Term' ::= epsilon | AND Factor Term'
Factor ::= [ NOT ] Primary
Primary ::= Literal | '(' Condition ')'
Literal ::= Id