2025, Oct 19 07:31

C में Cython मॉड्यूल embed करते समय Python को सही तरह initialize और import कैसे करें

जानें C में Cython के साथ Python embed का सही तरीका: PyImport_AppendInittab, Py_Initialize और मॉड्यूल import का क्रम। init की गलती से segfault से बचें.

C और Python को Cython के जरिये जोड़ना आम पैटर्न है, लेकिन एक नाज़ुक initialization चरण होता है जो आपके embedding सेटअप को बना भी सकता है और बिगाड़ भी सकता है। एक न्यूनतम “Hello World” जो Python से प्रिंट करता है और C प्रोग्राम से बुलाया जाता है, अगर Cython मॉड्यूल को इस्तेमाल से पहले ठीक से initialize और import नहीं किया गया हो, तो segfault कर देगा।

समस्या का सेटअप

मान लीजिए एक छोटा-सा Python फ़ंक्शन है, जिसे Cython रैपर के माध्यम से बाहर दिखाया गया है और C से कॉल किया जाता है। लक्ष्य है कि एंट्री पॉइंट C हो, लेकिन “Hello World” प्रिंट Python से हो।

# greetmod.py
def say_hi():
  print("Hello World")
# bridge.pyx
from greetmod import say_hi
cdef public void invoke_hi():
  say_hi()
/* main.c */
#include <Python.h>
#include "bridge.h"
int
main()
{
  Py_Initialize();
  invoke_hi();
  Py_Finalize();
}

इस तरह बिल्ड करने पर, बाइनरी चलाते ही क्रैश हो जाता है। बैकट्रेस दिखाता है कि मॉड्यूल-ग्लोबल नाम रिज़ॉल्व करते वक्त विफलता हुई—कुछ वैसा ही जैसे __Pyx__GetModuleGlobalName जैसी कॉल के भीतर null नाम पॉइंटर के साथ segfault।

असल में हो क्या रहा है

भले ही Cython में रैपर फ़ंक्शन को public घोषित किया गया हो, रैपर मॉड्यूल खुद एक Python मॉड्यूल ही रहता है। उसके Python-स्तरीय initialization का चलना ज़रूरी है, तभी उसका कोड या ग्लोबल्स इस्तेमाल किए जा सकते हैं—इसमें from greetmod import say_hi जैसी पंक्ति भी शामिल है। अगर आप Cython मॉड्यूल को import किए बिना सीधे एक्सपोर्ट हुए C फ़ंक्शन में कूद जाते हैं, तो मॉड्यूल का ग्लोबल namespace अनइनिशलाइज़्ड रहता है और उसके ग्लोबल्स तक पहुंचते ही क्रैश हो जाता है।

सही क्रम यह है: मॉड्यूल की init फ़ंक्शन को रजिस्टर करें, Python इंटरप्रेटर को initialize करें, सुनिश्चित करें कि sys.path पर Python आपके मॉड्यूल ढूंढ सके, और फिर Cython मॉड्यूल को import करें। इसके बाद ही public फ़ंक्शन को कॉल करना सुरक्षित है।

समाधान और काम करता हुआ कोड

एम्बेडिंग फ्लो में तीन अहम कदम हैं: इंटरप्रेटर शुरू होने से पहले PyImport_AppendInittab के साथ मॉड्यूल init फ़ंक्शन जोड़ें, Py_Initialize से Python को initialize करें, और PyImport_ImportModule के जरिए मॉड्यूल import करें ताकि उसका initialization और उसके imports चलें। साथ ही यह भी पक्का करें कि Python आपके मॉड्यूल ढूंढ सके, क्योंकि embedded परिदृश्य में current working directory अपने आप sys.path में नहीं आती।

/* main.c (सुधारा गया) */
#include <Python.h>
#include <stdio.h>
#include "bridge.h"
int
main()
{
    PyObject *modref;
    /* Py_Initialize से पहले "bridge" को बिल्ट-इन मॉड्यूल के रूप में रजिस्टर करें।
       PyInit_bridge Cython द्वारा bridge.pyx से जेनरेट किया जाता है। */
    if (PyImport_AppendInittab("bridge", PyInit_bridge) == -1) {
        fprintf(stderr, "Error: could not extend in-built modules table\n");
        exit(1);
    }
    /* Python इंटरप्रेटर शुरू करें। */
    Py_Initialize();
    /* आवश्यकता अनुसार import path सेट करें ताकि Python आपके मॉड्यूल ढूंढ सके। */
    // PyRun_SimpleString("import sys\nsys.path.insert(0,'')");
    /* Cython मॉड्यूल को import करें ताकि उसका initialization और imports चल सकें। */
    modref = PyImport_ImportModule("bridge");
    if (!modref) {
        PyErr_Print();
        fprintf(stderr, "Error: could not import module 'bridge'\n");
        goto fail;
    }
    /* अब public फ़ंक्शन को कॉल करना सुरक्षित है। */
    invoke_hi();
    Py_Finalize();
    return 0;
fail:
    Py_Finalize();
    return 1;
}

अगर आप import path समायोजित करने के लिए environment-आधारित तरीका पसंद करते हैं, तो अपना प्रोग्राम चलाते समय PYTHONPATH को current directory पर सेट करें, ताकि इंटरप्रेटर greetmod.py और जनरेट हुए मॉड्यूल को ढूंढ सके:

PYTHONPATH=. ./main

वैकल्पिक रूप से, ऊपर दिखाए गए इनलाइन तरीके को अपनाएँ: Py_Initialize के बाद और पहली import से पहले PyRun_SimpleString के जरिए sys.path में खाली स्ट्रिंग डाल दें।

यह क्यों मायने रखता है

import वाले चरण को छोड़ देना कोई निर्दोष शॉर्टकट नहीं है। Cython द्वारा बना C कोड एक Python मॉड्यूल की तरह import होने के लिए डिज़ाइन किया गया है; यही import मॉड्यूल का initialization चलाता है, जिसमें उसके टॉप-लेवल Python कोड का निष्पादन भी शामिल है। अगर आप initialization छोड़ेकर सीधे एक्सपोर्ट किए गए फ़ंक्शन चलाते हैं, तो क्रैश की पूरी संभावना है। Import को तब तक टाला जा सकता है जब तक उसकी ज़रूरत न पड़े, लेकिन उसे छोड़ा नहीं जा सकता।

मुद्दा यह है कि import कब हो, यह नहीं कि हो या न हो। मॉड्यूल को import करना ही होगा; आप समय चुन सकते हैं, विकल्प नहीं।

निष्कर्ष

जब आप Python को embed करते हैं और C से Cython-जनरेटेड कोड को कॉल करते हैं, तो अपने रैपर को एक सामान्य Python मॉड्यूल की तरह बरतें: इंटरप्रेटर शुरू करने से पहले उसे PyImport_AppendInittab से रजिस्टर करें, Python को initialize करें, सुनिश्चित करें कि sys.path में आपके मॉड्यूल का लोकेशन शामिल हो, मॉड्यूल को import करें ताकि उसका initialization चल सके, और तभी public फ़ंक्शनों को कॉल करें। यह छोटा-सा बूटस्ट्रैपिंग कदम कठिन-से-डिबग segfaults से बचाता है और आपके न्यूनतम “Hello World” को भरोसेमंद बनाता है।

यह लेख StackOverflow पर प्रश्न (लेखक: vibe) और Dunes के उत्तर पर आधारित है।