Erstellen von in Python implementierten JSL-FunktionenKürzlich habe ich einen internen JMP-Vortrag über Python-Tipps und -Tricks gehalten. Ich halte einen Tipp für besonders hilfreich und möchte ihn daher mit Ihnen teilen: Erstellen Sie eine JSL-Funktion, die den Aufruf einer Python-Funktion umschließt. Mit anderen Worten handelt es sich um Utility-Code, der in Python geschrieben werden kann, aber mit einer JSL-Schnittstelle versehen ist. Zur Vereinfachung habe ich eine einfache Python-Funktion implementiert, die zwei Objekte zusammenfügt und diesen Aufruf dann in eine JSL-Funktion umschließt. Ich beginne mit drei verschiedenen Datentypen (numerisch, Zeichenfolge und Listen) und einer Funktion, die die beiden Python-Objekte zusammenfügt.
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
]\");
Jedes Python-Objekt, das den Operator „+“ unterstützt, kann von dieser Python-Funktion verarbeitet werden.
// 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);
Das Ergebnis:
result = plus(one,two)
/*:
1.0
2.0
result = 3;
Hier wird es spannend! Insbesondere eine JSL-Funktion, die die in Python definierte plus()-Funktion umschließt.
// 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) );
Hier die Ergebnisse:
//:*/
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};
Unsere triviale Funktion plus() behandelte die Summierung numerischer Werte sowie die Verkettung von Zeichenfolgen und Listen, die alle als JSL-Funktion aufrufbar sind. Der Rest des JSL-Codes weiß nicht, dass das, was aufgerufen wurde, tatsächlich in Python geschah.
Vollständige Datei angehängt.
Sie möchten weiterhin in JSL codieren, wissen aber, dass es für eine bestimmte Funktion eine einfache Möglichkeit gibt, sie in Python zu schreiben. Wie erstellen Sie also eine JSL-Funktion, die ihre Funktionalität in Python implementiert?
Automatisierung und SkriptingBetreff: Erstellen von in Python implementierten JSL-FunktionenDanke für die verbesserte Python-Unterstützung, @Paul_Nelson !
Browser-Scripting mit Python Selenium zeigt dies
// 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)));
Die letzten beiden Zeilen rufen die Wrapper-Funktion auf, damit ein Webbrowser zur JMP-Website navigiert. Die JSL-Funktion funktioniert genauso wie in Pauls Beispiel.
Betreff: Erstellen von in Python implementierten JSL-Funktionenglobale, mehrzeilige Regex-Suche mit mehreren Rückgabewerten
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>Betreff: Erstellen von in Python implementierten JSL-FunktionenHinweis zu @hogi : Ich glaube, die Funktion () sollte eine return(matches);-Anweisung haben.
Durch die Verwendung von Python Execute( ) entfällt außerdem die Notwendigkeit, Sende- und Abrufvorgänge durchzuführen.
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);
),
<Betreff: Erstellen von in Python implementierten JSL-Funktionenah, richtig. Python Execute , viel schöner : )
In JSL-Funktionen ist der letzte Ausdruck der Rückgabewert – auch ohne einschließendes return() .
Betreff: Erstellen von in Python implementierten JSL-FunktionenIch kenne mich mit Python nicht so gut aus, aber es gibt einige Python-Bibliotheken (wie xxhash), die ich gerne verwenden würde.
Ich habe eine Frage zur Speichernutzung. Wenn ich Python auf diese Weise mit einer JSL-Funktion umschließe und im umschlossenen Python-Code ein Python-Objekt erstellt wird, erstellt dann ein Aufruf meiner JSL-Wrapper-Funktion bei jedem Aufruf der Funktion ein *neues* persistentes Python-Objekt (was z. B. Speicher verbraucht, wenn ich die Funktion zig Millionen Mal aufrufe)?
Was wäre, wenn ich einmal in meinem (z. B.) jmpstart.jsl-Code ein benanntes Python-Objekt erstelle und in meiner Prozedur auf dieses eine Objekt verweise? Wäre das besser?
Unten sehen Sie ein Beispiel dafür, was ich mit xxhash machen möchte. (Beachten Sie, dass ich xxhash in die Python-Umgebung in meinem Skript jmpstart.jsl importiere. Ich kann aus dieser Funktion keinen JSL-Float ausgeben, da JSL keine 8-Byte-Ganzzahlen unterstützt...)
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 ) );
)
)
)
Betreff: Erstellen von in Python implementierten JSL-FunktionenPS. Mir ist aufgefallen, dass dies schlechter Code ist, da es in ca. 0,8 % der Fälle vorkommt, dass der Hex-Digest ein ungültiger IEEE-Float ist, was dazu führt, dass die Prozedur einen fehlenden JSL-Wert zurückgibt. Trotzdem wundere ich mich immer noch über die angegebene Speicherverwaltung.
Betreff: Erstellen von in Python implementierten JSL-Funktionenhashlib und base64 sind beides integrierte Python-Bibliotheken.
Ich wollte mir diesen Fall gestern genauer ansehen. Ich kann Ihnen sagen, dass im schlimmsten Fall während der gesamten Laufzeit von JMP eine einzige Referenz auf Digest „durchsickern“ sollte. Wenn JMP heruntergefahren wird, wird diese Variable freigegeben, wenn Python heruntergefahren wird. Der Python Set()-Code erstellt die Digest- Variable und platziert sie im globals()-Wörterbuch der Python-Umgebung. Dadurch kann Python Get() den Wert nach Namen suchen und an JSL zurückgeben. Python Execute() ist im Grunde eine Komfortfunktion für Python Send/Set(), Python Submit() und Python Get(). Das Wörterbuch enthält also eine Referenz auf Digest . Sobald Python Execute() jedoch den Gültigkeitsbereich verlässt, sollte das Wörterbuch das einzige sein, das eine Referenz enthält. In der zweiten Schleife wird der Wert mit dem neuen Wert überschrieben, sodass die alte Referenz freigegeben werden sollte, wodurch die Garbage Collection diesen Speicher zurückgeben kann.
Betreff: Erstellen von in Python implementierten JSL-FunktionenDanke, Paul. Was den Speicher betrifft, hatte ich das gehofft. Ich werde mir Hashlib und Base64 ansehen.
Hintergrund: Ich wollte einen 8-Byte-Hash und habe einen geschrieben, der die nativen MD5-Funktionen von JSL verwendet und sie auf 8 Byte gekürzt hat – aber mit meinen String-Eingaben scheinen die Hashes am Ende gruppiert zu sein. Jemand hat mir xxhash64 empfohlen, weil es sehr zufällig verteilte Hashwerte liefert. Also habe ich das verwendet und es schien gut verteilte Hashes zu liefern.
Wie erwähnt kann xxhash64 eine lange Ganzzahl ausgeben, aber ich kann das nicht in JSL übertragen. Ich kann das Ergebnis als JSL-BLOB übertragen, aber JMP lässt mich BLOBS nicht als LINK-IDs verwenden – und dafür möchte ich die Hashes verwenden. Es weicht etwas von der ursprünglichen Frage ab, aber wenn Sie zufällig irgendwelche Ideen haben, wären diese sehr willkommen.
Betreff: Erstellen von in Python implementierten JSL-FunktionenPS. Ich könnte einfach den String-Wert des Hex als meine Link-ID verwenden, aber ich sehnte mich nach etwas Kleinem und Schnellem. Ich arbeite mit Tabellen mit ~80 Millionen Zeilen.
Betreff: Erstellen von in Python implementierten JSL-Funktionen@john_madden JMPs 64-Bit-Zahlen mit doppelter Genauigkeit können eine vorzeichenbehaftete Ganzzahl von -2^52 bis 2^52 enthalten. Ich denke, Sie können 13 Hex-Ziffern des 16-Hex-Ziffern-Hashes behalten, indem Sie hextonumber verwenden und Fragen zu ungültigen Gleitkommazahlen vermeiden. Aber 52 Bit sind wahrscheinlich nicht genug.
Ich habe auch in dieser Gegend gespielt und fand diese Geburtstagsangriffstabelle nützlich. Ich verwende einen Hash einer Datei, um einen eindeutigen Schlüssel zu erstellen, und wollte wissen, wie viele Bits ich dafür benötige. Aus der Tabelle erkenne ich, dass beim Hinzufügen von 6.100.000 Elementen zu einer 64-Bit-Hash-Tabelle eine Kollisionswahrscheinlichkeit von 1/1.000.000 besteht.
Für meinen Anwendungsfall ist die Festplatte des Dateiservers 1e11 groß und eine typische Datei ist 1e4, oder es gibt maximal 1e7 Dateien. Der 64-Bit-Hash ist zu klein und 96 wären ok. Ich habe mich natürlich für 256 entschieden ...

