RHEL'de SWIG ile Python C Eklentisi Derlemek

Selamlar, bu yazıda RHEL üzerinde SWIG kurup C ile yazılmış bir kütüphaneyi Python tarafına nasıl köprülediğimize bakacağız. Konu kulağa biraz eski okul gelebilir, ama performans gereken bir yerde elinizdeki C kodunu Python'a açmak hala günlük bir ihtiyaç. Hadi başlayalım.

Senaryo şu: İşin ağır kısmı C ile yazılmış, Python ekibi de bunu olduğu gibi kullanmak istiyor. ctypes ile uğraşmak fonksiyon başına manuel imza tanımlamak demek; cffi biraz daha rahat ama yine de elle yazılan glue kodu çok. SWIG araya bir interface dosyası alıp size hazır bir Python modülü üretiyor.

SWIG nedir, neden hala kullanılıyor?

SWIG (Simplified Wrapper and Interface Generator), C ve C++ kodunu Python, Ruby, Go gibi dillere otomatik bağlayan bir kod üreticisi. Siz .i uzantılı bir interface dosyası yazıyorsunuz, SWIG C başlıklarını parse edip Python'dan import edebileceğiniz bir wrapper modül çıkarıyor.

Bence SWIG'in en büyük avantajı, zaten var olan bir C kod tabanıyla çalışması. Sıfırdan yazacaksanız Cython muhtemelen daha rahat olur. Ama elinizde 20 yıllık bir matematik kütüphanesi varsa ve sadece Python'a açılması gerekiyorsa, SWIG hâlâ en kısa yol.

RHEL üzerinde kurulum

RHEL 9 ya da 10 üzerinde çalıştığımızı varsayalım. İhtiyacımız olan paketler: derleyici, Python geliştirme başlıkları ve SWIG'in kendisi.

sudo dnf install -y epel-release
sudo dnf groupinstall -y 'Development Tools'
sudo dnf install -y swig python3-devel

Kurulumun bittiğini doğrulamak için:

swig -version

Çıktı olarak SWIG Version 4.x.x benzeri bir satır gördüyseniz hazırsınız. EPEL deposundaki versiyon biraz geride kalabilir; günlük işlerde yeterli ama C++17 ile yazılmış modern bir kütüphaneyi wrap'liyorsanız kaynaktan derlemeyi düşünebilirsiniz.

Küçük bir örnek: C tarafı

Hadi minimum bir örnekle gidelim. İki sayının gcd'sini hesaplayan basit bir C dosyası yazalım:

/* mathx.c */
int gcd(int a, int b) {
    while (b != 0) {
        int t = b;
        b = a % b;
        a = t;
    }
    return a;
}

double area_of_circle(double r) {
    return 3.14159265358979 * r * r;
}

Bunun yanına kısa bir header koyalım:

/* mathx.h */
int gcd(int a, int b);
double area_of_circle(double r);

Interface dosyası

SWIG'e neyi wrap edeceğini söylediğimiz yer interface dosyası. Aslında çoğu zaman header'ı olduğu gibi include etmek yetiyor:

/* mathx.i */
%module mathx

%{
#include "mathx.h"
%}

%include "mathx.h"

Buradaki %{ ... %} bloğu, üretilen wrapper'ın başına aynen kopyalanıyor; yani SWIG'in C derleyicisi mathx.h'yi görsün diye. Aşağıdaki %include ise SWIG'e fonksiyon imzalarını gerçekten parse etmesini söylüyor. İki farklı amaç, bu yüzden ikisini birden yazıyoruz; ilk başta ben de bunu karıştırmıştım.

setup.py ile derleme

Derlemeyi elle yapmak yerine setuptools'a bırakmak hayatı çok kolaylaştırır. Çünkü setuptools, .i dosyalarını görünce SWIG'i otomatik çağırıyor.

# setup.py
from setuptools import setup, Extension

mathx_module = Extension(
    '_mathx',
    sources=['mathx.i', 'mathx.c'],
    swig_opts=['-py3'],
)

setup(
    name='mathx',
    version='0.1.0',
    ext_modules=[mathx_module],
    py_modules=['mathx'],
)

Sonra:

python3 setup.py build_ext --inplace

Bu komut size bir _mathx.cpython-*.so dosyası ve yanında SWIG'in ürettiği mathx.py modülünü bırakır. Python tarafından kullanımı şu kadar basit:

import mathx

print(mathx.gcd(48, 18))
print(mathx.area_of_circle(2.5))

SWIG, ctypes, cffi, Cython: hangisi ne zaman?

Bana sorarsanız tablo şöyle netleşiyor:

  • ctypes: Tek tük fonksiyon çağıracaksanız, ek bir build adımı istemiyorsanız. Küçük işlerde idealdir ama imzayı elle yazmak yorucu olur.
  • cffi: ctypes'in biraz daha zarif kuzeni. PyPy ile uyumu iyidir.
  • SWIG: Var olan bir C/C++ kütüphanesini birçok dile birden açmak istiyorsanız; başka ekipler Ruby ya da Java tarafından aynı kodu kullanacaksa.
  • Cython: Sıfırdan Python yakınlığı yüksek bir extension yazıyorsanız ya da Python kodunuzu hızlandırmak istiyorsanız. Bence yeni projelerde varsayılan tercih bu olmalı.

Sık karşılaşılan tuzaklar

  • python3-devel kurmadan derlemek: Python.h bulunamıyor diye hata alırsınız. Bu paketi çektiğinizden emin olun.
  • .i dosyasında %module adını .so ile karıştırmak: SWIG _mathx.so arar; %module mathx yazdıysanız dosya ismi de buna uygun olmalı, yoksa import patlar.
  • Header'da makro yoğunluğu: SWIG basit makroları çözer ama ağır preprocessor mantığını anlamayabilir. Böyle durumda interface dosyasında imzaları elle yazmak daha sağlıklı.
  • SELinux bağlamı: Build çıkan .so dosyasını /usr/lib64/python3.x/site-packages altına elle kopyaladıysanız restorecon -RF çalıştırmayı unutmayın; aksi halde import hatası yiyebilirsiniz.

Kapanış

Bu yazıda RHEL üzerinde SWIG'i kurup ufak bir C kütüphanesini setuptools ile Python tarafına nasıl açacağımızı gördük. Şahsi kanaatim, var olan C kodunu hızlıca Python'a köprülüyorsanız SWIG hâlâ en kestirme yol; ama sıfırdan yazacaksanız Cython'a yönelmek daha rahat olur. Umarım faydalı olur, görüşmek üzere.