Criando ligações Python para bibliotecas C / C ++ usando SIP. Parte 1

Às vezes, ao trabalhar em um projeto em Python, há um desejo de usar uma biblioteca que não seja escrita em Python, mas, por exemplo, em C ou C ++. As razões para isso podem ser diferentes: primeiro, o Python é uma linguagem maravilhosa, mas em algumas situações não é rápida o suficiente. E se você perceber que o desempenho é limitado pelos recursos da linguagem Python, faz sentido escrever parte do programa em outra linguagem (neste artigo, falaremos sobre C e C ++), organize essa parte do programa como uma biblioteca, faça ligações Python (ligações Python) por cima e use o módulo assim obtido como uma biblioteca Python normal. Em segundo lugar, geralmente ocorre uma situação quando você sabe que existe uma biblioteca que resolve o problema necessário, mas, infelizmente, essa biblioteca não está escrita em Python, mas no mesmo C ou C ++.Nesse caso, também podemos criar uma ligação Python sobre a biblioteca e usá-la sem pensar no fato de que a biblioteca não foi originalmente escrita em Python.

Existem várias ferramentas para criar ligações Python, desde as de nível inferior, como a API Python / C, até as de nível superior, como SWIG e SIP .

Eu não tinha o objetivo de comparar maneiras diferentes de criar ligações Python, mas gostaria de falar sobre o básico do uso de uma ferramenta, a saber, o SIP . Inicialmente, o SIP foi projetado para criar uma ligação em torno da biblioteca Qt - PyQt , e também é usado para desenvolver outras grandes bibliotecas Python, por exemplo, wxPython .

Neste artigo, o gcc será usado como o compilador para C e o g ++ será usado como o compilador C ++. Todos os exemplos foram testados no Arch Linux e Python 3.8. Para não complicar os exemplos, o tópico de compilação para diferentes sistemas operacionais e o uso de diferentes compiladores (por exemplo, Visual Studio) não está incluído no escopo deste artigo.

Você pode baixar todos os exemplos deste artigo no repositório no github .
O repositório com fontes SIP está localizado em https://www.riverbankcomputing.com/hg/sip/ . O Mercurial é usado como o sistema de controle de versão do SIP.

Fazendo uma ligação sobre uma biblioteca em C


Escrevendo uma biblioteca em C


Este exemplo está localizado na pasta pyfoo_c_01 na fonte, mas neste artigo iremos assumir que estamos fazendo tudo do zero.

Vamos começar com um exemplo simples. Primeiro, criaremos uma biblioteca C simples, que será executada a partir de um script Python. Deixe nossa biblioteca ser a única função

int foo(char*);

que pegará uma string e retornará seu comprimento multiplicado por 2. O

arquivo de cabeçalho foo.h pode parecer, por exemplo, assim:

#ifndef FOO_LIB
#define FOO_LIB

int foo(char* str);

#endif

E o arquivo com a implementação do foo.cpp :

#include <string.h>

#include "foo.h"

int foo(char* str) {
	return strlen(str) * 2;
}

Para testar a funcionalidade da biblioteca, escreveremos um programa main.c simples :

#include <stdio.h>

#include "foo.h"

int main(int argc, char* argv[]) {
	char* str = "0123456789";
	printf("%d\n", foo(str));
}

Para maior precisão, crie um Makefile :

CC=gcc
CFLAGS=-c
DIR_OUT=bin

all: main

main: main.o libfoo.a
	$(CC) $(DIR_OUT)/main.o -L$(DIR_OUT) -lfoo -o $(DIR_OUT)/main

main.o: makedir main.c
	$(CC) $(CFLAGS) main.c -o $(DIR_OUT)/main.o

libfoo.a: makedir foo.c
	$(CC) $(CFLAGS) foo.c -o $(DIR_OUT)/foo.o
	ar rcs $(DIR_OUT)/libfoo.a $(DIR_OUT)/foo.o

makedir:
	mkdir -p $(DIR_OUT)

