Definindo módulos de extensão

Uma extensão C para CPython é uma biblioteca compartilhada (por exemplo, um arquivo .so no Linux, uma DLL .pyd no Windows), que pode ser carregada no processo Python (por exemplo, ela é compilada com configurações de compilador compatíveis) e que exporta uma função de inicialização.

Para ser importável por padrão (ou seja, por importlib.machinery.ExtensionFileLoader), a biblioteca compartilhada deve estar disponível em sys.path e deve ser nomeada após o nome do módulo mais uma extensão listada em importlib.machinery.EXTENSION_SUFFIXES.

Nota

A construção, o empacotamento e a distribuição de módulos de extensão são melhor realizados com ferramentas de terceiros e estão fora do escopo deste documento. Uma ferramenta adequada é o Setuptools, cuja documentação pode ser encontrada em https://setuptools.pypa.io/en/latest/setuptools.html.

Normalmente, a função de inicialização retorna uma definição de módulo inicializada usando PyModuleDef_Init(). Isso permite dividir o processo de criação em várias fases:

  • Antes que qualquer código substancial seja executado, o Python pode determinar quais recursos o módulo oferece suporte e pode ajustar o ambiente ou se recusar a carregar uma extensão incompatível.

  • Por padrão, o próprio Python cria o objeto módulo — ou seja, faz o equivalente a object.__new__() para classes. Ele também define atributos iniciais como __package__ e __loader__.

  • Depois, o objeto do módulo é inicializado usando código específico da extensão — o equivalente a __init__() em classes.

Isso é chamado de inicialização multifásica para diferenciá-lo do esquema legado (mas ainda suportado) de inicialização monofásica, em que a função de inicialização retorna um módulo totalmente construído. Consulte a seção inicialização monofásica abaixo para obter detalhes.

Alterado na versão 3.5: Adicionado suporte para inicialização multifásica (PEP 489).

Várias instâncias de módulo

Por padrão, módulos de extensão não são singletons. Por exemplo, se a entrada sys.modules for removida e o módulo for reimportado, um novo objeto de módulo será criado e normalmente preenchido com novos métodos e tipos de objetos. O módulo antigo está sujeito à coleta de lixo normal. Isso reflete o comportamento dos módulos Python puros.

Instâncias adicionais do módulo podem ser criadas em subinterpretadores ou após a reinicialização do tempo de execução do Python (Py_Finalize() e Py_Initialize()). Nesses casos, compartilhar objetos Python entre instâncias do módulo provavelmente causaria travamentos ou comportamento indefinido.

Para evitar tais problemas, cada instância de um módulo de extensão deve ser isolada: alterações em uma instância não devem afetar implicitamente as outras, e todos os estados pertencentes ao módulo, incluindo referências a objetos Python, devem ser específicos de uma instância específica do módulo. Consulte Isolando módulos de extensão para obter mais detalhes e um guia prático.

Uma maneira mais simples de evitar esses problemas é levantar um erro na inicialização repetida.

Espera-se que todos os módulos ofereçam suporte a subinterpretador, ou então sinalizem explicitamente a falta de suporte. Isso geralmente é obtido por isolamento ou bloqueio de inicializações repetidas, como acima. Um módulo também pode ser limitado ao interpretador principal usando o slot Py_mod_multiple_interpreters.

Função de inicialização

A função de inicialização definida por um módulo de extensão tem a seguinte assinatura:

PyObject *PyInit_modulename(void)

Seu nome deve ser PyInit_<nome>, com <nome> substituído pelo nome do módulo.

Para módulos com nomes com somente ASCII, a função deve ser nomeada PyInit_<nome>, com <nome> substituído pelo nome do módulo. Ao usar Inicialização multifásica, nomes de módulos não ASCII são permitidos. Neste caso, o nome da função de inicialização é PyInitU_<nome>, com <nome> codificado usando a codificação punycode do Python com hífenes substituídos por sublinhados. Em Python:

def nome_func_iniciadora(nome):
    try:
        sufixo = b'_' + nome.encode('ascii')
    except UnicodeEncodeError:
        sufixo = b'U_' + nome.encode('punycode').replace(b'-', b'_')
    return b'PyInit' + sufixo

