I was bitten by using the is
operator when i should have been using ==
. Am aware that the former tests for equality of the objects’ identities and that the latter tests for equality of the objects’ contents.
Using is
, i thought, also implicitly tests for the equality of the contents. For example if a is b
is true, then the contents must be the same. This was the working assumption and i believe that it’s still true. But what was discovered, and should have been known, is that if if a is b
is false, then the contents of a and b may or may not be the same.
Example (Python 3.3.3):
class Food:
def favourite_restaurant(self):
return "the foo diner"
if __name__ == '__main__':
fr = "the juice bar"
print('%r is %r: %r' % (fr, 'the juice bar', (fr is 'the juice bar')))
print('%r == %r: %r' % (fr, 'the juice bar', (fr == 'the juice bar')))
f = Food()
result = f.favourite_restaurant()
print('%r is %r: %r' % (result, 'the foo diner', (result is 'the foo diner')))
print('%r == %r: %r' % (result, 'the foo diner', (result == 'the foo diner')))
The first print block says:
'the juice bar' is 'the juice bar': True
'the juice bar' == 'the juice bar': True
The second print block says:
result: 'the foo diner'
'the foo diner' is 'the foo diner': False
'the foo diner' == 'the foo diner': True
It seems that objects instantiated within the instance of a class have their own group of ids, separate from objects outside the class.
My understanding that encapsulation in python is really a matter for ‘consenting adults’. Do we actually have encapsulation at some lower level?
1
This has nothing to do with encapsulation, and everything with a Python implementation detail as to when string literals produce a string object.
What is specifically happening here is that Python uses constants to store literal values used in code. Your 'the juice bar'
value is such a constant.
The compiler has stored 'the juice bar'
with the code block object for the module, and is reusing that object. You are not creating new string values in the __name__ == '__main__'
block.
Functions get their own code blocks, which includes their own constants, and thus their own string objects.
Demonstration:
>>> import dis
>>> code = compile('''
... fr = "the juice bar"
... print('%r is %r: %r' % (fr, 'the juice bar', (fr is 'the juice bar')))
... print('%r == %r: %r' % (fr, 'the juice bar', (fr == 'the juice bar')))
... ''', '<stdin>', 'exec')
>>> code.co_consts
('the juice bar', '%r is %r: %r', '%r == %r: %r', None)
>>> dis.dis(code)
1 0 LOAD_CONST 0 ('the juice bar')
3 STORE_NAME 0 (fr)
2 6 LOAD_NAME 1 (print)
9 LOAD_CONST 1 ('%r is %r: %r')
12 LOAD_NAME 0 (fr)
15 LOAD_CONST 0 ('the juice bar')
18 LOAD_NAME 0 (fr)
21 LOAD_CONST 0 ('the juice bar')
24 COMPARE_OP 8 (is)
27 BUILD_TUPLE 3
30 BINARY_MODULO
31 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
34 POP_TOP
3 35 LOAD_NAME 1 (print)
38 LOAD_CONST 2 ('%r == %r: %r')
41 LOAD_NAME 0 (fr)
44 LOAD_CONST 0 ('the juice bar')
47 LOAD_NAME 0 (fr)
50 LOAD_CONST 0 ('the juice bar')
53 COMPARE_OP 2 (==)
56 BUILD_TUPLE 3
59 BINARY_MODULO
60 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
63 POP_TOP
64 LOAD_CONST 3 (None)
67 RETURN_VALUE
This is a disassembly of the bytecode for just the fr
test; note the LOAD_CONST
references; these load a constant (stored at position 0
to both set fr
and to later compare if fr is 'the juice bar'
) is the same object.
The function object has a similar co_const
construct, which is what Python dutifully returns when called:
>>> class Food:
... def favourite_restaurant(self):
... return "the foo diner"
...
>>> Food.favourite_restaurant.__code__.co_consts
(None, 'the foo diner')
>>> dis.dis(Food.favourite_restaurant)
3 0 LOAD_CONST 1 ('the foo diner')
3 RETURN_VALUE
As a result, the 'the foo diner'
string in the method is not the same object as the 'the foo diner'
string used in your __name__ == '__main__'
block further down.
Armed with this knowledge you can generate wholly new string objects:
>>> a = 'foo'
>>> b = 'bar'
>>> ab = a + ' ' + b
>>> ab is a + ' ' + b
False
>>> ab == a + ' ' + b
True
Generally speaking, you are right. is
always tests for identity, ==
for equality. If the identity test passes, then it is usually true that the objects are equal too.
There is one big exception in the standard library:
>>> nan = float('nan')
>>> nan is nan
True
>>> nan == nan
False
The Not-a-number floating point constant is never equal to anything. Not even with itself.
It should be noted that Python translates op1 == op2
into a call to the object.__eq__()
special hook method, which is free to return whatever it likes. Usually that’s a boolean, but that is not required:
By convention,
False
andTrue
are returned for a successful comparison. However, these methods can return any value, so if the comparison operator is used in a Boolean context (e.g., in the condition of an if statement), Python will callbool()
on the value to determine if the result is true or false.