clean:
	rm -rf $(DIR_OUT)/*

Deixe todas as fontes da biblioteca foo localizadas em uma subpasta de foo na pasta de origem:

foo_c_01 /
└── foo
    ├── foo.c
    ├── foo.h
    ├── main.c
    Make── Makefile


Vamos para a pasta foo e compilamos as fontes usando o comando

make

Durante a compilação, o texto será exibido.

mkdir -p bin
gcc -c main.c -o bin/main.o
gcc -c foo.c -o bin/foo.o
ar rcs bin/libfoo.a bin/foo.o
gcc bin/main.o -Lbin -lfoo -o bin/main

O resultado da compilação será colocado na pasta bin dentro da pasta foo :

foo_c_01 /
└── foo
    ├── bin
    │ ├── foo.o
    Lib ├── libfoo.a
    Main ├── main
    │ └── main.o
    ├── foo.c
    ├── foo.h
    ├── main.c
    Make── Makefile


Compilamos uma biblioteca para vinculação estática e um programa que a usa com o nome main . Após a compilação, você pode verificar se o programa principal inicia.

Vamos criar uma ligação Python sobre a biblioteca foo.

Fundamentos do SIP


Primeiro você precisa instalar o SIP. Isso é feito de maneira padrão, como em todas as outras bibliotecas usando pip:

pip install --user sip

Obviamente, se você estiver trabalhando em um ambiente virtual, o parâmetro --user, indicando que a biblioteca SIP precisa ser instalada na pasta do usuário e não globalmente no sistema, não deve ser especificado.

O que precisamos fazer para que a biblioteca foo possa ser chamada a partir do código Python? No mínimo, você precisa criar dois arquivos: um deles no formato TOML e denomine pyproject.toml , e o segundo é um arquivo com a extensão .sip. Vamos lidar com cada um deles sequencialmente.

Precisamos concordar com a estrutura da fonte. Dentro da pasta pyfoo_c está a pasta foo , que contém a fonte da biblioteca. Após a compilação , a pasta bin é criada dentro da pasta foo, que conterá todos os arquivos compilados. Posteriormente, adicionaremos a capacidade do usuário especificar os caminhos para os arquivos de cabeçalho e objeto da biblioteca através da linha de comando.

Os arquivos necessários para o SIP estarão localizados na mesma pasta que a pasta foo .

pyproject.toml


O arquivo pyproject.toml não é uma invenção dos desenvolvedores do SIP, mas o formato de descrição do projeto Python descrito no PEP 517 "Um formato independente do sistema de compilação para as árvores de origem" e no PEP 518 "Especificando os Requisitos Mínimos do Sistema de Compilação para Projetos Python" . Este é um arquivo TOML , que pode ser considerado como uma versão mais avançada do formato ini, na qual os parâmetros são armazenados na forma de "chave = valor" e os parâmetros podem ser localizados não apenas em seções como [foo], chamadas de tabelas em termos de TOML, mas e em subseções do formulário [foo.bar.spam]. Os parâmetros podem conter não apenas cadeias, mas também listas, números e valores booleanos.

Este arquivo deve descrever tudo o que é necessário para criar um pacote Python, e não necessariamente usando o SIP. No entanto, como veremos um pouco mais tarde, em alguns casos, esse arquivo não será suficiente e, além disso, será necessário criar um pequeno script Python. Mas vamos falar sobre tudo em ordem.

Uma descrição completa de todos os parâmetros possíveis do arquivo pyproject.toml relacionados ao SIP pode ser encontrada na página de documentação do SIP .

Para o nosso exemplo, crie o arquivo pyproject.toml no mesmo nível da pasta foo :

foo_c_01 /
├── foo
│ ├── bin
│ │ ├── foo.o
Lib │ ├── libfoo.a
│ │ ├── main
│ │ └── main.o
│ ├── foo.c
│ ├── foo.h
│ ├── main.c
Make └── Makefile
└── pyproject.toml


O conteúdo de pyproject.toml será o seguinte:

[build-system]
requires = ["sip >=5, <6"]
build-backend = "sipbuild.api"

[tool.sip.metadata]
name = "pyfoo"
version = "0.1"
license = "MIT"

[tool.sip.bindings.pyfoo]
headers = ["foo.h"]
libraries = ["foo"]
include-dirs = ["foo"]
library-dirs = ["foo/bin"]

A seção [build-system] ("table" em termos de TOML) é padrão e está descrita no PEP 518 . Ele contém dois parâmetros:


Outros parâmetros são descritos nas seções [tool.sip. *] .

A seção [tool.sip.metadata] contém informações gerais sobre o pacote: o nome do pacote a ser montado (nosso pacote será chamado pyfoo , mas não confunda esse nome com o nome do módulo, que importaremos posteriormente para o Python), o número da versão do pacote (no nosso caso) número da versão "0.1") e licença (por exemplo, " MIT ").

O mais importante do ponto de vista da montagem é descrito em [tool.sip.bindings. pyfoo ].

Anote o nome do pacote no cabeçalho da seção. Adicionamos dois parâmetros a esta seção:

  • cabeçalhos - uma lista de arquivos de cabeçalho necessários para usar a biblioteca foo.
  • bibliotecas - uma lista de arquivos de objetos compilados para vinculação estática.
  • incluem-dirs é o caminho onde procurar por arquivos de cabeçalho adicionais, além daqueles que estão ligados ao compilador C. Neste caso, para onde olhar para o foo.h arquivo .
  • library-dirs é o caminho para procurar arquivos de objetos adicionais além daqueles anexados ao compilador C. Nesse caso, esta é a pasta na qual o arquivo de biblioteca compilado foo é criado .

Então, criamos o primeiro arquivo necessário para o SIP. Agora passamos a criar o próximo arquivo que descreverá o conteúdo do futuro módulo Python.

pyfoo.sip


Crie o arquivo pyfoo.sip na mesma pasta que o arquivo pyproject.toml :

foo_c_01 /
├── foo
│ ├── bin
│ │ ├── foo.o
Lib │ ├── libfoo.a
│ │ ├── main
│ │ └── main.o
│ ├── foo.c
│ ├── foo.h
│ ├── main.c
Make └── Makefile
├── pyfoo.sip
└── pyproject.toml


Um arquivo com a extensão .sip descreve a interface da biblioteca de origem, que será convertida em um módulo em Python. Esse arquivo tem seu próprio formato, que consideraremos agora, e se assemelha ao arquivo de cabeçalho C / C ++ com marcação adicional, o que deve ajudar o SIP a criar um módulo Python.

No nosso exemplo, esse arquivo deve ser chamado pyfoo.sip , porque antes disso, no arquivo pyproject.toml, criamos as [tool.sip.bindings. pyfoo] No caso geral, pode haver várias partições e, portanto, deve haver vários arquivos * .sip. Mas se tivermos vários arquivos sip, esse é um caso especial do ponto de vista do SIP, e não o consideraremos neste artigo. Observe que, no caso geral, o nome do arquivo .sip (e, consequentemente, o nome da seção) pode não coincidir com o nome do pacote especificado no parâmetro name na seção [tool.sip.metadata] .

Considere o arquivo pyfoo.sip do nosso exemplo:

%Module(name=foo, language="C")

int foo(char*);

Linhas que começam com o caractere "%" são chamadas de diretivas. Eles devem informar ao SIP como montar e estilizar adequadamente o módulo Python. Uma lista completa de diretivas é descrita nesta página de documentação . Algumas diretivas possuem parâmetros adicionais. Os parâmetros podem não ser necessários.

Neste exemplo, usamos duas diretivas; conheceremos outras diretivas nos exemplos a seguir.

O arquivo pyfoo.sip começa com a diretiva % Module (nome = foo, idioma = “C”) . Observe que especificamos o valor do primeiro parâmetro ( nome ) sem aspas e o valor do segundo parâmetro ( idioma) entre aspas, como cadeias de caracteres em C / C ++. Este é um requisito desta diretiva, conforme descrito na documentação para a diretiva % Module .

Na diretiva % Module , apenas o parâmetro name é necessário , que define o nome do módulo Python do qual importaremos a função de biblioteca. Nesse caso, o módulo é chamado foo , ele conterá a função foo ; portanto, após a montagem e instalação, o importaremos usando o código:

from foo import foo

Poderíamos tornar esse módulo aninhado em outro módulo substituindo esta linha, por exemplo, por esta:

%Module(name=foo.bar, language="C")
...

Em seguida, importe a função foo seria a seguinte:

from foo.bar import foo

O parâmetro language da diretiva % Module indica o idioma em que a biblioteca de origem está gravada. O valor deste parâmetro pode ser "C" ou "C ++". Se esse parâmetro não for especificado, o SIP assumirá que a biblioteca está gravada em C ++.

Agora observe a última linha do arquivo pyfoo.sip :

int foo(char*);

Esta é uma descrição da interface da função da biblioteca que queremos colocar no módulo Python. Com base nesta declaração, o sip criará uma função Python. Eu acho que tudo deve ficar claro aqui.

Nós coletamos e verificamos


Agora tudo está pronto para criar um pacote Python com uma ligação para uma biblioteca C. Primeiro, você precisa construir a própria biblioteca. Vá para a pasta pyfoo_c_01 / foo / e inicie a compilação usando o comando make :

$ make

mkdir -p bin
gcc -c main.c -o bin/main.o
gcc -c foo.c -o bin/foo.o
ar rcs bin/libfoo.a bin/foo.o
gcc bin/main.o -Lbin -lfoo -o bin/main

Se tudo der certo, a pasta bin será criada dentro da pasta foo , na qual, entre outros arquivos, haverá uma biblioteca libfoo.a compilada . Deixe-me lembrá-lo que aqui, para não nos distrairmos do tópico principal, estamos apenas falando sobre a criação no Linux usando o gcc. Volte para a pasta pyfoo_c_01 . Agora é hora de conhecer as equipes do SIP. Após a instalação do SIP, os seguintes comandos da linha de comando ficarão disponíveis ( página de documentação ):



  • sip-build . Cria um arquivo de objeto de extensão Python.
  • sip-install . Cria um arquivo de objeto de extensão Python e o instala.
  • sip-sdist. .tar.gz, pip.
  • sip-wheel. wheel ( .whl).
  • sip-module. , , SIP. , , . , standalone project, , , , .
  • sip-distinfo. .dist-info, wheel.

Esses comandos precisam ser executados na pasta em que o arquivo pyproject.toml está localizado .

Para começar, para entender melhor a operação do SIP, execute o comando sip-build , com a opção --verbose para obter uma saída mais detalhada no console e veja o que acontece durante o processo de construção.

$ sip-build --verbose

Essas ligações serão criadas: pyfoo.
Gerando as ligações do pyfoo ...
Compilando o módulo 'foo' ... criando a
extensão 'foo'
criando build
criando build / temp.linux-x86_64-3.8
gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fno-semântica-interposição -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c sipfoocmodule.c -o build / temp.linux-x86_64-3.8 / sipfoocmodule.o
sipfoocmodule.c: Na função
func_foo : sipfoocmodule .c: 29: 22: aviso: declaração implícita de função “foo” [-Wimplicit-function-statement]
29 | sipRes = foo (a0);
| ^ ~~
gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fno-semântica-interposição -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c array.c -o build / temp.linux-x86_64-3.8 / array.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fno-semântica-interposição -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c bool.cpp -o build / temp.linux-x86_64-3.8 / bool.o
gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fno-semântica-interposição -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c objmap.c -o build / temp.linux-x86_64-3.8 / objmap.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fno-semântica-interposição -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c qtlib.c -o build / temp.linux-x86_64-3.8 / qtlib.o
gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fno-semântica-interposição -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c int_convertors.c -o build / temp.linux-x86_64-3.8 / int_convertors.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fno-semântica-interposição -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c voidptr.c -o build / temp.linux-x86_64-3.8 / voidptr.o
gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fno-semântica-interposição -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c apiversions.c -o build / temp.linux-x86_64-3.8 / apiversions.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fno-semântica-interposição -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c descriptors.c -o build / temp.linux-x86_64-3.8 / descriptors.o
gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fno-semântica-interposição -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c threadss.c -o build / temp.linux-x86_64-3.8 /
threadss.o gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fno-semântica-interposição -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -march = x86-64 -mtune = genérico -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c siplib.c -o build / temp.linux-x86_64-3.8 / siplib.o
siplib.c: Na função "slot_richcompare":
siplib.c : 9536: 16: aviso: "st" pode ser usado sem inicialização nesta função [-Pode ser não inicializado]
9536 | slot = findSlotInClass (ctd, st);
| ^ ~~~~~~~~~~~~~~~~~~~~~~~
siplib.c: 10671: 19: nota: "st" foi declarado aqui
10671 | sipPySlotType st;
| ^ ~
siplib.c: Na função "parsePass2":
siplib.c: 5625: 32: aviso: "proprietário" pode ser usado sem inicialização nesta função [-Pode ser não inicializado]
5625 | * proprietário = arg;
| ~~~~~~~ ^ ~
g ++ -pthread -shared -Wl, -O1, - classificar comum, - conforme necessário, -z, relro, -z, agora -fno-semântica-interposição -Wl, -O1, - classificar comum, Agora, você pode usar o seguinte comando: http: //support.microsoft.com/kb/610716/pt-br / bool.o build / temp.linux-x86_64-3.8 / objmap.o build / temp.linux-x86_64-3.8 / qtlib.o build / temp.linux-x86_64-3.8 / int_convertors.o build / temp.linux-x86_64 -3.8 / voidptr.o build / temp.linux-x86_64-3.8 / apiversions.o build / temp.linux-x86_64-3.8 / descriptors.o build / temp.linux-x86_64-3.8 / threadss.o build / temp.linux -x86_64-3.8 / siplib.o -L ../../ foo / bin -L / usr / lib -lfoo -o / home / jenyay / projects / soft / sip-examples / pyfoo_c_01 / build / foo / foo. cpython-38-x86_64-linux-gnu.so
O projeto foi construído.

Não vamos nos aprofundar no trabalho do SIP, mas pode ser visto pela saída que algumas fontes estão compilando. Essas fontes podem ser vistas na pasta build / foo / criada por este comando :

pyfoo_c_01
Build── compilação
│ └── foo
│ ├── apiversions.c
│ ├── array.c
│ ├── array.h
│ ├── bool.cpp
Build ├── compilação
Instalar o temp.linux-x86_64-3.8
│ │ ├── apiversions.o
│ │ ├── array.o
│ │ ├── bool.o
│ │ ├── descritores.o
Int │ ├── int_convertors.o
Map │ ├── objmap.o
│ │ ├── qtlib.o
Ip │ ├── sipfoocmodule.o
│ │ ├── siplib.o
│ │ ├── threads.o
│ │ └── voidptr.o
│ ├── descriptors.c
Fo ├── foo.cpython-38-x86_64-linux-gnu.so
Int ├── int_convertors.c
Map ├── objmap.c
│ ├── qtlib.c
Ip ├── sipAPIfoo.h
S ├── sipfoocmodule.c
│ ├── sip.h
│ ├── sipint.h
│ ├── siplib.c
│ ├── threads.c
│ └── voidptr.c
├── foo
│ ├── bin
│ │ ├── foo.o
Lib │ ├── libfoo.a
│ │ ├── main
│ │ └── main.o
│ ├── foo.c
│ ├── foo.h
│ ├── main.c
Make └── Makefile
├── pyfoo.sip
└── pyproject.toml


As fontes auxiliares apareceram na pasta build / foo . Por curiosidade, vejamos o arquivo sipfoocmodule.c , pois está diretamente relacionado ao módulo foo que será criado:

/*
 * Module code.
 *
 * Generated by SIP 5.1.1
 */

