Coding style

E

Erik Max Francis

tac-tics said:
I'm well aware that both of these snippets does the same thing. I'm
just spouting my opinion that lists and integers are not tests, ...

No, but testing their Boolean nature _is_ a test. Aggregate objects in
Python are true if they are non-empty, and false if they are empty.
That is reasonable, not uncommon a convention, and quite useful for
exactly the situations you were talking about. That convention exists
_so that_ writing::

if aContainer:
... container is not empty ...

is meaningful and convenient. So the answer to the original question
was, "The first one." Feel free to write it the other way with an
explicit test, but it's not Pythonic.
 
P

Patrick Maupin

PTY said:
It looks like there are two crowds, terse and verbose. I thought terse
is perl style and verbose is python style. BTW, lst = [] was not what
I was interested in :) I was asking whether it was better style to
use len() or not.

It's not canonical Python to use len() in this case. From PEP 8:

- For sequences, (strings, lists, tuples), use the fact that empty
sequences are false.

Yes: if not seq:
if seq:

No: if len(seq)
if not len(seq)

The whole reason that a sequence supports testing is exactly for this
scenario. This is not an afterthought -- it's a fundamental design
decision of the language.

Regards,
Pat
 
C

Carl Banks

PTY said:
Which is better?

lst = [1,2,3,4,5]

while lst:
lst.pop()

OR

while len(lst) > 0:
lst.pop()

I'm going to go against general opinion and suggest using "len(lst)>0",
though this is not a good example of why I think that.

In practice, I'd say the former is less generic. No, that's not a
typo.

If you were to write a function that expects a list (and actually uses
the list interface, indexing and such, rather than merely passing it
off to another function), the function would almost certainly fail if
you were to pass in an integer or some other non-sequence type.
However, functions expecting a list have a decent chance of working if
you were to pass in a numpy array.

But if you were to write "if lst" instead of "if len(lst)>0", then
you've just ensured your function will fail for numpy arrays. But you
get no benefit in genericity, because non-sequence types almost
certainly wouldn't work anyways.

Therefore, the only time I'd recommend using "if lst" is if all you're
doing is passing it to other functions.


Carl Banks
 
C

Carl Banks

Patrick said:
PTY said:
It looks like there are two crowds, terse and verbose. I thought terse
is perl style and verbose is python style. BTW, lst = [] was not what
I was interested in :) I was asking whether it was better style to
use len() or not.

It's not canonical Python to use len() in this case. From PEP 8:

- For sequences, (strings, lists, tuples), use the fact that empty
sequences are false.

Yes: if not seq:
if seq:

No: if len(seq)
if not len(seq)

The whole reason that a sequence supports testing is exactly for this
scenario. This is not an afterthought -- it's a fundamental design
decision of the language.

That might have made sense when Python and string, list, tuple were the
only sequence types around.

Nowadays, Python has all kinds of spiffy types like numpy arrays,
interators, generators, etc., for which "empty sequence is false" just
doesn't make sense. If Python had been designed with these types in
mind, I'm not sure "empty list is false" would have been part of the
language, let alone recommend practice.


Carl Banks
 
C

Carl Banks

PTY said:
Which is better?

lst = [1,2,3,4,5]

while lst:
lst.pop()

OR

while len(lst) > 0:
lst.pop()


Here's another reason not to use "if lst". Say you have a function
that looks like this:

def process_values(lst):
if not lst:
return
do_expensive_initialization_step()
for item in lst:
do_something_with(item)
do_expensive_finalization_step()

That works, right? No problem, right?

What if you called the function like this:

process_values(x.strip() for x in values_lst)

Oops, now we've just gone through an expensive initialization and
finalization for nothing (since values_lst was empty). Maybe some
subtle bugs introduced. If we're lucky, the finalization step will
throw an exception.

If we had used "if len(list)>0", we'd have gotten a nice exception
telling us that a generator is not welcome. Then we'd have changed the
argument to a list comprehension, or better, changed the function to
work for any iterator.


