Python: the thing good Objects come in
Object-Oriented programming is a programming paradigm that revolves round these things called “Objects” which are just data blocks that contain, twix bars. I mean data. For sure data.
Python….
Python is at it’s core, an object-oriented programming language. There are different implementations of Python, but I’ll be referencing the popular
C implementation, CPython. More specifically,
v3.4.
In Python, all object types inherit from one master object, declared as
PyObject . This master object has all of the information Python needs to process a pointer to an object as an actual object.
PyObject
Let’s take a look at the CPython implementation of the PyObject struct.
Breakdown:
_PyObject_HEAD_EXTRA :
- This is a macro for a doubly linked list pointing to a next
_object and a _prev object
- According to the documentation, these two field are only
present when the macro Py_TRACE_REFS is defined
Py__ssize_t ob_refcnt :
- references count of the object, initialized to 1
struct _typeobject *ob_typeob_type :
- The type's type. Or as the python documentation put's it, a
metatype.
ob_refcnt, id(), and type()
To get the reference count of an object, we can use the built in
id() function which returns the memory address of the value. Then we can use the
ctype library to get the
ob_refcnt value of an object.
The
id() function gets the memory address of the value, but as seen it is not the memory address in hex form but a Python Long object. That’s what the
PyLong_FromVoidPtr() does.
Here’s an example:
Breakdown
import ctypes :
- this import is to be able to to use the c_long.from_address
function which retrieves a number from an address
twix_count :
- a variable set to entice Julien
twix_address :
- a variable to store the id of the enticing twix_count var
hex(twix_address) :
- when converted to hex, you can see the address of the object
ob_refcnt = ctypes.c_long.from_address(twix_address)
- this uses the function mentioned above
The return value, as you can see, is one. Because as mentioned previously, the
ob_refcnt is initialized to 1.
Let’s now reference another variable to
twix_count .
As you can see, after assigning another variable to
twix_count the ob_refcnt of the object incremented to 2.
Note:
The
type() function. Like
id() types is a built in function that checks the
ob_type of a
PyObject struct to determine the type of an object.
This brings up a very interesting question with variable assignment.
Referencing
When I first started my
twix_count demo, I used a smaller number. The number
10 to be precise. But there was some “odd behavior” with retrieving the
ob_refcnt .
Why would the
ob_refcnt count be 15? I restarted the python3 interpreter and restarted my computer but still, the number was 15.
After doing some research on the interwebs, I found that python3 keeps an array of integer objects in the range of and including
-5 to
256 . So when you assign a variable to any number in that range, you are actually just referencing the existing object.
Immutable Data Types
An immutable data type is an object that cannot be manipulated, such as an
int ,
float ,
string ,
tuple , just to name a few. You can see a full list of immutable data types at this link.
If you pass an immutable object to a function or method, you are not able to mutate the object. Furthermore, you are also not allowed to rebind the outside reference.
There’s an example
def twix_ownership_change(person):
person = 'Ju'
print('{:s} tries to take the twix'.format(person))
twix_owner = "Bobby"
print("Currently: This twix belongs to {:s}".format(twix_owner))
twix_ownership_change(twix_owner)
print('Now, This twix still belongs to {:s}'.format(twix_owner))
The output should be:
Currently: This twix belongs to Bobby
Ju tries to take the twix
Now, This twix still belongs to Bobby
Because a string is an immutable object, although the string was change within the scope of the function
twix_ownership_change() , it was not affected on the outside.
Mutable Data Types
A mutable data types is an object that can be manipulated, such as a
list ,
dictionary,
set , just to name a few. Once again, you can see a full list of immutable data types at the same link above.
If you pass a mutable object into a function or method, because of the way arguments are passed in, the function receives a reference to the same object . Because of the function is referencing the same object, you are able to mutate the same object within the function. However, if you reassign or rebind the reference inside of your function, the outside reference will still be pointing at the original reference.
Here’s an example:
def twix_ownership_change(ownership_list):
ownership_list.append('Bobby')
print(ownership_list)
outside_ownership_list = ["The", "twix", "belongs", "to"]
print(outside_ownership_list)
twix_ownership_change(outside_ownership_list)
print(outside_ownership_list)
The output should be:
['The', 'twix', 'belongs', 'to']
['The', 'twix', 'belongs', 'to', 'Bobby']
['The', 'twix', 'belongs', 'to', 'Bobby']