#include "sipAPIfoo.h"

/* Define the strings used by this module. */
const char sipStrings_foo[] = {
    'f', 'o', 'o', 0,
};

PyDoc_STRVAR(doc_foo, "foo(str) -> int");

static PyObject *func_foo(PyObject *sipSelf,PyObject *sipArgs)
{
    PyObject *sipParseErr = SIP_NULLPTR;

    {
        char* a0;

        if (sipParseArgs(&sipParseErr, sipArgs, "s", &a0))
        {
            int sipRes;

            sipRes = foo(a0);

            return PyLong_FromLong(sipRes);
        }
    }

    /* Raise an exception if the arguments couldn't be parsed. */
    sipNoFunction(sipParseErr, sipName_foo, doc_foo);

    return SIP_NULLPTR;
}

/* This defines this module. */
sipExportedModuleDef sipModuleAPI_foo = {
    0,
    SIP_ABI_MINOR_VERSION,
    sipNameNr_foo,
    0,
    sipStrings_foo,
    SIP_NULLPTR,
    SIP_NULLPTR,
    0,
    SIP_NULLPTR,
    SIP_NULLPTR,
    0,
    SIP_NULLPTR,
    0,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    {SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR,
            SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR},
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR
};

/* The SIP API and the APIs of any imported modules. */
const sipAPIDef *sipAPI_foo;