Carl Banks
 
P

Paul Rubin

Carl Banks said:
What if you called the function like this:

process_values(x.strip() for x in values_lst)

Oops, now we've just gone through an expensive initialization and
finalization for nothing (since values_lst was empty). Maybe some
subtle bugs introduced. If we're lucky, the finalization step will
throw an exception.

You've got a function written to take a list arg and are passing it
something other than a list. Why do you expect it to work?
As soon as the function uses lst[3] for something, it will crash
if you pass it a sequence like that. Your example is mainly an
argument for static type checking.
 
C

Carl Banks

Paul said:
You've got a function written to take a list arg and are passing it
something other than a list. Why do you expect it to work?

I don't expect it to work as is. Disregarding the test for emptiness
at the beginning, I would expect it to work for any iterable, even if
the author only intended it for a list. That's the problem: it looks
like it should work for any iterable, but it doesn't. I would think
it's a pretty easy mistake to make. (I suspect lots of programmers
don't even realize that empty generator expressions are true. Even if
they do, it's easy to overlook that test if you're not careful.)
As soon as the function uses lst[3] for something,

Notice that the function I wrote never indexes the list. It only
iterates.
it will crash
if you pass it a sequence like that.

The particular function I wrote might crash. Or it might merely do
unnecessary work. Or it might have subtle bugs. (OTOH, if it had used
the "if len(lst)>0", it would crash right away; no subtle bugs.)
Your example is mainly an
argument for static type checking.

Not really. I'm merely putting forth one argument for using "if
len(lst)>0" rather than "if lst". ISTM the scenerio I described isn't
terribly rare: you write a function that iterates over an iterable, and
carelessly use a test ("if lst") that doesn't work for all iterables,
and the result could lead to subtle bugs. Which is not as bad as
carelessly using a test ("if len(lst)>0") that doesn't work for all
iterables, and the result is an immediate exception.

I've given two pretty good reasons for using "if len(lst)>0": it allows
functions to be written generically for lists and numpy arrays, and it
catches the rather plausible mistake of testing the truth value an
iterable (which can lead to subtle bugs). This is contrasted to the
good reasons for using "if lst", which are... um... hmm... saving a few
keystrokes?


Carl Banks
 
P

Peter Otten

Carl said:
    def process_values(lst):
        if not lst:
            return
        do_expensive_initialization_step()
        for item in lst:
            do_something_with(item)
        do_expensive_finalization_step()
What if you called the function like this:

    process_values(x.strip() for x in values_lst)

Oops, now we've just gone through an expensive initialization and
finalization for nothing (since values_lst was empty).  Maybe some
subtle bugs introduced.  If we're lucky, the finalization step will
throw an exception.

The good news is that the above has a 99 percent chance that it just works
with iterators/generators -- even though the writer may not have been aware
of them/they didn't exist when the code was written...

Peter
 
C

Carl Banks

Peter said:
The good news is that the above has a 99 percent chance that it just works
with iterators/generators -- even though the writer may not have been aware
of them/they didn't exist when the code was written...

There's a litmus test I like to use when justifying something with
percentages: I imagine that I'm presenting it to my boss, and ask
myself if I still expect to have a job the next day. :)

Yes, I agree with you, it most cases I expect merely unnecessary work.
(Which is not the best news; the best news would be an exception right
away.) That's why I think this is only a "pretty good" reason to use
"if len(lst)>0", not a slam dunk.


Carl Banks
 
S

Steve Holden

tac-tics said:
dwelch91 said:
Uh, no, empty lists are False in a boolean context:

http://docs.python.org/lib/truth.html

-Don


Perhaps I should have specified it like this:

empty_list = []
empty_list is not False

True

I'm well aware that both of these snippets does the same thing. I'm
just spouting my opinion that lists and integers are not tests, and in
an ideal world (Java??? X-) if statements support only boolean types.

