1. Python’ı Başka Bir Uygulamaya Gömme

Önceki bölümlerde Python’un nasıl genişletileceği, yani Python’a bir C fonksiyonları kitaplığı ekleyerek Python’un işlevselliğinin nasıl genişletileceği tartışıldı. Bunu ayrıca tam tersi şekilde de yapmak mümkündür: Python’u içine gömerek C/C++ uygulamanızı zenginleştirin. Gömme işlemi, uygulamanıza bazı işlevlerini C veya C++ yerine Python’da uygulama yeteneği sağlar. Bu birçok amaç için kullanılabilir; bir örnek olarak kullanıcıların Python’da bazı komut dosyaları yazarak uygulamayı ihtiyaçlarına göre uyarlamalarına izin vermek olabilir. Bazı işlevler Python’da daha kolay yazılabilecekse kendiniz de kullanabilirsiniz.

Python’u gömmek, onu genişletmeye benzer, ancak tam olarak değil. Aralarındaki farksa Python’u genişlettiğinizde, uygulamanın ana programının hala Python yorumlayıcısı olması; Python’u gömerseniz, ana programın Python ile hiçbir ilgisi olmayabilmesidir — bunun yerine, uygulamanın bazı bölümleri bazı Python kodlarını çalıştırmak için zaman zaman Python yorumlayıcısını çağırır.

Yani Python’u gömüyorsanız, kendi ana programınızı sağlıyorsunuz demektir. Bu ana programın yapması gereken şeylerden biri Python yorumlayıcısını başlatmaktır. En azından Py_Initialize() fonksiyonunu çağırmalısınız. Python’a komut satırı argümanlarını iletmek için opsiyonel çağrılar vardır. Daha sonra uygulamanın herhangi bir yerinden yorumlayıcıyı çağırabilirsiniz.

Yorumlayıcıyı çağırmanın birkaç farklı yolu vardır: Python deyimlerini içeren bir dizeyi PyRun_SimpleString() öğesine veya bir stdio dosya işaretçisini ve bir dosya adını (yalnızca hata mesajlarında tanımlama için) PyRun_SimpleFile() ‘a iletebilirsiniz. Python nesnelerini oluşturmak ve kullanmak için önceki bölümlerde açıklanan alt düzey işlemleri de çağırabilirsiniz.

Ayrıca bakınız

Python/C API Referans Kılavuzu

Python’un C arayüzünün detayları bu kılavuzda verilmiştir. Çok sayıda gerekli bilgi burada bulunabilir.

1.1. Çok Üst Düzey Gömme

Python’u gömmenin en basit şekli, çok yüksek seviyeli arayüzün kullanılmasıdır. Bu arabirim, uygulamayla doğrudan etkileşime girmeye gerek kalmadan bir Python betiği yürütmeyi amaçlamaktadır. Bu örnek olarak bir dosya üzerinde bazı işlemler gerçekleştirmek için kullanılabilir.

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    wchar_t *program = Py_DecodeLocale(argv[0], NULL);
    if (program == NULL) {
        fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
        exit(1);
    }
    Py_SetProgramName(program);  /* optional but recommended */
    Py_Initialize();
    PyRun_SimpleString("from time import time,ctime\n"
                       "print('Today is', ctime(time()))\n");
    if (Py_FinalizeEx() < 0) {
        exit(120);
    }
    PyMem_RawFree(program);
    return 0;
}

Py_SetProgramName() işlevi, yorumlayıcıyı Python çalışma zamanı kütüphanelerine giden yollar hakkında bilgilendirmek için Py_Initialize() öncesinde çağrılmalıdır. Ardından, Python yorumlayıcısı Py_Initialize() ile başlatılır, ardından tarih ve saati yazdıran sabit kodlanmış bir Python betiği yürütülür. Daha sonra, Py_FinalizeEx() çağrısı yorumlayıcıyı kapatır ve ardından programın sonu gelir. Gerçek bir programda, Python betiğini başka bir kaynaktan, belki bir metin düzenleyici rutininden, bir dosyadan veya bir veri tabanından almak isteyebilirsiniz. Python kodunu bir dosyadan almak, sizi bellek alanı ayırma ve dosya içeriğini yükleme zahmetinden kurtaran PyRun_SimpleFile() işlevi kullanılarak daha iyi yapılabilir.

1.2. Çok Yüksek Düzeyde Gömmenin Ötesinde: Genel Bir Bakış

Yüksek seviyeli arayüz size uygulamanızdan rastgele Python kodu parçalarını yürütme yeteneği verir, ancak veri değerleri alışverişi en hafif tabirle oldukça zahmetlidir. Bunu istiyorsanız, daha düşük seviyeli aramalar kullanmalısınız. Daha fazla C kodu yazmak zorunda kalma pahasına neredeyse her şeyi başarabilirsiniz.

