class MyDict(dict):
    # keep a reference around to avoid infinite recursion
    print = print
    dict = dict
    def __getitem__(self, key):
        self.print("getting:", key)
        # Can't use super here because we'd have to keep a reference around instead of looking it up
        # in __builtins__ (to prevent infinite recursion), but then there's no __class__ cell which
        # breaks the lookup mechanism. Instead, just refer to dict by name
        return self.dict.__getitem__(self, key)

__builtins__ = MyDict(vars(__builtins__))

int            # prints "getting: int"
__import__     # prints "getting: __import__"
class X: pass  # prints "getting: __build_class__"
import math    # does not print "getting: __import__" because it uses dictobject internal lookup

################################################################################

# try these individually in the Python shell, because they all error on their own

__builtins__ = "not a dictionary"

int            # TypeError: string indices must be integers (because it's trying to do effectively `"not a dictionary"["int"]`)
__import__     # same error
class X: pass  # same error (trying to load __build_class__)
import math    # SystemError: Objects/dictobject.c:1440: bad argument to internal function