DISCLAIMER: I do not promote the use of Java.
You don't promote use of Python either if you deliberately ignore
programming paradigms that have stood the test of time. Under what set
of rules could it make sense to test a list for equality with a Boolean
value (note also that capital "B": the word is based on someone's name).

The

if lst:

metaphor is so well established as a means of guarding statements that
should be executed only when there are elements in the list (or other
container, come to that), promoting any other way to perform the test
will only lead to confusion: there should be one (and preferably only
one) obvious way to do it.

regards
Steve
 
L

Lawrence D'Oliveiro

I'd go even one step further. Turn it into English (or your favorite
non-computer language):

1. While list, pop.

2. While the length of the list is greater than 0, pop.

Which one makes more sense? Guess which one I like. CPU cycles be
damned.
:)

One of my rules is, always program like the language actually has a Boolean
type, even if it doesn't. That means, never assume that arbitrary values
can be interpreted as true or false, always put in an explicit comparison
if necessary so it's obvious the expression is a Boolean.
 
P

Peter Otten

Carl said:
There's a litmus test I like to use when justifying something with
percentages: I imagine that I'm presenting it to my boss, and ask
myself if I still expect to have a job the next day. :)

That 99 percent chance is not meant to exempt you from actually verifying
that it works. Docstrings can do wonders here

def process_values(lst):
"Foos all bars in a list"

vs

def process_values(lst):
"Foos all bars in an iterable"

If in doubt, pass a list (-comprehension) as there are other ways of quiet
failure (the function may loop twice over its argument).
Yes, I agree with you, it most cases I expect merely unnecessary work.
(Which is not the best news; the best news would be an exception right
away.) That's why I think this is only a "pretty good" reason to use
"if len(lst)>0", not a slam dunk.

If process_values() were /aware/ of iterators it would probably be written

def process_values(items):
first_pass = True
for item in items:
if first_pass:
do_expensive_initialization_step()
first_pass = False
do_something_with(item)
if not first_pass:
do_expensive_finalization_step()

or just with an additional

items = list(items)

as its first statement.

Peter
 
B

Bruno Desthuilliers

tac-tics said:
Or even just:

lst = []

;-)


Indeed.

I'd say the second one.

And you'd be plain wrong.
Empty lists are not false.

In a bolean context, empty containers (lists, tuples, dicts, sets etc),
empty strings, integer 0, float 0.0 and None are all false. This is part
of the language specs.
 
B

Bruno Desthuilliers

tac-tics said:
dwelch91 said:
Uh, no, empty lists are False in a boolean context:

http://docs.python.org/lib/truth.html

-Don


Perhaps I should have specified it like this:

empty_list = []
empty_list is not False

True

Physical identity is not structural equality.

I'm well aware that both of these snippets does the same thing. I'm
just spouting my opinion that lists and integers are not tests, and in
an ideal world (Java??? X-)

You naughty troll
if statements support only boolean types.

if statements supports only boolean *expressions*. An expression is
boolean if it's result can be coerced to a boolean value, ie fed to the
bool type's constructor. So your example is wrong wrt/ if statements -
it should read:

empty_list = []
bool(empty_list) is False
=> True
 
B

Bruno Desthuilliers

dwelch91 said:
PTY said:
Which is better?

lst = [1,2,3,4,5]

while lst:
lst.pop()

OR

while len(lst) > 0:
lst.pop()

I think the first one is better, but if all you are doing is removing
all the items in the list, this is definitely better:

lst = []

Not if there are other names bound to the same list. You are only
rebinding this name, which does *not* empty the list object. The correct
solution here is

del lst[:]

which will remove all content from the list object,
 
B

Bruno Desthuilliers

PTY said:
Bob said:
PTY wrote:

Which is better?

lst = [1,2,3,4,5]

while lst:
lst.pop()

OR

while len(lst) > 0:
lst.pop()

A dozen posts, but nobody has posted the right
answer yet, so I will :)

It doesn't matter -- use whichever you prefer (*)
This is an angels on the head of a pin issue.

