Christoffer Karlsson
Christoffer Karlsson

Reputation: 4963

Set variable to the name of the module that has imported it

Assume I have two modules:

first.package.module

# Assign module to a variable
which_module = ???

print("I originally live in {}".format(__name__))   # Prints first.package.module
print("I was run from {}".format(which_module))

second.package.module

from first.package.module import *

third.package.module

from first.package.module import *

How can I get the second row, "I was run from", to print second.package.module after the import in second.package.module? Or third.package.module when it is run from there.

The reason I want this behaviour is due to building an app in Django that is used several times in the same project, i.e. there are several instances of the same app. Each of these instances has their own models, which inherits from an abstract model. To build reusable views and urls, I want to load the models dynamically, and to do that, I need to know which module that has imported the app and runs it.

Upvotes: 1

Views: 131

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1124000

You can print the name of the module triggering the module load with:

import sys

print sys._getframe(1).f_globals['__name__']

In Python 3 with the new importlib stack you'll need to increase the framecount to 6 (at least for 3.5, 3.6 and 3.7):

print(sys._getframe(6).f_globals['__name__'])

A cross-CPython solution would be to filter out any importlib._bootstrap* elements in the stack:

import sys

def imported_from(depth=0):
    # skip the frames of this function, and the caller
    f = sys._getframe(2 + depth)
    while f and f.f_code.co_filename.startswith("<frozen importlib._bootstrap"):
        f = f.f_back
    return f and f.f_globals['__name__']

print(imported_from())

The above imported_from() function can be defined in a utility module and imported; the sys._getframe(2) call ensures that the starting context is whatever triggered the imported_from() call. Provide a positive integer as the depth argument to increase the number of frames skipped. The function works on any Python version that provides a sys._getframe() function, whether or not it uses the importlib stack.

Note that this:

  • Relies on a CPython implementation detail (exposing the frame stack is not available on other Python implementations). See the sys._getframe() function documentation:

    CPython implementation detail: This function should be used for internal and specialized purposes only. It is not guaranteed to exist in all implementations of Python.

  • In Python 3, it depends on the exact implementation details of the importlib stack; ongoing development could add or remove calls in that stack. For example, in Python 3.3, which first introduced importlib, the correct stack count to skip is 9, in 3.4 it went down to 7, and 3.5 dropped it to 6 (a number that's since been stable). imported_from() works around this, but introduces a new implementation detail-dependent assumption: that the stack frames involved with importing can be detected by looking for the <frozen importlib._bootstrap prefix in the filename. In theory it is possible to compile CPython with importlib._bootstrap left un-frozen (not included in the interpreter binary as array of marshal data).

  • Only works when a module is imported for the first time, at which point Python executes the module code to produce a sys.modules object.

I cannot emphasise the last point enough. Python only ever loads a module once. Importing is a two-step process: loading and binding. The load step is only executed if the module object doesn't exist yet, all other imports for the module only bind names.

Binding names is not something you can practically hook into; import modulename is nothing more than a modulename = sys.modules['modulename'] assignment once the module is already in memory.

Upvotes: 3

Related Questions