Documentation, assignment in expression.

A

Alexander Blinne

Hi,

I think this section of the docs needs some kind of rewrite:

<http://docs.python.org/faq/design.html#why-can-t-i-use-an-assignment-in-an-expression>

While it is great to discuss the reasons for not allowing an assignment
in an expression, I feel that the given example is some kind of
outdated. The last sentence "For example, in the current version of
Python file objects support the iterator protocol, so you can now write
simply (for line in file:)" makes me think that this section was written
while that syntax was still new. No one I know would ever write
something like this:
while True:
line = f.readline()
if not line:
break
... # do something with line

I think at least we need a new example. Any ideas?

Greetings
Alexander
 
D

Dennis Lee Bieber

Hi,

I think this section of the docs needs some kind of rewrite:

<http://docs.python.org/faq/design.html#why-can-t-i-use-an-assignment-in-an-expression>

While it is great to discuss the reasons for not allowing an assignment
in an expression, I feel that the given example is some kind of
outdated. The last sentence "For example, in the current version of
Python file objects support the iterator protocol, so you can now write
simply (for line in file:)" makes me think that this section was written
while that syntax was still new. No one I know would ever write
something like this:


I think at least we need a new example. Any ideas?
But remember -- this is a section on why no "assignment in
expression"...

That means

for line in f:
#do stuff

is the counter to the equivalent C-style

while (line = f.readline() ): #mixing C style with Python calls
# do stuff

The section is not meant to be an advocate for or against your true
Python design using an infinite loop, and separate input line.

The emphasis on "current version" might do with updating -- by
explicitly stating when the iteration came in...
 
R

Roy Smith

Alexander Blinne said:
The last sentence "For example, in the current version of Python file
objects support the iterator protocol, so you can now write simply
(for line in file:)" ...

In general, words like "current", "previous", and "next" don't belong in
documentation. The timepoint to which they refer changes as soon as
they're published. Much better to say, "as of version x.y..."
 
A

Alexander Blinne

I am not sure I understand your argument. The doc section states that

" [...] in Python you’re forced to write this:

while True:
line = f.readline()
if not line:
break
... # do something with line".

That simply isn't true as one can simply write:

for line in f:
#do stuff

which is the exact counter of the C idiom that C or perl people try to
use and complained about when they were really forced to write the above
lengthy while True loop. So there is no reason to want to do "assignment
in expression", so no real reason to use this example to explain why
they aren't there in python. I totally agree with the
error-handling-reason not allowing them.

I agree with Roy Smith saying that documentation shouldn't refer to the
"current version".
 
T

Tim Chase

I am not sure I understand your argument. The doc section states that

" [...] in Python you’re forced to write this:

while True:
line = f.readline()
if not line:
break
... # do something with line".

That simply isn't true as one can simply write:

for line in f:
#do stuff

I think the complaint was backed by a bad example. Perhaps a DB
example works better. With assignment allowed in an evaluation,
you'd be able to write

while data = conn.fetchmany():
for row in data:
process(row)

whereas you have to write

while True:
data = conn.fetchmany()
if not data: break
for row in data:
process(row)

Granted, this can be turned into an iterator with a yield, making
the issue somewhat moot:

def db_iter(conn, *args, **kwargs):
while True:
data = conn.fetchmany(rows, *args, **kwargs)
if not data: break
for row in data:
yield row

for row in db_iter(conn):
proecss(row)

-tkc
 
C

Chris Angelico

Granted, this can be turned into an iterator with a yield, making the issue
somewhat moot:

No, just moving the issue to the iterator. Your iterator has exactly
the same structure in it.

Personally, I quite like assignment-in-conditional notation. Yes, it's
a pretty common cause of problems; but what happened to the
"consenting adults" policy? Python permits operator overloading and
even the reassignment of builtins, both of which can cause similar
confusion.

But, that's the choice Python's made. And being able to use the same
symbol for assignment and comparison does have its advantages.

ChrisA
 
T

Tim Chase

No, just moving the issue to the iterator. Your iterator has exactly
the same structure in it.

Yeah, it has the same structure internally, but I'm somewhat
surprised that the DB connection object doesn't have an
__iter__() that does something like this automatically under the
covers.
Personally, I quite like assignment-in-conditional notation. Yes, it's
a pretty common cause of problems; but what happened to the
"consenting adults" policy? Python permits operator overloading and
even the reassignment of builtins, both of which can cause similar
confusion.