(*) -- If your code is part of an existing body of
code that uses one or the other style consistently,
then you should do the same.

I'd go even one step further. Turn it into English (or your favorite
non-computer language):

1. While list, pop.

2. While the length of the list is greater than 0, pop.

Which one makes more sense? Guess which one I like. CPU cycles be damned.
:)

Bob



It looks like there are two crowds, terse and verbose.

Nope, there are two crowds: those who RTFM, and those who don't.
I thought terse
is perl style and verbose is python style.

s/terse/cryptic/
s/verbose/readable/

Python is much more readable than Java because it's *less* verbose than
Java.
BTW, lst = [] was not what
I was interested in :)

Nor is it the correct functional equivalent of your code snippet.
I was asking whether it was better style to
use len() or not.

The idiomatic solution is clearly derivable from Python's language
documentation : in a boolean context, empty lists (etc...) eval to
False. FWIW, it's also more generic (you could have an object supporting
pop() but not __len__()), less error-prone, and can allow optimisations
(a container may know that it is empty without computing it's length).
 
B

Bruno Desthuilliers

Carl said:
Patrick said:
PTY wrote:

It looks like there are two crowds, terse and verbose. I thought terse
is perl style and verbose is python style. BTW, lst = [] was not what
I was interested in :) I was asking whether it was better style to
use len() or not.

It's not canonical Python to use len() in this case. From PEP 8:

- For sequences, (strings, lists, tuples), use the fact that empty
sequences are false.

Yes: if not seq:
if seq:

No: if len(seq)
if not len(seq)

The whole reason that a sequence supports testing is exactly for this
scenario. This is not an afterthought -- it's a fundamental design
decision of the language.


That might have made sense when Python and string, list, tuple were the
only sequence types around.

Nowadays, Python has all kinds of spiffy types like numpy arrays,
interators, generators,
etc., for which "empty sequence is false" just
doesn't make sense.

Iterators and generators are *not* sequences types. wrt/ non-builtin
container types, I suggest you re-read section 3.3.1 of the language
references:

"""
__nonzero__( self)
Called to implement truth value testing, and the built-in operation
bool(); should return False or True, or their integer equivalents 0 or
1. When this method is not defined, __len__() is called, if it is
defined (see below). If a class defines neither __len__() nor
__nonzero__(), all its instances are considered true.
"""
http://docs.python.org/ref/customization.html

If Python had been designed with these types in
mind, I'm not sure "empty list is false" would have been part of the
language, let alone recommend practice.

FWIW, this magic method already existed in 1.5.2 :
http://www.python.org/doc/1.5.2p2/ref/customization.html
 
B

Bruno Desthuilliers

Lawrence said:
One of my rules is, always program like the language actually has a Boolean
type, even if it doesn't.

Python has a boolean type.
That means, never assume that arbitrary values
can be interpreted as true or false,

There's nothing to assume, and nothing arbitrary in it. It's all clearly
defined in whole letters in the language references.
always put in an explicit comparison
if necessary so it's obvious the expression is a Boolean.

The fact that the expression is used in the context of a if statement is
clearly enough to denote a boolean expression. Explicitly testing
against a boolean is uselessly redundant - and doesn't change anything,
since it's always a boolean expression. FWIW, getting rid of theses
"explicit" redundant tests was one of the first things I learned about
programming.
 
C

Carl Banks

Bruno said:
Carl said:
Patrick said:
PTY wrote:


It looks like there are two crowds, terse and verbose. I thought terse
is perl style and verbose is python style. BTW, lst = [] was not what
I was interested in :) I was asking whether it was better style to
use len() or not.

It's not canonical Python to use len() in this case. From PEP 8:

- For sequences, (strings, lists, tuples), use the fact that empty
sequences are false.

Yes: if not seq:
if seq:

No: if len(seq)
if not len(seq)

The whole reason that a sequence supports testing is exactly for this
scenario. This is not an afterthought -- it's a fundamental design
decision of the language.


That might have made sense when Python and string, list, tuple were the
only sequence types around.

