admin管理员组文章数量:1193758
Suppose I have two Python script files: foo
and utils/bar.py
in some directory. In foo
, I have:
import os
import sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from utils.bar import func1, func2
# ... code using func1 or func2
# ... perhaps some code using utils.bar.func3
that is, the subdirectory utils
is added to the module search path, and then utils/bar.py
is used as the source of utils.bar functions.
I'm not much of a pythonista, so I'm not sure if this hack is customary or not, but regardless - I need a single deployable script file, not a hierarchy of files. So, I want to "fold" the file hierarchy all into a single file - put the contents of bar.py
into foo
in some way, so that the script will continue working even if I then removed the utils/
subdirectory and the bar.py
file.
I could remove all references to utils.bar
and just copy the plain functions from bar.py
into foo
; but I was wondering if there was something less "brute-force" than that.
(There are actually multiple files in multiple subdirectories, I just gave a simplified example.)
Suppose I have two Python script files: foo
and utils/bar.py
in some directory. In foo
, I have:
import os
import sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from utils.bar import func1, func2
# ... code using func1 or func2
# ... perhaps some code using utils.bar.func3
that is, the subdirectory utils
is added to the module search path, and then utils/bar.py
is used as the source of utils.bar functions.
I'm not much of a pythonista, so I'm not sure if this hack is customary or not, but regardless - I need a single deployable script file, not a hierarchy of files. So, I want to "fold" the file hierarchy all into a single file - put the contents of bar.py
into foo
in some way, so that the script will continue working even if I then removed the utils/
subdirectory and the bar.py
file.
I could remove all references to utils.bar
and just copy the plain functions from bar.py
into foo
; but I was wondering if there was something less "brute-force" than that.
(There are actually multiple files in multiple subdirectories, I just gave a simplified example.)
Share Improve this question edited Jan 23 at 18:43 einpoklum asked Jan 23 at 13:29 einpoklumeinpoklum 131k76 gold badges411 silver badges841 bronze badges 5 |3 Answers
Reset to default 2The zipapp
Module
If you want to bundle your app into a single runnable file then you can use the standard library module zipapp
to create a zip archive that the python binary knows how to execute, or optionally make the archive itself runnable.
If you have a project structured like:
└── myapp/
├── foo.py # main script
└── utils/
├── __init__.py
└── bar.py
And where foo.py
has an entrypoint function eg.
from util.bar import greet
def main():
print(greet(who='world'))
if '__main__' == __name__:
main()
Then you can create a runnable archive like so:
cd path/to/dir/containing/myapp
python -m zipapp myapp --main foo:main --output myapp.pyz
Here --main foo:main
means upon starting the app, import a module called foo
and execute a function in it called main()
.
You can then run the archive like so:
python myapp.pyz
If you want to make the archive runnable (only works for unix-based systems), then you can also add the --python
flag. This sets the shebang (#!
) for the archive. Example:
python -m zipapp myapp \
--main foo:main \
--output myapp.pyz \
--python '/usr/bin/env python3.10'
# You can now run the archive like so:
./myapp.pyz
By default, files in the archive are stored uncompressed. You can compress the files by using the --compress
flag.
Using Third Party Packages
zipapp
only includes the files in the source directory. If you have third party packages you need to use, then you will either need to setup a virtual environment on the target machine or use pip
to directory install these packages into your source directory before you package it. Example:
pip install --target path/to/myapp requests
To stop bloating your source directory with installed packages, you may find it easier to create a build directory where you copy your source to and install your desired packages when building the zip archive.
Distributing Libraries Separately
If the size of the third party libraries is large, it may become problematic to constantly redistribute these libraries with every change you make to your own source code. You can instead distribute them once as a zip file, and use PYTHONPATH
to tell python to make the libraries in the zip file available to your application. To package the requests
package in a separate zip file and have it be importable from your own code you would do:
python -m pip install requests --target build
pushd build # Temporarily cd to build dir
# This is required for files to be packaged in the zip file correctly
python -m zipfile --create ../lib.zip .
popd
You would run your app like so:
PYTHONPATH=lib.zip python -m zipapp myapp.pyz
NB: Be sure to build both your app zip file and your library zip file separately and in isolation. You do not want the app zip file to accidentally include library code, or vice versa.
Accessing Resources
If you have config files or other data files you want to package up with the archive, then you will need to access them a different way. This is because they will no longer be directly accessible on the file system. That is open('path/to/file')
will not work as you want it to. Instead you will need to use the importlib.resources
module, and to have your data files in python package.
As example, suppose you have a file called config.toml
you want to package with your archive. You would need to add it to your project like so:
└── myapp/
├── foo.py # main script
├── utils/
│ ├── __init__.py
│ └── bar.py
└── data/ # The file needs to be contained by a package.
├── __init__.py # A proper package, not just a namespace package.
└── config.toml
You would then load the contents of the file like so:
from importlib.resources import read_text
text = read_text('data', 'config.toml')
NB. This form will also work when running your app as a standard script. So NO need for constructions like:
if is_archive(): # pseudo-function
text = importlib.resources.read_text('data', 'config.toml')
else: # if script
with open('data/config.toml') as f:
text = f.read()
Assuming this is an import issue.
You are treating this as a full module (With a __init__.py & all of that.), but it isn't. You are trying to import a single file, which can be done using the 'sys' module:
# importing sys
from bar.py import func1, func2
import sys
# adding utils path
sys.path.insert(0, 'Path/To/Utils') # Must be absolute path, I recommend using os.getcwd()
# Code Using func1 & func2
Source: Geeks For Geeks (Click the link to see the specific post.)
I would suggest to structure your project like this.
.
├── app -> The main package, which contains all source code files
│ ├── __init__.py -> This file must exist so that app is treated as a package
│ ├── utils -> The utils package
│ │ ├── __init__.py
│ │ └── bar.py
│ └── main.py -> The entry point of your code
├── tests -> Contains all unit tests
│ └── __init__.py
├── .gitignore
└── README.md
Make sure that you have __init.py__
file inside the app
and utils
directory to turn them into packages.
In your main.py
:
import os
import sys
from app.utils.bar import func1, func2
if __name__ == '__main__':
print('Start my program')
func1()
func2()
To start your program, make sure that you stay at the root of the project, then run:
$ python -m app.main
Note that the program is executed as a module, not a script. This answer has the difference well-explained.
本文标签: How to quotfoldquot python files used as modules into the main script fileStack Overflow
版权声明:本文标题:How to "fold" python files used as modules into the main script file? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1738493127a2089823.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
No module named 'utils'
. – einpoklum Commented Jan 23 at 15:04