Everything is an object

Related article:

Transcript:

In Python, everything is an object.

Classes are objects

We have a class called Product:

class Product:

    unknown_price = "Ask for details"

    def __init__(self, name, price=None):
        self.name = name
        self.price = price

    def display_price(self):
        if self.price is None:
            return self.unknown_price
        return f"{self.price:.2f}"

And we have a variable called duck that points to an instance of this Product class:

>>> duck = Product("duck")
>>> duck
<product.Product object at 0x7f3e50741fa0>

Variables point to objects, and class instances are objects.

But classes are also objects. So, we can point a variable to a class:

>>> my_class = Product

We now have a variable my_class pointing to the Product class:

>>> my_class
<class 'product.Product'>

And in fact, Product is also just a variable that points to the Product class:

>>> Product
<class 'product.Product'>

So anything that we could do with Product, we could do with my_class. For example, we could look-up an attribute on the Product class by accessing either one of these two variables:

>>> my_class.unknown_price
'Ask for details'
>>> Product.unknown_price
'Ask for details'

Or we could call the my_class variable:

>>> duck = my_class("duck")

Calling my_class does the same thing as calling the Product class. What do you get when you call a class? You get an instance of that class!

>>> duck
<product.Product object at 0x7f3e5076d220>

So while class instances are objects, classes are also objects. You can point variables to either class instances or classes.

Modules are objects

Modules are objects too.

So we can point variables to module objects:

>>> import math
>>> silly_math = math

We've imported the math module and pointed the silly_math variable to the math module object:

>>> silly_math
<module 'math' from '/home/trey/.pyenv/versions/3.9.0/lib/python3.9/lib-dynload/math.cpython-39-x86_64-linux-gnu.so'>

And if we change the answer attribute on silly_math:

>>> silly_math.answer = 42

This will add a new attribute to the math module object:

>>> silly_math.answer
42

Meaning math.answer is now 42 also:

>>> math.answer
42

This was possible because modules are mutable objects, meaning you can add attributes to them and you can update attributes on them.

If we take math.pi (already defined in math module) and set it to 3 :

>>> math.pi
3.141592653589793
>>> math.pi = 3
>>> math.pi
3

math.pi will now be 3 everywhere in our current Python process.

Note that this is a bad idea. You really shouldn't add or update attributes in other modules after you've imported them, but it's possible to do because modules are objects in Python and they're mutable objects, meaning they can be changed.

Functions are objects

Functions are even objects in Python.

We have a function called greet :

>>> def greet(name="world"):
...     """Greet a user, or the whole world."""
...     print("Hello", name)
...

The one thing you normally do with a function is call it.

This function greet can be called with one argument or with no arguments:

>>> greet()
Hello world
>>> greet("Trey")
Hello Trey

Because functions are objects, we could point another variable, f, to our function object:

>>> f = greet

The variable f points to the greet function now:

>>> f
<function greet at 0x7fe7cd8619d0>

That means anything we could do with greet, we can do with f:

>>> f()
Hello world

Both of these variables point to the same function object.

Just like modules, classes, and class instances, functions have attributes.

There is a __defaults__ attribute that Python adds to every function. This attribute represents the default values for all arguments that that function might accept:

>>> greet.__defaults__
('world',)

That __defaults__ attribute points to a tuple that has the string world in it, meaning if this function is called without its first argument, we'll see Hello world:

>>> greet()
Hello world

If we wanted to be devious, we could assign the __defaults__ attribute to a different tuple:

>>> greet.__defaults__ = ('Trey',)

So if we call greet now, it's now going to say Hello Trey, rather than Hello world:

>>> greet()
Hello Trey

This is possible to do because functions are objects, meaning we can point other variables to them, and since they're mutable objects, we can even update the attributes that exist on functions.

Summary

In Python, everything is an object. Classes are objects, instances of classes are objects, modules are objects, and functions are objects.

Anything that you can point a variable to is an object.