Reputation: 6026
To check whether a method is called correctly, I want to check each character and make it impossible for the programmer to use the method incorrectly. Because the method will be called in a web server as return redirect(...)
, instead of returning an error value (like False
or None
), I want to raise an exception.
def redirect(uri):
[raise ValueError('URI must be URL-encoded, ASCII only!') for c in uri if not (32 <= ord(c) <= 127)]
This gives an 'invalid syntax' exception:
File "server.py", line 115
[raise ValueError('URI must be URL-encoded, ASCII only!') for c in uri if not (32 <= ord(c) <= 127)]
^
SyntaxError: invalid syntax
I can work around the problem in various ways, but I wonder: why is raising inside a list comprehension not allowed?
Upvotes: 1
Views: 1129
Reputation: 8180
From a syntaxic point of view, your answer is in the full grammar spec.
The raise
terminal only appears in rules derived from stmt
(statement):
stmt: simple_stmt | compound_stmt
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
small_stmt: (... | flow_stmt | ...)
flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt
raise_stmt: 'raise' [test ['from' test]]
Whereas the first part of a list comprehension is a test
(boolean or expression) or a star_expr
(*expr
):
atom: ... | '[' [testlist_comp] ']' | ...
testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] )
The point is there is no way a statement can be derived from the (test|star_expr)
(left part of a list comprehension). Hence, your expression is syntactically wrong.
As pointed by @Neb in a comment, a list comprehension trying to return a raise
does not make sense.
You probably remember that print
was a statement in Python 2 and became a function in Python 3:
Python 2:
>>> [print(1) for _ in range(1)]
File "<stdin>", line 1
[print(1) for _ in range(1)]
^
SyntaxError: invalid syntax
Python 3:
>>> [print(1) for _ in range(1)]
1
[None]
The list comprehension is now syntactically correct. Similarly, no syntaxic rule prevents you from writing this:
>>> def raiser(): raise ValueError('URI must be URL-encoded, ASCII only!')
...
>>> def redirect(uri): [raiser() for c in uri if not (32 <= ord(c) <= 127)]
...
>>> redirect("abc")
>>> redirect("éàç")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in redirect
File "<stdin>", line 1, in <listcomp>
File "<stdin>", line 1, in raiser
ValueError: URI must be URL-encoded, ASCII only!
But the semantic remains unclear: do you want to perform an action (ie use a side effect from a function), or to build a list? Remember that list comprehensions are a borrowing to functional langages, expecially Haskell I think. Hence, they are not here to perform actions.
I quote @Mark Ransom comment to an answer to the "Is it Pythonic to use list comprehensions for just side effects?" question:
I would go even further and state that side effects inside a list comprehension are unusual, unexpected, and therefore evil, even if you're using the resulting list when you're done. – Mark Ransom
I use this rule of thumb: avoid any side effects in list comprehensions, even if you use the result.
Upvotes: 1