Como criar UDFs de Python

Esse tópico ajuda você a criar UDFs de Python.

Neste tópico:

Nota

Há uma API de lote de UDF de Python que permite definir funções de Python que recebem lotes de linhas de entrada como DataFrames do Pandas e retornam lotes de resultados como Arrays ou Series do Pandas. A interface de lote resulta em um desempenho muito melhor com cenários de inferência de machine learning. Para obter mais informações, consulte API de lote de UDF de Python.

Como escolher seus tipos de dados

Antes de escrever seu código:

  • Escolha os tipos de dados que sua função deve aceitar como argumentos e o tipo de dados que sua função deve retornar.

  • Leve em conta questões relacionadas ao fuso horário.

  • Decida como lidar com valores NULL.

Mapeamentos de tipos de dados de SQL-Python para tipos de parâmetros e retorno

A tabela abaixo mostra os mapeamentos de tipos entre SQL e Python. Esses mapeamentos geralmente se aplicam tanto aos argumentos passados à UDF de Python quanto aos valores retornados da UDF.

Tipo de SQL

Tipo Python

Notas

NUMBER

int ou decimal.Decimal

Se a escala do tipo NUMBER for 0, o tipo int de Python será usado. Caso contrário, será usado o tipo decimal.Decimal.

FLOAT

float

Operações de ponto flutuante podem ter pequenos erros de arredondamento, que podem se acumular, especialmente quando funções agregadas processam um grande número de linhas. Erros de arredondamento podem variar a cada vez que uma consulta é executada se as linhas forem processadas em uma ordem diferente. Para obter mais informações, consulte Tipos de dados numéricos: float.

VARCHAR

str

BINARY

bytes

BOOLEAN

bool

DATE

datetime.date

TIME

datetime.time

Embora o Snowflake possa armazenar valores de tempo com precisão de nanossegundos, o tipo datetime.time de Python mantém apenas uma precisão de milissegundos. A conversão entre os tipos de dados do Snowflake e de Python pode reduzir a precisão efetiva para milissegundos.

TIMESTAMP_LTZ

datetime.datetime

Use o fuso horário local para converter a hora interna UTC para um datetime “ingênuo” local. Requer um datetime “ingênuo” como tipo de retorno.

TIMESTAMP_NTZ

datetime.datetime

Converta diretamente para um datetime “ingênuo”. Requer um datetime “ingênuo” como tipo de retorno.

TIMESTAMP_TZ

datetime.datetime

Converta para datetime “ciente” com informações de fuso horário. Requer um datetime “ciente” como tipo de retorno.

VARIANT

dict, list, int, float, str ou bool

Cada linha de variante é convertida dinamicamente para um tipo do Python para argumentos e vice-versa para valores de retorno. As entradas do tipo cadeia de caracteres devem ser convertidas explicitamente em SQL, por exemplo, usando TO_VARIANT('foo'). Outros tipos não exigem uma conversão explícita. Os seguintes tipos são convertidos em cadeias de caracteres em vez de tipos nativos do Python: decimal, binary, date, time, timestamp_ltz, timestamp_ntz, timestamp_tz. Quando um tipo de dados Python é convertido em VARIANT, se houver algum dado decimal Python embutido, o decimal Python embutido será convertido em uma cadeia de caracteres no VARIANT.

OBJECT

dict

Quando um tipo de dados Python é convertido em OBJECT, se houver algum dado decimal Python embutido, o decimal Python embutido será convertido em uma cadeia de caracteres no OBJECT.

ARRAY

lista

Quando um tipo de dados Python é convertido em ARRAY, se houver algum dado decimal Python embutido, o decimal Python embutido será convertido em uma cadeia de caracteres no ARRAY.

GEOGRAPHY

dict

Formata o geography como GeoJSON e depois o converte em um dict de Python.

Valores TIMESTAMP_LTZ e fusos horários

Uma UDF de Python fica bastante isolada do ambiente no qual é chamada. Entretanto, o fuso horário é herdado do ambiente de chamada. Se a sessão do chamador definir um fuso horário padrão antes de chamar a UDF de Python, a UDF de Python tem o mesmo fuso horário padrão. Para obter mais informações sobre fusos horários, consulte TIMEZONE.

