Search code examples
pythonbuilt-inmonkeypatching

python: builtin function redefinition, with different arguments


Preface: the question is to understand python internals, so pls don't answer with 'upgrade python' or 'import six'

Let take for example, 'print'

To replace the 'print' builtin, I can write my python function.

python 2.6 : at runtime, I get syntax error

solution

from __future__ import print_function

Now I can redefine print. Why ? I think because now my print declaration is the same as the new print function.

python 2.4 : at runtime, I get syntax error

There isn't

from __future__ import print_function

So, for print (or any other builtin): is it possible to monkey patch with a new function, with a different declaration - args and kwargs ?


In other words.

from __future__ import print_function substitutes the keyword with a function. I want to understand HOW, in general. Please don't be focused exclusively on print, it is an handy example.


Solution

  • All from __future__ import print_function does is toggle a flag in the parser to make it stop treating print as a keyword; once that's done, references to print seamlessly become like references to any other non-keyword that constitutes a legal variable name (they go through LEGB lookup, and get found in the B of LEGB, the builtins scope). This behavior is hard-coded deep in the Python interpreter; there is no way to achieve a similar effect for any other keyword without building a custom version of Python, or engaging in some other egregious hackery well beyond the scope of any reasonable problem.

    As of 2.6, __builtin__ has a print function on it, so any module that uses from __future__ import print_function (and can therefore reference the name print as opposed to the keyword print) will see __builtin__.print (if it hasn't been shadowed by something in the local, nested or global scope). It's still there for every module, but in modules without the __future__ import, references to print are resolved to the keyword at compile time and replaced with the raw bytecode implementing the special print statement (the same way del and return behave; you can't name a variable either of those things for the same reason, they're keywords), so modules without the import never get a chance to lookup the print function.

    This doesn't generalize to other cases, because __future__ has no other cases where one of its features converts a keyword to a non-keyword. For all other actual built-in functions, being able to overwrite them on a per-module basis is as simple as assigning to the name at global scope (shadowing it for that module), e.g.:

    def abs(x):
        return x  # Who needs absolute value anyway?
    # From here on out, references to abs in this module see your override, not the built-in
    

    While it's possible to reassign the built-in globally, it's a terrible idea (since every other module that uses it likely relies on the built-ins original behavior). That said, it's not hard:

    import __builtin__
    
    def abs(x):
        return x  # Who needs absolute value anyway?
    
    __builtin__.abs = abs
    # *Every* module now sees your terrible monkeypatch, on your own head be it