Under the Hood

Let’s take a look under the hood, and explore how Python objects work exactly.

You probably know the built-in len() function. It simply returns the length of the object you give it. But what is the length of, say, the number five? Let’s ask Python:

>>> len(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'int' has no len()

I love errors because they illustrate how Python works internally. In this case, Python is telling us that 5 is an object, and it has no len(). In Python, everything is an object. Stings, booleans, numbers, and even functions are objects. We can inspect an object in the REPL using the built-in function dir(). When we try dir on the number five, it reveals a big list of functions that are part of any number object:

>>> dir(5)
['__abs__', '__add__', 
'__and__', '__bool__', 
'__ceil__', '__class__', 
'__str__', '__sub__',
 '__subclasshook__', '__truediv__',
 '__trunc__', '__xor__', 
'bit_length', 'conjugate', 
'denominator', 'from_bytes',
 'imag', 'numerator', 
'real', 'to_bytes']

I truncated the list a little for the sake of clarity.

The list starts with these weirdly named functions containing underscores, like __add__. These are called magic methods, or dunder (short for double underscore) methods. If you look closely, you’ll see that there’s no __len__ dunder method for objects of type int. That’s how Python’s len() function knows that a number does not have a length. All len() does, is call the __len__() method on the object you offered it. That’s also why Python complained that “objects of type ‘int’ have no len().”

I casually introduced the word methods here. Let me define it more formally:

When a function is part of an object, we call it a method.

So if a string does have a length, it must have a len method, right? Let’s find out!

>>> dir("test")
['__add__', '__class__',
'__contains__', '__delattr__', 
'__dir__', '__doc__', 
'__eq__', '__format__', 
'__ge__', '__getattribute__', 
'__getitem__', '__getnewargs__', 
'__gt__', '__hash__', '__init__', 
'__init_subclass__', '__iter__', 
'__le__', '__len__', '__lt__', 
'__mod__', '__mul__', '__ne__', 
'__new__', '__reduce__', 
'__reduce_ex__', '__repr__', 
'__rmod__', '__rmul__', 
'__setattr__', '__sizeof__', 
'__str__', '__subclasshook__', 
'capitalize', 'casefold', 'center', 
'count', 'encode', 'endswith', 
'expandtabs', 'find', 'format', 
'format_map', 'index', 'isalnum', 
'isalpha', 'isascii', 'isdecimal', 
'isdigit', 'isidentifier', 'islower', 
'isnumeric', 'isprintable', 'isspace', 
'istitle', 'isupper', 'join', 'ljust', 
'lower', 'lstrip', 'maketrans', 
'partition', 'replace', 'rfind', 
'rindex', 'rjust', 'rpartition', 
'rsplit', 'rstrip', 'split', 
'splitlines', 'startswith', 'strip', 
'swapcase', 'title', 'translate', 
'upper', 'zfill']

Yup, there it is. And since this is a method, we can call it too:

>>> "test".__len__()

This is equivalent to len("test") but a lot less elegant, so don’t do this. It’s just to illustrate how this stuff works.

There’s also a list of other, less magical methods that dir() revealed to us. Feel free to try a few, like islower:

>>> "test".islower()

This method checks if the entire string is lower-case, which it is, so Python returns the boolean True. Some of these methods require one or more arguments, like replace:

>>> 'abcd'.replace('a', 'b')

It replaces all occurrences of ‘a’ with ‘b’.

Share this