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:
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*
, ouNULL
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çãosum
e uma classe de exceçãoerror
:>>> 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 chamadaPyInit_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()
.