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
and True
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 call bool()
on the value to determine if the result is true or false.
A is B
==id(A) == id(B)
(whereid
returns an integer unique to that object for the duration that object exists)