Create an executable file (EXE) by PyInstaller in a hybrid environment (Nimporter) of Python + Nim

Hybrid environment that I've always been interested in

In this article, I found out that there is a cool library called nimporter, and made a slight [comment](/uesseu/items/477bbabe8ea354cb9d49 # comment-4601239e890a54de0ccf), but I was always interested, so I tried to verify ..

The sample source is here.

There are many articles on PyInstaller in Qiita, so please refer to that as well.

environment

It seems that PyInstaller and pyenv and virtualenv are not compatible, so Python is running without a virtual environment.

Trouble during development, etc.

First, play with Nimporter

Click here for the directory structure of this sample.

File folder Explanation
└─ nimporter-sample Project directory
├─nimutils Package for nim source
│ ├─__init__.py Promise file
│ ├─ calc.nim Calculation sample
│ ├─ thread_test.nim Thread sample
│ └─ uuid.nim Another library call sample
├─ nimporter_sample.py Main python script
└─ nimporter_sample.spec.sample Sample Spec file for PyInstaller

Import the Nim module in the package from Python

You can access Nim methods in packages (folders) just like regular Python, even if they are not in the same directory as the Python files.

nimutils/calc.nim


import nimpy
import strformat

proc add(a: int, b: int): int {.exportpy.} =
    echo fmt("{ a + b = }")
    return a + b

nimporter_sample.py


import nimporter
from nimutils import calc # nimutils/calc.import nim

# call nim method
print(calc.add(2, 4))  # 6

Try using the module installed by Nimble

You can also import a module called nuuid that was previously installed with the nimble command in the nim source and return the execution result to Python.

#Module installation
$ nimble install nuuid

nimutils/uuid.nim


import nimpy
import strformat
import nuuid      # import uuid library 

proc generate(): string {.exportpy.} =
    return generateUUID()

nimporter_sample.py


import nimporter
from nimutils import uuid

print(uuid.generate())

Try running Nim multithreading from Python

This also worked fine.

nimutils/calc.nim


import nimpy
import strformat
import os

proc threadFunc(param: tuple[a, b: int]) {.thread.} = 
    echo fmt("This is Thread-{param.a}")

proc threadTest(): int {.exportpy.} =
    var thr: array[0..1, Thread[tuple[a, b: int]]]
    echo "start threads"
    defer:
        echo "wait threads"
    thr[0].createThread(threadFunc, (1, 1000))
    thr[1].createThread(threadFunc, (2, 1000))
    sleep(1000)
    joinThreads(thr)

nimporter_sample.py


from nimutils import thread_test

#Call the method that is threading & executing
thread_test.threadTest()

However, it seems that Nim methods cannot be called from ** Python threads **. (Issue is here) Therefore, at present, when using Nimporter, it seems that it can only be called from the main thread of Python. It seems that it is not possible to call a Nim module from within a request handler in a web framework.

Creating a single Exe file with PyInstaller

Exe the Python file that called the above 3 patterns with Pyinstaller, and confirm that it works in another Windows 10 environment.

Exe is not completed in one shot, so follow the steps below to create it.

  1. Start Pyinstaller and create a Spec file
  2. Add Pyd file information to Spec file
  3. Start Pyinstaller with Spec file and create Exe file
  4. If you get an error, add the missing module to the Spec file

After that, repeat steps 3 and 4 until no error occurs when starting Exe.

Creating a Spec file

If it is a Python script with a simple configuration, you may be able to create an EXE file with Pyinstaller in one shot, but start Pyinstaller and modify the generated Spec file appropriately to prepare the environment for creating Exe. I will.

First, specify the Python script that will be the entry point and execute PyInstaller. A spec file will be generated in the same folder as the script, and an Exe file will also be created in the dist folder. However, even if you start the completed Exe file, an error will occur and it will end, so we will describe the modules etc. that are missing in the generated Spec file.

$ pyinstaller nimporter_sample.py --onefile
・ ・ ・

$ dir dist
2021/01/04  19:36    <DIR>          .
2021/01/04  19:36    <DIR>          ..
2021/01/04  19:36         7,725,805 nimporter_sample.exe
               1 File(s)      7,725,805 bytes
               2 Dir(s)  232,853,856,256 bytes free

$ dist\nimporter_sample.exe

#I get an error when I start it
Traceback (most recent call last):
  File "nimporter_sample.py", line 1, in <module>
    import nimporter
  File "c:\apps\python\python39\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 493, in exec_module
    exec(bytecode, module.__dict__)
  File "nimporter.py", line 13, in <module>
  File "c:\apps\python\python39\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 493, in exec_module
    exec(bytecode, module.__dict__)
  File "setuptools\__init__.py", line 24, in <module>
  File "c:\apps\python\python39\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 493, in exec_module
    exec(bytecode, module.__dict__)
  File "setuptools\depends.py", line 6, in <module>
ModuleNotFoundError: No module named 'setuptools.py33compat'
[431764] Failed to execute script nimporter_sample

Add Pyd files to Spec binaries

Nimporter generates a Python extension module (extension is pyd), so modify it so that Pyinstaller includes it in the Exe module. The Spec file is actually a Python script, so you can write Python code to modify the configuration file.

In this sample, the Nim file is placed in the nimutils folder, so the pyd is output to the nimutils \ __ pycache__ folder, so modify the Spec file as follows.

nimporter_sample.spec


# -*- mode: python ; coding: utf-8 -*-
import os

curDir = os.getcwd()
cacheDir = os.path.join(curDir, 'nimutils', '__pycache__')
pydDir = os.path.join('.', 'nimutils')
block_cipher = None

a = Analysis(['nimporter_sample.py'],
             pathex=[curDir],
             #Add the nimporter-generated pyd file as a binary
             binaries=[
               (os.path.join(cacheDir, 'calc.pyd'), pydDir),
               (os.path.join(cacheDir, 'thread_test.pyd'), pydDir),
               (os.path.join(cacheDir, 'uuid.pyd'), pydDir),
             ],

You can specify multiple binary file settings, and specify each file with a tuple (Pyd file location, where to place it when the EXE starts). In this example, it is specified that the pyd file in nimutils/__ pycache __ / should be expanded to the nimutils folder when Exe is started. Isn't it the __pycache__ folder even after extraction? I thought, but when executing Exe, even if there is pyd in the __pycache__ folder, it does not seem to be read from there.

Start Pyinstaller with Spec file and create Exe file

Specify the Spec file as an argument of PyInstaller and execute it.

#Spec file and execute
$ pyinstaller nimporter_sample.spec

#Start Exe created in dist
$ dist\nimporter_sample.exe

If you get an error, add the missing module to the Spec file

If the following error occurs when you start the Exe created by PyInstaller, add the module name to hiddenimports in the Spec file of PyInstaller to solve the error.


ModuleNotFoundError: No module named 'setuptools.py33compat'

I repeated it several times and added two modules to hidden imports and the error disappeared.

nimporter_sample.spec


  hiddenimports=['setuptools.py33compat','setuptools.py27compat'],

State of execution

It's a rough capture, but for your reference.

pyinstaller.gif

Summary

I introduced the procedure to convert a Python script using Nimporter to Exe with PyInstaller. The point was to include the Pyd file generated by Nimporter as a binary.

Although there is a limitation that modules created with Nimporter cannot be called from Python multithreading, it is possible to distribute a small utility as Exe, such as using the attractive library of Python and creating the part you want to process at high speed with Nim. Isn't it an attractive environment?

However, I think that it is not a technology that can be used in production, but is limited to hobby use (hobby) use.

Referenced site

https://qiita.com/rebellious-wimp/items/61f16389f957b2ace163

Recommended Posts

Create an executable file (EXE) by PyInstaller in a hybrid environment (Nimporter) of Python + Nim
Create an exe file that works in a Windows environment without Python with PyInstaller
Create an executable file in a scripting language
[Python Kivy] How to create an exe file with pyinstaller
Create a binary file in Python
Create an instance of a predefined class from a string in Python
[GPS] Create a kml file in Python
Creating an exe file with Python PyInstaller: PC freezes in parallel processing
Test & Debug Tips: Create a file of the specified size in Python
Create a Vim + Python test environment in 1 minute
Create a GIF file using Pillow in Python
How to create a JSON file in Python
Create an environment of 64bit Windows + python 2.7 + MeCab 0.996
Create a virtual environment with conda in Python
GUI (WxPython) executable file (pyInstaller) [Windows] in Python3
Create a MIDI file in Python using pretty_midi
[Docker] Create a jupyterLab (python) environment in 3 minutes!
Create a Python environment
Group by consecutive elements of a list in Python
How to use NUITKA-Utilities hinted-compilation to easily create an executable file from a Python script
Create a function in Python
Create a dictionary in Python
How to create an instance of a particular class from dict using __new__ () in python
How to develop in a virtual environment of Python [Memo]
[Note] Import of a file in the parent directory in Python
[Django] Memo to create an environment of Django + MySQL + Vue.js [Python]
Create a Python environment for professionals in VS Code on Windows
[Python] A memo of frequently used phrases (by myself) in Python scripts
A simple data analysis of Bitcoin provided by CoinMetrics in Python
[Understanding in the figure] Management of Python virtual environment by Pipenv
Read the standard output of a subprocess line by line in Python
Various ways to create an array of numbers from 1 to 10 in Python.
Get the formula in an excel file as a string in Python
Create a DI Container in Python
Create a Python environment on Mac (2017/4)
Create a virtual environment with Python!
Create an Excel file with Python3
Create a python environment on centos
Create a Kubernetes Operator in Python
Create a random string in Python
Create a shortcut to run a Python file in VScode on your terminal
A memo organized by renaming the file names in the folder with python
Get a list of packages installed in your current environment with python
I want to color a part of an Excel string in Python
Open an Excel file in Python and color the map of Japan
I made a program to check the size of a file in Python
[Python] [Word] [python-docx] Try to create a template of a word sentence in Python using python-docx
Display a list of alphabets in Python 3
How to convert Python to an exe file
The story of making Python an exe
Create a python environment on your Mac
Create a simple GUI app in Python
[Python] Create a virtual environment with Anaconda
Quickly create an excel file with Python #python
[Python] Create a Batch environment using AWS-CDK
Read the file line by line in Python
Read the file line by line in Python
Create a deb file from a python package
Create an OpenCV3 + python3 environment on OSX
Creating a virtual environment in an Anaconda environment
Create a Python development environment in 10 minutes (Mac OS X + Visual Studio Code)