/* The Python module initialisation function. */
#if defined(SIP_STATIC_MODULE)
PyObject *PyInit_foo(void)
#else
PyMODINIT_FUNC PyInit_foo(void)
#endif
{
    static PyMethodDef sip_methods[] = {
        {sipName_foo, func_foo, METH_VARARGS, doc_foo},
        {SIP_NULLPTR, SIP_NULLPTR, 0, SIP_NULLPTR}
    };

    static PyModuleDef sip_module_def = {
        PyModuleDef_HEAD_INIT,
        "foo",
        SIP_NULLPTR,
        -1,
        sip_methods,
        SIP_NULLPTR,
        SIP_NULLPTR,
        SIP_NULLPTR,
        SIP_NULLPTR
    };

    PyObject *sipModule, *sipModuleDict;
    /* Initialise the module and get it's dictionary. */
    if ((sipModule = PyModule_Create(&sip_module_def)) == SIP_NULLPTR)
        return SIP_NULLPTR;

    sipModuleDict = PyModule_GetDict(sipModule);

    if ((sipAPI_foo = sip_init_library(sipModuleDict)) == SIP_NULLPTR)
        return SIP_NULLPTR;

    /* Export the module and publish it's API. */
    if (sipExportModule(&sipModuleAPI_foo, SIP_ABI_MAJOR_VERSION, SIP_ABI_MINOR_VERSION, 0) < 0)
    {
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }
    /* Initialise the module now all its dependencies have been set up. */
    if (sipInitModule(&sipModuleAPI_foo,sipModuleDict) < 0)
    {
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }

    return sipModule;
}

Se você trabalhou com a API Python / C, verá funções familiares. Preste atenção especial à função func_foo iniciando na linha 18.

Como resultado da compilação dessas fontes, o arquivo build / foo / foo.cpython-38-x86_64-linux-gnu.so será criado e contém a extensão Python, que ainda precisa ser instalada corretamente.

Para compilar a extensão e instalá-la imediatamente, você pode usar o comando sip-install , mas não o usaremos, porque, por padrão, ele tenta instalar a extensão Python criada globalmente no sistema. Este comando possui um parâmetro --target-dir, com o qual você pode especificar o caminho em que deseja instalar a extensão, mas é melhor usarmos outras ferramentas que criam pacotes, que podem ser instalados usando o pip.

Primeiro, use o comando sip-sdist . Usá-lo é muito simples:

$ sip-sdist

The sdist has been built.

Depois disso, o arquivo pyfoo-0.1.tar.gz será criado , que pode ser instalado usando o comando:

pip install --user pyfoo-0.1.tar.gz

Como resultado, as seguintes informações serão mostradas e o pacote será instalado:

Processing ./pyfoo-0.1.tar.gz
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
    Preparing wheel metadata ... done
Building wheels for collected packages: pyfoo
  Building wheel for pyfoo (PEP 517) ... done
  Created wheel for pyfoo: filename=pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl size=337289 sha256=762fc578...
  Stored in directory: /home/jenyay/.cache/pip/wheels/54/dc/d8/cc534fff...
Successfully built pyfoo
Installing collected packages: pyfoo
  Attempting uninstall: pyfoo
    Found existing installation: pyfoo 0.1
    Uninstalling pyfoo-0.1:
      Successfully uninstalled pyfoo-0.1
Successfully installed pyfoo-0.1

Vamos garantir que conseguimos fazer uma ligação em Python. Iniciamos o Python e tentamos chamar a função Deixe-me lembrá-lo de que, de acordo com nossas configurações, o pacote pyfoo contém o módulo foo , que tem a função foo .