É recomendável definir a função de inicialização usando uma macro auxiliar:

PyMODINIT_FUNC

Declara uma função de inicialização do módulo de extensão. Esta macro:

  • especifica o tipo de retorno PyObject*,

  • adiciona quaisquer declarações de vinculação especiais exigidas pela plataforma e

  • para C++, declara a função como extern "C".

Por exemplo, um módulo chamado spam seria definido assim:

static struct PyModuleDef spam_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "spam",
    ...
};

PyMODINIT_FUNC
PyInit_spam(void)
{
    return PyModuleDef_Init(&spam_module);
}

É possível exportar vários módulos de uma única biblioteca compartilhada, definindo várias funções de inicialização. No entanto, importá-los requer o uso de links simbólicos ou um importador personalizado, porque por padrão apenas a função correspondente ao nome do arquivo é encontrada. Veja a seção Multiple modules in one library na PEP 489 para detalhes.

A função de inicialização normalmente é o único item não static definido no código-fonte C do módulo.

Inicialização multifásica

Normalmente, a função de inicialização (PyInit_modulename) retorna uma instância de PyModuleDef com m_slots não NULL. Antes de ser retornada, a instância PyModuleDef deve ser inicializada usando a seguinte função:

PyObject *PyModuleDef_Init(PyModuleDef *def)
Parte da ABI Estável desde a versão 3.5.

Garante que uma definição de módulo é um objeto Python devidamente inicializado que reporta corretamente seu tipo e uma contagem de referências.

Retorna def convertido para PyObject*, ou NULL se ocorrer um erro.

A chamada desta função é necessária para Inicialização multifásica. Ela não deve ser usada em outros contextos.

Observe que o Python assume que as estruturas PyModuleDef são alocadas estaticamente. Esta função pode retornar uma nova referência ou uma referência emprestada; esta referência não deve ser liberada.

Adicionado na versão 3.5.

Inicialização monofásica legada

Atenção

A inicialização monofásica é um mecanismo legado para inicializar módulos de extensão, com desvantagens e falhas de projeto conhecidas. Os autores de módulos de extensão são incentivados a utilizar a inicialização multifásica.

Na inicialização monofásica, a função de inicialização (PyInit_modulename) deve criar, preencher e retornar um objeto de módulo. Isso normalmente é feito usando PyModule_Create() e funções como PyModule_AddObjectRef().

A inicialização monofásica difere do padrão nas seguintes maneiras:

  • Módulos monofásicos são, ou melhor, contêm, “singletons”.

    Quando o módulo é inicializado pela primeira vez, o Python salva o conteúdo do __dict__ do módulo (ou seja, normalmente, as funções e os tipos do módulo).

    Para importações subsequentes, o Python não chama a função de inicialização novamente. Em vez disso, ele cria um novo objeto de módulo com um novo __dict__ e copia o conteúdo salvo para ele. Por exemplo, dado um módulo monofásico _testsinglephase [1] que define uma função sum e uma classe de exceção error:

    >>> import sys
    >>> import _testsinglephase as one
    >>> del sys.modules['_testsinglephase']
    >>> import _testsinglephase as two
    >>> one is two
    False
    >>> one.__dict__ is two.__dict__
    False
    >>> one.sum is two.sum
    True
    >>> one.error is two.error
    True
    

    O comportamento exato deve ser considerado um detalhe da implementação do CPython.

  • Para contornar o fato de que PyInit_modulename não aceita um argumento spec, parte do estado do mecanismo de importação é salvo e aplicado ao primeiro módulo adequado criado durante a chamada PyInit_modulename. Especificamente, quando um submódulo é importado, esse mecanismo adiciona o nome do pacote pai ao nome do módulo.

    Uma função monofásica PyInit_modulename deve criar “seu” objeto de módulo o mais rápido possível, antes que qualquer outro objeto de módulo possa ser criado.

  • Nomes de módulos não ASCII (PyInitU_modulename) não são suportados.

  • Módulos monofásicos oferecem suporte a funções de pesquisa de módulos como PyState_FindModule().