This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: "and" operator tests the first argument twice
Type: Stage:
Components: Interpreter Core Versions:
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: amaury.forgeotdarc, loewis, nnorwitz, tim.peters
Priority: normal Keywords:

Created on 2003-11-21 13:08 by amaury.forgeotdarc, last changed 2022-04-11 14:56 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
nonzero.py amaury.forgeotdarc, 2003-11-24 08:40 script showing that "if a and b" can be broken.
nonzero.py amaury.forgeotdarc, 2003-11-24 08:41 script showing that "if a and b" can be broken.
Messages (7)
msg19068 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2003-11-21 13:08
When the first operand of "and" results in False, its truth 
value is calculated again.

Example:
class myBool:
  def __init__(self,value):
    self.value = value
  def __nonzero__(self):
    print 'testing myBool(%s)' % self.value
    return bool(self.value)

if myBool(0) and myBool(1):
  pass

will print:
testing myBool(0)
testing myBool(0)

The same thing occurs with the "or" operator, when the 
first argument has a True truth value:

if myBool(2) and myBool(3):
  pass
will print:
testing myBool(2)
testing myBool(2)

This can be a problem when the "__nonzero__" function 
is slow or has side-effects. I agree this is not often the 
case...

But imagine an object which truth value means "there 
are more data to read in a stream". If python evaluates 
__nonzero__ twice, the expression: "stream and 
otherTest()" can become True *without* evaluating the 
otherTest!
msg19069 - (view) Author: Neal Norwitz (nnorwitz) * (Python committer) Date: 2003-11-21 19:07
Logged In: YES 
user_id=33168

Ouch!  This happens in 2.2 and CVS (I assume 2.3 too).  I'll
look into this.  Fixing this should be a good way to improve
performance. :-)

Thanks for the report!
msg19070 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2003-11-21 19:29
Logged In: YES 
user_id=31435

Don't panic <wink>.  "and" doesn't evaluate anything twice.  
The subtlety here is that "and" and "or" return one of their 
arguments.  If x evaluates to false in "x and y", then "x and y" 
returns x:

>>> class C:
...     def __nonzero__(self): return False
...
>>> x, y = C(), C()
>>> (x and y) is x
True
>>> (x or y) is y
True
>>>

The second evaluation occurs because "if expr:" has to 
evaluate expr.  That part's got nothing to do with "and", it's 
entirely to do with "if".

None of this is going to change, of course.
msg19071 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2003-11-24 08:40
Logged In: YES 
user_id=389140

I don't mind the __nonzero__ be called twice, but the test 
can be totally wrong if the value truth changes:

import random
class C:
  def __nonzero__(self):
    return bool(random.randint(0,1))

if C() and False:
  print "Should we really allow this?"

There are 25% chances for the condition to be true. I find 
this odd.
OK, this kind of script is not likely to happen in real life.
So I attached a script where the object is a kind of pipe; it is 
True if there are data in it. And while we only fill it with "1", 
we sometimes enter the block where a "2" is found!

If this behaviour is expected, it should at least be clearly 
documented!
This remind me the macros in C, where a "variable" can be 
evaluated several times, so we have to be aware of side-
effects. I did not know that "if a and b" was such a macro...
msg19072 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2003-11-24 08:41
Logged In: YES 
user_id=389140

I don't mind the __nonzero__ be called twice, but the test 
can be totally wrong if the value truth changes:

import random
class C:
  def __nonzero__(self):
    return bool(random.randint(0,1))

if C() and False:
  print "Should we really allow this?"

There are 25% chances for the condition to be true. I find 
this odd.
OK, this kind of script is not likely to happen in real life.
So I attached a script where the object is a kind of pipe; it is 
True if there are data in it. And while we only fill it with "1", 
we sometimes enter the block where a "2" is found!

If this behaviour is expected, it should at least be clearly 
documented!
This remind me the macros in C, where a "variable" can be 
evaluated several times, so we have to be aware of side-
effects. I did not know that "if a and b" was such a macro...
msg19073 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2003-11-24 22:28
Logged In: YES 
user_id=21627

This is clearly not a bug. I'm closing it as "works for me":
the "and" operator does *not* evaluate its argument twice
(as Tim explains, and in contrast to what the subject claims).

That the semantics of the if statement (*not* the "and"
expression) is surprising if the result of __nonzero__
changes in subsequent invokcations might be a fact (strictly
speaking, whether you are surprised depends on what you
expect). However, the behaviour of Python is not at all
random in itself, and there are very good reasons for things
being just the way they are. 

If changing zero-ness of objects surprises you: Don't do
that, then.
msg19074 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2003-11-24 22:34
Logged In: YES 
user_id=31435

Just noting that someone who creates objects with insanely 
surprising __nonzero__ behavior can easily avoid the "double 
evaluation" by coding

if bool(x) and bool(y):

instead.  I agree with Martin that there's no bug here, so 
agree with his closing the bug report.
History
Date User Action Args
2022-04-11 14:56:01adminsetgithub: 39578
2003-11-21 13:08:39amaury.forgeotdarccreate