>>> from foo import foo
>>> foo(b'123456')
12

Observe que, como parâmetro para a função, passamos não apenas uma string, mas uma string de bytes b'123456 '- um análogo direto de char * para C. Um pouco mais tarde, adicionaremos a conversão de char * em str e vice-versa. O resultado era esperado. Deixe-me lembrá-lo de que a função foo retorna o tamanho duplo de uma matriz do tipo char * , transmitida a ela como parâmetro.

Vamos tentar passar uma string Python regular para a função foo , em vez de uma lista de bytes.

>>> from foo import foo
>>> foo('123456')

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo(str): argument 1 has unexpected type 'str'

A ligação criada não pôde converter a string em char * ; discutiremos como ensiná-lo a fazer isso na próxima seção.

Parabéns, criamos a primeira encadernação em uma biblioteca escrita em C.

Saia do interpretador Python e monte o assembly no formato de roda. Como você provavelmente sabe, wheel é um formato de pacote relativamente novo que tem sido usado universalmente recentemente. O formato é descrito no PEP 427, “The Wheel Binary Package Format 1.0”, mas uma descrição dos recursos do formato wheel é um tópico digno de um grande artigo separado. É importante para nós que o usuário possa instalar facilmente o pacote no formato de roda usando pip.

Um pacote no formato wheel não é mais complicado do que um pacote no formato sdist. Para fazer isso, na pasta com o arquivopyproject.toml precisa executar o comando

sip-wheel

Após executar este comando, o processo de compilação será mostrado e poderá haver avisos do compilador:

$ sip-wheel

Essas ligações serão criadas: pyfoo.
Gerando as ligações do pyfoo ...
Compilando o módulo 'foo' ...
sipfoocmodule.c: Na função func_foo:
sipfoocmodule.c: 29: 22: warning: declaração implícita da função foo [-Wimplicit-function-statement]
29 | sipRes = foo (a0);
| ^ ~~
siplib.c: Na função "slot_richcompare":
siplib.c : 9536: 16: aviso: "st" pode ser usado sem inicialização nesta função [-O que talvez não seja inicializado]
9536 | slot = findSlotInClass (ctd, st);
| ^ ~~~~~~~~~~~~~~~~~~~~~~~
siplib.c: 10671: 19: observação: "st" foi declarado aqui
10671 sipPySlotType st;
| ^ ~
siplib.c: Na função "parsePass2":
siplib.c: 5625: 32: aviso: "proprietário" pode ser usado sem inicialização nesta função [-Pode ser não inicializado]
5625 | * proprietário = arg;
| ~~~~~~~ ^ ~ ~
A roda foi construída.

Quando a montagem é concluída (nosso pequeno projeto é compilado rapidamente), um arquivo com o nome pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl ou similar aparecerá na pasta do projeto . O nome do arquivo gerado pode ser diferente, dependendo do sistema operacional e da versão do Python.

Agora podemos instalar este pacote usando o pip:

pip install --user --upgrade pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl

A opção --upgrade é usada aqui para que o pip substitua o módulo pyfoo instalado anteriormente.

Além disso, o módulo foo e o pacote pyfoo podem ser usados, como mostrado acima.

Adicione regras de conversão ao char *


Na seção anterior, encontramos um problema em que a função foo pode aceitar apenas um conjunto de bytes, mas não seqüências de caracteres. Agora vamos corrigir essa falha. Para fazer isso, usaremos outra ferramenta SIP - anotações . As anotações são usadas nos arquivos .sip e são aplicadas a alguns elementos de código: funções, classes, argumentos de função, exceções, variáveis ​​etc. As anotações são escritas entre barras: / anotação / .

Uma anotação pode funcionar como um sinalizador, que pode estar no estado definido ou não, por exemplo: / ReleaseGIL / , ou algumas anotações precisam receber alguns valores, por exemplo: / Encoding = "UTF-8" /. Se várias anotações precisarem ser aplicadas a algum objeto, elas serão separadas por vírgulas nas barras: / anotação_1, anotação_2 /.

No exemplo a seguir, localizado na pasta pyfoo_c_02 , adicione a função foo do parâmetro de anotação pyfoo.sip do arquivo :

%Module(name=foo, language="C")

int foo(char* /Encoding="UTF-8"/);

A anotação Encoding indica em qual codificação a string a ser passada para a função deve ser codificada. Os valores para esta anotação podem ser: ASCII, Latim-1, UTF-8 ou Nenhum. Se a anotação Encoding não for especificada ou for igual a None , o parâmetro para essa função não estará sujeito a nenhuma codificação e será passado para a função como está, mas, neste caso, o parâmetro no código Python deve ser do tipo bytes , ou seja, uma matriz de bytes, como vimos no exemplo anterior. Se a codificação for especificada, esse parâmetro poderá ser uma sequência (digite str em Python). A anotação de codificação pode ser aplicada apenas a parâmetros do tipo char , const char ,char * ou const char * .

Vamos verificar como o foo função do foo módulo trabalha agora . Para fazer isso, como antes, você deve primeiro compilar a biblioteca foo chamando o comando make dentro da pasta foo e depois chamar o comando, por exemplo, sip-wheel, da pasta de exemplo pyfoo_c_02 . O arquivo pyfoo-0.2-cp38-cp38-manylinux1_x86_64.whl ou com um nome semelhante será criado , que pode ser definido usando o comando:

pip install --user --upgrade pyfoo-0.2-cp38-cp38-manylinux1_x86_64.whl

Se tudo correu bem, inicie o interpretador Python e tente chamar a função foo com um argumento string:

>>> from foo import foo

>>> foo(b'qwerty')
12

>>> foo('qwerty')
12

>>> foo('')
24

Primeiro, garantimos que o uso de bytes ainda seja possível. Depois disso, garantimos que agora também podemos passar argumentos de string para a função foo . Observe que a função foo para um argumento de string com letras russas retornou um valor duas vezes maior do que para uma string contendo apenas letras latinas. Isso aconteceu devido ao fato de a função foo não contar o comprimento da string em caracteres (e dobrá-la), mas o comprimento da matriz char * e, como na codificação UTF-8, as letras russas ocupam 2 bytes, o tamanho da matriz char * após a conversão de Seqüências de caracteres Python eram duas vezes maiores.

