2

If I code a mistake and I do something like this:

__builtins__ = 'abcd'

and before I didn't code import builtins is there a way to restore __builtins__ to its default value?

zer0uno
  • 7,521
  • 13
  • 57
  • 86

2 Answers2

6

Let's assume even a worse case than the described situation: you completely ruin, nuke, destroy, annihilate and wipe out __builtins__:

__builtins__.__dict__.clear()

@Matrtijn's method will fail since every module will have the very same builtins instance.


First, we will have to restore the basic types:

__builtins__.object = "".__class__.__mro__[-1]
__builtins__.type = object.__class__

According to python docs, __mro__ attribute: is a tuple of classes that are considered when looking for base classes during method resolution. Since every python modern class extends object, it allows us to gain instance of the object type.

Second, We will implement a class lookup. Given a class name we will return the class' type. For that we will use type.__subclasses__() recursively (See this question for more information about that):

def all_subclasses_of(cls):
    return type.__subclasses__(cls) + [g for s in type.__subclasses__(cls) for g in all_subclasses_of(s)]
def lookup(s):
    for cls in all_subclasses_of(object):
        if cls.__name__ == s:
            return cls

Third, We'll extract the already loaded modules using BuiltinImporter:

bi = lookup('BuiltinImporter')
modules = bi.load_module.__globals__['sys'].modules

We take advantage of the fact that BuiltinImporter imports sys module and then we use sys.modules: a dictionary that maps module names to modules which have already been loaded.

Then, We will patch __builtins__ with the required methods and classes for the next step:

__builtins__.hasattr = lambda obj, key: key in obj.__dict__
__builtins__.KeyError = lookup('KeyError')

And now, for the magic to happen!

bi.load_module('builtins')
__builtins__ = bi.load_module.__globals__['module_from_spec'](modules['builtins'].__spec__)

We generate the __spec__ to the builtins module using builtinImporter.load_module. Then, we load the module from the generated spec. AND IT WORKS! (I don't really know why, but it indeed works)

However, the freshly generated builtins module misses the method open and all of the exceptions. We will have to add them manually to get import working again:

__builtins__.BaseException = lookup('BaseException')
__builtins__.open = lambda name, mode: modules['io'].FileIO(name, mode)
for error in all_subclasses_of(BaseException):
    __builtins__.__dict__[error.__name__] = error
Yoav Sternberg
  • 6,421
  • 3
  • 28
  • 30
4

Congratulations, you managed to muck up your namespace good and proper! There is no easy escape from this mess, no.

You can grab the name from any Python module you perhaps have imported, or from an imported Python function:

__builtins__ = some_python_module.__builtins__

or

__builtins__ = some_python_function.__globals__['__builtins__']

The function has to be one you imported from elsewhere, so that the __globals__ reference points to a different namespace that still has a reference to the __builtins__ mapping.

One name that I found will almost always work is the __loader__ reference in modules; it is an object with methods that will still give you access a module globals object:

__builtins__ = __loader__.find_spec.__func__.__globals__['__builtins__']

Otherwise, restart your Python session, and start again.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I'm getting the following error: `Traceback (most recent call last): File "", line 1, in TypeError: string indices must be integers` – Yoav Sternberg Feb 19 '18 at 10:37
  • @YoavSternberg that means the name you used was not found. Python tried to find it in the `__builtins__` object by using subscription, after searching your globals first. You can make the error less confusing by using `__builtins__ = {}` first. – Martijn Pieters Feb 19 '18 at 11:11
  • OK. and what if I use `__builtins__.__dict__.clear()`? It will have the cleared builtins. I was able to restore it, but the `import` statement doesn't work after that: https://pastebin.com/DxvVFBaz I can only import modules that were already imported by the interpreter. – Yoav Sternberg Feb 19 '18 at 11:41
  • @YoavSternberg yeah, that’s about the most complete method of making sure you can’t recover again. There *may* be modules that still have references to some of the builtins but you’d have to hunt through all the objects and the references they hold (recursively) to see what you could recover. Using subclasses of `obect` is helpful, but many will be instances (functions). It would be much, much easier to just restart Python. – Martijn Pieters Feb 19 '18 at 12:17
  • Actually, I've found a way to fix even that, see my answer below. – Yoav Sternberg Feb 20 '18 at 12:38