Execute Program

Python in Detail: Everything Is an Object

Welcome to the Everything Is an Object lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • Every value in Python is an object. But what does it mean to "be an object"? It's a subtle question! We'll look at it from two different angles.

  • One way to think of objects is that they have attributes. And sure enough, every value in Python has attributes. We can see that by calling the built-in dir function, short for "directory". It lists all of the attributes on an object, whether it has a .__dict__ or not.

  • >
    class Cat:
    def __init__(self, name):
    self.name = name

    keanu = Cat("Keanu")
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    dir(keanu)
    Result:
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    '__doc__' in dir(keanu)
    Result:
    TruePass Icon
  • That's a long list, but many of these attributes are familiar from past lessons. When we do keanu == other_cat, Python calls keanu.__eq__(other_cat). When we call len(keanu), Python calls keanu.__len__(). We didn't define those methods ourselves, so Keanu has the default implementations for them.

  • Integers have even more attributes, including many dunder methods that implement the various mathematical operators and comparison operators.

  • >
    dir(1)
    Result:
  • We can access any of these attributes directly. For example, we can make addition more complicated than it needs to be.

  • >
    (1).__add__(2)
    Result:
    3Pass Icon
  • In Python, even functions are objects!

  • >
    def double(x):
    return x * 2
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    dir(double)
    Result:
  • These are all regular attributes that we can access. For example, we've already seen .__name__.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    double.__name__
    Result:
    'double'Pass Icon
  • The .__call__ method does what it sounds like: it calls the function.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    double.__call__(3)
    Result:
    6Pass Icon
  • Most of the function attributes are mundane. But one is very special: .__code__.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    double.__code__
    Result:
  • It's the function's code, represented as a Python object. But it's not the source code. It's all of the other stuff about the code: the compiled byte code, line numbers, variable names, how many arguments the function takes, etc.

  • Like everything in Python, .__code__ is an object, so it works with dir.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    dir(double.__code__)
    Result:
  • This is an esoteric part of Python, but it's also a good demonstration of how deep Python goes. For example, the .co_argcount attribute tells us how many arguments the function takes.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    double.__code__.co_argcount
    Result:
    1Pass Icon
  • The .co_varnames attribute returns a tuple with the names of locals variables available in double, including arguments that it takes.

  • >
    def double(x):
    return x * 2

    double.__code__.co_varnames
    Result:
  • >
    def add(a, b):
    return a + b

    add.__code__.co_varnames
    Result:
    ('a', 'b')Pass Icon
  • We can even see the function's byte code, which is what the Python virtual machine actually executes when we call the function. Byte code isn't human-readable, but it's still accessible.

  • >
    def double(x):
    return x * 2

    double.__code__.co_code
    Result:
  • Python provides the dis ("disassembler") module to help demystify this bytecode. Calling dis.dis on a function prints out the bytecode in a more human-readable way.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    import dis

    print("code for double")
    print("===============")
    dis.dis(double)
    console output
  • Most code doesn't need to access any of this metadata. But some developer tools do need this data, so it's there. And it shows us that even functions are just another kind of object, with a rich set of their own attributes.

  • Everything in Python is an object, so every value has attributes. But there's another way to think about what constitutes an object. We'll see it in a future lesson!