Farklı amaçlara rağmen Python’u genişletmek ve Python’u gömmek tamamen aynı aktivitedir. Önceki bölümlerde tartışılan konuların çoğu hala geçerlidir. Bunu göstermek için Python’dan C’ye uzantı kodunun gerçekten ne yaptığını düşünün:

  1. Veri değerlerini Python’dan C’ye çevirin,

  2. Çevrilen değerleri kullanarak bir C rutinine bir fonksiyon çağrısı yapın, ve

  3. Çağrıdaki veri değerlerini C’den Python’a çevirin.

Python’u yerleştirirken, arayüz kodu şunları yapar:

  1. Veri değerlerini C’den Python’a çevirin,

  2. Çevrilen değerleri kullanarak bir Python arabirim rutinine bir fonksiyon çağrısı gerçekleştirin ve

  3. Çağrıdaki veri değerlerini C’den Python’a dönüştürün.

Gördüğünüz gibi, veri dönüştürme adımları, diller arası aktarımın farklı yönüne uyum sağlamak için basitçe değiştirilir. Tek fark, her iki veri dönüşümü arasında çağırdığınız rutindir. Uzatırken C rutini çağırırsınız, gömerken Python rutini çağırırsınız.

Bu bölüm, verilerin Python’dan C’ye nasıl dönüştürüleceğini ve bunun tam tersini tartışmayacaktır. Ayrıca, referansların doğru kullanımı ve hataların ele alınmasının anlaşıldığı varsayılmaktadır. Bu hususlar, yorumlayıcının genişletilmesinden farklı olmadığı için, gerekli bilgiler için önceki bölümlere başvurabilirsiniz.

1.3. Saf Gömme

İlk program, bir Python betiğinde bir fonksiyonu çalıştırmayı amaçlar. Çok yüksek seviyeli arayüzle ilgili bölümde olduğu gibi, Python yorumlayıcısı uygulama ile doğrudan etkileşime girmez (ancak bu bir sonraki bölümde değişecektir).

Python betiğinde tanımlanan bir işlevi çalıştırma kodu:

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    PyObject *pName, *pModule, *pFunc;
    PyObject *pArgs, *pValue;
    int i;

    if (argc < 3) {
        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
        return 1;
    }

    Py_Initialize();
    pName = PyUnicode_DecodeFSDefault(argv[1]);
    /* Error checking of pName left out */

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, argv[2]);
        /* pFunc is a new reference */

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(argc - 3);
            for (i = 0; i < argc - 3; ++i) {
                pValue = PyLong_FromLong(atoi(argv[i + 3]));
                if (!pValue) {
                    Py_DECREF(pArgs);
                    Py_DECREF(pModule);
                    fprintf(stderr, "Cannot convert argument\n");
                    return 1;
                }
                /* pValue reference stolen here: */
                PyTuple_SetItem(pArgs, i, pValue);
            }
            pValue = PyObject_CallObject(pFunc, pArgs);
            Py_DECREF(pArgs);
            if (pValue != NULL) {
                printf("Result of call: %ld\n", PyLong_AsLong(pValue));
                Py_DECREF(pValue);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr,"Call failed\n");
                return 1;
            }
        }
        else {
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    }
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
        return 1;
    }
    if (Py_FinalizeEx() < 0) {
        return 120;
    }
    return 0;
}

Bu kod, argv[1] kullanarak bir Python betiği yükler ve argv[2] içinde adlandırılan fonksiyonu çağırır. Tamsayı argümanları, argv dizisinin diğer değerleridir. Bu programı derler, ve bağlarsanız (bitmiş yürütülebilir dosyayı call olarak adlandıralım) ve onu aşağıdaki gibi bir Python betiğini çalıştırmak için kullanırsanız:

def multiply(a,b):
    print("Will compute", a, "times", b)
    c = 0
    for i in range(0, a):
        c = c + b
    return c

o zaman sonuç olmalıdır:

$ call multiply multiply 3 2
Will compute 3 times 2
Result of call: 6

Program işlevselliği açısından oldukça büyük olmasına rağmen, kodun çoğu Python ve C arasında veri dönüştürme ve hata raporlama içindir. Python’u gömmekle ilgili ilginç kısım şununla başlar

Py_Initialize();
pName = PyUnicode_DecodeFSDefault(argv[1]);
/* Error checking of pName left out */
pModule = PyImport_Import(pName);

Yorumlayıcıyı başlattıktan sonra komut dosyası PyImport_Import() kullanılarak yüklenir. Bu rutin, argümanı olarak PyUnicode_FromString() veri dönüştürme rutini kullanılarak oluşturulan bir Python dizesine ihtiyaç duyar.

pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc is a new reference */

if (pFunc && PyCallable_Check(pFunc)) {
    ...
}
Py_XDECREF(pFunc);

Komut dosyası yüklendikten sonra, aradığımız ad PyObject_GetAttrString() kullanılarak alınır. Ad varsa ve döndürülen nesne çağrılabilirse bunun bir fonksiyon olduğunu güvenle varsayabilirsiniz. Program daha sonra normal olarak bir dizi argüman oluşturarak devam eder. Python işlevine yapılan çağrı şu şekilde yapılır:

pValue = PyObject_CallObject(pFunc, pArgs);