Valores NULL

Para todos os tipos do Snowflake exceto Variant, um argumento NULL de SQL para uma UDF de Python é traduzido como o valor None de Python, e um valor None de Python retornado é traduzido de volta para um NULL de SQL.

Um valor do tipo Variant pode ser um NULL de SQL ou um null de JSON VARIANT. Para obter mais informações sobre o VARIANT NULL do Snowflake, consulte Valores NULL.

  • Um null JSON VARIANT é traduzido para um None de Python.

  • Um NULL de SQL é traduzido para um objeto de Python com o atributo is_sql_null.

Para obter um exemplo, consulte Tratamento de NULL em UDFs de Python.

Criação de UDFs de Python que ficam dentro das restrições impostas pelo Snowflake

Para garantir a estabilidade dentro do ambiente do Snowflake, o Snowflake impõe as seguintes restrições a UDFs de Python. A menos que o contrário seja indicado, essas limitações são aplicadas quando a UDF é executada, não quando ela é criada.

O treinamento de modelos de machine learning (ML) podem consumir muitos recursos às vezes. Os warehouses otimizados para Snowpark são de um tipo de warehouse virtual do Snowflake que pode ser usado para cargas de trabalho que exigem uma grande quantidade de recursos de memória e computação. Para obter mais informações sobre modelos de machine learning e Snowpark Python, consulte Treinamento dos modelos de machine learning com Snowpark Python.

Memória

Evite consumir memória demais.

  • Grandes valores de dados podem consumir uma grande quantidade de memória.

  • Uma profundidade de pilha excessiva pode consumir uma grande quantidade de memória.

UDFs retornam um erro se elas consumirem memória demais. O limite específico está sujeito a mudanças.

Se os UDFs falharem devido ao alto consumo de memória, considere usar Warehouses otimizados para Snowpark.

Hora

Evite algoritmos que demorem muito tempo por chamada.

Se uma UDF leva tempo demais para ser concluída, o Snowflake interrompe a instrução SQL e retorna um erro ao usuário. Isso limita o impacto e o custo de erros como loops infinitos.

Criação do módulo

Quando uma instrução SQL chama sua UDF de Python, o Snowflake chama uma função Python que você escreveu. Sua função de Python é chamada de “método do manipulador”, ou “manipulador” abreviadamente. O manipulador é uma função implementada dentro de um módulo fornecido pelo usuário.

Como em qualquer função de Python, sua função deve ser declarada como parte de um módulo.

O manipulador é chamado uma vez para cada linha passada para a UDF de Python. O módulo que contém a função não é reimportado para cada linha. O Snowflake pode chamar a função do manipulador do mesmo módulo mais de uma vez.

Para otimizar a execução de seu código, o Snowflake supõe que a inicialização pode ser lenta, enquanto a execução da função do manipulador é rápida. O Snowflake define um tempo limite mais longo para executar a inicialização (incluindo o tempo para carregar sua UDF e o tempo para inicializar o módulo) do que para executar o manipulador (o tempo para chamar seu manipulador com uma linha de entrada).

Informações adicionais sobre a criação do módulo podem ser encontradas em Como criar UDFs de Python.

Como otimizar a inicialização e controlar o estado global em UDFs escalares

A maioria das UDFs escalares deve seguir as diretrizes abaixo:

  • Se você precisar inicializar o estado compartilhado que não muda entre linhas, inicialize-o no módulo e não na função do manipulador.

  • Escreva sua função do manipulador de forma a ser thread-safe.

  • Evite armazenar e compartilhar o estado dinâmico em linhas diferentes.

Se sua UDF não puder seguir essas orientações, saiba que o Snowflake espera que UDFs escalares sejam processadas de forma independente. Confiar em um estado compartilhado entre invocações pode resultar em comportamento inesperado, pois o sistema pode processar linhas em qualquer ordem e espalhar essas invocações por várias instâncias. Além disso, pode haver várias execuções da mesma função do manipulador dentro do mesmo intérprete de Python em vários threads.

UDFs devem evitar confiar em um estado compartilhado entre diferentes chamadas para a função do manipulador. Entretanto, há duas situações em que você pode querer que uma UDF armazene um estado compartilhado:

  • Código que contém uma lógica de inicialização cara que você não quer repetir para cada linha.

  • Código que use um estado compartilhado em linhas diferentes, como um cache.

