Ir para o conteúdo

Última atualização:2026-10-12

Notas sobre desenvolvimento de extensões CPython

Este artigo toma como pano de fundo um feature que enviei recentemente ao Apache para falar dos problemas encontrados com CPython, além da tentativa de escrever uma biblioteca do zero; o código também já foi colocado na planeta — quem se interessar pode baixar para estudar.

0.Contexto

Recentemente, um feature que submeti ao apache arrow provocou mudanças nas interfaces de outras linguagens por causa de uma mudança na interface C++, e alguns testes também precisaram ser corrigidos em seguida.

Quem está familiarizado com o PyArrow não vai estranhar nada: essa mudança de interface também exigiu modificar essa biblioteca, pois ela está no mesmo repositório — caso contrário, o ci não passa. E como o PyArrow é implementado via CPython, e eu nunca tinha estudado CPython a fundo, acabou surgindo uma série de problemas; dois deles são bem importantes e registro aqui.

Problema 1: a função de inicialização não dá suporte à construção padrão de outras classes.

Exemplo:

 def __init__(self, mode="only_valid", filter=Expression._scalar(True)):
        pass
Erro:

TypeError: descriptor '_scalar' for 'pyarrow._compute.Expression' objects doesn't apply to a 'bool' object
Vê-se que não foi reconhecido, mas na prática Expression._scalar(True) é válido; vejamos a implementação interna:

@staticmethod
def _scalar(value):
    cdef:
        Scalar scalar

    if isinstance(value, Scalar):
        scalar = value
    else:
        scalar = lib.scalar(value)

    return Expression.wrap(CMakeScalarExpression(scalar.unwrap()))
Dá para ver que bool normal é suportado lá dentro. Suspeito que seja uma limitação do cpython, então mudei para a forma abaixo e funcionou:

def __init__(self, mode="only_valid", filter=None):
    if filter is None:
        filter = Expression._scalar(True)
Problema 2: ordem de definição

Quando uso o _true criado mais abaixo, o valor padrão passado a cada vez é vazio — isso é fácil de entender, pois, no final, depois de compilado, tudo é traduzido para um arquivo xxx.cpp e, pelas regras do C++, o que é lido antes naturalmente fica vazio.

def __init__(self, mode="only_valid", filter=_true):
        pass


cdef CExpression _true = CMakeScalarExpression(
    <shared_ptr[CScalar]> make_shared[CBooleanScalar](True)
)
Pronto. Com base nesse contexto, também quis escrever um exemplo: por exemplo, escrever uma classe em C++, encapsulando sort e sum, e depois chamá-la com Python.

1.Exemplo completo de CPython

  1. Crie um arquivo .h

void sort(std::vector<int>& nums) {
    std::sort(nums.begin(), nums.end());
}
int sum(std::vector<int>& nums) {
    int sum = 0;
    for (int num : nums) {
        sum += num;
    }
    return sum;
}
2) Crie foo.pyx

Ponto importante: o vector acima precisa de:

from libcpp.vector cimport vector
Em seguida, defina uma class que chame a interface C++.

cdef class PyFoo:
    cdef Foo* f

    def __cinit__(self):
        self.f = new Foo()

    def __dealloc__(self):
        del self.f

    def sort(self, nums):
        cdef vector[int] c_nums = nums
        self.f.sort(c_nums)

    def sum(self, nums):
        cdef vector[int] c_nums = nums
        return self.f.sum(c_nums)
3. Crie o arquivo setup.py

ext = Extension('Foo', sources=["foo.pyx"], language="c++", include_dirs=[numpy.get_include()])

setup(name="Foo", ext_modules = cythonize([ext]))
4. Execute

python3 setup.py build_ext --inplace
Por fim, você pode escrever um script de teste para usar a interface python que você mesmo escreveu.

import Foo

f = Foo.PyFoo()
nums = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
f.sort(nums)
print("Sorted nums:", nums)
print("Sum of nums:", f.sum(nums))
O CPython é bastante usado em alguns projetos; vamos estudá-lo~

Execução:

➜  cpython_examples python3 test.py 
Sorted nums: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
Sum of nums: 44

Tópicos relacionados

Comentários