Yaroslav Nikitenko
Yaroslav Nikitenko

Reputation: 1853

Raise a custom exception on import

Short question: I have a module with objects. How can I do that if someone imports an object from my module, my specified exception is raised?

What I want to do: I write an architectural framework. A class for output depends on jinja2 external library. I want the framework to be usable without this dependency as well. In the package's __init__.py I write conditional import of my class RenderLaTeX (if jinja2 is available, the class is imported, otherwise not).

The problem with this approach is that I have some code which uses this class RenderLaTeX, but when I run it on a fresh setup, I receive an error like Import error: no class RenderLaTeX could be imported from output. This error is pretty unexpected and ununderstandable before I recall that jinja2 must be installed beforehand.

I thought about this solution: if the class is not available, __init__.py can create a string with this name. If a user tries to instantiate this object with the usual class constructor, they'll get a more meaningful error. Unfortunately, simple import

from output import RenderLaTeX

won't raise an error in this case.

What would you suggest, hopefully with the description of benefits and drawbacks?

Important UPD: I package my classes in modules and import them to the module via __init__.py, so that I import 'from lena.flow import ReadROOTFile', rather than 'from lena.flow.read_root_file import ReadROOTFile.'

Upvotes: 0

Views: 2251

Answers (2)

Yaroslav Nikitenko
Yaroslav Nikitenko

Reputation: 1853

After a year of thinking, the solution appeared.

First of all, I think that this is pretty meaningless to overwrite an exception's type. The only good way would be to add a useful message for a missing import.

Second, I think that the syntax

from framework.renderers import MyRenderer

is really better than

from framework.renderers.my_renderer import MyRenderer

because it hides implementation details and requires less code from user (I updated my question to reflect that). For the former syntax to work, I have to import MyRenderer in __init__.py in the module.

Now, in my_renderer.py I would usually import third-party packages with

import huge_specific_library

in the header. This syntax is required by PEP 8. However, this would make the whole framework.renderers module depend on huge_specific_library.

The solution for that is to violate PEP 8 and import the library inside the class itself:

class MyRenderer():

    def __init__(self):
        import huge_specific_library
        # ... use that...

Here you can catch the exception if that is important, change its message, etc. There is another benefit for that: there exist guides how to reduce import time, and they propose this solution (I read them a long time ago and forgot). Large modules require some time to be loaded. If you follow PEP 8 Style Guide (I still think that you usually should), this may lead to large delays just to make all imports to your program, not having done anything useful yet.

The only caveat is this: if you import the library in __init__, you should also import that to other class methods that use it, otherwise it won't be visible there.

For those who still doubt, I must add that since Python imports are cached, this doesn't affect performance if your method that uses import is not called too often.

Upvotes: 0

jontypreston
jontypreston

Reputation: 448

When Python imports a module all of the code inside the file from which you are importing is executed.

If your RenderLaTeX class is therefore placed into a seperate file you can freely add logic which would prevent it from being imported (or run) if required dependencies are missing.

For example:

try:
    import somethingidonthave
except ImportError:
    raise Exception('You need this module!')

class RenderLaTeX(object):
    pass

You can also add any custom message you want to the exception to better describe the error. This should work in both Python2 and Python3.

Upvotes: 1

Related Questions