In my past years of C programming, I've accidentally omitted the
second "=" in a comparison test numerous times, requiring me to
track down the missing character. When I finally catch it, it's
obvious what the problem is, but I've come to love having Python
yell at me contextually.
But, that's the choice Python's made. And being able to use the same
symbol for assignment and comparison does have its advantages.

The old curmudgeon in me likes the Pascal method of using "=" for
equality-testing, and ":=" for assignment which feels a little
closer to mathematical use of "=".

-tkc
 
C

Chris Angelico

Yeah, it has the same structure internally, but I'm somewhat surprised that
the DB connection object doesn't have an __iter__() that does something like
this automatically under the covers.

Sure. That's definitely the truly Pythonic technique. If I build a C++
object that acts like an array, it's going to overload the []
dereference operator - and if I build a Python object that returns a
series of things, it's going to be an iterable.
In my past years of C programming, I've accidentally omitted the second "="
in a comparison test numerous times, requiring me to track down the missing
character.  When I finally catch it, it's obvious what the problem is, but
I've come to love having Python yell at me contextually.

This is where compiler warnings come in handy. GCC will, in what I
told someone was "Perls of Wisdom mode" (with the -Wall option - yes,
it is that bad a pun), recommend clarification of the worst offenders
of this sort. And in the common case where you're comparing against a
constant, quite a few compilers will alert you to the fact that your
condition is always true/always false (eg "if (x=3) ;"). Many problems
can be solved in multiple ways; sometimes there's a clear "best way",
other times it really doesn't matter matter matter matter matter.

ChrisA
 
R

rusi

The old curmudgeon in me likes the Pascal method of using "=" for
equality-testing, and ":=" for assignment which feels a little
closer to mathematical use of "=".

-tkc

Carroll Morgan author of programming from specifications
http://www.cs.ox.ac.uk/publications/books/PfS/ called colon in ':=' as
'make'. So := is 'make equal' This generalizes nicely to other
specification operators (though not computable) eg :> is 'make greater
than'

So x :> x
can be refined to x := x+1
or to x := x*x if the precondition x > 1 is available
 
T

Tim Chase

Unfortunately, ":=" means "is defined as" in mathematics. The "right"
operator would have been "<-".

Yeah, while I like the "<-" better too, I'm not *quite* old
enough to have influenced Wirth in his design decisions ;-)

-tkc
 
D

Dennis Lee Bieber

Yeah, it has the same structure internally, but I'm somewhat
surprised that the DB connection object doesn't have an
__iter__() that does something like this automatically under the
covers.
I believe being able to use the connection object directly for
queries is considered a short-cut feature... If you use the longer form

con = db.connect()
cur = con.cursor()

the cursor object, in all that I've worked with, does function for
iteration

for rec in cur:
#do stuff
 
M

mwilson

Tim said:
Yeah, it has the same structure internally, but I'm somewhat
surprised that the DB connection object doesn't have an
__iter__() that does something like this automatically under the
covers.

Most of my database programs wind up having the boilerplate (not tested):

def rowsof (cursor):
names = [x[0] for x in cursor.description]
r = cursor.fetchone()
while r:
yield dict (zip (names, r))
r = cursor.fetchone()


Mel.
 
D

Dennis Lee Bieber

Most of my database programs wind up having the boilerplate (not tested):

def rowsof (cursor):
names = [x[0] for x in cursor.description]
r = cursor.fetchone()
while r:
yield dict (zip (names, r))
r = cursor.fetchone()

In my (limited) experience, the main loop above could be replaced
with:

for r in cursor:
yield dict(zip(names, r))

Though some DB adapters provide a "dictionary cursor" that already
does what this modules does; or provides a way to specify a subclassed
cursor that implements the dictionary cursor internally.
 
S

Steven D'Aprano

I think the complaint was backed by a bad example. Perhaps a DB example
works better. With assignment allowed in an evaluation, you'd be able
to write

while data = conn.fetchmany():
for row in data:
process(row)

Yes, but why would you want to? Apart from making your code hard to read.
Better written as:

while data = conn.fetchmany(): # THIS IS NOT A TYPO
for row in data:
process(row)


When you need to write a comment explaining that something isn't a typo,
that's a good clue that you shouldn't do it *wink*

But seriously, assignment as an expression is a pretty major code-smell.
Even when it's innocent, it should still fire off warning alarms, because
how do you know if it's innocent or not until you've studied the code?

