Entwerfen von Python-UDFs¶
Unter diesem Thema finden Sie Informationen zum Entwerfen von Python-UDFs.
Unter diesem Thema:
Bemerkung
Mithilfe der Python-UDF-Batch-API können Sie Python-Funktionen definieren, die Batches von Eingabezeilen als Pandas DataFrames empfangen und Batches von Ergebnissen als Pandas-Arrays oder Pandas Series zurückgeben. Die Batchschnittstelle führt zu einer wesentlichen Leistungssteigerung bei Szenarios mit Machine Learning-Inferenz. Weitere Informationen dazu finden Sie unter Python-UDF-Batch-API.
Auswählen Ihrer Datentypen¶
Bevor Sie Ihren Code schreiben:
Wählen Sie die Datentypen aus, die Ihre Funktion als Argumente akzeptieren soll, und den Datentyp, den Ihre Funktion zurückgeben soll.
Berücksichtigen Sie die Zeitzonenproblematik.
Entscheiden Sie, wie NULL-Werte behandelt werden sollen.
SQL-Python-Zuordnung von Datentypen für Parameter und Rückgabetypen¶
Die folgende Tabelle zeigt die Typzuordnungen zwischen SQL und Python. Diese Zuordnungen gelten im Allgemeinen sowohl für die Argumente, die an die Python-UDF übergeben werden, als auch für die Werte, die von der UDF zurückgegeben werden.
SQL-Typ |
Python-Typ |
Anmerkungen |
---|---|---|
NUMBER |
int oder decimal.Decimal |
Wenn die Skalierung des NUMBER-Typs 0 ist, wird der Python-Typ int verwendet. Andernfalls wird der Typ decimal.Decimal verwendet. |
FLOAT |
float |
Bei Gleitkommaoperationen können kleine Rundungsfehler auftreten, die sich summieren, insbesondere wenn Aggregatfunktionen eine große Anzahl von Zeilen verarbeiten. Rundungsfehler können bei jeder Ausführung der Abfrage variieren, wenn die Zeilen in einer anderen Reihenfolge verarbeitet werden (z. B. bei unterschiedlicher Partitionierung in einem verteilten System). Weitere Informationen dazu finden Sie unter Numerische Datentypen: Gleitkomma. |
VARCHAR |
str |
|
BINARY |
bytes |
|
BOOLEAN |
bool |
|
DATE |
datetime.date |
|
TIME |
datetime.time |
Obwohl Snowflake Zeitwerte mit Nanosekundengenauigkeit speichern kann, bietet der Python-Typ datetime.time nur eine Genauigkeit im Millisekundenbereich. Die Konvertierung zwischen Snowflake- und Python-Datentypen kann die effektive Genauigkeit auf Millisekunden reduzieren. |
TIMESTAMP_LTZ |
datetime.datetime |
Verwenden Sie die lokale Zeitzone, um die interne UTC-Zeit in die lokale „naive“ datetime-Zeit umzuwandeln. Erfordert „naive“ datetime als Rückgabetyp. |
TIMESTAMP_NTZ |
datetime.datetime |
Direkte Konvertierung in „naive“ datetime. Erfordert „naive“ datetime als Rückgabetyp. |
TIMESTAMP_TZ |
datetime.datetime |
Konvertierung in „aware“ datetime mit Zeitzoneninformation. Erfordert „aware“ datetime als Rückgabetyp. |
VARIANT |
dict, list, int, float, str oder bool |
Jede Variant-Zeile wird für Argumente dynamisch in einen Python-Typ konvertiert und umgekehrt für Rückgabewerte. Eingaben vom Typ Zeichenfolge (String) müssen explizit in SQL umgewandelt werden, z. B. durch Verwendung von |
OBJECT |
dict |
Wenn ein Python-Datentyp in einen OBJECT-Wert konvertiert wird und eingebettete Python-Dezimaldaten vorliegen, werden die eingebetteten Python-Dezimalwerte im OBJECT-Wert in String-Werte (Zeichenfolgen) konvertiert. |
ARRAY |
list |
Wenn ein Python-Datentyp in einen ARRAY-Wert konvertiert wird und eingebettete Python-Dezimaldaten vorliegen, werden die eingebetteten Python-Dezimalwerte im ARRAY-Wert in String-Werte (Zeichenfolgen) konvertiert. |
GEOGRAPHY |
dict |
Formatiert den Geography-Typ in GeoJSON und wandelt ihn dann in Python-Typ dict um. |
TIMESTAMP_LTZ-Werte und Zeitzonen¶
Eine Python-UDF ist weitgehend isoliert von der Umgebung, in der sie aufgerufen wird. Die Zeitzone wird jedoch von der aufrufenden Umgebung geerbt. Wenn für die Sitzung des Aufrufers vor dem Aufruf der Python-UDF eine Standardzeitzone eingestellt wurde, dann hat die Python-UDF die gleiche Standardzeitzone. Weitere Informationen zu Zeitzonen finden Sie unter TIMEZONE.
NULL-Werte¶
Für alle Snowflake-Typen mit Ausnahme von Variant wird ein SQL-NULL
-Argument einer Python-UDF in den Python-Wert None
konvertiert, und ein zurückgegebener Python-Wert None
wird zurück in SQL-NULL
konvertiert.
Ein Wert vom Typ Variante kann entweder ein SQL-NULL
- oder ein VARIANT-JSON-null
-Wert sein. Weitere Informationen zu Snowflake-VARIANT-NULL finden Sie unter NULL-Werte.
Ein VARIANT-JSON
null
-Wert wird in Python-None
konvertiert.Ein SQL-
NULL
-Wert wird in ein Python-Objekt übersetzt, das das Attributis_sql_null
hat.
Ein Beispiel dazu finden Sie unter NULL-Verarbeitung in Python-UDFs.
Entwerfen von Python-UDFs unter Berücksichtigung der Snowflake-bedingten Einschränkungen¶
Um die Stabilität in der Snowflake-Umgebung sicherzustellen, hat Snowflake die folgenden Einschränkungen für Python-UDFs definiert. Wenn nicht anders angegeben, werden diese Einschränkungen bei der Ausführung der Java-UDF durchgesetzt, nicht bei der Erstellung.
Das Training von Modellen des maschinellen Lernens (ML) kann manchmal sehr ressourcenintensiv sein. Snowpark-optimierte Warehouses sind ein Typ von virtuellen Snowflake-Warehouse, der für Workloads verwendet werden kann, die eine große Menge an Arbeitsspeicher und Computeressourcen benötigen. Weiter Informationen zu Machine Learning-Modellen und zu Snowpark Python finden Sie unter Training von Machine Learning-Modellen mit Snowpark Python.
Speicher¶
Vermeiden Sie einen zu hohen Verbrauch von Speicher.
Große Datenwerte können eine große Menge an Arbeitsspeicher verbrauchen.
Eine zu große Stapeltiefe kann eine große Menge an Speicher verbrauchen.
UDFs geben einen Fehler zurück, wenn sie zu viel Speicher verbrauchen. Das jeweilige Limit kann sich ändern.
Wenn UDFs nicht funktioniert, weil sie zu viel Arbeitsspeicher verbrauchen, sollten Sie die Verwendung von Snowpark-optimierte Warehouses erwägen.
Dauer¶
Vermeiden Sie Algorithmen, die pro Aufruf sehr viel Zeit benötigen.
Wenn eine UDF-Anweisung zu lange dauert, bricht Snowflake die SQL-Anweisung ab und gibt einen Fehler an den Benutzer zurück. Dieses Limit begrenzt die Auswirkungen (und Kosten) von Fehlern wie Endlosschleifen.
Entwerfen des Moduls¶
Wenn eine SQL-Anweisung Ihre Python-UDF aufruft, ruft Snowflake eine von Ihnen geschriebene Python-Funktion auf. Ihre Python-Funktion wird als „Handler-Funktion“ oder kurz „Handler“ bezeichnet. Der Handler ist eine Funktion, die in einem vom Benutzer bereitgestellten Modul implementiert ist.
Wie bei jeder Python-Funktion muss auch Ihre Funktion als Teil eines Moduls deklariert werden.
Der Handler wird für jede an die Python-UDF übergebene Zeile einmal aufgerufen. Das Modul, das die Funktion enthält, wird nicht für jede Zeile neu importiert. Snowflake kann die Handler-Funktion desselben Moduls mehr als einmal aufrufen.
Um die Ausführung Ihres Codes zu optimieren, geht Snowflake davon aus, dass die Initialisierung langsam sein kann, während die Ausführung der Handler-Funktion schnell ist. Snowflake verwendet ein längeres Timeout für die Ausführung der Initialisierung (einschließlich Zeit für Laden der UDF und zum Initialisieren des Moduls) als für die Ausführung des Handlers (Zeit für Aufruf Ihres Handlers mit einer Eingabezeile).
Weitere Informationen zum Entwerfen des Moduls finden Sie unter Erstellen von Python-UDFs.
Optimieren von Initialisierung und Steuerung des globalen Zustands in skalaren UDFs¶
Die meisten skalaren UDFs müssen den folgenden Richtlinien folgen:
Wenn Sie einen gemeinsamen Zustand (Shared State) initialisieren müssen, der sich über Zeilen hinweg nicht ändert, initialisieren Sie ihn nicht in der Handler-Funktion sondern im Modul.
Schreiben Sie Ihre Handler-Funktion so, dass sie threadsicher ist.
Vermeiden Sie das Speichern und Freigeben eines dynamischen Zustands über Zeilen hinweg.
Wenn Ihre UDF diese Richtlinien nicht erfüllen kann, müssen Sie beachten, dass Snowflake erwartet, dass skalare UDFs unabhängig verarbeitet werden. Wenn Sie sich zwischen Aufrufen auf einen gemeinsamen Zustand verlassen, kann dies zu unerwartetem Verhalten führen, da das System Zeilen in beliebiger Reihenfolge verarbeiten und diese Aufrufe auf mehrere Instanzen verteilen kann. Darüber hinaus kann dieselbe Handler-Funktion innerhalb desselben Python-Interpreters auf mehreren Threads mehrfach ausgeführt werden.
Bei UDFs sollte es vermieden werden, dass sich beim Aufrufen der Handler-Funktion auf einen gemeinsamen Zustand verlassen wird. Es gibt jedoch zwei Situationen, in denen Sie möglicherweise möchten, dass eine UDF einen gemeinsamen Zustand speichert:
Code, der teure Initialisierungslogik enthält, die nicht bei jeder Zeile wiederholt werden soll.
Code, der einen gemeinsamen Zustand über Zeilen hinweg nutzt, z. B. als Cache.
Wenn es notwendig ist, einen globalen Zustand zu erhalten, der über Handler-Aufrufe hinweg gemeinsam genutzt wird, müssen Sie den globalen Zustand gegen eine Data Race-Situation (Wettlaufsituation) schützen, indem Sie die unter Threading – Thread-basierte Parallelität beschriebenen Synchronisierungsprimitive verwenden.
Optimierung für Skalierung und Performance¶
Python-Batch-API mit Data Science-Bibliotheken verwenden¶
Wenn Ihr Code Machine Learning- oder Data Science-Bibliotheken verwendet, verwenden Sie die Python-UDF-Batch-API. Mit der Batch-API können Sie Python-Funktionen definieren, die Eingabezeilen in Batches erhalten, für die diese Bibliotheken optimiert sind.
Weitere Informationen dazu finden Sie unter Python-UDF-Batch-API.
Single-Thread-UDF-Handler schreiben¶
Schreiben Sie UDF-Handler, die mit einem einzigen Thread arbeiten. Snowflake übernimmt die Partitionierung der Daten und die Skalierung der UDF auf die Computeressourcen des virtuellen Warehouses.
Teure Initialisierung über das Modul ausführen¶
Legen Sie aufwändigen Initialisierungscode in den Modulbereich. Dort wird er einmalig bei der Initialisierung der UDF durchgeführt. Vermeiden Sie die Wiederholung des teuren Initialisierungscodes bei jedem Aufruf eines UDF-Handlers.
Fehlerbehandlung¶
Eine Python-Funktion, die als UDF verwendet wird, kann die normalen Python-Ausnahmebehandlungsverfahren verwenden, um Fehler innerhalb der Funktion abzufangen.
Wenn eine Ausnahme innerhalb der Funktion auftritt und nicht von der Funktion abgefangen wird, gibt Snowflake einen Fehler aus, der die Stapelüberwachung für die Ausnahme enthält.
Sie können explizit einen Fehler auszulösen, ohne ihn abzufangen, um die Abfrage zu beenden und einen SQL-Fehler zu erzeugen. Beispiel:
if (x < 0):
raise ValueError("x must be non-negative.");
Beim Debugging können Sie in den Text der SQL-Fehlermeldung Werte integrieren. Setzen Sie dazu den gesamten Textkörper der Python-Methode in einen „try-catch“-Block, und fügen Sie Argumentwerte zur abgefangenen Fehlermeldung hinzu. Lösen Sie dann eine Ausnahme mit der erweiterten Meldung aus. Um die Offenlegung sensibler Daten zu verhindern, entfernen Sie die Argumente vor der Bereitstellung in der Produktionsumgebung.
Einsetzen von bewährten Sicherheitsmethoden¶
Ihre Funktion (und alle Bibliotheksfunktionen, die Sie aufrufen) muss sich wie eine reine Funktion verhalten, die nur auf die empfangenen Daten einwirkt und einen auf diesen Daten basierenden Wert zurückgibt, ohne Nebeneffekte zu verursachen. Ihr Code sollte nicht versuchen, den Zustand des zugrunde liegenden Systems zu beeinflussen, abgesehen davon, dass er eine angemessene Menge an Speicher und Prozessorzeit verbraucht.
Python-UDFs werden innerhalb einer eingeschränkten Engine ausgeführt. Weder Ihr Code noch der Code in den Bibliotheksfunktionen, die Sie verwenden, darf irgendwelche verbotenen Systemaufrufe bereitstellen, einschließlich:
Prozesssteuerung Sie können z. B. einen Prozess nicht aufspalten. (Sie können jedoch mehrere Threads verwenden.)
Zugriff auf das Dateisystem.
Bis auf die folgenden Ausnahmen dürfen Python-UDFs keine Dateien lesen oder schreiben:
Python-UDFs können Dateien lesen, die in der
imports
-Klausel desCREATE FUNCTION
-Befehls angegeben sind.Weitere Informationen dazu finden Sie unter Lesen von Dateien mit einem UDF-Handler. Ein Beispiel dazu finden Sie unter Laden einer Datei aus einem Stagingbereich in eine Python-UDF.
Python-UDFs können Dateien, wie z. B. Protokolldateien, in das
/tmp
-Verzeichnis schreiben.Jede Abfrage erhält ein eigenes, mit Speicher unterstütztes Dateisystem mit eigenem „/tmp“-Verzeichnis, sodass bei verschiedenen Abfragen keine Dateinamenkonflikte auftreten können. Konflikte innerhalb einer Abfrage sind jedoch möglich, wenn eine einzelne Abfrage mehr als eine UDF aufruft und diese UDFs versuchen, in Dateien mit demselben Namen zu schreiben. Da Python-UDFs in separaten Worker-Prozessen parallel ausgeführt werden kann, sollten Sie außerdem vorsichtig sein, wenn Sie in das „/tmp“-Verzeichnis schreiben.
Weitere Informationen zum Schreiben von Dateien finden Sie unter Schreiben von Dateien mit einem UDF-Handler. Ein Beispiel dazu finden Sie unter Entpacken einer Stagingdatei.
Netzwerkzugriff.
Bemerkung
Da Ihr Code weder direkt noch indirekt auf das Netzwerk zugreifen kann, können Sie für den Zugriff auf die Datenbank nicht den Code im Snowflake-Python-Konnektor verwenden. Ihre UDF kann selbst nicht als Client von Snowflake agieren.