Betreff: Erstellen von in Python implementierten JSL-FunktionenHallo Craig. Das ist großartig! Welchen JMP-Typ verwenden Sie für Ihre 256-Bit-Werte?
Nebenbei bemerkt wäre es schön, wenn die Entwickler irgendwann eine modernere Hash-Funktion als MD5 zu JSL hinzufügen würden.
Betreff: Erstellen von in Python implementierten JSL-FunktionenHier ist mein neuster Versuch, der ganz gut zu funktionieren scheint:
"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>Betreff: Erstellen von in Python implementierten JSL-FunktionenIch würde vermuten, dass Craige eine Zeichenfolge aus Hexadezimalziffern oder Base64-codierten Ziffern verwendet.
Einer der großen Vorteile der Python-Integration besteht darin, dass Sie JMP nicht brauchen, um „aufzuholen“ und modernere Hash-Funktionen bereitzustellen. Sie haben die Macht über Python. Einer der Gründe, warum ich hashlib und base64 erwähne, ist, dass sie Teil der Python-Standardbibliothek sind, sodass sie allen JMP-Benutzern zur Verfügung stehen, ohne ein externes Paket installieren zu müssen.
Betreff: Erstellen von in Python implementierten JSL-Funktionen@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.
Vor langer Zeit wusste ich, wie JSL-Zeichenfolgen im Speicher gehalten werden; soweit ich mich erinnere, gab es bei etwa 24 Zeichen eine erhebliche Leistungsänderung. Vielleicht möchten Sie die 96-Bit-Version verwenden und sicherstellen, dass sie bei oder unter 24 Hex-Ziffern bleibt. Für 10.000.000 Zeichenfolgen beträgt der Speicherplatz auf Ihrem 16-GB-Computer 1/4 GB ... und die Wahrscheinlichkeit, auf einen Fehler zu stoßen, ist von 1e-6 auf 1e-15 gesunken. Wenn ich kein Pech hätte, hätte ich überhaupt kein Glück - 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.
Betreff: Erstellen von in Python implementierten JSL-FunktionenNochmals vielen Dank, Paul und Craige. Übrigens hat mich die Python-Integration so begeistert, dass ich jetzt versuche, Python zu lernen, da meine IT-Kollegen hier einen Teil meines JSL-Codes verstehen wollen und mich immer fragen, was das Python-Äquivalent wäre. Ich bin Paul für all diese Arbeit sehr dankbar.
Ich muss auch die Berechnung der Anzahl der Hash-Werte in meinem obigen Skript korrigieren – ich glaube, es liegt eher bei 2^64 – 2^63, also viel weniger als ich gesagt habe.
Was das Speichern der Hash-Werte angeht, haben Sie mich davon überzeugt, nicht so niedlich zu sein. Ich glaube, Sie haben mich davon überzeugt, dass es am Ende besser ist, einfach die hexadezimalen Zeichen zu speichern. Ich muss mit einigen Verknüpfungen an großen Tabellen experimentieren, um zu sehen, ob es wirklich einen Geschwindigkeitsunterschied macht, ob ich Spalten mit Hex-Strings oder Spalten mit Floats verknüpfe oder ob ich Strings oder Floats als Link-IDs verwende.
Wenn ich meine großen JMP-Tabellen jemals in eine RDB exportiere, könnte ich sie an diesem Punkt in Longints konvertieren.
Betreff: Erstellen von in Python implementierten JSL-FunktionenCraige, was die Anzahl der Hash-Werte angeht, haben Sie absolut Recht. Ich war wieder verwirrt und hatte Ihren Beitrag nicht gelesen. 2^52 * 2046 ist richtig.