[Tutor] if...elif question

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Tue, 2 Oct 2001 19:13:57 -0700 (PDT)


On Wed, 3 Oct 2001, Kalle Svensson wrote:

> [Ignacio Vazquez-Abrams]
> > On Tue, 2 Oct 2001, Chris Keelan wrote:
> > >     elif percent >= 60 < 70:
> > It should read "elif percent>=60 and percent<70:". As you have it, it
> > evaluates "percent>=60" and then compares that against 70. Since the result
> > of the first is always less than 70 (either 0 or 1), it is always true.
> 
> It does?  I thought python special-cased here and evaluated it as
> ((percent >= 60) and (60 < 70))
> 
> Same result, anyway.


[WARNING WARNING: If you're starting out with Python, please avoid this
message.  I was just curious to see how Python actually understood the
expression "percent >= 60 < 70".]





I've always wanted to play with the Python disassembler, and now's finally
an opportunity to try it out.  We can use the disassembler to show exactly
how Python interprets our programs, almost at the "atomic" level:

###
>>> import dis
>>> def mycmp(x):
...     return x >= 60 < 70
... 
>>> dis.dis(mycmp)
          0 SET_LINENO               1

          3 SET_LINENO               2
          6 LOAD_FAST                0 (x)
          9 LOAD_CONST               1 (60)
         12 DUP_TOP             
         13 ROT_THREE           
         14 COMPARE_OP               5 (>=)
         17 JUMP_IF_FALSE           10 (to 30)
         20 POP_TOP             
         21 LOAD_CONST               2 (70)
         24 COMPARE_OP               0 (<)
         27 JUMP_FORWARD             2 (to 32)
    >>   30 ROT_TWO             
         31 POP_TOP             
    >>   32 RETURN_VALUE        
         33 LOAD_CONST               0 (None)
         36 RETURN_VALUE
>>> def mycmp2(x):
...     return x >= 60 and 60 < 70
... 
>>> dis.dis(mycmp2)
          0 SET_LINENO               1

          3 SET_LINENO               2
          6 LOAD_FAST                0 (x)
          9 LOAD_CONST               1 (60)
         12 COMPARE_OP               5 (>=)
         15 JUMP_IF_FALSE           10 (to 28)
         18 POP_TOP             
         19 LOAD_CONST               1 (60)
         22 LOAD_CONST               2 (70)
         25 COMPARE_OP               0 (<)
    >>   28 RETURN_VALUE        
         29 LOAD_CONST               0 (None)
         32 RETURN_VALUE
###


Wow.  After staring at:

    http://python.org/doc/current/lib/bytecodes.html

for a few minutes, I think I sorta understand what this is doing.  Yes,
Python does make a special case when it sees 'a <= b < c', since we can
see that the output of the first is different from the second in many
details.

It feels like the first example is much more complicated compared to the
second, at least in terms the byte-compiled code length.  The first, for
example, makes its stack dance around a lot with ROTations here and there.  

But I can see why it goes through all the hoops of DUPlicating and
ROTating elements in its internal stack --- it's all there to make sure
that

    a <= b < c        and        z <= b and b < c

behave differently in Python.  In the first case, Python will evaluate 'b'
only once.  In the second case, 'b' can be evaluated twice.  What the
first case is doing by "dup"licating is trying to avoid recomputing 'b'.
For example:

###
>>> def return10():
...     print "hello"
...     return 10
... 
>>> 1 <= return10() <= 100
hello
1
>>> 1 <= return10() and return10() <= 100
hello
hello
1
###

A somewhat silly example, but still sorta neat in a perverse way.  *grin*