Fonksiyon döndürüldüğünde, pValue ya NULL olur ya da fonksiyonun dönüş değerine bir başvuru içerir. Değeri inceledikten sonra referans bıraktığınızdan emin olun.

1.4. Gömülü Python’u Genişletme

Şimdiye kadar, gömülü Python yorumlayıcısının uygulamanın kendisinden işlevselliğe erişimi yoktu. Python API, gömülü yorumlayıcıyı genişleterek buna izin verir. Yani, gömülü yorumlayıcı, uygulama tarafından sağlanan rutinlerle genişletilir. Kulağa karmaşık gelse de, o kadar da kötü değil. Uygulamanın Python yorumlayıcısını başlattığını bir süreliğine unutun. Bunun yerine, uygulamayı bir dizi altyordam olarak düşünün ve tıpkı normal bir Python uzantısı yazacağınız gibi, Python’un bu rutinlere erişmesini sağlayan bir tutkal kodu yazın. Örneğin:

static int numargs=0;

/* Return the number of arguments of the application command line */
static PyObject*
emb_numargs(PyObject *self, PyObject *args)
{
    if(!PyArg_ParseTuple(args, ":numargs"))
        return NULL;
    return PyLong_FromLong(numargs);
}

static PyMethodDef EmbMethods[] = {
    {"numargs", emb_numargs, METH_VARARGS,
     "Return the number of arguments received by the process."},
    {NULL, NULL, 0, NULL}
};

static PyModuleDef EmbModule = {
    PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,
    NULL, NULL, NULL, NULL
};

static PyObject*
PyInit_emb(void)
{
    return PyModule_Create(&EmbModule);
}

Yukarıdaki kodu main() fonksiyonunun hemen üstüne ekleyin. Ayrıca, Py_Initialize(): çağrısından önce aşağıdaki iki ifadeyi ekleyin:

numargs = argc;
PyImport_AppendInittab("emb", &PyInit_emb);

These two lines initialize the numargs variable, and make the emb.numargs() function accessible to the embedded Python interpreter. With these extensions, the Python script can do things like

import emb
print("Number of arguments", emb.numargs())

Gerçek bir uygulamada, yöntemler uygulamanın API’sini Python’a gösterir.

1.5. Python’u C++’a Gömmek

Python’u bir C++ programına yerleştirmek de mümkündür; bunun tam olarak nasıl yapılacağı, kullanılan C++ sisteminin ayrıntılarına bağlı olacaktır; genel olarak ana programı C++ ile yazmanız ve programınızı derlemek ve bağlamak için C++ derleyicisini kullanmanız gerekecektir. Python’un kendisini C++ kullanarak yeniden derlemeye gerek yoktur.

1.6. Unix benzeri sistemler altında Derleme ve Bağlama

Python yorumlayıcısını uygulamanıza gömmek için derleyicinize (ve bağlayıcınıza) iletilecek doğru bayrakları bulmak mutlaka önemli değildir, özellikle Python’un C dinamik uzantıları buna karşı bağlatılı olan (.so dosyaları) olarak uygulanan kütüphane modüllerini yüklemesi gerektiğinden.

Gerekli derleyici ve bağlayıcı bayraklarını bulmak için, yükleme işleminin bir parçası olarak oluşturulan pythonX.Y-config betiğini çalıştırabilirsiniz (bir python3-config betiği de mevcut olabilir). Bu komut dosyası, size doğrudan yardımcı olacak birkaç seçeneğe sahiptir:

  • pythonX.Y-config --cflags derleme sırasında size önerilen bayrakları verecektir:

    $ /opt/bin/python3.11-config --cflags
    -I/opt/include/python3.11 -I/opt/include/python3.11 -Wsign-compare  -DNDEBUG -g -fwrapv -O3 -Wall
    
  • pythonX.Y-config --ldflags --embed, bağlantı kurarken size önerilen bayrakları verecektir:

    $ /opt/bin/python3.11-config --ldflags --embed
    -L/opt/lib/python3.11/config-3.11-x86_64-linux-gnu -L/opt/lib -lpython3.11 -lpthread -ldl  -lutil -lm
    

Not

Birkaç Python kurulumu arasında (ve özellikle sistem Python ile kendi derlenmiş Python’unuz arasında) karışıklığı önlemek için, yukarıdaki örnekte olduğu gibi mutlak pythonX.Y-config yolunu kullanmanız önerilir.

Bu prosedür sizin için işe yaramazsa (tüm Unix benzeri platformlar için çalışması garanti edilmez; ancak, memnuniyetle karşılıyoruz: hata raporları) dinamik hakkında sisteminizin belgelerini okumanız gerekecektir ve/veya Python’un Makefile (konumunu bulmak için sysconfig.get_makefile_filename() kullanın) ve derleme seçeneklerini bağlamayı inceleyin. Bu durumda, sysconfig modülü, birleştirmek isteyeceğiniz konfigürasyon değerlerini programlı olarak çıkarmak için kullanışlı bir araçtır. Örneğin:

>>> import sysconfig
>>> sysconfig.get_config_var('LIBS')
'-lpthread -ldl  -lutil'
>>> sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'