Bem! Resolvemos o problema com o argumento da funçãofoo , mas e se tivermos dezenas ou centenas dessas funções em nossa biblioteca, você precisará especificar a codificação de parâmetro para cada uma delas? Freqüentemente, a codificação usada em um programa é a mesma e não há propósito para funções diferentes indicar codificações diferentes. Nesse caso, o SIP pode especificar a codificação padrão e, para alguma função, a codificação precisar de outra, ela poderá ser redefinida usando a anotação Encoding .

Para definir a codificação dos parâmetros da função por padrão, a diretiva % DefaultEncoding é usada . Seu uso é mostrado no exemplo localizado na pasta pyfoo_c_03 .

Para aproveitar a diretiva % DefaultEncoding , altere o arquivo pyfoo.sip, agora seu conteúdo é o seguinte:

%Module(name=foo, language="C")
%DefaultEncoding "UTF-8"

int foo(char*);

Agora, se o argumento for uma função do tipo char , char * , etc. Se não houver anotação de codificação , a codificação será obtida da diretiva % DefaultEncoding e, se não houver, a conversão não será executada e, para todos os parâmetros char * , etc. é necessário transferir não linhas, mas bytes .

Um exemplo da pasta pyfoo_c_03 é coletado e verificado da mesma maneira que um exemplo da pasta pyfoo_c_02 .

Brevemente sobre project.py. Automatizar montagem


Até agora, usamos dois arquivos utilitários para criar uma ligação Python - pyproject.toml e pyfoo.sip . Agora, vamos nos familiarizar com outro arquivo desse tipo, que deve ser chamado de project.py . Com esse script, podemos influenciar o processo de compilação do nosso pacote. Vamos fazer a automação de compilação. Para coletar os exemplos pyfoo_c_01 - pyfoo_c_03 das seções anteriores, primeiro você deve ir para a pasta foo / , compilar usando o comando make , retornar à pasta onde o arquivo pyproject.toml está localizado e só então começar a compilar o pacote usando um dos seguintes comandos sip- * .

Agora, nosso objetivo é garantir que, ao executar os comandos sip-build , sip-sdist e sip-wheel , o conjunto da biblioteca C foo seja iniciado primeiro e, em seguida, o próprio comando já seja iniciado.

Um exemplo criado nesta seção está localizado na pasta de origem pyfoo_c_04 .

Para alterar o processo de compilação, podemos declarar uma classe no arquivo project.py (o nome do arquivo deve ser exatamente isso) derivado da classe sipbuild.Project . Essa classe possui métodos que podemos substituir por conta própria. Atualmente, estamos interessados ​​nos seguintes métodos:

  • construir . Chamado durante a chamada para o comando sip-build .
  • build_sdist . Chamado quando o comando sip-sdist é chamado .
  • build_wheel . Chamado quando o comando sip-wheel é chamado .
  • instalar . Chamado quando o comando sip-install é chamado .

Ou seja, podemos redefinir o comportamento desses comandos. Estritamente falando, os métodos listados são declarados na classe abstrata sipbuild.AbstractProject , a partir da qual a classe derivada sipbuild.Project é criada .

Crie um arquivo project.py com o seguinte conteúdo:

import os
import subprocess

from sipbuild import Project

class FooProject(Project):
    def _build_foo(self):
        cwd = os.path.abspath('foo')
        subprocess.run(['make'], cwd=cwd, capture_output=True, check=True)

    def build(self):
        self._build_foo()
        super().build()

    def build_sdist(self, sdist_directory):
        self._build_foo()
        return super().build_sdist(sdist_directory)

    def build_wheel(self, wheel_directory):
        self._build_foo()
        return super().build_wheel(wheel_directory)

    def install(self):
        self._build_foo()
        super().install()

Declaramos a classe FooProject , derivada da classe sipbuild.Project, e definimos os métodos build , build_sdist , build_wheel e install nela . Em todos esses métodos, chamamos os métodos com o mesmo nome da classe base, antes de chamar o método _build_foo , que inicia a execução do comando make na pasta foo .

Note-se que os build_sdist e métodos build_wheel deve retornar o nome do arquivo que eles criaram. Isso não está escrito na documentação, mas é indicado nas fontes SIP.

Agora não precisamos executar o comando makemanualmente para criar a biblioteca foo , isso será feito automaticamente.

Se você agora executar o comando sip-wheel na pasta pyfoo_c_04 , será criado um arquivo com o nome pyfoo-0.4-cp38-cp38-manylinux1_x86_64.whl ou similar, dependendo do sistema operacional e da versão do Python. Este pacote pode ser instalado usando o comando:



pip install --user --upgrade pyfoo-0.4-cp38-cp38-manylinux1_x86_64.whl

Depois disso, você pode ter certeza que o foo função do foo módulo ainda funciona.

Adicione opções de linha de comando para criar


O exemplo a seguir está na pasta pyfoo_c_05 e o pacote possui um número de versão 0,5 (consulte as configurações no arquivo pyproject.toml ). Este exemplo é baseado em um exemplo da documentação com algumas correções. Neste exemplo, refazeremos o arquivo project.py e adicionaremos novas opções de linha de comando para a compilação.

Em nossos exemplos, estamos construindo uma biblioteca muito simples foo, e em projetos reais, a biblioteca pode ser bastante grande e, portanto, não fará sentido incluí-la no código-fonte do projeto de ligação do Python. Deixe-me lembrá-lo de que o SIP foi criado originalmente para criar uma ligação para uma biblioteca tão grande como o Qt. É claro que você pode argumentar que os submódulos do git podem ajudar a organizar a fonte, mas esse não é o ponto. Suponha que a biblioteca não esteja na pasta com a fonte de ligação. Nesse caso, surge a pergunta: onde o coletor SIP deve procurar o cabeçalho da biblioteca e os arquivos de objeto? Nesse caso, diferentes usuários podem ter suas próprias maneiras de colocar a biblioteca.

