4

I'm still trying to get a hang of python 3 and I'm running into an issue where I can either run a .py file as a script or import it as a module, but not both.

Directory Structure

test/
  __init__.py
  test.py
  subwayclock/
    __init__.py
    subwayclock.py
    build/
      gen/
        __init__.py
        gtfs_realtime_pb2.py
        nyct_subway_pb2.py
      __init__.py

in this scenario test.py looks like this and works (rawFEED() is a function in subwayclock.subwayclock):

from subwayclock.subwayclock import *

print(rawFEED())

However, I cannot run the script directly i.e.

python subwayclock/subwayclock.py

because it gives the following error:

Traceback (most recent call last):
  File "subwayclock.py", line 32, in <module>
    from .build.gen.gtfs_realtime_pb2 import FeedMessage
SystemError: Parent module '' not loaded, cannot perform relative import

HOWEVER, if I modify the import statement in subwayclock/subwayclock.py to state (i.e. with the leading '.' removed):

from subwayclock.subwayclock import FeedMessage

I can run the subwayclock.py script directly through the command line, calling the main function perfectly.

BUT, when I run the original test.py file, the import statement no longer works, and I get the following error:

  Traceback (most recent call last):
  File "test.py", line 1, in <module>
        from subwayclock.subwayclock import *
      File "/var/www/test/subwayclock/subwayclock.py", line 32, in <module>
        from build.gen.gtfs_realtime_pb2 import FeedMessage
    ImportError: No module named 'build'

Can I make this script independently runnable and importable?

Willipedia
  • 51
  • 1
  • 2

3 Answers3

1

In this case, you could just use absolute imports instead of relative imports, but depending on the dependencies between your modules, it could result in some odd behavior, where objects and classes are technically being defined twice (once in your __main__ script, and once when another module imports your module)

The proper way to do this would be to create a proper python package with a setup.py script and use the console_scripts entry point feature to expose a function as a command line script.

Your project should be organized something like this.

/subwayclock
    /subwayclock
        __init__.py
        subwayclock.py
        ...
    setup.py

Your setup.py would look like this

from setuptools import setup, find_packages

setup(name='subwayclock',
      version='0.1',
      packages=find_packages(),
      zip_safe=False,
      entry_points = {
          'console_scripts': ['subwayclock_script_name=subwayclock.subwayclock:rawFEED'],
      }
)

Then you just install the package

$ python setup.py install

(you can also use develop mode so you can still work on it)

$ python setup.py develop

And you will be able to run that command line script

$ subwayclock_script_name
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
0

You can run a script from a package using the -m switch and the fully qualified package path

python -m subwayclock.subwayclock
MaxNoe
  • 14,470
  • 3
  • 41
  • 46
0

I will try to describe you, how it works and, also, to help.


Relative import

First of all, python has a number of different methods to import some. Some of them is a relative import (from .package import somemodule)
The dot means that we want to import a somemodule from the current package. That means that we should declare our package(when we import this module, we import it from the package , which has a name and etc.)


Absolute import(maybe there is another name)

This import is used nearly everywhere in simple scripts and you must know this.
Example:

from app import db

Where app is a python module(app.py file) and db is a variable in it.If you want to know more, read the docs.



Solution

I don't really know a pretty way to avoid this, but if I were you, I would do like this:

if __name__ == '__main__':
    from mypackage.module import function
else:
    from .module import function

Also you can run python -m package.module.function in simple cases, but it's not quiet a good idea.
Or you can add your package directory to a PYTHONPATH variable. See the good answer to the nearly the same question.

sakost
  • 250
  • 4
  • 15