5.19. OOP Object Identity

  • = assignment

  • == checks for object equality

  • is checks for object identity

>>> valid = True
>>>
>>> valid == True
True
>>> valid is True
True

5.19.1. Identity

  • id(obj) -> int

  • id() will change every time you execute script

  • id() returns an integer which is guaranteed to be unique and constant for object during its lifetime

  • Two objects with non-overlapping lifetimes may have the same id() value

  • In CPython it's also the memory address of the corresponding C object

>>> id('Watney')  
4499664048
>>>
>>> hex(id('Watney'))  
'0x10c336cb0'

5.19.2. Identity Check

  • is checks for object identity

  • is compares id() output for both objects

  • CPython: compares the memory address a object resides in

  • Testing strings with is only works when the strings are interned

  • Since Python 3.8 - Compiler produces a SyntaxWarning when identity checks (is and is not) are used with certain types of literals (e.g. str, int). These can often work by accident in CPython, but are not guaranteed by the language spec. The warning advises users to use equality tests (== and !=) instead.

>>> name = None
>>>
>>> name is None
True
>>> name is not None
False

5.19.3. Caching

>>> a = 256
>>>
>>> a == 256
True
>>>
>>> a is 256  
SyntaxWarning: "is" with a literal. Did you mean "=="?
True
>>> b = 257
>>>
>>> b == 257
True
>>>
>>> b is 257  
SyntaxWarning: "is" with a literal. Did you mean "=="?
False

5.19.4. Integer Caching

  • Values between -5 and 256 are cached from start

  • After using any integer two times it is being cached

  • Python caches also the next integer

  • Cached numbers are invalidated after a while

>>> id(256)  
4514832592
>>>
>>> id(256)  
4514832592
>>>
>>> id(256)  
4514832592
>>>
>>> id(256)  
4514832592
>>>
>>>
>>> id(257)  
4561903248
>>>
>>> id(257)  
4561904272
>>>
>>> id(257)  
4561903344
>>>
>>> id(257)  
4561903344
>>> id(-5)  
4423729200
>>>
>>> id(-5)  
4423729200
>>>
>>>
>>> id(-6)  
4463320144
>>>
>>> id(-6)  
4463321840

5.19.5. Float Caching

  • It takes a bit more hits for float to start being cached

  • Cached numbers are invalidated after a while

>>> id(1.0)  
4491972048
>>>
>>> id(1.0)  
4492804656
>>>
>>> id(1.0)  
4491972048
>>>
>>> id(1.0)  
4492804656
>>>
>>> id(1.0)  
4492811728
>>>
>>> id(1.0)  
4492817392
>>>
>>> id(1.0)  
4492811792
>>>
>>> id(1.0)  
4492817392
>>>
>>> id(1.0)  
4492817616

5.19.6. Bool Type Identity

  • Bool object is a singleton

  • It always has the same identity (during one run)

>>> id(True)  
4469679168
>>>
>>> id(True)  
4469679168
>>> id(False)  
4469679896
>>>
>>> id(False)  
4469679896

5.19.7. None Type Identity

  • NoneType object is a singleton

  • It always has the same identity (during one run)

>>> id(None)  
4469761584
>>>
>>> id(None)  
4469761584

5.19.8. String Type Identity

>>> a = 'Mark Watney'
>>> b = 'Mark Watney'
>>>
>>> a == b
True
>>> a is b
False
>>> 'Mark Watney' is 'Mark Watney'  
<...>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
True

5.19.9. String Interning

  • Caching mechanism

  • String intern pool

  • String is immutable

Each time an instance of a string is created Python will create a new object with completely new identity:

>>> id('Watney')  
4354445296
>>>
>>> id('Watney')  
4354447728

However if we create an identifier, then each time a string is created it will result with the same interned string. Value of an identifier will add to the string interning pool, from which Python returns a new objects:

>>> name = 'Watney'
>>>
>>> id('Watney')  
4354447984
>>>
>>> id('Watney')  
4354447984

However if we delete entry from string interning pool, Python will now create a new instance of a string each time:

>>> del name
>>>
>>> id('Watney')  
4354449136
>>>
>>> id('Watney')  
4354449328

5.19.10. Type Identity

>>> name = ...
>>>
>>> type(name) is int
False
>>> type(name) is float
False
>>> type(name) is complex
False
>>> type(name) is bool
False
>>> type(name) is None
False
>>> type(name) is str
False
>>> type(name) is bytes
False
>>> type(name) is list
False
>>> type(name) is tuple
False
>>> type(name) is set
False
>>> type(name) is frozenset
False
>>> type(name) is dict
False

5.19.11. Object Identity

