Questo non ha nulla a che fare con l'incapsulamento, e tutto con un dettaglio di implementazione Python come quando le stringhe letterali producono un oggetto stringa.
Ciò che sta accadendo in questo caso è che Python utilizza costanti per memorizzare valori letterali usati nel codice. Il tuo valore 'the juice bar' è una costante di questo tipo.
Il compilatore ha memorizzato 'the juice bar' con l'oggetto del blocco di codice per il modulo e sta riutilizzando quell'oggetto. Non stai creando nuovi valori di stringa nel blocco __name__ == '__main__' .
Le funzioni ottengono i propri blocchi di codice, che includono le proprie costanti e quindi i propri oggetti stringa.
Manifestazione:
>>> 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
Questo è uno smontaggio del bytecode solo per il test fr ; nota i riferimenti LOAD_CONST ; questi caricano una costante (memorizzata in posizione 0 in entrambi gli insiemi fr e per confrontarli in seguito se fr is 'the juice bar' ) è lo stesso oggetto.
L'oggetto funzione ha un costrutto co_const simile, che è ciò che Python restituisce doverosamente quando viene chiamato:
>>> 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
Di conseguenza, la stringa 'the foo diner' nel metodo non è lo stesso oggetto della stringa 'the foo diner' utilizzata nel blocco __name__ == '__main__' più in basso.
Armati di questa conoscenza puoi generare interamente nuovi oggetti stringa:
>>> a = 'foo'
>>> b = 'bar'
>>> ab = a + ' ' + b
>>> ab is a + ' ' + b
False
>>> ab == a + ' ' + b
True
In generale, hai ragione. is verifica sempre l'identità, == per l'uguaglianza. Se il test di identità passa, allora è solitamente true che anche gli oggetti sono uguali.
C'è una grande eccezione nella libreria standard:
>>> nan = float('nan')
>>> nan is nan
True
>>> nan == nan
False
La costante a virgola mobile Not-a-number non è mai uguale a nulla. Neanche con se stesso.
Va notato che Python traduce op1 == op2 in una chiamata al object.__eq__() speciale metodo hook , che è libero di restituire ciò che gli piace. Di solito è un booleano, ma non è obbligatorio:
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.