Reputation: 399
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
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
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
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