>>> class Astronaut:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
>>>
>>>
>>> astro1 = Astronaut('Mark', 'Watney')
>>> astro2 = Astronaut('Mark', 'Watney')
>>>
>>> astro1 is astro2
False
>>>
>>> id(astro1)  
4421890496
>>> id(astro2)  
4421893328
>>>
>>> hex(id(astro1))  
'0x10790b1c0'
>>> hex(id(astro2))  
'0x10790bcd0'
>>>
>>> print(astro1)  
<Astronaut object at 0x107905820>
>>> print(astro2)  
<Astronaut object at 0x10790bcd0>
>>> class Astronaut:
...     pass
>>>
>>> class Cosmonaut:
...     pass
>>>
>>>
>>> Astronaut is Astronaut
True
>>>
>>> Cosmonaut is Cosmonaut
True
>>>
>>> Astronaut is Cosmonaut
False
>>>
>>> id(Astronaut)  
140570740200304
>>>
>>> id(Cosmonaut)  
140570185653984

5.19.12. Object Equality

>>> class Astronaut:
...     pass
>>>
>>> class Cosmonaut:
...     pass
>>>
>>> a = Astronaut()
>>> a.firstname = 'Mark'
>>> a.lastname = 'Watney'
>>>
>>> c = Cosmonaut()
>>> c.firstname = 'Mark'
>>> c.lastname = 'Watney'
>>>
>>> a is c
False
>>>
>>> a == c
False
>>>
>>>
>>> id(a)  
4503461584
>>>
>>> id(c)  
4503287120
>>>
>>> id(a.firstname)  
4488983024
>>>
>>> id(c.firstname)  
4488983024
>>>
>>> id(a.lastname)  
4503976496
>>>
>>> id(c.lastname)  
4503976496
>>>
>>> id(a.__dict__)  
4503717056
>>>
>>> id(c.__dict__)  
4503973504
>>>
>>> a.__dict__ is c.__dict__
False
>>>
>>> a.__dict__ == c.__dict__
True

5.19.13. Value Comparison

  • == checks for object equality

>>> 'Mark Watney' == 'Mark Watney'
True
>>> a = 'Mark Watney'
>>> b = 'Mark Watney'
>>>
>>> a == b
True
>>> class Astronaut:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
>>>
>>>
>>> astro1 = Astronaut('Mark', 'Watney')
>>> astro2 = Astronaut('Mark', 'Watney')
>>>
>>> astro1 == astro2
False

5.19.14. Compare Value vs. Identity

>>> name = 'Mark Watney'
>>> expected = 'Mark Watney'
>>>
>>> name == expected
True
>>> name is expected
False
>>> name = 'Mark Watney'
>>>
>>> name == 'Mark Watney'
True
>>>
>>> name is 'Mark Watney'  
<...>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
False

5.19.15. String Value vs Identity Problem

  • CPython optimization

  • Can be misleading

>>> a = 'Mark Watney'
>>> b = 'Mark Watney'
>>>
>>> a == b
True
>>> a is b
False
>>> a is 'Mark Watney'  
<...>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
False
>>> a = 'Mark'
>>> b = 'Mark'
>>>
>>> a == b
True
>>> a is b
True
>>> a is 'Mark'  
<...>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
True

5.19.16. Use Case - 0x01

  • Make Equal

>>> class Astronaut:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
...
...     def __eq__(self, other):
...         return self.firstname == other.firstname \
...            and self.lastname == other.lastname
>>>
>>>
>>> a1 = Astronaut('Mark', 'Watney')
>>> a2 = Astronaut('Mark', 'Watney')
>>>
>>> a1 == a2
True
>>> a1 is a2
False

5.19.17. Use Case - 0x02

  • Equal Problem

>>> class Astronaut:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
...
...     def __eq__(self, other):
...         return self.firstname == other.firstname \
...            and self.lastname == other.lastname
>>>
>>>
>>> class Cosmonaut:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
>>>
>>>
>>> a = Astronaut('Mark', 'Watney')
>>> c = Cosmonaut('Mark', 'Watney')
>>>
>>> a == c
True
>>> a is c
False

5.19.18. Use Case - 0x03

  • Make Unequal

>>> class Astronaut:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
...
...     def __eq__(self, other):
...         return self.__class__ is other.__class__ \
...            and self.firstname == other.firstname \
...            and self.lastname == other.lastname
>>>
>>>
>>> class Cosmonaut:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
>>>
>>>
>>> a = Astronaut('Mark', 'Watney')
>>> c = Cosmonaut('Mark', 'Watney')
>>>
>>> a == c
False
>>> a is c
False

5.19.19. Use Case - 0x04

>>> 
... from functools import singledispatchmethod
...
...
... class Astronaut:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
...
...     @singledispatchmethod
...     def __eq__(self, other):
...         return False
...
...     @__eq__.register
...     def _(self, other: 'Astronaut'):
...         return self.firstname == other.firstname \
...            and self.lastname == other.lastname
...
...     @__eq__.register
...     def _(self, other: 'Cosmonaut'):
...         return False
...
...
... class Cosmonaut:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
...
...
... a = Astronaut('Mark', 'Watney')
... c = Cosmonaut('Mark', 'Watney')
...
... a == c
False
>>> a is c  
False