Para resolver esse problema, adicionaremos duas novas opções de linha de comando ao sistema de construção, com as quais você pode especificar o caminho para o arquivo foo.h (parâmetro --foo-include-dir) e para o arquivo de objeto da biblioteca (parâmetro --foo-library-dir ). Além disso, assumiremos que, se esses parâmetros não forem especificados, a biblioteca foo ainda estará localizada junto com as fontes de ligação.

Precisamos criar o arquivo project.py novamente e declarar uma classe derivada de sipbuild.Project . Vamos examinar a nova versão do arquivo project.py primeiro e depois ver como ele funciona.

import os

from sipbuild import Option, Project

class FooProject(Project):
    """        
       foo.
    """

    def get_options(self):
        """     . """

        tools = ['build', 'install', 'sdist', 'wheel']

        #   .
        options = super().get_options()

        #   
        inc_dir_option = Option('foo_include_dir',
                                help="the directory containing foo.h",
                                metavar="DIR",
                                default=os.path.abspath('foo'),
                                tools=tools)
        options.append(inc_dir_option)

        lib_dir_option = Option('foo_library_dir',
                                help="the directory containing the foo library",
                                metavar="DIR",
                                default=os.path.abspath('foo/bin'),
                                tools=tools)

        options.append(lib_dir_option)

        return options

    def apply_user_defaults(self, tool):
        """    . """

        #     
        super().apply_user_defaults(tool)

        #  ,         
        self.foo_include_dir = os.path.abspath(self.foo_include_dir)
        self.foo_library_dir = os.path.abspath(self.foo_library_dir)

    def update(self, tool):
        """   . """

        #   pyfoo
        # (  pyproject.toml  [tool.sip.bindings.pyfoo])
        foo_bindings = self.bindings['pyfoo']

        #   include_dirs  
        if self.foo_include_dir is not None:
            foo_bindings.include_dirs = [self.foo_include_dir]

        #   library_dirs  
        if self.foo_library_dir is not None:
            foo_bindings.library_dirs = [self.foo_library_dir]

        super().update(tool)

Criamos novamente a classe FooProject , derivada de sipbuild.Project . Neste exemplo, a montagem automática da biblioteca foo está desativada , porque agora supõe-se que ela possa estar em outro local e, quando a ligação for criada, os arquivos de cabeçalho e de objeto deverão estar prontos. Três métodos são redefinidos

no FooProject classe : get_options , apply_user_defaults, e atualização . Considere-os com mais cuidado.

Vamos começar com o método get_options . Este método deve retornar uma lista de instâncias da classe sipbuild.Option. Cada item da lista é uma opção de linha de comando. Dentro do método substituído, obtemos a lista de opções padrão (a variável options ) chamando o método da classe base com o mesmo nome, depois criamos duas novas opções ( --foo_include_dir e --foo_library_dir ) e as adicionamos à lista e, em seguida, retornamos essa lista da função.

O construtor da classe Option aceita um parâmetro necessário (nome da opção) e um número suficientemente grande de opcionais que descrevem o tipo de valor desse parâmetro, o valor padrão, a descrição do parâmetro e alguns outros. Este exemplo usa as seguintes opções para o construtor Option :

  • help , , sip-wheel -h
  • metavar — , , . metavar «DIR», , — .
  • default — . , , foo , ( ).
  • tools — , . sip-build, sip-install, sip-sdist sip-wheel, tools = ['build', 'install', 'sdist', 'wheel'].

O seguinte método apply_user_defaults sobrecarregado foi projetado para definir valores de parâmetros que o usuário pode passar pela linha de comandos. O método apply_user_defaults da classe base cria uma variável (membro da classe) para cada parâmetro da linha de comando criado no método get_options , portanto, é importante chamar o método da classe base com o mesmo nome antes de usar as variáveis ​​criadas, para que todas as variáveis ​​criadas usando os parâmetros da linha de comando sejam criadas e inicializadas com valores padrão . Depois disso, em nosso exemplo, as variáveis self.foo_include_dir e self.foo_library_dir serão criadas. Se o usuário não tiver especificado os parâmetros de linha de comando correspondentes, eles receberão valores padrão de acordo com os parâmetros do construtor da classe Option (parâmetro padrão ). Se o parâmetro padrão não estiver definido, dependendo do tipo de valor esperado do parâmetro, ele será inicializado None ou uma lista vazia ou 0.

Dentro do método apply_user_defaults , garantimos que os caminhos nas variáveis self.foo_include_dir e self.foo_library_dir sejam sempre absolutos. Isso é necessário para que não dependa de qual será a pasta de trabalho no momento em que a montagem for iniciada.

O último método sobrecarregado nesta classe é atualização.. Esse método é chamado quando é necessário aplicar ao projeto as alterações feitas antes disso. Por exemplo, altere ou adicione os parâmetros especificados no arquivo pyproject.toml . Nos exemplos anteriores, definimos os caminhos para os arquivos de cabeçalho e objeto usando os parâmetros include-dirs e library-dirs , respectivamente, dentro da seção [tool.sip.bindings.pyfoo] . Agora, definiremos esses parâmetros no script project.py , portanto, no arquivo pyproject.toml , removeremos esses parâmetros:

[build-system]
requires = ["sip >=5, <6"]
build-backend = "sipbuild.api"

[tool.sip.metadata]
name = "pyfoo"
version = "0.3"
license = "MIT"

[tool.sip.bindings.pyfoo]
headers = ["foo.h"]
libraries = ["foo"]

Dentro do método, atualize- nos do dicionário self.bindings com chave pyfoo gat instance sipbuild.Bindings . O nome da chave corresponde às [tool.sip.bindings. pyfoo ] do arquivo pyproject.toml , e a instância da classe assim obtida descreve as configurações descritas nesta seção. Em seguida, os membros desta classe include_dirs e library_dirs (os nomes dos membros correspondem aos parâmetros include-dirs e library-dirs com um hífen substituído por sublinhado) recebem listas designadas que contêm os caminhos armazenados nos membros self.foo_include_dir e self.foo_library_dir. Neste exemplo, para precisão, verificamos que os valores self.foo_include_dir e self.foo_library_dir não são iguais a None , mas neste exemplo essa condição é sempre cumprida porque os parâmetros da linha de comando que criamos têm valores padrão.

