Importing wrong packages when calling a python script from another python script via subprocess
I am trying to automate executable creation for my program in 32 and 64bit with cx_Freeze. I have two separate python 3.4 installations (32 and 64 bit) with all required packages, which all correspond to their appropriate architectures.
However, when I execute the script that calls the different python installations via subprocess, the called python subprocesses import the wrong packages. They both import the packages, the original script was called from and not the packages the python installation, that was called in the subprocess.
Minimal example called with a 64bit python:
#! coding=utf-8
import subprocess as sp
sp.call(["python34-32","test.py"])
sp.call(["python34","test.py"])
The test.py contains only the line:
import cx_Freeze
The second process, that also calls the 64bit python won't have a problem. But the 32bit will throw the following error:
ImportError: DLL load failed: %1 is not a valid Win32 application.
This is because the subprocess, which is calling 32bit python, imports cx_freeze from the 64bit python. The situation can also be reversed. When the main script is executed with a 32bit python, the same error occurs, however, now it is because the 64bit python called in the suprocess is importing the 32bit freeze package.
How can I stop this behavior and tell it to import from the appropriate source?
I am using Windows 7 x64 and PyDev as IDE, in case it is relevant, which it probably is.
EDIT: So apparently it works when the Main Script is executed from command line inside the source directory. Thanks to the comments below, I guess the problem has to with how PyDev sets the environment variables.
Answer
The issue you're facing arises because the subprocesses you spawn are inheriting the environment of the parent process (your main script). Since your parent process is using one Python installation (e.g., 64-bit), the subprocesses, when invoked, are picking up environment variables related to the parent environment (like the Python path, etc.) and using the wrong Python package (32-bit or 64-bit).
Explanation of the Problem:
-
Python Environment Variables: The
subprocess
calls spawn new processes, but they inherit environment variables from the parent process, such asPYTHONPATH
andPATH
. These environment variables determine which Python libraries are loaded. If the parent process uses a 64-bit Python installation, the subprocesses will also pick up the 64-bit environment, even if you explicitly call the 32-bit Python executable. -
Wrong Architecture Loading: Since the subprocess is using the wrong environment, it's loading the wrong version of packages, such as the 64-bit version of
cx_Freeze
when the subprocess expects the 32-bit version, and vice versa. This leads to the error you are seeing:ImportError: DLL load failed: %1 is not a valid Win32 application
.
Solution: Ensure Correct Environment for Each Python Version
To solve this, you'll need to explicitly set the environment variables for each subprocess to ensure that the correct Python installation (32-bit or 64-bit) is used.
Here’s a way to set the correct environment for each subprocess call:
Approach 1: Modify PATH
and PYTHONHOME
Environment Variables
You can modify the environment variables for each subprocess to point to the appropriate Python installation before calling the subprocess.call()
function.
import subprocess
import os
def call_subprocess(python_executable, script):
# Get the current environment variables
env = os.environ.copy()
# Set the correct Python executable and related environment variables for subprocess
env["PATH"] = os.path.dirname(python_executable) + ";" + env.get("PATH", "")
env["PYTHONHOME"] = os.path.dirname(python_executable) # Optional: helps to control Python environment
# Call the subprocess with the adjusted environment
subprocess.call([python_executable, script], env=env)
# Main script that calls 32-bit and 64-bit Python
call_subprocess("python34-32", "test.py") # Calling 32-bit Python
call_subprocess("python34", "test.py") # Calling 64-bit Python
Explanation:
-
Set
PATH
: By adjusting thePATH
environment variable, you ensure that the subprocess will use the correct Python binary (32-bit or 64-bit). Theos.path.dirname(python_executable)
will give you the directory where the Python executable resides, which should contain the correctpython.dll
(or similar). -
Set
PYTHONHOME
(Optional): Sometimes setting thePYTHONHOME
environment variable can help ensure that the subprocess uses the correct Python installation's libraries. However, it's not always required, so you can try it both with and without this line to see if it makes a difference. -
Use
subprocess.call()
: Theenv=env
argument ensures that the subprocess uses the modified environment.
Approach 2: Using Full Path to Python Executable
Alternatively, you can use the full path to the Python executables (python34-32
or python34
) to avoid issues with environment variables. This method doesn't rely on manipulating the environment variables.
import subprocess
# Full path to the 32-bit and 64-bit Python executables
python_32bit = r"C:\path\to\32bit\python34.exe" # Full path to 32-bit python
python_64bit = r"C:\path\to\64bit\python34.exe" # Full path to 64-bit python
def call_subprocess(python_executable, script):
subprocess.call([python_executable, script])
# Main script that calls 32-bit and 64-bit Python
call_subprocess(python_32bit, "test.py") # Calling 32-bit Python
call_subprocess(python_64bit, "test.py") # Calling 64-bit Python
In this case, you directly specify the full path to the Python executable. The subprocess will not inherit the environment, and this avoids the issue of conflicting cx_Freeze
installations.
Approach 3: Running Subprocesses with Different Virtual Environments
Another solution (especially if you have different Python environments for 32-bit and 64-bit) is to use virtualenv
or venv
to isolate the two Python environments. Each virtual environment will have its own set of installed packages, and this can prevent cross-contamination.
- Create two virtual environments for your 32-bit and 64-bit installations.
- Activate the respective environment when running subprocesses.
For example:
python32 -m venv venv_32bit
python64 -m venv venv_64bit
Then, run your subprocesses within the appropriate virtual environment, ensuring isolation between the two architectures.
Conclusion:
- The main problem is that subprocesses inherit the environment of the calling script.
- To ensure that each subprocess uses the correct Python installation (32-bit or 64-bit), you can either modify the environment variables (
PATH
,PYTHONHOME
) or use the full path to the correct Python executables. - Additionally, using virtual environments is a good strategy to ensure isolation between the different Python versions.