Re: [Python-ideas] Extending expressions using ellipsis
On 09/01/2016 02:04 PM, Paul Moore wrote: Thanks. That's a nice example of how the proposal might help. But you could of course have written your original code as 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})) That's not going to completely alleviate the problem, but it does make the intent clearer. And it's possible that you could propose a style rule that a dedent in a bracketed expression is not allowed - that might well be something that flake8 could add, and then you'd get a much clearer error message (but only if you ran flake8 - if you just saw a syntax error from Python, you'd probably be just as likely to "fix" it as you said above, without even trying to run flake8). Also, of course, most text editors would highlight matching parentheses - which makes it much easier to spot the "correct" place for the missing parenthesis. Good points. That style does look clearer than my example. One other thing, I'm not at all keen on using "..." for the syntax - it's almost completely invisible when I read this in gmail, and as others have pointed out, it already has a meaning, as Ellipsis. But I don't have a better suggestion to offer, I'm afraid. Now that I remember that "..." is a literal value in Python 3 (surprise!), I've been thinking about alternatives. I wonder if a double backslash would work. Double backslash at the end of a line is currently a syntax error. Let's see how it looks: 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}) That looks OK to me right now. Double backslash seems to convey the idea that it's not only a line continuation, but a line continuation with special properties. I would never write it this way, BTW: 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}) I never seem to be able to sprinkle those single backslashes in the right places consistently. I forget one, or I leave one in after rearranging lines, leading to mayhem. Also, it's very definitely "yet another way to write expressions across multiple lines". But the indented expression format is pretty readable for cases when you *do* have a long expression and no convenient way to bracket it. Well, you could say the same about triple quoted strings. Sure there are other ways to write strings longer than a single line, but triple quoted strings are clearly worth the extra parser complexity. Shane ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] Extending expressions using ellipsis
On 09/01/2016 01:24 PM, Brendan Barnwell wrote: I sometimes write similar code with a sequence of pandas operations. I think you can get a lot of mileage out of accepting the loss in precious vertical space and just putting the closing parenthesis on its own line, indented to the same level as the beginning of the line where the corresponding open parenthesis was. Your example then becomes: rows = ( dbsession.query(Table1) .join( Table2, Table2.y = Table1.y ) .filter(Table1.x = xx) .all() ) This is essentially the way I write such code. This gives you most of the important advantages you seek. You do need one extra set of parentheses around the whole thing, but you never have to move or alter those parentheses. You can freely add new lines (i.e., add the ".all()" line if it wasn't there, as you suggested in a later post) without interfering with existing lines. Moreover, some of the reasons you cite in your later post for wanting indentation enforcement become less important if you do things this way, because you are less likely to add a parenthesis in the wrong place when your parentheses are clearly set off like this. (It's true, though, that it might help even more if Python would enforce the requirement that, when a line ends with an open parenthesis, its counterpart must appear at the same indentation level.) That's a good point. I've been waffling about where to put the closing parenthesis. If flake8 had an option to force the closing parenthesis to be on its own line and at the same indentation level as the line with the opening parenthesis, I would certainly use it. In my experience, the tradeoff between compactness and clarity is rarely a major issue, because most expressions are either simple enough that clarity is not at risk, or complex enough that compactness is not a real benefit. If your expression is two or three lines long with no long nested parentheses, it will probably be clear enough even if you fudge a bit on the formatting. If your expression is ten lines long, having to add an extra line or two (or even three) for closing parentheses is not such a big deal. Basically, adding extra lines for parentheses is only a pain if the expression was previously "short" and now becomes "long", but not many expressions are very close to that boundary. You may be right, but I'm not sure your experience matches mine. I seem to recall changing single-line expressions into multi-line expressions fairly often. I'll pay more attention in the future. All that said, I do think it is worth considering some syntax to make this sort of thing easier. I'm not sure the Ellipsis approach is the right one, but I'm interested to see where this thread goes. Thanks for the well considered thoughts. Shane ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] Extending expressions using ellipsis
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 = d
Re: [Python-ideas] Extending expressions using ellipsis
On 08/31/2016 07:25 PM, Guido van Rossum wrote: On Wed, Aug 31, 2016 at 2:46 PM, Shane Hathaway wrote: [...] I'd like to suggest a small change to the Python parser that would make long expressions read better: rows = dbsession.query(Table1) ... .join( Table2, Table2.y = Table1.y) .filter(Table1.x = xx) .all() [...] (And no, this isn't equivalent to using '\'.) Exactly. Would this be enforced in the grammar or by the lexer? Since you say you expect the indentation to be enforced, that suggests it would be done by the grammar, but then the question is how you would modify the grammar? You could take the rule that says an expression can be followed by ".NAME" and extended it to also allow "... INDENT x DEDENT" where the x is whatever's allowed at ".NAME" (i.e. ".NAME" followed by other tails like "(..)" or "[...]". But then you could only use this new idea for chaining method calls, and not for spreading other large expressions across multiple lines. Yes, I was hoping the enhancement might be useful for more than just chaining method calls; if possible, the ellipsis should be allowed between any expression tokens. I'll study the grammar and see if my idea fits cleanly somehow. Thanks! Shane ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Extending expressions using ellipsis
Hi, I write a lot of SQLAlchemy code that looks more or less like this: rows = ( dbsession.query(Table1) .join( Table2, Table2.y = Table1.y) .filter(Table1.x = xx) .all()) The expressions get very long and nearly always need to be spread to multiple lines. I've tried various styles and have chosen the style above as the most tasteful available. Pros of the existing syntax: - It's possible to indent clearly and consistently. - Nested indentation works out OK. - It's flexible; I can combine lines or separate them for emphasis. Cons: - Extra parentheses are required. - The indentation is not enforced by the parser, so I have unnecessary freedom that could let various mistakes slip through. - 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'd like to suggest a small change to the Python parser that would make long expressions read better: rows = dbsession.query(Table1) ... .join( Table2, Table2.y = Table1.y) .filter(Table1.x = xx) .all() The idea is to use an ellipsis at the end of a line to spread an expression over multiple indented lines, terminated by a return to an earlier indentation level. You can still indent more deeply as needed, as shown above by the join() method call. This syntax has all the pros of the existing syntax and resolves all the cons: - No extra parentheses are required. - The indentation is enforced, so my mistakes are more likely to be caught early. - Without a closing parenthesis, there is no diff noise when I append to or reorder an expression. I've thought about using a colon instead of an ellipsis, but in Python, a colon starts a list of statements; that's not my goal. Instead, I'm looking for ways to use parser-enforced indentation to avoid mistakes and help my code read better without changing any semantics. Feedback is welcome! Shane ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/