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:
- Method
- 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__() 4
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() True
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')
'bbcd'
It replaces all occurrences of ‘a’ with ‘b’.