简体   繁体   中英

How can I make a python file runnable as a script and importable as a package?

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 ie

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 (ie 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?

您可以使用-m开关和标准软件包路径从软件包中运行脚本。

python -m subwayclock.subwayclock

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM