Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 694
user2004245 Avatar asked Mar 12 '15 19:03

user2004245


People also ask

How to use a decorator in Python?

To use a decorator ,you attach it to a function like you see in the code below. We use a decorator by placing the name of the decorator directly above the function we want to use it on. You prefix the decorator function with an @ symbol. Here is a simple example. This decorator logs the date and time a function is executed:

What is the decorator module used for?

The goal of the decorator module is to make it easy to define signature-preserving function decorators and decorator factories. It also includes an implementation of multiple dispatch and other niceties (please check the docs).

Is it safe to copy the decorator from one module to another?

It is safe even to copy the module decorator.py over an existing one, since we kept backward-compatibility for a long time. The project is hosted on GitHub.

How to call a function from another function in decorator?

In Decorators, functions are taken as the argument into another function and then called inside the wrapper function. In the above code, gfg_decorator is a callable function, will add some code on the top of some another callable function, hello_decorator function and return the wrapper function. print("This is inside the function !!")


2 Answers

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)
like image 181
Dunes Avatar answered Sep 28 '22 08:09

Dunes


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).

like image 30
BrenBarn Avatar answered Sep 28 '22 06:09

BrenBarn