Is Python an Interpreted language?

Part 2 - Practice & Quick questions
Python
interpreter
C
numpy
bytes
Author
Published

September 15, 2026

This is part 2 of a series. Read Part 1 first.

1 Quick questions

Right now, I will dive into specific questions that come up when writing Python code:

1.1 Why is Numpy faster than pure Python code?

It happens because Numpy is vectorized, while Python does not. And by vectorized, it means that the code will pass through the interpreter only once and all the routine will be executed in a loop in C; instead of invoking the interpreter on every iteration of a regular Python for loop.

Moreover, NumPy arrays use unboxed, contiguous, C-native buffers — more on why that matters in Section 1.2.

I will show an example in order to make it clearer:

import dis
import numpy as np
import time


def pure_python_version(n):
   total = 0.0
   for i in range(n):
      total += i * i
   return total


def numpy_version(n):
   arr = np.arange(n, dtype=np.float64)
   return np.sum(arr * arr)


N = 100_000_000

start = time.perf_counter()
result_py = pure_python_version(N)
py_time = time.perf_counter() - start

print("=" * 60)
print(f"PURE PYTHON VERSION THAT TOOK {round(py_time, 2)} seconds in BYTECODE")
print("=" * 60)
dis.dis(pure_python_version)

start = time.perf_counter()
result_numpy = numpy_version(N)
numpy_time = time.perf_counter() - start

print()
print("=" * 60)
print(f"NUMPY VERSION THAT TOOK {round(numpy_time, 2)} seconds in BYTECODE")
print("=" * 60)
dis.dis(numpy_version)
============================================================
PURE PYTHON VERSION THAT TOOK 16.59 seconds in BYTECODE
============================================================
  6           0 RESUME                   0

  7           2 LOAD_CONST               1 (0.0)
              4 STORE_FAST               1 (total)

  8           6 LOAD_GLOBAL              1 (NULL + range)
             16 LOAD_FAST                0 (n)
             18 CALL                     1
             26 GET_ITER
        >>   28 FOR_ITER                10 (to 52)
             32 STORE_FAST               2 (i)

  9          34 LOAD_FAST                1 (total)
             36 LOAD_FAST                2 (i)
             38 LOAD_FAST                2 (i)
             40 BINARY_OP                5 (*)
             44 BINARY_OP               13 (+=)
             48 STORE_FAST               1 (total)
             50 JUMP_BACKWARD           12 (to 28)

  8     >>   52 END_FOR

 10          54 LOAD_FAST                1 (total)
             56 RETURN_VALUE

============================================================
NUMPY VERSION THAT TOOK 2.6 seconds in BYTECODE
============================================================
 13           0 RESUME                   0

 14           2 LOAD_GLOBAL              0 (np)
             12 LOAD_ATTR                3 (NULL|self + arange)
             32 LOAD_FAST                0 (n)
             34 LOAD_GLOBAL              0 (np)
             44 LOAD_ATTR                4 (float64)
             64 KW_NAMES                 1 (('dtype',))
             66 CALL                     2
             74 STORE_FAST               1 (arr)

 15          76 LOAD_GLOBAL              0 (np)
             86 LOAD_ATTR                7 (NULL|self + sum)
            106 LOAD_FAST                1 (arr)
            108 LOAD_FAST                1 (arr)
            110 BINARY_OP                5 (*)
            114 CALL                     1
            122 RETURN_VALUE

I am not an expert on bytecode instructions in Python, but see that for pure Python, there is an operation called FOR_ITER that goes to L2, and then, it returns to L1, which is the loop itself.

When we look at the Numpy version, the for loop is gone, and all instructions run only once, not N times.

1.2 Why is C generally faster than Python?

  1. C does not have an interpreter; it dispatches all code ahead of time into machine code during compilation;
  2. Defining the variable types at declaration makes the code’s memory layout known to the machine ahead of time:
    • It does not have to make Runtime type checking.
    • It can calculate in advance how much of memory the operation will take, and optimize it.
  3. Python values are boxed — every int/float is a full object with type info and a reference count, not a raw number sitting in memory. C works directly with raw bytes, no wrapper.

In order to exemplify the third point, I will run an example to show how many bytes a Python integer uses:

import sys

x = 5
print(f"type(x)        = {type(x)}")
print(f"sys.getsizeof(x)   = {sys.getsizeof(x)} bytes")
type(x)        = <class 'int'>
sys.getsizeof(x)   = 28 bytes

It uses 28 bytes, against 4 (or 8) in C (normally), which: + 8 bytes is used to save its reference count (it counts how many variables are referencing to it); + 8 bytes for the type pointer (so that the interpreter knows the basic operations available on this variable); + 8 bytes for the size field; + 4 bytes for the cost of each additional digital chunk

1.3 If I annotate my functions, will my code run faster?

NO. It happens because CPython ignores all type hints during compilation. See that in the below example the bytecode is the same:

import dis
import time


def add_no_hint(a, b):
    return a + b


def add_with_hint(a: int, b: int) -> int:
    return a + b


print("=== WITHOUT type hint ===")
dis.dis(add_no_hint)
print()
print("=== WITH type hint ===")
dis.dis(add_with_hint)
=== WITHOUT type hint ===
  5           0 RESUME                   0

  6           2 LOAD_FAST                0 (a)
              4 LOAD_FAST                1 (b)
              6 BINARY_OP                0 (+)
             10 RETURN_VALUE

=== WITH type hint ===
  9           0 RESUME                   0

 10           2 LOAD_FAST                0 (a)
              4 LOAD_FAST                1 (b)
              6 BINARY_OP                0 (+)
             10 RETURN_VALUE

Happy coding!

Back to top