Nowadays, Python has all kinds of spiffy types like numpy arrays,
interators, generators,
etc., for which "empty sequence is false" just
doesn't make sense.

Iterators and generators are *not* sequences types. wrt/ non-builtin
container types, I suggest you re-read section 3.3.1 of the language
references:

I'm aware of the semantics, thank you, and that they're as advertised
in the docs. It doesn't matter whether you call it a sequence or not.
Iterables, lists, arrays, and whatever else have overlapping uses, but
bool(obj) behaves differently for different types, making it unsuitable
for writing generic functions that might use some other types that
aren't vanilla list, tuple, and string.

All I'm saying is, knowing there's lots of roughly-list-like types that
don't consider "empty" to be "false", and that there's not reasonable
way to get consistent behavior as to what the boolean value of such
types is, it's possible that these types wouldn't even have a boolean
value and any tests for emptiness would have to be explicit.

But who knows? Maybe this is just one case where brevity trumps
everything else.


Carl BAnks
 
B

Bruno Desthuilliers

Carl said:
Bruno said:
Carl said:
Patrick Maupin wrote:


PTY wrote:



It looks like there are two crowds, terse and verbose. I thought terse
is perl style and verbose is python style. BTW, lst = [] was not what
I was interested in :) I was asking whether it was better style to
use len() or not.

It's not canonical Python to use len() in this case. From PEP 8:

- For sequences, (strings, lists, tuples), use the fact that empty
sequences are false.

Yes: if not seq:
if seq:

No: if len(seq)
if not len(seq)

The whole reason that a sequence supports testing is exactly for this
scenario. This is not an afterthought -- it's a fundamental design
decision of the language.


That might have made sense when Python and string, list, tuple were the
only sequence types around.

Nowadays, Python has all kinds of spiffy types like numpy arrays,
interators, generators,
etc., for which "empty sequence is false" just
doesn't make sense.

Iterators and generators are *not* sequences types. wrt/ non-builtin
container types, I suggest you re-read section 3.3.1 of the language
references:


I'm aware of the semantics, thank you, and that they're as advertised
in the docs. It doesn't matter whether you call it a sequence or not.

Your opinion - not mine. Sequences are iterable, but not all iterables
are sequences.
Iterables, lists, arrays, and whatever else have overlapping uses, but
bool(obj) behaves differently for different types,

bool(obj) will mainly look for __len__(), then for __nonzero__(), then
return True. You can only call len(obj) on objects implementing
__len__(), so relying on (implicit) 'bool(obj)' is clearly more generic
than 'len(obj) > 0'. Also, since bool(obj) will try to call
obj.__len__(), explicitely calling len(obj) doesn't make any difference.
making it unsuitable
for writing generic functions that might use some other types that
aren't vanilla list, tuple, and string.

Using an explicit test against the length of an iterator or generator
*won't* work anyway, since iterators and generators are unsized. And
that's one of the reasons why the difference between sequences and
iterables does matter.

And if you want your own sequence types to be well-behaved sequence
types, just take time to implement the needed protocol(s), including
__len__().

For short: either your function expects an iterable or it expects a
sequence. If it expects an iterable, treat it as an iterable wether it
happens to be a sequence or not. If it expects a sequence, it obviously
won't work with a non-sequence.
All I'm saying is, knowing there's lots of roughly-list-like

"roughly-list-like" ?
types that
don't consider "empty" to be "false",

This means they are unsized (ie don't implement __len__).
and that there's not reasonable
way to get consistent behavior as to what the boolean value of such
types is,

I don't know what "types" you are talkink about, but seems like either
they're not designed to be treated as sequences or they are badly
implemented. In the first case, you're not using them correctly. In the
second case, send a bug report to the authors.
it's possible that these types wouldn't even have a boolean
value and any tests for emptiness would have to be explicit.

Yes ? How ? Calling len() on them ?-)
(hint: reread how len() and bool() works wrt/ __len__())
 

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,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top