Assim, preparamos os arquivos de configuração para que durante a montagem fosse possível indicar os caminhos para os arquivos de cabeçalho e objeto. Vamos verificar o que aconteceu.

Primeiro, verifique se os valores padrão funcionam. Para fazer isso, vá para a pasta pyfoo_c_05 / foo e construa a biblioteca usando o comando make , pois desativamos a compilação automática da biblioteca neste exemplo.

Depois disso, vá para a pastapyfoo_c_05 e execute o comando sip-wheel . Como resultado deste comando, o arquivo pyfoo-0.5-cp38-cp38-manylinux1_x86_64.whl ou com um nome semelhante será criado .

Agora mova a pasta foo para algum lugar fora da pasta pyfoo_c_05 e execute o comando sip-wheel novamente . Como resultado, obtemos o erro esperado informando que não temos um arquivo de biblioteca de objetos:

usr/bin/ld:   -lfoo
collect2: :  ld     1
sip-wheel: Unable to compile the 'foo' module: command 'g++' failed with exit status 1

Depois disso, execute o sip-wheel usando a nova opção de linha de comando:

sip-wheel --foo-include-dir ".../foo" --foo-library-dir ".../foo/bin"

Em vez das reticências, é necessário especificar o caminho para a pasta em que você moveu a pasta foo com a biblioteca montada. Como resultado, o assembly deve conseguir criar o arquivo .whl. O módulo criado pode ser instalado e testado da mesma maneira que nas seções anteriores.

Verifique a ordem dos métodos de chamada em project.py


O próximo exemplo, que consideraremos, será muito simples: demonstrará a ordem de chamar os métodos da classe Project , que sobrecarregamos nas seções anteriores. Isso pode ser útil para entender quando as variáveis ​​podem ser inicializadas. Este exemplo está localizado na pasta pyfoo_c_06 no repositório de origem.

A essência deste exemplo é sobrecarregar todos os métodos que usamos anteriormente na classe FooProject , que está localizada no arquivo project.py , e adicionar chamadas à função print a eles , que exibe o nome do método em que está localizado:

from sipbuild import Project

class FooProject(Project):
    def get_options(self):
        print('get_options()')
        options = super().get_options()
        return options

    def apply_user_defaults(self, tool):
        print('apply_user_defaults()')
        super().apply_user_defaults(tool)

    def apply_nonuser_defaults(self, tool):
        print('apply_nonuser_defaults()')
        super().apply_nonuser_defaults(tool)

    def update(self, tool):
        print('update()')
        super().update(tool)

    def build(self):
        print('build()')
        super().build()

    def build_sdist(self, sdist_directory):
        print('build_sdist()')
        return super().build_sdist(sdist_directory)

    def build_wheel(self, wheel_directory):
        print('build_wheel()')
        return super().build_wheel(wheel_directory)

    def install(self):
        print('install()')
        super().install()

Os leitores atentos devem observar que, além dos métodos usados ​​anteriormente, o método apply_nonuser_defaults () , sobre o qual não falamos antes , está sobrecarregado neste exemplo . Este método recomenda definir valores padrão para todas as variáveis ​​que não podem ser alteradas através dos parâmetros da linha de comando.

No arquivo pyproject.toml, retorne o caminho explícito para a biblioteca:

[build-system]
requires = ["sip >=5, <6"]
build-backend = "sipbuild.api"

[tool.sip.metadata]
name = "pyfoo"
version = "0.4"
license = "MIT"

[tool.sip.bindings.pyfoo]
headers = ["foo.h"]
libraries = ["foo"]
include-dirs = ["foo"]
library-dirs = ["foo/bin"]

Para que o projeto seja compilado com êxito, você precisa ir para a pasta foo e criar a biblioteca usando o comando make . Depois disso, retorne à pasta pyfoo_c_06 e execute, por exemplo, o comando sip-wheel . Como resultado, se você descartar os avisos do compilador, o seguinte texto será exibido:

get_options ()
apply_nonuser_defaults ()
get_options ()
get_options ()
apply_user_defaults ()
get_options ()
update ())
Essas ligações serão criadas: pyfoo.
build_wheel ()
Gerando as ligações do pyfoo ...
Compilando o módulo 'foo' ...
A roda foi construída.

Linhas em negrito são exibidas que são geradas pelo nosso arquivo project.py . Portanto, vemos que o método get_options é chamado várias vezes, e isso deve ser levado em consideração se você for inicializar qualquer variável de membro na classe derivada do Project . O método get_options não é o melhor lugar para isso.

Também é útil lembrar que o método apply_nonuser_defaults é chamado antes do método apply_user_defaults , ou seja, no método apply_user_defaults , você já pode usar variáveis ​​cujos valores estão configurados no método apply_nonuser_defaults .

Depois disso, o método de atualização é chamado, e no final, o método diretamente responsável pela montagem, no nosso caso, build_wheel .

Conclusão para a primeira parte


Neste artigo, começamos a estudar a ferramenta SIP projetada para criar ligações Python para bibliotecas escritas em C ou C ++. Nesta primeira parte do artigo, examinamos os conceitos básicos do uso do SIP usando o exemplo da criação de uma ligação Python para uma biblioteca muito simples escrita em C.

Descobrimos os arquivos que você precisa criar para trabalhar com o SIP. O arquivo pyproject.toml contém informações sobre o pacote (nome, número da versão, licença e caminhos para os arquivos de cabeçalho e objeto). Usando o arquivo project.py , você pode influenciar o processo de compilação do pacote Python, por exemplo, começar a criar a biblioteca C ou permitir que o usuário especifique o local dos arquivos de cabeçalho e objeto da biblioteca.

No arquivo * .sipdescreve a interface do módulo Python, listando as funções e classes que estarão contidas no módulo. Diretivas e anotações são usadas para descrever a interface no arquivo * .sip .

Na segunda parte do artigo, criaremos uma ligação sobre uma biblioteca orientada a objetos escrita em C ++ e, por seu exemplo, estudaremos técnicas que serão úteis na descrição da interface de classes C ++ e, ao mesmo tempo, trataremos de novas diretivas e anotações para nós.

Continua.

Referências



All Articles