An object in python is created by storing a memory address that points to the object. Example:
class SomeObject():
def __init__(self, something, something_else):
self.something = something
self.something_else = something_else
def some_method(self):
return str(self.something)
a_variable_holding_a_memory_address = SomeObject(something=94, something_else=243)
a = a_variable_holding_a_memory_address
print(id(a) == id(a_variable_holding_a_memory_address))
Output:
True
If you don't know, id() returns the memory address of a variable in python.
Thus if you delete a_variable_holding_a_memory_address:
class SomeObject():
def __init__(self, something, something_else):
self.something = something
self.something_else = something_else
def some_method(self):
return str(self.something)
a_variable_holding_a_memory_address = SomeObject(something=94, something_else=243)
a = a_variable_holding_a_memory_address
print(a_variable_holding_a_memory_address)
del a_variable_holding_a_memory_address
print(a)
Output:
<__main__.SomeObject object at 0x7f80787ea940>
<__main__.SomeObject object at 0x7f80787ea940>
All that happened was a variable holding the memory address of an object (or a reference) was deleted, but the address itself was not as another reference to it exists.
Now look at this:
# In this example I use ctypes to print out the value at a memory address
import ctypes
class SomeObject():
def __init__(self, something, something_else):
self.something = something
self.something_else = something_else
def some_method(self):
return str(self.something)
a_variable_holding_a_memory_address = SomeObject(something=94, something_else=243)
a = a_variable_holding_a_memory_address
# print out the value at the memory address that a_variable_holding_a_memory_address holds
object_id = id(a_variable_holding_a_memory_address)
print(ctypes.cast(object_id, ctypes.py_object).value)
del a_variable_holding_a_memory_address
# now lets try it when only one of the references is deleted
print(ctypes.cast(object_id, ctypes.py_object).value)
del a
# the same thing but when both of the references are deleted
print(ctypes.cast(object_id, ctypes.py_object).value)
Output:
<__main__.SomeObject object at 0x7fbd04b5d3a0>
<__main__.SomeObject object at 0x7fbd04b5d3a0>
<__main__.SomeObject object at 0x7fbd04b5d3a0>
zsh: segmentation fault python file.py
This segmentation fault happened because the program tried to reference an empty memory address.
This is one of the advantages of using a language of python versus c because it prevents this type of memory error unless you really try to make the program crash like in this example. This includes automatic garbage collection (a part of the python interpreter that frees up memory by emptying unused memory addresses).
But you see, when both of the references existed the memory address held an object.
When one is deleted the address is not because there still exists one reference to the address. That is the automatic garbage collector at work.
When both of them are deleted the address is too, thus the error. It should also be noted that simply assigning a different value to a variable like so:
a = "I now hold a str object"
Instead of:
del a
works too because the reference to the object is replaced with a reference to another object, thus one less reference.
This not only happens for user defined objects, but for very basic objects that come with python, often taken for granted, like str or int or list or dict, the list goes on. In the end, thats what variables hold in python, an object. Unless it holds a boolean like True or False or None. But those are objects too. Python docs for None:
An object frequently used to represent the absence of a value, as when
default arguments are not passed to a function. Assignments to None
are illegal and raise a SyntaxError. None is the sole instance of the
NoneType type.
Reference count:
The reference count to an object in python is the amount of variables holding a reference to the object. When that count reached zero, __del__ is called of the object is called and it is destroyed (unless __del__ does the following as outlined in the docs:
It is possible (though not recommended!) for the del() method to
postpone destruction of the instance by creating a new reference to
it. This is called object resurrection. It is implementation-dependent
whether del() is called a second time when a resurrected object is
about to be destroyed; the current CPython implementation only calls
it once.
)
If you want a reference to an object that does not keep the object alive, there is a type of reference called a weakref:
import weakref
class SomeObject():
def __init__(self, something, something_else):
self.something = something
self.something_else = something_else
def some_method(self):
return str(self.something)
reference = SomeObject('something', 'something_else')
print(reference)
weak_reference = weakref.ref(reference)
print(weak_reference)
del reference
print(weak_reference)
Output:
<__main__.SomeObject object at 0x7f305b017880>
<weakref at 0x7f305ae7c630; to 'SomeObject' at 0x7f305b017880>
<weakref at 0x7f305ae7c630; dead>
The weakref, weakreference becomes a reference to nothing, a dead object.
Circling back the garbage collection, by using the gc built in library you can change some things about how the python garbage collector frees up memory.
One of those is disabling garbage collection:
import gc
# once again using ctypes
import ctypes
# disabling automatic garbage collection
gc.disable()
class SomeObject():
def __init__(self, something, something_else):
self.something = something
self.something_else = something_else
def some_method(self):
return str(self.something)
a_variable_holding_a_memory_address = SomeObject(something=94, something_else=243)
a = a_variable_holding_a_memory_address
object_id = id(a_variable_holding_a_memory_address)
print(ctypes.cast(object_id, ctypes.py_object).value)
del a_variable_holding_a_memory_address
print(ctypes.cast(object_id, ctypes.py_object).value)
del a
print(ctypes.cast(object_id, ctypes.py_object).value)
Output:
<__main__.SomeObject object at 0x7f305ae6df10>
<__main__.SomeObject object at 0x7f305ae6df10>
<_ast.Interactive object at 0x7f305ae6df10>
The object is left even though there are no references to it. Usually the automatic garbage collector would free up the memory space used to store the object, but I disabled is with gc.disable().
What is the _ast.Interactive object that is left?
First here is a simple overview of how python is interpreted by the c code it is written in:
- Code is parsed into an array of tokens
- The array of tokens is used to make an abstract syntax tree (
ast as in _ast).
- Byte code is generated from the abstract syntax tree
- The byte code is run
So, after all the references to an object are removed and automatic garbage collection is disabled all that is left is the code of the object one level of abstraction above byte code.
pygameor something similar? The library probably provides a way to remove the objects from the display. AssigningNoneto those variables may or may not do anything because you are just removing a reference, but the library could have other references to those objects that keep them alive.barbox1?barbox1 = None, asbarbox1is the only reference to the object, you've already deleted the object and released the memory taken by the object, no need to worry about the variable name itself.