[False,True] and [True,True] --> [True, True]?????

S

Steven D'Aprano

The reason I preferred len(), btw., was only that len() make it clear
that the argument is a sequence.

Maybe I was just too annoyed by lots of Python code I read that looked
like this:

def foo(x, y, z):
if x:
...
else:
...

with poorly named variables where I didn't know what the heck the
variables are (bool, list, instance, ...).


Have you considered that perhaps you don't need to know what the type of
the variables are? Duck typing and all that.

Besides, if the problem is "poorly named variables", the solution *isn't*
to call arbitrary functions on them.

len(x)

x could be a set, a dict, an unsorted list, a sorted list, a tuple, a
deque, a heap, a binary tree, a graph, a stack, a queue, ...
 
S

Steven D'Aprano

Like Gerhard, I prefer the construction that explicitly says, "This is a
list, and this is what I'll do if it's not empty." To me, and I suspect
to a great many programmers, "if x:" does *not* mean "if x is not
empty", it means "if x is (in some sense) True, including the
possibility that x is an object from which a True or False value must be
extracted by means that might not be at all obvious."

That's *exactly* what it means. This is a feature, not a bug.

No matter what x is (excluding buggy classes), "if x" means "test whether
x is true in a boolean context".

If x happens to be a list, that means "x is empty". If x is a float, it
means "x is positive or negative zero". If x is a phlange, it means the
doofer is unset or it has more than three frobs.

You shouldn't care exactly why x is true or false, only that it is. Why
should you have to manually count the frobs when the class can do it for
you?


For an object
lesson in the perils of said extraction, see the recent thread on
[False,True] and [True,True] == [True,True].

That would be this thread :)

The OP's problem was not with boolean contexts, but with the mistaken
idea that the "and" operator does element by element comparison.

Pop quiz: what's the difference between:

if [False, False]:
print "something"

and

if len([False, False]) != 0:
print "something"

?
 
J

John Machin

Peter said:
bdb112 wrote:
Your explanation of Boolean ops on lists was clear.
It leads to some intriguing results:
bool([False])
--> True
I wonder if python 3 changes any of this?
No. Tests like
if items:
   ...
to verify that items is a non-empty list are a widespread idiom in Python.
They rely on the behaviour you observe.
Are they widespread? I haven't noticed, yet.
I prefer to write it explicitly:
if len(lst) > 0:
    ...
if item is None:
    ...
etc.

Consider this snippet:

while stack and some_condition(stack[-1]):
# do stuff including popping and breaking
else:
# raise an exception

Would you use an explicit len(stack) > 0 ?
 
P

Peter Pearson

That's *exactly* what it means. This is a feature, not a bug.

No matter what x is (excluding buggy classes), "if x" means "test whether
x is true in a boolean context".

If x happens to be a list, that means "x is empty". If x is a float, it
means "x is positive or negative zero". If x is a phlange, it means the
doofer is unset or it has more than three frobs.

You shouldn't care exactly why x is true or false, only that it is. Why
should you have to manually count the frobs when the class can do it for
you?

That whimsical example is surprisingly persuasive for me.

For an object
lesson in the perils of said extraction, see the recent thread on
[False,True] and [True,True] == [True,True].

That would be this thread :)

Oh, dear. Indeed, it is exactly this increasing scarcity of
at-hand working memory that makes me resist non-obvious features
in programming languages. Perhaps it's time to take up golf.
 
S

Steven D'Aprano

In message <25f4735b-52a2-4d53-9097-
Is there any obvious reason why
[False,True] and [True,True]
gives [True, True]

This kind of confusion is why conditional constructs should not accept
any values other than True and False

You've already said this, more than a week ago, in the thread titled "Why
does Python show the whole array?". It wasn't correct then, and it isn't
correct now. Any programming feature is subject to errors from people who
try to guess what it does instead of reading the Fine Manual, and Python
has no obligation to make every language feature match the random
preconceptions of every user. Or even some subset of users.

If people guess wrongly what an operator does, then let them learn what
it actually *does* do instead of crippling the operator's functionality.
 
L

Leo

Steven said:
For built-in lists, but not necessarily for arbitrary list-like sequences.

There's also the performance issue. I might have a sequence-like
structure where calling len() takes O(N**2) time, (say, a graph) but
calling __nozero__ might be O(1). Why defeat the class designer's
optimizations?
Is that true?
Calling len() actually traverses the whole list?
 
P

Peter Otten

Leo said:
Is that true?
Calling len() actually traverses the whole list?

