Seemingly odd 'is' comparison.

A

Arnaud Delobelle


[You don't need to wrap your floats in float()]
... return 3.0 is 3.0, 3.0*1.0 is 3.0
... 2 0 LOAD_CONST 1 (3.0)
3 LOAD_CONST 1 (3.0)
6 COMPARE_OP 8 (is)
9 LOAD_CONST 3 (3.0)
12 LOAD_CONST 1 (3.0)
15 COMPARE_OP 8 (is)
18 BUILD_TUPLE 2
21 RETURN_VALUE

As you can see when "3.0 is 3.0" is evaluated the same float object is
put on the stack twice so the 'is' comparison is True (LOAD_CONST 1 /
LOAD_CONST 1 / COMPARE_OP 8).

Whereas when "3.0*1.0 is 3.0" is evaluated, *two* different float
objects are put on the stack and compared (LOAD_CONST 3 / LOAD_CONST
1 / COMPARE_OP 8). Therefore the result is False.

HTH
 
D

Duncan Booth

Tobiah said:
Subject: Seemingly odd 'is' comparison.

Please put your question into the body of the message, not just the
headers.
Thanks,

Tobiah
Your values are already all floats so float() just returns its arguments.
In other words you can omit it:
False

3.0 used twice in the same compilation unit is the same constant value used
twice. 3.0 * 1.0 creates a new float value.

Compare with:False

Here two separate compilations result in two separate values.

In general any immutable results of calculations which are the same may or
may not share the same object and this can vary according to the version of
Python or the phase of the moon. Only use 'is' when you actually care about
object identity, don't use it for a shorthand for '=='.
 
C

Christian Heimes

Thumb rule: Never compare strings, numbers or tuples with "is". Only
compare an object with a singleton like a type or None. "is" is not a
comparison operator.

Christian
 
E

Erik Max Francis

It's implementation dependent what values these expressions will take.

If you're trying to test equality, use `==`, not `is`.
 
S

Steven D'Aprano

Thumb rule: Never compare strings, numbers or tuples with "is". Only
compare an object with a singleton like a type or None. "is" is not a
comparison operator.


I know why you're saying it, I agree with your reasons, but I wouldn't
say it that way.

Never use "is" when you want to test for EQUALITY, regardless of whether
the objects are strings, numbers, tuples, or anything else. To test for
equality, use "==".

Always use "is" when you wish to compare objects for IDENTITY, such as
testing to see whether an object IS None (as opposed to some random
object which just happens to compare equal to None).

"is" is a comparison operator: it compares identity, not equality. It is
more or less equivalent to the expression id(x) == id(y).

Except for documented singletons such as modules and None, which objects
have the same identity is platform dependent, version dependent, and even
dependent on the execution history of your code.
 
A

Asun Friere

Except for documented singletons such as modules and None, which objects
have the same identity is platform dependent, version dependent, and even
dependent on the execution history of your code.

The advice not to identity test strings and numbers (since they are
interred in the main implementation), or tuples, since they
potentially could be, seems sound enough. But given the nature of
immutables, is the identity of these even potentially implementation
dependant (ie. they couldn't be interred could they)? One might
conceivably want to know that a list into which one is about to stuff
something is the same (or perhaps not the same) list as that pointed
to by another name, which operation, hopefully, remains possible
across the range of potential implementations.
 
A

Asun Friere

Except for documented singletons such as modules and None, which objects
have the same identity is platform dependent, version dependent, and even
dependent on the execution history of your code.

The advice not to identity test strings and numbers (since they are
interred in the main implementation), or tuples, since they
potentially could be, seems sound enough. But given the nature of
mutables, is the identity of these even potentially implementation
dependant (ie. they couldn't be interred could they)? One might
conceivably want to know that a list into which one is about to stuff
something is the same (or perhaps not the same) list as that pointed
to by another name, which operation, hopefully, remains possible
across the range of potential implementations.
 
T

Terry Reedy

| On Feb 19, 9:44 am, Steven D'Aprano <st...@REMOVE-THIS-
| cybersource.com.au> wrote:
|
| > Except for documented singletons such as modules and None, which
objects
| > have the same identity is platform dependent, version dependent, and
even
| > dependent on the execution history of your code.
|
| The advice not to identity test strings and numbers (since they are
| interred in the main implementation),

They may or may not be. But for almost all purposes, that is irrelevant.

| or tuples, since they potentially could be, seems sound enough.

Ditto for tuples, unless possibly when they have mutable members.

| But given the nature of
| immutables, is the identity of these even potentially implementation
| dependant (ie. they couldn't be interred could they)?

The word is 'interned', not 'interred' (buried).

| One might
| conceivably want to know that a list into which one is about to stuff
| something is the same (or perhaps not the same) list as that pointed
| to by another name, which operation, hopefully, remains possible
| across the range of potential implementations.

Lists are mutable, and identity is often important. Id(list) can be used
to debug or solve puzzling behavior.

tjr
 
A

Asun Friere

| The advice not to identity test strings and numbers (since they are
| interred in the main implementation),

They may or may not be.

Obviously, and that is the problem. The behaviour will appear
inconsistent unless one is familiar with the conditions under which
they are or are not. So since the numbers and strings are interned
(under certain conditions), it is probably best not to identity test
them.
Ditto for tuples, unless possibly when they have mutable members.

Which is the reason that they are never interned in CPython, no? So I
was wrong, the categorical avoidance of identity testing is probably
_not_ sound advice with regard to tuples.
| But given the nature of
| immutables, is the identity of these even potentially implementation
| dependant (ie. they couldn't be interred could they)?

The word is 'interned', not 'interred' (buried).

Sorry I'm a goth, so you can understand my mistake ;=

So was that a yes or no? I mean is it even possible for the identity
behaviour of mutables to vary between implementations? I can't see
how they can possibly be interned, but is there some other factor I'm
missing in regard to identity behaviour which could in fact vary
between implementations?
 
T

Terry Reedy

| So was that a yes or no? I mean is it even possible for the identity
| behaviour of mutables to vary between implementations? I can't see
| how they can possibly be interned, but is there some other factor I'm
| missing in regard to identity behaviour which could in fact vary
| between implementations?

Not that I can think of, so 'No'. The semantics of creation, rebinding,
and copying are well defined and the behavior is predictable once one
understands the rules. The problem some new Pythoneers have is mistakenly
thinking that binding statements ('=') make copies, or that assignments in
the body of a class statement or function header (default values) are made
more than once (per class or function definition execution) or that
somelist*n copies the contents. Id() can help elucidate the rules even if
it is not so useful in running code.

The difference between mutables and immutables is that the interpreter may
optionally not create a new immutable when it would have created a new
mutable, but may instead return a reference to an existing immutable of the
same value. I call this an application of the 'as if' rule because (except
for calls to id() and use of 'is') the future behavior of the program is
the same as if the interpreter has created the new immutable. (The other
exception to 'the same' is that the program may run instead of crash due
the the space saving. ;-)

tjr
 
B

Boris Borcic

Arnaud said:

[You don't need to wrap your floats in float()]
... return 3.0 is 3.0, 3.0*1.0 is 3.0
...2 0 LOAD_CONST 1 (3.0)
3 LOAD_CONST 1 (3.0)
6 COMPARE_OP 8 (is)
9 LOAD_CONST 3 (3.0)
12 LOAD_CONST 1 (3.0)
15 COMPARE_OP 8 (is)
18 BUILD_TUPLE 2
21 RETURN_VALUE

As you can see when "3.0 is 3.0" is evaluated the same float object is
put on the stack twice so the 'is' comparison is True (LOAD_CONST 1 /
LOAD_CONST 1 / COMPARE_OP 8).

Whereas when "3.0*1.0 is 3.0" is evaluated, *two* different float
objects are put on the stack and compared (LOAD_CONST 3 / LOAD_CONST
1 / COMPARE_OP 8). Therefore the result is False.

Looks good, but doesn't pass the sanity check ;) Consider
return 3 is 3, 3*1 is 3
2 0 LOAD_CONST 1 (3)
3 LOAD_CONST 1 (3)
6 COMPARE_OP 8 (is)
9 LOAD_CONST 3 (3)
12 LOAD_CONST 1 (3)
15 COMPARE_OP 8 (is)
18 BUILD_TUPLE 2
21 RETURN_VALUE(True, True)

Cheers, BB
 
D

Duncan Booth

Boris Borcic said:
Looks good, but doesn't pass the sanity check ;) Consider

return 3 is 3, 3*1 is 3

2 0 LOAD_CONST 1 (3)
3 LOAD_CONST 1 (3)
6 COMPARE_OP 8 (is)
9 LOAD_CONST 3 (3)
12 LOAD_CONST 1 (3)
15 COMPARE_OP 8 (is)
18 BUILD_TUPLE 2
21 RETURN_VALUE
(True, True)

s/different/possibly different depending on implementation details/

Arnaud's point remains valid: in the first comparison you can see that the
same object is used, in the second case all bets are off.
 
B

Boris Borcic

Duncan said:
s/different/possibly different depending on implementation details/

"0 substitutions made", but whatever

s/on implementation details/on further implementations details/
Arnaud's point remains valid: in the first comparison you can see that the
same object is used, in the second case all bets are off.

Sure, but the issue was to explain the strangeness that

(3.0 is 3.0)!=(3.0*1.0 is 3.0)

and I just pointed out - without drawing any conclusion - that a structurally
identical explanation would "explain" the contrafactual (3 is 3)!=(3*1 is 3).

Best, BB
 
C

Christian Heimes

Steven said:
"is" is a comparison operator: it compares identity, not equality. It is
more or less equivalent to the expression id(x) == id(y).

Yes, the implementation of the is operator comes down to the comparison
of PyObject* pointer addresses and in CPython id() returns the address
of the PyObject*.

But it's very important to understand that "a is b" can have a different
result than "id(a) == id(b)". CPython uses all sorts of tricks to speed
up common operations. Memory allocation/de-allocation and object
creation can have a huge speed impact. Therefor Python uses free lists
to keep some empty objects around for recycling. Small integers are
cached, too.

Compare
135689200

with
3084440752

to see the effect of small int cache vs. int free list.

Christian
 
C

Christian Heimes

Asun said:
So was that a yes or no? I mean is it even possible for the identity
behaviour of mutables to vary between implementations? I can't see
how they can possibly be interned, but is there some other factor I'm
missing in regard to identity behaviour which could in fact vary
between implementations?

In CPython some strings are interned (see the intern builtin). The free
list of lists, tuples, dict and the block allocation of ints and floats
can have an interning like effect, too.

IronPython and Jython may not have interning or free list at all. Even
in CPython the interning and free lists are an implementation detail
that may chance between releases. In a matter of fact I'm messing around
with free lists to speed up Python 2.6/3.0 and to give back memory earlier.

Christian
 
P

Paul Rubin

Christian Heimes said:
3084440752

Python 2.4.4 (#1, Oct 23 2006, 13:58:00) False

From the docs:

id( object) Return the ``identity'' of an object. This is an integer
(or long integer) which is guaranteed to be unique and constant
for this object during its lifetime. Two objects with
non-overlapping lifetimes may have the same id()
value. (Implementation note: this is the address of the object.)

What has happened is

allocates a memory cell and puts 2000 in it, prints out the id, then
deallocates the cell. id(2001) then re-uses the same cell. With the
equality comparison, both cells are alive at the same time, so the
uniqueness requirement guarantees that their id's are unequal.

A Python implementation with a compacting garbage collector might
choose not to store a unique id in any object, unless you actually
call the id function and remember the value. So, calling id(x) could
end up costing some cpu time and memory by permanently attaching an id
number to x.
 
A

Arnaud Delobelle

[You don't need to wrap your floats in float()]
...     return 3.0 is 3.0, 3.0*1.0 is 3.0
...
f() (True, False)
import dis
dis.dis(f)
  2           0 LOAD_CONST               1 (3.0)
              3 LOAD_CONST               1 (3.0)
              6 COMPARE_OP               8 (is)
              9 LOAD_CONST               3 (3.0)
             12 LOAD_CONST               1 (3.0)
             15 COMPARE_OP               8 (is)
             18 BUILD_TUPLE              2
             21 RETURN_VALUE
As you can see when "3.0 is 3.0" is evaluated the same float object is
put on the stack twice so the 'is' comparison is True (LOAD_CONST 1 /
LOAD_CONST 1 / COMPARE_OP 8).
Whereas when "3.0*1.0 is 3.0" is evaluated, *two* different float
objects are put on the stack and compared (LOAD_CONST 3 / LOAD_CONST
1 / COMPARE_OP 8).  Therefore the result is False.

Looks good, but doesn't pass the sanity check ;) Consider

 >>> def f():
        return 3 is 3, 3*1 is 3

 >>> import dis
 >>> dis.dis(f)
   2           0 LOAD_CONST               1 (3)
               3 LOAD_CONST               1 (3)
               6 COMPARE_OP               8 (is)
               9 LOAD_CONST               3 (3)
              12 LOAD_CONST               1 (3)
              15 COMPARE_OP               8 (is)
              18 BUILD_TUPLE              2
              21 RETURN_VALUE
 >>> f()
(True, True)

Cheers, BB

Looks good, but doesn't pass the sanity check ;) Consider:
... return 1000000 is 1000000, 1000000*1 is 1000000
... 2 0 LOAD_CONST 1 (1000000)
3 LOAD_CONST 1 (1000000)
6 COMPARE_OP 8 (is)
9 LOAD_CONST 3 (1000000)
12 LOAD_CONST 1 (1000000)
15 COMPARE_OP 8 (is)
18 BUILD_TUPLE 2
21 RETURN_VALUE

Small integers are special because they are interned, that's why you
got True for the second comparison. With a larger integer that
doesn't happen.
 

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
474,432
Messages
2,571,681
Members
48,796
Latest member
Greg L.

Latest Threads

Top