Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Override globals in function imported from another module

Let's say I have two modules:

a.py

value = 3
def x()
    return value

b.py

from a import x
value = 4

My goal is to use the functionality of a.x in b, but change the value returned by the function. Specifically, value will be looked up with a as the source of global names even when I run b.x(). I am basically trying to create a copy of the function object in b.x that is identical to a.x but uses b to get its globals. Is there a reasonably straightforward way to do that?

Here is an example:

import a, b

print(a.x(), b.x())

The result is currently 3 3, but I want it to be 3 4.

I have come up with two convoluted methods that work, but I am not happy with either one:

  1. Re-define x in module b using copy-and paste. The real function is much more complex than shown, so this doesn't sit right with me.
  2. Define a parameter that can be passed in to x and just use the module's value:

    def x(value):
        return value
    

    This adds a burden on the user that I want to avoid, and does not really solve the problem.

Is there a way to modify where the function gets its globals somehow?

like image 597
Mad Physicist Avatar asked Oct 27 '25 06:10

Mad Physicist


1 Answers

I've come up with a solution through a mixture of guess-and-check and research. You can do pretty much exactly what I proposed in the question: copy a function object and replace its __globals__ attribute.

I am using Python 3, so here is a modified version of the answer to the question linked above, with an added option to override the globals:

import copy
import types
import functools

def copy_func(f, globals=None, module=None):
    """Based on https://stackoverflow.com/a/13503277/2988730 (@unutbu)"""
    if globals is None:
        globals = f.__globals__
    g = types.FunctionType(f.__code__, globals, name=f.__name__,
                           argdefs=f.__defaults__, closure=f.__closure__)
    g = functools.update_wrapper(g, f)
    if module is not None:
        g.__module__ = module
    g.__kwdefaults__ = copy.copy(f.__kwdefaults__)
    return g

b.py

from a import x
value = 4
x = copy_func(x, globals(), __name__)

The __globals__ attribute is read-only, which is why it must be passed to the constructor of FunctionType. The __globals__ reference of an existing function object can not be changed.

Postscript

I've used this enough times now that it's implemented in a utility library I wrote and maintain called haggis. See haggis.objects.copy_func.

like image 122
Mad Physicist Avatar answered Oct 28 '25 23:10

Mad Physicist