Creando funciones JSL implementadas en PythonRecientemente di una charla interna de JMP sobre consejos y trucos de Python. Hay un consejo en particular que creo que es poderoso, así que me gustaría compartirlo con ustedes: crear una función JSL que encapsule la llamada a una función de Python. En otras palabras, este es un código de utilidad que se puede escribir en Python pero con una interfaz JSL. Para facilitar la ilustración, he implementado una función de Python simple que toma dos objetos y los suma, luego envuelve esa llamada en una función JSL. Empiezo con tres tipos diferentes de datos (numéricos, cadenas y listas) y una función que suma los dos objetos de Python.
Names Default to Here(1);
one = 1;
two = 2;
author = "Paul Nelson";
devel = "Developer: ";
list1 = { 1, 3, 5, 7 };
list2 = { 2, 4, 6, 8 };
Python Submit("\[
def plus(a,b):
print(a, b)
return a+b
]\");
Cualquier objeto de Python que admita el operador "+" puede ser manejado por esta función de Python.
// Python Execute is an underutilized gem. You can send a list
// of inputs and get a list of results and execute code all in one
// JSL statement.
Python Execute( {one, two}, {result}, "result = plus(one,two)" );
show(result);
El resultado:
result = plus(one,two)
/*:
1.0
2.0
result = 3;
¡Aquí es donde las cosas se ponen divertidas! En particular, una función JSL que envuelve la función plus() definida en Python.
// create a JSL function that wraps the the Python function
blow_mind = Function( {first, second}, // parameters
{pyresult}, // local variables
Python Execute( {first, second}, {pyresult}, "pyresult = plus(first,second)");
Return( pyresult ); // value to return
); // end of the definition
// Call the Python function through our JSL function. Good way to extend JSL
// using code written in Python. Could be useful for add-ins.
// Notice the same Python function handling different types of Python objects.
show( blow_mind(devel, author) );
show( blow_mind(list1, list2) );
Dando los resultados:
//:*/
pyresult = plus(first,second)
/*:
Developer:
Paul Nelson
blow_mind(devel, author) = "Developer: Paul Nelson";
//:*/
pyresult = plus(first,second)
/*:
[1, 3, 5, 7]
[2, 4, 6, 8]
blow_mind(list1, list2) = {1, 3, 5, 7, 2, 4, 6, 8};
Nuestra función trivial plus() maneja la suma de valores numéricos, así como la concatenación de cadenas y listas, todas las cuales se pueden llamar como una función JSL. El resto del código JSL ignora felizmente que lo que realmente se llamó ocurrió en Python.
Archivo completo adjunto.
Quieres seguir codificando en JSL, pero para una función particular, sabes que hay una manera fácil de escribirla en Python. Entonces, ¿cómo se crea una función JSL que implementa su funcionalidad en Python?
Automatización y secuencias de comandosRe: Crear funciones JSL implementadas en Python¡Gracias por el soporte mejorado de Python @Paul_Nelson !
La secuencia de comandos del navegador con Python Selenium muestra esto
// 1: navigate to jmp.com
nav = Function( {url}, {rc},
Python Execute( {url}, {rc},
"\[
try:
driver.get(url)
rc = "ok"
except Exception as e:
rc = repr(e)
]\" );
return(rc);
);
rc = nav( "https://www.jmp.com/" );
if( rc != "ok", throw("nav: "||char(rc)));
Las dos últimas líneas llaman a través de la función contenedora para hacer que un navegador web navegue al sitio web de JMP. La función JSL funciona exactamente de la misma manera que el ejemplo de Paul.
Re: Crear funciones JSL implementadas en PythonBúsqueda global de expresiones regulares multilínea con múltiples valores de retorno
Add Custom Functions(
New Custom Function(
"hogi",
"Regex",
Function({input, pattern},
SubstituteInto(input,"\!n"," ","\!r", " ") ;
Python Send( input );
Python Send( pattern );
error = Python Submit( "\[
import re
print(input)
matches = re.findall(pattern,input)
]\" );
matches = Python Get( matches );
),
<<DESCRIPTION></DESCRIPTION>Re: Crear funciones JSL implementadas en PythonNota con respecto a @hogi. Creo que Function() debería tener un return(matches); declaración.
Además, el uso de Python Execute() elimina la necesidad de realizar envíos y recepciones.
Add Custom Functions(
New Custom Function(
"hogi",
"Regex",
Function({input, pattern},
SubstituteInto(input,"\!n"," ","\!r", " ") ;
Python Execute( {input, pattern}, {matches}, "\[
import re
matches = re.findall(pattern,input)
]\" );
return( matches);
),
<Re: Crear funciones JSL implementadas en PythonAh bien. Ejecución de Python , mucho mejor 
En las funciones JSL, la última expresión es el valor de retorno, incluso sin incluir return() .
Re: Creación de funciones JSL implementadas en PythonNo soy experto en Python, pero hay algunas bibliotecas de Python (como xxhash) que me gustaría usar.
Tengo una pregunta sobre el uso de memoria. Si envuelvo una función JSL de Python de esta manera y se crea un objeto Python en el código Python envuelto, ¿una llamada a mi función de envoltura JSL creará un *nuevo* objeto Python persistente cada vez que se llame a la función (por ejemplo, se usará memoria si llamo a la función decenas de millones de veces)?
¿Qué sucede si creo un objeto Python nombrado una vez en mi código (por ejemplo) jmpstart.jsl y hago referencia a ese objeto dentro de mi procedimiento? ¿Sería mejor?
A continuación se muestra un ejemplo de lo que quiero hacer con xxhash. (Nota: importo xxhash al entorno de Python en mi script jmpstart.jsl. Estoy atascado al emitir un flotante JSL desde esta función porque JSL no admite números enteros de 8 bytes...)
Add Custom Functions(
New Custom Function(
"Global",
"xxhash64",
Function( {str},
{digest},
Python Execute( {str}, {digest}, "digest = xxhash.xxh64( str ).hexdigest()" );
Return( Hex To Number( digest ) );
)
)
)
Re: Creación de funciones JSL implementadas en PythonPD: Me di cuenta de que este código es pésimo porque hay una incidencia de alrededor del 0,8 % de que el resumen hexadecimal sea un punto flotante IEEE no válido, lo que hace que el procedimiento devuelva un valor JSL faltante. Dicho esto, todavía tengo dudas sobre la gestión de memoria como se indica.
Re: Creación de funciones JSL implementadas en Pythonhashlib y base64 son bibliotecas integradas de Python.
Quería analizar este caso específicamente ayer. Puedo decirle que, en el peor de los casos, debería "filtrar" una única referencia a digest durante todo el tiempo que JMP esté ejecutándose. Cuando JMP se apaga, esa variable se liberará cuando Python se apague. El código Set() de Python crea la variable digest y la coloca en el diccionario globals() del entorno de Python. Esto permite que Python Get() pueda buscar el valor por nombre y devolver el valor a JSL. Python Execute() es básicamente una función de conveniencia para Python Send/Set(), Python Submit(), Python Get(). Entonces, el diccionario tiene una referencia a digest , sin embargo, una vez que Python Execute() sale del alcance, el diccionario debería ser el único que contenga una referencia. En el segundo bucle, el valor se sobrescribe con el nuevo valor, por lo que la referencia anterior debería liberarse, lo que permite que la recolección de basura devuelva esa memoria.
Re: Creación de funciones JSL implementadas en PythonGracias, Paul. En cuanto a la memoria, eso era lo que esperaba. Voy a echar un vistazo a hashlib y base64.
El contexto es el siguiente: quería un hash de 8 bytes y escribí uno que utilizaba las funciones MD5 nativas de JSL y las truncaba a 8 bytes, pero con mis entradas de cadena, los hashes parecen terminar agrupados. Alguien me recomendó xxhash64 porque proporciona valores hash distribuidos de forma muy aleatoria. Así que lo utilicé y pareció proporcionar hashes bien distribuidos.
Como se mencionó, xxhash64 puede emitir un longint, pero no puedo transmitirlo a JSL. Puedo transmitir el resultado como un BLOB de JSL, pero JMP no me permite usar BLOBS como identificadores de ENLACE, que es para lo que quiero usar los hashes. Se aleja un poco de la pregunta original, pero si tienes alguna idea, te la agradecería mucho.
Re: Creación de funciones JSL implementadas en PythonPD: Podría usar el valor de cadena del hexadecimal como mi ID de enlace, pero necesitaba algo pequeño y rápido. Estoy trabajando con tablas con aproximadamente 80 millones de filas.
Re: Creación de funciones JSL implementadas en Python@john_madden Los números de doble precisión de 64 bits de JMP pueden contener un entero con signo de -2^52... 2^52. Creo que puedes conservar 13 dígitos hexadecimales del hash de 16 dígitos hexadecimales utilizando hextonumber y evitar cualquier duda sobre números de punto flotante no válidos. Pero 52 bits probablemente no sean suficientes.
También he estado jugando en este vecindario y encontré útil esta tabla de ataques de cumpleaños . Estoy usando un hash de un archivo para crear una clave única y quería saber cuántos bits necesitaría. Según la tabla, veo que agregar 6.100.000 elementos en una tabla hash de 64 bits tiene una probabilidad de 1/1.000.000 de que se produzca una colisión.
En mi caso de uso, el disco del servidor de archivos es 1e11 y un archivo típico es 1e4, o un límite de 1e7 archivos. El hash de 64 bits es demasiado pequeño y el de 96 estaría bien. Por supuesto, opté por 256...

Re: Creación de funciones JSL implementadas en PythonHola Craig. ¡Esto es genial! ¿Qué tipo de JMP estás usando para tus valores de 256 bits?
Como nota al margen, sería bueno que los desarrolladores agregaran una función hash más moderna que MD5 a JSL en algún momento.
Re: Creación de funciones JSL implementadas en PythonAquí está mi último intento, que parece funcionar bien:
"Accepts a string as input, and returns a JSL floating point number that encodes a 'nearly'-64-bit hash of the string. The hash is generated using the Python xxhash package with the default (0) seed.<BR />Python returns the 64-bit hash as a 16-character hexadecimal string. If the bits 2-12 are 11111111111, they are changed to 01111111111 before being processed further by JSL.<BR />This avoids generating an IEEE 754 floating point value of NaN or ∞, which JSL would interpret as Missing.<BR />This trick cuts the number of possible floating point exponent values<BR />from 2048 to 2046, resulting in a hash with 2^63 * 2046 possible values (= 1.89e22).<BR />For 10 million different inputs, this should yield a collision probability of roughly 1 in 380 milion<BR />according to Craige's reference if I calculcated right. On my M3 Max MacBook Pro, the routine takes around 26 μsec per iteration with random strings of length 11.
Names Default To Here( 1 );
Add Custom Functions(
New Custom Function(
"Global",
"xxhash64",
Function( {str},
{digest},
Python Execute( {str}, {digest}, "digest = xxhash.xxh64( str ).hexdigest()", echo( 0 ) );
startChars = Substr( digest, 1, 3 );
Hex To Number(
Match( startChars,
"7ff", "3ff" || Substr( digest, 4, 13 ),
"fff", "bff" || Substr( digest, 4, 13 ),
digest
)
);
)
) << Description(
"Accepts a string as input, and returns a JSL floating point number that encodes a 'nearly'-64-bit hash of the string.
Python must have already been initialized as follows:
Python Execute( {}, {},
\!"
import jmputils
jmputils.jpip('install', 'numpy pandas scikit-learn xxhash')
import jmp
import numpy
import pandas as pd
import xxhash
\!""
) //
<<PROTOTYPE></PROTOTYPE>Re: Creación de funciones JSL implementadas en PythonSupongo que Craige está usando una cadena de dígitos hexadecimales o dígitos codificados en base64.
Una de las grandes ventajas de la integración con Python es que no es necesario que JMP se ponga al día y proporcione funciones hash más modernas. El poder se consigue a través de Python. Una de las razones por las que menciono hashlib y base64 es que forman parte de la biblioteca estándar de Python, por lo que todos los usuarios de JMP los tendrán disponibles sin necesidad de instalar un paquete externo.
Re: Creación de funciones JSL implementadas en Python@john_madden - you want <SPAN class="token number">2</SPAN><SPAN class="token operator">^</SPAN><SPAN class="token number">52</SPAN> <SPAN class="token operator">*</SPAN> <SPAN class="token number">2046</SPAN> rather than <SPAN class="token number">2</SPAN><SPAN class="token operator">^</SPAN><SPAN class="token number">63</SPAN> <SPAN class="token operator">*</SPAN> <SPAN class="token number">2046</SPAN> . Or just use the 64 bit row.
Hace mucho tiempo que sabía cómo se guardaban las cadenas JSL en la memoria; si mal no recuerdo, hubo un cambio de rendimiento significativo en torno a los 24 caracteres. Es posible que desees utilizar la versión de 96 bits y asegurarte de que se mantenga en 24 dígitos hexadecimales o menos. Para 10 000 000 de cadenas, el espacio es de 1/4 GB en tu máquina de 16 GB... y has pasado de 1e-6 a 1e-15 de posibilidades de encontrar un error. Si no fuera por la mala suerte, no tendría suerte en absoluto - https://quoteinvestigator.com/2020/09/02/bad-luck/ .
I'm working with python/django/postgresql currently and I store the hex digits as a database key...a 64 character string gives me the 256 bits.
Re: Creación de funciones JSL implementadas en PythonNuevamente, gracias Paul y Craige. Por cierto, la integración con Python me tiene lo suficientemente entusiasmado como para intentar aprender Python ahora, ya que mis colegas de TI aquí quieren entender parte de mi código JSL y siempre me preguntan cuál sería el equivalente en Python. Estoy muy agradecido con Paul por todo este trabajo.
También necesito corregir el cálculo de la cantidad de valores hash en mi script anterior: creo que es más bien 2^64 - 2^63, mucho menos de lo que dije.
En cuanto al almacenamiento de los valores hash, me convenciste de no intentar ser tan cursi. Creo que me has convencido de que, al final, es mejor almacenar solo los caracteres hexadecimales. Tendré que experimentar haciendo algunas uniones en tablas enormes para ver si realmente hay alguna diferencia en la velocidad si me uno en columnas de cadenas hexadecimales o columnas de números flotantes; o si uso cadenas o números flotantes como identificadores de enlace.
Si alguna vez exporto mis grandes tablas JMP a una RDB, podría convertirlas a longints en ese punto.
Re: Creación de funciones JSL implementadas en PythonCraige, con respecto a la cantidad de valores hash, tienes toda la razón y estaba confundido nuevamente y no había leído tu publicación. 2^52 * 2046 es correcto.