On 09/01/2016 09:35 AM, Steven D'Aprano wrote:
On Wed, Aug 31, 2016 at 03:46:13PM -0600, Shane Hathaway wrote:
Cons:
- Extra parentheses are required.
You have to have extra *something* no matter how you do it. An extra semi-colon at the end of the statement, in semi-colon language. An extra backslash at the end of the line. An extra pair of parens. I don't see this as a meaningful negative.
I agree that multi-line expressions should always be explicit. Neither of us would want implicit multi-line expressions. I admire the style Python uses to format classes, functions, loops, conditions, and so on. There's always a block start marker, an indented block, and an implicit block end (implied by a reduction in the indentation level). That's a very tidy pattern and I try to use it as much as possible. I currently can't use that pattern for multi-line expressions. Because my SQLAlchemy code has a lot of multi-line expressions, some of my code doesn't look very much like Python. I'd like to keep my style more consistent so it's easier to read.
- The indentation is not enforced by the parser, so I have unnecessary freedom that could let various mistakes slip through.
I don't see how. Its precisely because the indentation is not enforced that you *can't* get errors related to the indentation. Of course you can still get other errors, like dropping a comma between function arguments, or failing to close a pair of brackets, but enforcing indentation doesn't protect you against those.
Can you demonstrate an error that can be prevented by enforcing indentation inside an expression?
Sometimes I fix unbalanced parentheses incorrectly. Here's something I might type. There should be another closing parenthesis in the middle: def update(names, value): (dbsession.query(Table1) .filter(Table1.name.in_(names)) .update({'value': value}) (dbsession.query(Table2) .filter(Table2.name.in_(names)) .update({'value': value})) Either Python or flake8 will tell me there's some kind of syntax error, so I might fix it by adding a closing parenthesis at the end: def update(names, value): (dbsession.query(Table1) .filter(Table1.name.in_(names)) .update({'value': value}) (dbsession.query(Table2) .filter(Table2.name.in_(names)) .update({'value': value}))) This will fix the syntax error but fail at runtime. With my proposed syntax, I would probably never create the error in the first place because I would only need to scan for balanced parentheses on each line, not over multiple lines: def update(names, value): dbsession.query(Table1) ... .filter(Table1.name.in_(names)) .update({'value': value}) dbsession.query(Table2) ... .filter(Table2.name.in_(names)) .update({'value': value})
In fact, can you explain what indentation rules you want to enforce? Its not clear to me exactly what you want.
The indentation must be consistent, and the expression ends when the indentation level drops to the same level as the first line or an earlier line.
- The closing parenthesis has to move every time I append to or reorder the expression, leading to diff noise in version control. (Alternatively, I could put the closing parenthesis on its own line, but that consumes precious vertical reading space.)
I don't get this argument either. There are two situations: you have the closing paren just after the final token, or you have it on a line of its own. If its on a line of its own, you don't need to change it if you modify the expression (as you state yourself). But if its on the same line as the final token, and you modify that line by appending or re-ordering, there's going to be a diff regardless:
result = ( blah blah blah blah blah blah blah + x - y or spam.eggs())
becomes:
result = ( blah blah blah blah blah x - y blah blah or spam.eggs() or default)
The presence or absence of that outermost pair of brackets doesn't affect the diff in any way. You either change the line, and get a diff, or you don't, and don't. At least that's how I see it.
Can you show the sort of diff noise that you're talking about?
Let's say I write this code: rows = ( dbsession.query(Table1) .filter(Table1.name.in_(names))) That code will work, but it will be slow (because it will fetch rows one at a time). I later realize I need to call the all() method to make it fast: rows = ( dbsession.query(Table1) .filter(Table1.name.in_(names)) .all()) One line of code had to change and another had to be added. With my proposed syntax, let's say I write the same code again: rows = dbsession.query(Table1) ... .filter(Table1.name.in_(names)) To make it fast, I add a line: rows = dbsession.query(Table1) ... .filter(Table1.name.in_(names)) .all() In this case, I only added one line of code and didn't change any existing lines, so the signal to noise ratio is better.
Three dots is already syntax for Ellipsis, so this is going to be ambiguous. There will be expressions where ... is valid, and the parser cannot tell if it is intended as a line continuation or not.
result = spam + ... (eggs or cheese)(arg)
Is that a line continuation, or an accidentally indented line which ought to raise a SyntaxError?
Ah, that's very interesting. I had forgotten until now that "..." in Python 3 is a literal value. For anyone else reading along, this now works in Python 3:
x = ... print(x, ...) Ellipsis Ellipsis
I wasn't aware of that complication. Maybe I should limit the idea to method chaining as Guido hinted. Shane