2.4. OOP Interning

2.4.1. 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

2.4.2. 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

>>> x = 256
>>> id(x)  
4474506792
>>>
>>> del x
>>>
>>> x = 256
>>> id(x)  
4474506792
>>> x = 257
>>> id(x)  
4509456400
>>>
>>> del x
>>>
>>> x = 257
>>> id(x)  
4509455696
>>> 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

Mind, that address for objects less or equal to 256 is the same, but above 256 object address is different:

>>> a = 256
>>> b = 257
>>>
>>> id(a)  
4565299048
>>> id(b)  
4602009488
>>>
>>> del a
>>> del b
>>>
>>> a = 256
>>> b = 257
>>>
>>> id(a)  
4565299048
>>> id(b)  
4602005616

Mind, that address for objects less or equal to 256 is the same, but above 256 object address is different:

>>> a = 256
>>> b = 256
>>> x = 257
>>> y = 257
>>>
>>> id(a)  
4565299048
>>>
>>> id(b)  
4565299048
>>>
>>> id(x)  
4602004784
>>>
>>> id(y)  
4602012112

2.4.3. 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

2.4.4. 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

2.4.5. None Type Identity

  • NoneType object is a singleton

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

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

2.4.6. 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

2.4.7. 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

Example:

>>> a = 'Mark'
>>> b = 'Mark'
>>> c = format('Mark')
>>> d = str('Mark')
>>> e = str('Mark'+'')
>>> f = str.__new__(str, 'Mark')
>>> g = a + ''
>>>
>>> id(a)  
4498017136
>>> id(b)  
4498017136
>>> id(c)  
4498017136
>>> id(d)  
4498017136
>>> id(e)  
4498017136
>>> id(f)  
4498017136
>>> id(g)  
4498017136

Intuitively, we can think of the string interning pool as a cache of strings:

>>> string_interning_pool = {
...     'hello': '0x106f37e40',
...     'world': '0x106db8120',
... }

2.4.8. Object Identity

>>> class User:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
>>>
>>>
>>> a = User('Mark', 'Watney')
>>> b = User('Mark', 'Watney')
>>>
>>> a is b
False
>>>
>>> id(a)  
4421890496
>>> id(b)  
4421893328
>>>
>>> hex(id(a))  
'0x10790b1c0'
>>> hex(id(b))  
'0x10790bcd0'
>>>
>>> print(a)  
<User object at 0x107905820>
>>> print(b)  
<User object at 0x10790bcd0>

2.4.9. Class Identity

  • Class object is a singleton

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

>>> class User:
...     pass
>>>
>>> class Admin:
...     pass
>>>
>>>
>>> User is User
True
>>>
>>> Admin is Admin
True
>>>
>>> User is Admin
False
>>>
>>> id(User)  
140570740200304
>>>
>>> id(Admin)  
140570185653984

2.4.10. Performance

Cached int:

>>> %%timeit -r 1000 -n 1000  
... x = 1
15.5 ns ± 5.22 ns per loop (mean ± std. dev. of 1000 runs, 1,000 loops each)
>>>
>>>
>>> %%timeit -r 1000 -n 1000  
... x = int(1)
69.4 ns ± 22.2 ns per loop (mean ± std. dev. of 1000 runs, 1,000 loops each)

Uncached int:

>>> %%timeit -r 1000 -n 1000  
... x = 257
16 ns ± 8.24 ns per loop (mean ± std. dev. of 1000 runs, 1,000 loops each)
>>>
>>>
>>> %%timeit -r 1000 -n 1000  
... x = int(257)
64.7 ns ± 19.6 ns per loop (mean ± std. dev. of 1000 runs, 1,000 loops each)

str:

>>> %%timeit -r 1000 -n 1000  
... x = 'Mark'
17.8 ns ± 6.41 ns per loop (mean ± std. dev. of 1000 runs, 1,000 loops each)
>>>
>>>
>>> %%timeit -r 1000 -n 1000  
... x = str('Mark')
33.3 ns ± 6.99 ns per loop (mean ± std. dev. of 1000 runs, 1,000 loops each)