Nota che l'editor dei contenuti ha eliminato un ritorno a capo dal contenuto di defs.py e avrebbe dovuto essere visualizzato come:
def f(x):
return x*x
Durante le vacanze, ho avuto il tempo di armeggiare con Python. Il problema che abbiamo riscontrato con l'elaborazione parallela dall'interfaccia Python/JSL è che Python in genere avvia più copie di JMP, non Python. Il motivo è che incorporiamo Python in JMP, quindi l'eseguibile è JMP invece di Python. Ho scoperto che il pacchetto multiprocessing consente di impostare l'eseguibile per l'avvio durante l'esecuzione del codice parallelo. Questo è il pezzo mancante del puzzle.
Lo script seguente l'ho testato su JMP 15.2.1 sia su Mac che su Windows con Python 3.9. Sulla mia macchina Windows ho dovuto usare l'argomento Path() su Python Init(). Ho più versioni di Python e voglio assicurarmi che venga scelta quella corretta. Sul mio Mac, Python Init() senza argomenti funziona bene per la mia configurazione. C'è un problema che ho riscontrato su Windows, ho dovuto codificare il percorso del mio pythonw.exe. Dovrai cambiarlo in modo che corrisponda alla tua posizione Python. Cerca FixMe:
Oltre ai parametri Python Init() e al percorso di pythonw.exe su Windows, questo dovrebbe essere eseguito senza problemi su JMP 15.2.1 e versioni successive su Mac o Windows. Può essere eseguito su versioni precedenti di JMP che supportano Python, ma non è stato testato.
Dovrai creare un file defs.py nella stessa directory di questo script. Il pacchetto multiprocessing richiede che il codice per l'esecuzione in parallelo provenga da codice importabile, non locale allo script. Quindi la maggior parte del lavoro in questo script è stato assicurarsi che sys.path sia impostato correttamente in modo che il file defs.py possa essere semplicemente importato dallo script.
L'intero contenuto del file defs.py è nei commenti del file, il che semplifica l'incollaggio in un nuovo file. Quel contenuto duplicato di seguito è:
def f(x):
return x*x
Lo script prende una lista di numeri [1,2,3] e in parallelo chiama f(x) per quadrare il valore che dà i risultati [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)
]\");