Importing from builtin library when module with same name exists

PythonImport

Python Problem Overview


Situation:

  • There is a module in my project_folder called calendar
  • I would like to use the built-in Calendar class from the Python libraries
  • When I use from calendar import Calendar it complains because it's trying to load from my module.

I've done a few searches and I can't seem to find a solution to my problem.

Any ideas without having to rename my module?

Python Solutions


Solution 1 - Python

Changing the name of your module is not necessary. Rather, you can use absolute_import to change the importing behavior. For example with stem/socket.py I import the socket module as follows:

from __future__ import absolute_import
import socket

This only works with Python 2.5 and above; it's enabling behavior that is the default in Python 3.0 and higher. Pylint will complain about the code but it's perfectly valid.

Solution 2 - Python

Actually, solving this is rather easy, but the implementation will always be a bit fragile, because it depends python import mechanism's internals and they are subject to change in future versions.

(the following code shows how to load both local and non-local modules and how they may coexist)

def import_non_local(name, custom_name=None):
    import imp, sys

    custom_name = custom_name or name

    f, pathname, desc = imp.find_module(name, sys.path[1:])
    module = imp.load_module(custom_name, f, pathname, desc)
    f.close()

    return module

# Import non-local module, use a custom name to differentiate it from local
# This name is only used internally for identifying the module. We decide
# the name in the local scope by assigning it to the variable calendar.
calendar = import_non_local('calendar','std_calendar')

# import local module normally, as calendar_local
import calendar as calendar_local

print calendar.Calendar
print calendar_local

The best solution, if possible, is to avoid naming your modules with the same name as standard-library or built-in module names.

Solution 3 - Python

The only way to solve this problem is to hijack the internal import machinery yourself. This is not easy, and fraught with peril. You should avoid the grail shaped beacon at all costs because the peril is too perilous.

Rename your module instead.

If you want to learn how to hijack the internal import machinery, here is where you would go about finding out how to do this:

There are sometimes good reasons to get into this peril. The reason you give is not among them. Rename your module.

If you take the perilous path, one problem you will encounter is that when you load a module it ends up with an 'official name' so that Python can avoid ever having to parse the contents of that module ever again. A mapping of the 'official name' of a module to the module object itself can be found in sys.modules.

This means that if you import calendar in one place, whatever module is imported will be thought of as the module with the official name calendar and all other attempts to import calendar anywhere else, including in other code that's part of the main Python library, will get that calendar.

It might be possible to design a customer importer using the imputil module in Python 2.x that caused modules loaded from certain paths to look up the modules they were importing in something other than sys.modules first or something like that. But that's an extremely hairy thing to be doing, and it won't work in Python 3.x anyway.

There is an extremely ugly and horrible thing you can do that does not involve hooking the import mechanism. This is something you should probably not do, but it will likely work. It turns your calendar module into a hybrid of the system calendar module and your calendar module. Thanks to Boaz Yaniv for the skeleton of the function I use. Put this at the beginning of your calendar.py file:

import sys

def copy_in_standard_module_symbols(name, local_module):
    import imp

    for i in range(0, 100):
        random_name = 'random_name_%d' % (i,)
        if random_name not in sys.modules:
            break
        else:
            random_name = None
    if random_name is None:
        raise RuntimeError("Couldn't manufacture an unused module name.")
    f, pathname, desc = imp.find_module(name, sys.path[1:])
    module = imp.load_module(random_name, f, pathname, desc)
    f.close()
    del sys.modules[random_name]
    for key in module.__dict__:
        if not hasattr(local_module, key):
            setattr(local_module, key, getattr(module, key))

copy_in_standard_module_symbols('calendar', sys.modules[copy_in_standard_module_symbols.__module__])

Solution 4 - Python

The accepted solution contains a now-deprecated approach.

The importlib documentation here gives a good example of the more appropriate way to load a module directly from a file path for python >= 3.5:

import importlib.util
import sys

# For illustrative purposes.
import tokenize
file_path = tokenize.__file__  # returns "/path/to/tokenize.py"
module_name = tokenize.__name__  # returns "tokenize"

spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)

So, you can load any .py file from a path and set the module name to be whatever you want. So just adjust the module_name to be whatever custom name you'd like the module to have upon importing.

To load a package instead of a single file, file_path should be the path to the package's root __init__.py

Solution 5 - Python

I'd like to offer my version, which is a combination of Boaz Yaniv's and Omnifarious's solution. It will import the system version of a module, with two main differences from the previous answers:

  • Supports the 'dot' notation, eg. package.module
  • Is a drop-in replacement for the import statement on system modules, meaning you just have to replace that one line and if there are already calls being made to the module they will work as-is

Put this somewhere accessible so you can call it (I have mine in my _init_.py file):

class SysModule(object):
    pass

def import_non_local(name, local_module=None, path=None, full_name=None, accessor=SysModule()):
    import imp, sys, os

    path = path or sys.path[1:]
    if isinstance(path, basestring):
        path = [path]
    
    if '.' in name:
        package_name = name.split('.')[0]
        f, pathname, desc = imp.find_module(package_name, path)
        if pathname not in __path__:
            __path__.insert(0, pathname)
        imp.load_module(package_name, f, pathname, desc)
        v = import_non_local('.'.join(name.split('.')[1:]), None, pathname, name, SysModule())
        setattr(accessor, package_name, v)
        if local_module:
            for key in accessor.__dict__.keys():
                setattr(local_module, key, getattr(accessor, key))
        return accessor
    try:
        f, pathname, desc = imp.find_module(name, path)
        if pathname not in __path__:
            __path__.insert(0, pathname)
        module = imp.load_module(name, f, pathname, desc)
        setattr(accessor, name, module)
        if local_module:
            for key in accessor.__dict__.keys():
                setattr(local_module, key, getattr(accessor, key))
            return module
        return accessor
    finally:
        try:
            if f:
                f.close()
        except:
            pass

Example

I wanted to import mysql.connection, but I had a local package already called mysql (the official mysql utilities). So to get the connector from the system mysql package, I replaced this:

import mysql.connector

With this:

import sys
from mysql.utilities import import_non_local         # where I put the above function (mysql/utilities/__init__.py)
import_non_local('mysql.connector', sys.modules[__name__])

Result

# This unmodified line further down in the file now works just fine because mysql.connector has actually become part of the namespace
self.db_conn = mysql.connector.connect(**parameters)

Solution 6 - Python

Change the import path:

import sys
save_path = sys.path[:]
sys.path.remove('')
import calendar
sys.path = save_path

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontwigView Question on Stackoverflow
Solution 1 - PythonDamianView Answer on Stackoverflow
Solution 2 - PythonBoaz YanivView Answer on Stackoverflow
Solution 3 - PythonOmnifariousView Answer on Stackoverflow
Solution 4 - PythonBrandon SquizzatoView Answer on Stackoverflow
Solution 5 - PythoncaseyView Answer on Stackoverflow
Solution 6 - PythonlinutsView Answer on Stackoverflow