user2004245
user2004245

Reputation: 399

Python Import Module from Decorator

I'm working on an application in Python 3, and what I'm doing is unconventional.

cx_Oracle is a difficult module to setup, and for my application is an optional dependency. What I would like to do is wrap the import of the module in a decorator, to place only above the functions that use it. This will keep from having to import at the top of my module and allow it to not be setup.

class Loader():
    def load_cx_oracle(func):
        def inner(*args, **kwargs):

            # Additional code before import.

            import cx_Oracle

            return func(*args, **kwargs)
        return inner

    @load_cx_oracle
    def function_using_cx_oracle(self):
        conn = cx_Oracle.connect()

However, when I try the above, I get NameError: name 'cx_Oracle' is not defined

Upvotes: 5

Views: 4761

Answers (3)

Dunes
Dunes

Reputation: 40833

There a couple of problems with the accepted answer. The most important of which is that it runs the import logic each and every time the function is called. The second is that the decorator must be defined in the same module it is used in otherwise the decorator and the decorated will have different globals. You can directly access a function's globals through the __globals__ attribute of the function. The code example first checks if module exists in the function's globals before executing the import logic. The example also uses the functools.wraps decorator to preserve doc strings, function name and also argument names when using things like help(func).

from functools import wraps

def load_operator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if "operator" not in func.__globals__:
            # setup logic -- only executed once
            import operator
            func.__globals__["operator"] = operator
        return func(*args, **kwargs)
    return wrapper

class A:
    @load_operator
    def add(self, x, y):
        return operator.add(x, y)

    def subtract(self, x, y):
        return operator.subtract(x, y)

a = A()
try:
    a.subtract(1, 2)
except NameError:
    print("operator not yet loaded")
print(a.add(1, 2))
print(A.add)

Upvotes: 8

BrenBarn
BrenBarn

Reputation: 251538

import only binds the module name in the namespace where you use it. if you do import cx_Oracle inside a function, the name cx_Oracle will only be available inside that function.

However, you can use global to make the assignment global. Change your decorator to use:

global cx_Oracle
import cx_Oracle

Whether this approach is really the right one is debatable. Depending on how your code is being used, it may be cleaner to just have a function that users call if they want to make cx_Oracle available, to use a try-wrapped import as Daniel's answer suggests, or to have the loading be defined by some external setting (e.g., a config file).

Upvotes: 4

Daniel
Daniel

Reputation: 42778

If you want an optional import, use try-except at the beginning of your module:

try:
    import cx_Oracle
except ImportError:
    cx_Oracle = None

Upvotes: 0

Related Questions