0

I have three scripts:

C:\code\voiceTerm\master.py:

from voice_terminal_module.voice_terminal import VoiceTerminal

vterm = VoiceTerminal()

C:\code\voiceTerm\voice_terminal_module\voice_terminal.py:

from chatbot_module.chatbot_module import Chatbot
class VoiceTerminal:
    print("INITIALIZING VOICE TERMINAL")
    cb = Chatbot()

C:\code\voiceTerm\voice_terminal_module\chatbot_module\chatbot_module.py:

class Chatbot:
    print("CHATBOT INITIALIZED")

Here is the wierd thing: When I run chatbot_module.py it works, and if I run voice_terminal.py it works. For some reason however, master.py errors out with the following message:

Traceback (most recent call last):
    File "c:\code\voiceTerm\master.py", line 1, in <module>
      from voice_terminal_module.voice_terminal import VoiceTerminal
    File "c:\code\voiceTerm\voice_terminal_module\voice_terminal.py", line 1, in <module>
      from chatbot_module.chatbot_module import Chatbot
ModuleNotFoundError: No module named 'chatbot_module'

Why does it work sometimes, but sometimes not?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • 1
    One of the places Python looks for modules is relative to the `current working directory` of *the program* - **not** necessarily relative to the `.py` file that contains the `import` statement in question. – Karl Knechtel Feb 01 '22 at 23:13
  • Could i somehow use path strings or something to import a module? Like straight from C: or D: or whatever root to the direct file or something? In other words do something like from "C:/code/voiceTerm/voice_terminal_module/chatbot_module/chatbot_module" import Chatbot? – HermanBajo Feb 01 '22 at 23:16
  • 1
    You can technically specify additional search directories for the import utility by manipulating `sys.path`, but that is not advisable as that can easily make your code unnecessarily confusing. – Ali Samji Feb 01 '22 at 23:23
  • The proper solutions to the problem are to either a) explicitly specify the path from an expected and known root (this works better if you *install* your code into a virtual environment), or b) use *relative imports* (this requires that you start the project from outside the package directory). This is a somewhat broad topic and a single Stack Overflow answer isn't going to cover it properly. – Karl Knechtel Feb 01 '22 at 23:51
  • I changed my mind and tried to write a general answer anyway. I will probably incorporate it into a greater work off-site at some point. – Karl Knechtel Feb 02 '22 at 00:30

3 Answers3

0

You'll need to restructure your project in a way that you have either: (1) two separate and installed packages containing each module or; (2) one package containing all modules. The simplest way forward is (2) and it looks like:

master.py
src/                 # Name it anything you wish
  __init__.py
  voice_terminal.py
  chatbot_module.py

Then use relative imports in voice_terminal.py

from .chatbot_module import Chatbot

Rule: Packages does not know others exists unless the other package is installed, added to sys.path, a subpackage, or part of a parent package.

Keto
  • 1,470
  • 1
  • 12
  • 25
0

Python general importing guide (for your own code)

I'm sure there should be a duplicate for this by now, but I can't find it, so I'm writing a full essay.

To solve the problem properly, you must take care of two things:

  1. Make sure that sys.path - the path that Python searches for modules - includes the path to the project root.

  2. Make sure that the import statements in the code are written to work with that path.

The search path

For the first part, you must understand how sys.path works. It is a list of folders where Python will look, that is set up automatically at startup (even if you don't import sys - just like sys.argv is).

Normally, it will be a list which contains, in order:

  • A path to the entry point for execution (the details of this will depend on how Python is started; typically this is '' when running a .py file directly from the same directory, a path the another directory if you do e.g. python somewhere/else/file.py, and an explicit path when using the -m switch)

  • Paths to system libraries

  • Paths to virtual environment libraries, if a virtual environment is active

  • Paths to things that were explicitly installed by the user

You can modify this list, with the expected impact on future import statements; but you ordinarily should not.

To ensure your project root is on the path, normally you should just install the project - ideally into a virtual environment. Please see the Python packaging guide for details. Failing that, make sure to start the project from just outside the folder containing your "top-level" code. In OP's case, that means C:\code. This will ensure that C:\code (or something equivalent) is on sys.path, which we can then rely upon for the second step.

The imports

We can fundamentally do this in two ways: By absolute imports specifying the path from the root, or by relative imports specifying the path from the current source file. Read more on Stack Overflow about relative imports here and here.

Absolute imports

In either case, we want to treat the root folder for our project (here, C:\code\voiceTerm) as a package. For absolute imports, this means we will always mention the root folder name in our import path.

Thus:

In C:\code\voiceTerm\master.py: from voiceTerm.voice_terminal_module.voice_terminal import VoiceTerminal

In C:\code\voiceTerm\voice_terminal_module\voice_terminal.py: from voiceTerm.voice_terminal_module.chatbot_module.chatbot_module import Chatbot

(You don't really want to have _module in your folder or file names. It doesn't really add information, makes this considerably harder to type, and is actually a bit misleading.)

We can also import entire modules: import voiceTerm.master, import voiceTerm.voice_terminal_module.voice_terminal; import voiceTerm.voice_terminal_module.chatbot_module.chatbot_module. Additionally, by naming a file __init__.py, we become able to import the folder as a package: import voiceTerm; import voiceTerm.voice_terminal_module; import voiceTerm.voice_terminal_module.chatbot_module. We can also import a module from one of those packages: from voiceTerm import master; from voiceTerm.voice_terminal_module import voice_terminal; from voiceTerm.voice_terminal_module.chatbot_module import chatbot_module.

Relative imports

With relative imports, we must use the from syntax, and we can only import things from within our own package hierarchy. However, we have the advantage that we don't have to specify full paths, and we can rename packages without having to edit the code. My personal recommendation is to use relative imports where possible; this also makes a strong visual distinction with imports from system libraries or other third-party packages.

For a relative import, first we specify the package or sub-package that we're importing from, using a relative import path. By starting with a single ., we start from the current directory, looking for another module or package within the same folder.

Thus:

In C:\code\voiceTerm\master.py: from .voice_terminal_module.voice_terminal import VoiceTerminal to import the class; from .voice_terminal_module import voice_terminal to import the module.

In C:\code\voiceTerm\voice_terminal_module\voice_terminal.py: from .chatbot_module.chatbot_module import Chatbot to import the class; from .chatbot_module import chatbot_module to import the module.

Additional .s navigate up the package hierarchy (but we cannot go beyond the root this way). For example, from ... import master to import the top-level master.py from the lower-down chatbot_module.py. (Of course, this wouldn't actually work in this case, because it would be a circular import.) To import another source file (as a module) from the same directory, simply from . import other_file. You get the idea.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • Assuming `master.py` is intended to be an "entry point" for the project, it will often be better to restructure the code such that `master.py` is outside the package hierarchy. On the other hand, it is possible to set up multiple entry points, and even do things like make your packages runnable with `python -m` as if they were modules. These topics are beyond the scope of this answer, however. – Karl Knechtel Feb 02 '22 at 00:33
-1

I solved it. You can appearantly use importlib for this. I replaced the import code in my voice_terminal.py script with this: chatbot_module = SourceFileLoader("chatbot_module", "C:/code/voiceTerm/voice_terminal_module/chatbot_module/chatbot_module.py").load_module()

  • oh and you have to do "from importlib.machinery import SourceFileLoader" – HermanBajo Feb 01 '22 at 23:40
  • 1
    I strongly recommend against doing this. `importlib` exists for situations where you don't know until runtime exactly what to import, and/or where the code will come from - such as with a dynamic plugin system. – Karl Knechtel Feb 02 '22 at 00:34