No. Steven constructed the theoretical case where determining whether a
sequence (not Python's list) is empty is a cheap operation while getting
the actual length is costly.

The only "popular" data structure I know that implements its length like
this are strings in C.

Peter
 
H

Hrvoje Niksic

Peter Otten said:
The only "popular" data structure I know that implements its length
like this are strings in C.

Linked lists and trees also tend to do the same, with the exception of
those that explicitly store their length to optimize length queries.
 
L

Lawrence D'Oliveiro

Steven said:
In message <25f4735b-52a2-4d53-9097-
Is there any obvious reason why
[False,True] and [True,True]
gives [True, True]

<http://groups.google.co.nz/group/comp.lang.python/msg/396c69e9498d9ad4>

Any programming feature is subject to errors from people who
try to guess what it does instead of reading the Fine Manual, and Python
has no obligation to make every language feature match the random
preconceptions of every user. Or even some subset of users.

<http://groups.google.co.nz/group/comp.lang.python/msg/b12e7b7cbcc82eb0>
 
L

Lie Ryan

Steven said:
For built-in lists, but not necessarily for arbitrary list-like sequences.

I think a list-like sequence that does not emulate this behavior (False
when empty, True otherwise) should be considered a buggy implementation
of the list interface.
 
L

Lie Ryan

Gerhard said:
len() make it clear
that the argument is a sequence.

Not necessarily. Classes that overrides __len__ may fool that assumption
(well, in python classes that overrides special functions may do
anything it is never intended to do).

I often think an "if item:" as "is item workable?". What the exact
definition of "workable" is just something I don't care, at least until
I need to debug the code. A container is workable if it contains
something; most of the time, an empty sequence does not have any value
(pun intended) and thus unworkable.
 
S

Steven D'Aprano

Is that true?
Calling len() actually traverses the whole list?

Not for built-in lists, but for sequences created by somebody else, who
knows what it does?
 
P

Paul Rubin

Steven D'Aprano said:
len() works on dicts and sets, and they're not sequences.

Of course dicts and sets are sequences. But there are also sequences
on which len doesn't work.

You could use: sum(1 for x in seq)
Of course iterating through seq may have undesired side effects.
 
P

Peter Otten

Paul said:
Steven D'Aprano said:
len() works on dicts and sets, and they're not sequences.

Of course dicts and sets are sequences. But there are also sequences
from collections import *
for obj in [(), [], {}, set()]:
.... print(("%r: " % (obj,)) + ", ".join(a.__name__ for a in [Iterable,
Container, Sequence] if isinstance(obj, a)))
....
(): Iterable, Container, Sequence
[]: Iterable, Container, Sequence
{}: Iterable, Container
set(): Iterable, Container

Peter
 
S

Steven D'Aprano

Of course dicts and sets are sequences.

Dicts and sets are explicitly described as "other containers":

http://docs.python.org/library/stdtypes.html#sequence-types-str-unicode-
list-tuple-buffer-xrange

Note that addition is included in the table of sequence operations, but
neither dicts nor sets support it:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

Nor do they support slicing.

But there are also sequences on which len doesn't work.

You could use: sum(1 for x in seq)
Of course iterating through seq may have undesired side effects.

It's also not guaranteed to terminate.

Well, technically no user-defined function is guaranteed to terminate,
but you know what I mean :)
 
M

Martin v. Löwis

That was my intuition, too. But Python takes a different stance:

It's a sequence if it can be indexed by numbers in range(len(seq)).
Neither dicts nor sets can be indexed that way.

Regards,
Martin
 
J

jazbees

I'm surprised to see that the use of min and max for element-wise
comparison with lists has not been mentioned. When fed lists of True/
False values, max will return True if there is at least one True in
the list, while min will return False if there is at least one False.
Going back to the OP's initial example, one could wrap a min check on
each list inside another min.
A = [False, True]
B = [True, True]
min(A) False
min(B) True
min(min(A), min(B))
False

In a recent work project, I made use of this behavior to condense the
code required to test whether any item in a list of strings could be
found in another string. Here's a variation on that concept that
checks to see if a string contains any vowels:
hasvowels = lambda x:max([y in x for y in "aeiou"])
hasvowels("parsnips") True
hasvowels("sfwdkj")
False

If using Python 2.5 or later, this could be combined with Python's
version of the ternary operator if your code is such that the source
list may be empty, which is what I ended up using for my project.
foo = ["green", "orange"]
bar = ["blue", "green", "red", "yellow"]
found = lambda x,y:max([z in y for z in x] if x else [False])
found(foo, bar) True
foo = []
found(foo, bar)
False
 
J

John Posner

jazbees said:
> I'm surprised to see that the use of min and max for element-wise
> comparison with lists has not been mentioned. When fed lists of True/
> False values, max will return True if there is at least one True in
> the list, while min will return False if there is at least one False.
> Going back to the OP's initial example, one could wrap a min check on
> each list inside another min.

I agree with Duncan Booth that any() is equivalent to -- and better than
-- max() for handling boolean values. But what boolean-oriented function
is equivalent to min()? How about this, which returns the negation of
the min() result:

def at_least_one_false(value_list):
"""
return True if at least one value in value_list is logically FALSE
"""
return any( [not val for val in value_list] )

This works, but the short-circuit feature of any() is undermined by the
process-the-whole-sequence behavior of the list comprehension. So, let's
delete the square brackets, converting the list comprehension into a
generator expression:

return any( not val for val in value_list )

According to timeit.Timer.timeit(), this change improves performance
from 14.59 seconds to 10.49 seconds, with a long value_list:

[True]*20 + [False] + [True]*30

But the generator expression produces worse performance with a shorter
value_list:

[True]*2 + [False] + [True]*3
 

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

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,012
Latest member
RoxanneDzm

Latest Threads

Top