Quando for necessário manter um estado global que será compartilhado em diferentes invocações do manipulador, você deve proteger o estado global contra corridas de dados usando os primitivos de sincronização descritos em threading — Thread-based parallelism.

Otimização para escala e desempenho

Use a API de lote de Python com bibliotecas de ciência de dados

Quando seu código usar bibliotecas de aprendizado de máquina ou ciência de dados, use a API de lote de UDF de Python. Com a API de lote, você pode definir funções de Python que recebem linhas de entrada em lotes nos quais a operação dessas bibliotecas é otimizada.

Para obter mais informações, consulte API de lote de UDF de Python.

Escrever manipuladores de UDF com um único thread

Escreva UDF manipuladores com um único thread. O Snowflake lidará com o particionamento dos dados e o escalonamento da UDF nos diferentes recursos computacionais do warehouse virtual.

Colocar uma inicialização cara no módulo

Coloque código de inicialização caro no escopo do módulo. Lá, ele será executado quando a UDF for inicializada. Evite executar novamente o código de inicialização caro em cada invocação do manipulador da UDF.

Tratamento de erros

Uma função de Python usada como UDF pode usar as técnicas normais de tratamento de exceções de Python para capturar erros dentro da função.

Se uma exceção ocorrer dentro da função e não for capturada pela função, o Snowflake gerará um erro incluindo o rastreamento de pilha para a exceção.

Você pode gerar uma exceção explicitamente sem capturá-la para encerrar a consulta e produzir um erro de SQL. Por exemplo:

if (x < 0):
  raise ValueError("x must be non-negative.");

Ao depurar, você pode incluir valores no texto da mensagem de erro do SQL. Para fazer isso, coloque todo um corpo de método de Python em um bloco try-catch; anexe valores de argumento à mensagem do erro capturado; e gere uma exceção com a mensagem estendida. Para evitar revelar dados sensíveis, remova valores de argumento antes de lançar um ambiente de produção.

Boas práticas de segurança

  • Sua função (e qualquer função de biblioteca que você chamar) deve agir como uma função pura, agindo somente sobre os dados que recebe e retornando um valor baseado nesses dados, sem causar efeitos colaterais. Seu código não deve tentar afetar o estado do sistema subjacente, a não ser consumir uma quantidade razoável de memória e tempo de processador.

  • UDFs de Python são executadas dentro de um mecanismo restrito. Nem seu código nem o código nas funções de biblioteca que você utiliza devem empregar quaisquer chamadas de sistema proibidas, inclusive:

    • Controle de processo. Por exemplo, você não pode bifurcar um processo. (Entretanto, você pode usar threads múltiplos).

    • Acesso ao sistema de arquivos.

      Com as seguintes exceções, UDFs de Python não devem ler ou escrever arquivos:

      • UDFs de Python podem ler arquivos especificados na cláusula imports do comando CREATE FUNCTION.

        Para obter mais informações, consulte Como ler arquivos com um manipulador de UDF. Para obter um exemplo, consulte Como carregar um arquivo de um estágio em uma UDF de Python.

      • UDFs de Python podem escrever arquivos, tais como arquivos de log, no diretório /tmp.

        Cada consulta recebe seu próprio sistema de arquivo com memória, no qual seu próprio /tmp é armazenado, de modo que consultas diferentes não podem ter conflitos de nome de arquivo. Entretanto, conflitos dentro de uma consulta são possíveis se uma única consulta chamar mais de uma UDF e essas UDFs tentarem escrever no mesmo nome de arquivo. Além disso, como UDFs de Python podem ser executadas em processos separados de trabalhadores paralelamente, você deve ter cuidado ao escrever no diretório /tmp.

        Para saber mais sobre como escrever arquivos, consulte Como escrever arquivos com um manipulador de UDF. Para obter um exemplo, consulte Como descompactar um arquivo preparado.

    • Acesso à rede.

      Nota

      Como seu código não pode acessar a rede direta ou indiretamente, você não pode usar o código no Conector Python do Snowflake para acessar o banco de dados. Sua UDF em si não pode agir como cliente do Snowflake.