(Comments, like the above, are prone to get out of date with the code,
and can't be entirely trusted.)

Python avoids this code smell by prohibiting assignment as an expression.
Functional languages avoid it by prohibiting assignment. Other languages
may choose to do differently. If all languages did exactly what C does,
they'd all be C.

(I seem to recall a language that used a single = for both assignment and
equality testing, guessing which one you meant from context. BASIC
perhaps? Whatever it was, I'm pretty sure they regretted it.)

whereas you have to write

while True:
data = conn.fetchmany()
if not data: break
for row in data:
process(row)

Or even:

data = 'SENTINEL' # Any true value will do.
while data:
data = conn.fetchmany()
for row in data:
process(row)


There's no need to break out of the while loop early, because `for row in
<empty>` is a null-op.
 
S

Steven D'Aprano

Unfortunately, ":=" means "is defined as" in mathematics. The "right"
operator would have been "<-".

That's hardly universal. In approx 20 years of working with mathematics
textbooks, I've never seen := used until today.

I see that Wikipedia lists no fewer than seven different symbols for "is
defined as":

http://en.wikipedia.org/wiki/Table_of_mathematical_symbols

including the one I'm familiar with, â‰, or "def" over "=".

Besides, just because mathematicians use a symbol, doesn't mean
programming languages can't use it for something else.
 
D

Devin Jeanpierre

Unfortunately, ":=" means "is defined as" in mathematics. The "right"
operator would have been "<-".


"Is defined as" is actually pretty reasonable. "Define this to be
that" is a common way to speak about assignment. Its only difference
is the present tense. For example, in Python, "def" stands for
"define", but we can overwrite previous definitions::

def f(x): return x
def f(x): return 2
f(3) == 2

In fact, in pretty every programming language that I know of with a
"define" assignment verb, this is so. For example, in Scheme, x is 2
at the end::

(define x 1)
(define x 2)
x

-- Devin
 
T

Tim Chase

I believe being able to use the connection object directly for
queries is considered a short-cut feature... If you use the longer form

con = db.connect()
cur = con.cursor()

the cursor object, in all that I've worked with, does function for
iteration

for rec in cur:
#do stuff

Interesting. Either this is something special for a particular
DB back-end, or has been added since I went hunting (back with
mxODBC and Python2.4). I wouldn't be surprised if the latter was
the case, as my code is nigh identical to yours.

conn = db.DriverConnect(connection_string)
cursor = conn.cursor()
cursor.execute(sql, params)
for row in cursor: # in the above 2.4 + mxODBC, this fails
process(row)

I'd be interested to know the underlying implementation's
efficiency, hopefully using .fetchmany() under the hood. My
understanding is that the .fetchmany() loop is the best way to do
it, as the .fetchone() gets chatty with the DB (but is more
efficient if you only need one row from a multi-result query),
and the .fetchall() can blow out memory on large datasets.

-tkc
 
J

Jussi Piitulainen

Kiuhnm said:
When you write
(define x 1)
(define x 2)
x
or, in F# and OCaml,
let x = 1
let x = 2
x
you're saying
x = 1
{
x = 2
x
}
You don't modify 'x': you hide it by defining another "value" (not
variable) with the same name.
Indeed,
let x = 1
let x = 2
x
is shorthand for
let x = 1 in
let x = 2 in
x

No, Devin is right about Scheme. On "top level" re-definition is
interpreted as assignment. The following mean the same:

(define x 1) (define x 2) x
(define x 1) (set! x 2) x

Local definitions in the beginning of a "body" do not allow duplicate
names at all. The following mean the same:

(let () (define x 1) (define y 2) x)
(letrec* ((x 1) (y 2)) x) ;letrec in older versions (not sure of R6RS)

But (let () (define x 1) (define x 2) x) is still an error. Some
implementations may give it a meaning. Not sure.
 
M

mwilson

Dennis said:
Most of my database programs wind up having the boilerplate (not tested):

def rowsof (cursor):
names = [x[0] for x in cursor.description]
r = cursor.fetchone()
while r:
yield dict (zip (names, r))
r = cursor.fetchone()

In my (limited) experience, the main loop above could be replaced
with:

for r in cursor:
yield dict(zip(names, r))

I think your experience is more recent than mine. I'll change my
boilerplate next time around.

Mel.
 
T

Thomas Rachel

Am 26.03.2012 00:59 schrieb Dennis Lee Bieber:
If you use the longer form

con = db.connect()
cur = con.cursor()

the cursor object, in all that I've worked with, does function for
iteration

I use this form regularly with MySQLdb and am now surprised to see that
this is optional according to http://www.python.org/dev/peps/pep-0249/.

So a database cursor is not required to be iterable, alas.


Thomas
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top