I was playing around with the pickle
library, when I noticed that sometimes, different class instances are at the same memory location.
Both of the below examples are showcasing said behaviour:
class DemoClass:
def __init__(self):
self.name = 'demoName'
#example 1
for i in range(3):
print (DemoClass())
#example 2
[print(DemoClass()) for i in range(3)]
#Output for both example 1 and example 2
#Note that the memory locations are identical in the output
<__main__.DemoClass object at 0x00CEE610>
<__main__.DemoClass object at 0x00CEE610>
<__main__.DemoClass object at 0x00CEE610>
This was quite startling to me, so maybe you could explain why this occurs.
The way in which the program acts as I would expect it to is as follows.
demo = [DemoClass() for i in range(3)]
for i in demo:
print (i)
#Output
<__main__.DemoClass object at 0x01F7E630>
<__main__.DemoClass object at 0x01F7ED30>
<__main__.DemoClass object at 0x01F7E670>
The answer is No : If you create two objects using the new keyword, they will never point to the same memory location. This holds true for String objects as well. if you create two String objects using new , the two references to these objects will point to two different memory locations.
For instance and class variables, they are part of the object, hence, on the heap. While Objects are stored on heap. Class objects, including method code and static fields: heap.
We can get an address using the id() function. id() function gives the address of the particular object.
A program may create many objects of the same class. Objects are also called instances, and they can be stored in either a named variable or in an array or collection. Client code is the code that uses these variables to call the methods and access the public properties of the object.
Your question concerns how Python allocates memory. tldr; Python uses a heap to store memory. When a resource is freed, it goes to the top of the heap.
Python has to allocate memory to create an instance of an object. For memory efficiency, the Python memory manager has a heap of memory locations available to give or reserve for the instantiation of objects. Using some of your examples, you can see how this works in practice.
>>> for i in range(3):
... print(DemoClass())
...
<test.DemoClass instance at 0x288b248>
<test.DemoClass instance at 0x288b248>
<test.DemoClass instance at 0x288b248>
During the first iteration of the for
loop, python uses the first available address in its current heap, namely <0x288b248>
, to create an instance of DemoClass
for the print
call. Once the print
command has finished, the memory address is freed and returns to the top of the heap. During the next iteration of the loop, python utilizes the first available memory address, which again is address <0x288b248>
. Etc.
>>> for j in [DemoClass() for i in range(3)]:
... print(j)
...
<test.DemoClass instance at 0x288bcf8>
<test.DemoClass instance at 0x288b290>
<test.DemoClass instance at 0x288b638>
Here python generates a list that it will then iterate through. The creation of the list requires that a new instance of DemoClass be created for each element. This will take the top three addresses off the heap. After the loop is done, the list is freed from memory and if we again make a call to print(DemoClass)
we will find that the python is again reusing memory.
>>> print DemoClass()
<test.DemoClass instance at 0x288bcf8>
>>> for i in range(4):
... Demo = DemoClass()
... print(Demo)
...
<test.DemoClass instance at 0x288bcf8>
<test.DemoClass instance at 0x288b290>
<test.DemoClass instance at 0x288bcf8>
<test.DemoClass instance at 0x288b290>
In this example, each time Demo
is instantiated as an instance of DemoClass
, a segment of memory is assigned to Demo
. However, the call to print(Demo)
does not free the memory assigned to Demo
. At the beginning of the next loop, a new segment of memory is allocated to Demo
and then Demo
is overwritten, at which point it's original memory address returns to the top of the heap. The memory address used for Demo
then alternates between two memory addresses.
In your first 2 examples, once the call to print
has finished, the object no longer has a reference, and so can be re-used the next time that type of object is created.
In the second, all of the instances have references at the same time, and thus must be distinct.
I wouldn't rely on this behavior, though: for example, there might be other unreferenced instances of that object lying about.
When an object doesn't exist any more, its location is available for reuse immediately.
Since Python objects are reference-counted, they disappear as soon as nothing refers to them.
If things worked the way you expect, you could run out of memory rather quickly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With