Re: [Python-ideas] Extending expressions using ellipsis

2016-09-01 Thread Shane Hathaway

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

2016-09-01 Thread Shane Hathaway

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

2016-09-01 Thread Shane Hathaway

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

2016-08-31 Thread Shane Hathaway

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

2016-08-31 Thread Shane Hathaway

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/