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