Tenga en cuenta que el editor de contenido eliminó un retorno de carro del contenido de defs.py y debería haberse mostrado como:
def f(x):
return x*x
Durante las vacaciones, tuve tiempo de jugar con Python. El problema con el que nos encontramos con el procesamiento paralelo desde la interfaz de Python/JSL es que Python normalmente lanza varias copias de JMP, no de Python. El motivo de esto es que incorporamos Python en JMP, por lo que el ejecutable es JMP en lugar de Python. Descubrí que el paquete de multiprocesamiento permite configurar el ejecutable para que se inicie cuando se ejecuta el código paralelo. Esta es la pieza que falta en el rompecabezas.
El siguiente script lo probé en JMP 15.2.1 tanto en Mac como en Windows con Python 3.9. En mi máquina con Windows tuve que usar el argumento Path() para Python Init(). Tengo varias versiones de Python y quiero asegurarme de que se elija la correcta. En mi Mac, Python Init() sin argumentos funciona bien para mi configuración. Hay un problema con el que me encontré en Windows, tuve que codificar la ruta a mi pythonw.exe. Deberá cambiar eso para que coincida con su ubicación de Python. Buscar FixMe:
Aparte de los parámetros Python Init() y la ruta a pythonw.exe en Windows, esto debería ejecutarse sin problemas en JMP 15.2.1 y posteriores en Mac o Windows. Puede ejecutarse en versiones anteriores de JMP que admitan Python, pero no se han probado.
Deberá crear un archivo defs.py en el mismo directorio que este script. El paquete de multiprocesamiento requiere que el código que se ejecuta en paralelo provenga de un código importable, no local del script. Por lo tanto, la mayor parte del trabajo en este script fue asegurarse de que sys.path esté configurado correctamente para que el script pueda importar el archivo defs.py.
Todo el contenido del archivo defs.py se encuentra en los comentarios del archivo, lo que facilita pegarlo en un nuevo archivo. Ese contenido duplicado a continuación es:
def f(x):
return x*x
El script toma una lista de números [1,2,3] y en paralelo llama a f(x) para elevar al cuadrado el valor que da los resultados [1,4,9].
pyParallel.jsl
Names Default To Here( 1 );
/* Description: Python mulltiprocessing from JMP JSL
* File: pyParallel.jsl, and defs.py
* Author: Paul Nelson
* JMP Statistical Discovery, LLC
*
* Python multiprocessing from an embedded context adds additional challenges.
* Just attempting to run Python parallel processing modules, typically launches
* multiple copies of JMP, not executing the Python code in parallel.
*
* The multiprocessing module allows setting the executable to launch, since we
* are loading Python into JMP, it's an embedded case thus sys.executable => JMP, not Python.
*
*
* Note: Functions to be run in parallel must come from importable code, not local
* code definitions.
* A multiprocessing 'Feature', not bug, See: https://bugs.python.org/issue25053
*
* Known Bugs:
* On Windows within JMP, sys.exec_prefix is empty, so path to pythonw.exe has to be set explicitly
*
* Create a defs.py file with the contents below. Place in the same directory as this JSL file.
def f(x):
return x*x
*/
// On my windows machine since I have multiple versions of Python, I have to specify the
// full path to the python39.dll, on my Mac it just works.
If( Host Is("Windows"),
Python Init(Path("C:\\Users\\panels\\AppData\\Local\\Programs\\Python\\Python39\\python39.dll")),
Python Init()
);
// send the current working directory where we loaded this script, down to Python so we can find defs.py
script_path = Get Default Directory();
Python Send(script_path);
Python Submit("\[
import sys
import os
import multiprocessing
from multiprocessing import Pool
import platform
# The loaded Python does not have the current working directory initialized, so change it
# to the directory of the script, as sent in from JSL
print('script_path: ' + script_path)
hostPlatform = platform.system()
print(hostPlatform)
try:
if 'Windows' == hostPlatform:
# strip leading / off from path if it exists
if '/' == script_path[0]:
script_path = script_path[1:]
norm_path = os.path.normpath(script_path)
print(norm_path)
os.chdir(norm_path)
else:
os.chdir(script_path)
except:
print("Unable to change current working directory", sys.exc_info())
finally:
print(os.getcwd())
# if '.' is not at the beginning of sys.path, prepend so we can look for modules within CWD
if sys.path[0] != '.':
sys.path.insert(0, '.')
print( sys.path )
# here argv is [''] empty string, doesn't seem to bother us in this use case.
#print( 'argv: ', sys.argv )
# prints the path to JMP, not Python
print('sys.executable: ' + sys.executable)
# build up path prefix to python3 exectable
if 'Darwin' == hostPlatform:
pyPath = os.path.join(sys.exec_prefix, 'bin', 'python3')
elif 'Windows' == hostPlatform:
# Bug - on windows sys.exec_prefix is empty in Python within JMP!
pyPath = None
if sys.exec_prefix:
pyPath = os.path.join(sys.exec_prefix, 'pythonw.exe')
else:
# FixMe: hardcode for now
pyPath = 'C:\\Users\\panels\\AppData\\Local\\Programs\\Python\\Python39\\pythonw.exe'
print ('Python Executable: ' + pyPath)
# Finally down to running something in parallel.
#
# Import the function code that will be executed in parallel.
# JMP is not reporting ImportError or ModuleNotFound errors, the scripting just returns -1
# on an import failure, so wrap it within our own try: except: block
try:
import defs as d
# print( d.f(2) )
# Tell multiprocessing to use the Python executable, not JMP to spawn the workers.
multiprocessing.set_executable(pyPath)
# Run the tasks in parallel - (separate Python instances)
with Pool(5) as p:
print(p.map(d.f, [1,2,3])) # Results in [1,4,9] the square of the numbers
except ModuleNotFoundError as mnf:
print(mnf)
print('Check that the moudule is located on your sys.path:')
print( sys.path )
except ImportError as error:
print(error)
except Exception as ex:
print(ex)
]\");