9

मॉड्यूल लोडिंग हाइड के तहत सीपीथॉन में कैसे काम करता है? खासकर, सी काम में लिखे गए एक्सटेंशन की गतिशील लोडिंग कैसे होती है? मैं इसके बारे में कहां से सीख सकता हूं?मॉड्यूल लोडिंग कैसे सीपीथॉन में काम करता है?

मुझे स्रोत कोड स्वयं बल्कि भारी लगता है। मैं देख सकता हूं कि भरोसेमंद ओल 'dlopen() और दोस्तों का उपयोग उन प्रणालियों पर किया जाता है जो इसका समर्थन करते हैं लेकिन बड़ी तस्वीर के किसी भी भाव के बिना इसे स्रोत कोड से समझने में काफी समय लगेगा।

इस विषय पर एक बड़ी राशि लिखी जा सकती है, लेकिन जहां तक ​​मैं कह सकता हूं, लगभग कुछ भी नहीं हुआ है - पाइथन भाषा का वर्णन करने वाले वेबपृष्ठों की बहुतायत स्वयं को खोजना मुश्किल बनाती है। एक महान उत्तर संसाधनों के लिए एक संक्षिप्त संक्षिप्त अवलोकन और संदर्भ प्रदान करेगा जहां मैं और जान सकता हूं।

मैं ज्यादातर इस बात से चिंतित हूं कि यह यूनिक्स जैसी प्रणालियों पर कैसे काम करता है क्योंकि यह मुझे पता है लेकिन मुझे इस बात की दिलचस्पी है कि प्रक्रिया कहीं और समान है।

अधिक विशिष्ट होने के लिए (लेकिन बहुत अधिक होने का जोखिम भी), सीपीथॉन मॉड्यूल विधियों तालिका और प्रारंभिक फ़ंक्शन का उपयोग गतिशील रूप से लोड किए गए सी के "समझने" के लिए कैसे करता है?

+1

[आयात प्रणाली प्रलेखन] में मॉड्यूल लोडिंग के बारे में बहुत सारी जानकारी है (https://docs.python.org/3/reference/import.html) – GWW

+0

यह एक अच्छा सवाल है; मैं अब उत्सुक हूँ। अगर मैं 10k के लिए लक्ष्य नहीं रख रहा था तो मैं और अधिक बकाया होता। – Veedrac

+1

पायथन किसी दिए गए मॉड्यूल के लिए कई अलग-अलग नामों की खोज करता है; आप यहां प्रक्रिया के उस हिस्से के बारे में पढ़ सकते हैं: http://stackoverflow.com/questions/6319379/python-shared-object-module-naming-convention –

उत्तर

12

टीएलडीआर लघु संस्करण बोल्ड।

पायथन स्रोत कोड के संदर्भ संस्करण 2.7.6 पर आधारित हैं।

पायथन गतिशील लोडिंग के माध्यम से सी में लिखे गए अधिकांश एक्सटेंशन आयात करता है। गतिशील लोडिंग एक गूढ़ विषय है जो अच्छी तरह से प्रलेखित नहीं है लेकिन यह एक पूर्ण शर्त है। समझाए जाने से पहले पायथन इसका उपयोग करता है, मुझे को संक्षेप में समझा जाना चाहिए और क्यों पायथन इसका उपयोग करता है।

पाइथन के ऐतिहासिक रूप से सी एक्सटेंशन स्थाई रूप से पाइथन दुभाषिया के खिलाफ जुड़े हुए थे। इसके लिए पाइथन उपयोगकर्ताओं को प्रत्येक बार जब वे सी में लिखे गए नए मॉड्यूल का उपयोग करना चाहते थे तो दुभाषिया को फिर से इकट्ठा करने की आवश्यकता होती है। जैसा कि आप कल्पना कर सकते हैं, और Guido van Rossum describes के रूप में, समुदाय बढ़ने के साथ यह अव्यवहारिक हो गया। आज, अधिकांश पायथन उपयोगकर्ता कभी दुभाषिया को संकलित नहीं करते हैं। हम बस "पाइप इंस्टॉल मॉड्यूल" और फिर "आयात मॉड्यूल" भले ही उस मॉड्यूल में संकलित सी कोड शामिल है।

लिंकिंग हमें कोड की संकलित इकाइयों में फ़ंक्शन कॉल करने की अनुमति देती है। डायनामिक लोडिंग कोड को जोड़ने की समस्या हल करती है जब रनटाइम पर लिंक करने का निर्णय लिया जाता है। यही है, यह एक चलने वाले प्रोग्राम को लिंकर के साथ इंटरफ़ेस करने की अनुमति देता है और लिंकर को बताता है कि वह किससे लिंक करना चाहता है। पाइथन दुभाषिया के लिए सी कोड के साथ मॉड्यूल आयात करने के लिए, यही कहा जाता है। लेखन कोड जो रनटाइम पर यह निर्णय करता है वह काफी असामान्य है और अधिकांश प्रोग्रामर आश्चर्यचकित होंगे कि यह संभव है। सीधे शब्दों में कहें, एक सी फ़ंक्शन का पता है, यह अपेक्षा करता है कि आप कुछ स्थानों पर कुछ डेटा डालें, और यह कुछ निश्चित स्थानों पर कुछ डेटा वापस करने का वादा करता है। यदि आप गुप्त हैंडशेक जानते हैं, तो आप इसे कॉल कर सकते हैं।

गतिशील लोडिंग के साथ चुनौती यह है कि यह प्रोग्रामर पर हैंडशेक सही पाने के लिए है और कोई सुरक्षा जांच नहीं है। कम से कम, वे हमारे लिए उपलब्ध नहीं कराए जाते हैं। आम तौर पर, अगर हम किसी गलत हस्ताक्षर के साथ फ़ंक्शन नाम कॉल करने का प्रयास करते हैं तो हमें संकलन या लिंकर त्रुटि मिलती है। गतिशील लोडिंग के साथ हम लिंकर को रनटाइम पर नाम (एक "प्रतीक") द्वारा फ़ंक्शन के लिए पूछते हैं। लिंकर हमें बता सकता है कि क्या वह नाम मिला था लेकिन यह हमें नहीं बता सकता कि उस समारोह को कैसे कॉल करें। यह सिर्फ हमें एक पता देता है - एक शून्य सूचक। हम किसी प्रकार के फ़ंक्शन पॉइंटर पर डालने का प्रयास कर सकते हैं लेकिन कलाकारों को पूरी तरह कास्ट करने के लिए प्रोग्रामर तक ही है। अगर हमें हमारे कलाकार में फ़ंक्शन हस्ताक्षर गलत लगता है, तो संकलक या लिंकर के लिए हमें चेतावनी देने में बहुत देर हो चुकी है। कार्यक्रम के नियंत्रण के बाद हमें एक सेगफॉल्ट मिल जाएगा और स्मृति को अनुपयुक्त तरीके से एक्सेस करना समाप्त हो जाएगा। गतिशील लोडिंग का उपयोग करने वाले कार्यक्रमों को पूर्व-व्यवस्थित सम्मेलनों और उचित कार्य कॉल करने के लिए रनटाइम पर एकत्र की गई जानकारी पर भरोसा करना चाहिए। पाइथन दुभाषिया से निपटने से पहले यहां एक छोटा सा उदाहरण दिया गया है।

फ़ाइल 1: main.c

/* gcc-4.8 -o main main -ldl */ 
#include <dlfcn.h> /* key include, also in Python/dynload_shlib.c */ 

/* used for cast to pointer to function that takes no args and returns nothing */ 
typedef void (say_hi_type)(void); 

int main(void) { 
    /* get a handle to the shared library dyload1.so */ 
    void* handle1 = dlopen("./dyload1.so", RTLD_LAZY); 

    /* acquire function ptr through string with name, cast to function ptr */ 
    say_hi_type* say_hi1_ptr = (say_hi_type*)dlsym(handle1, "say_hi1"); 

    /* dereference pointer and call function */ 
    (*say_hi1_ptr)(); 

    return 0; 
} 
/* error checking normally follows both dlopen() and dlsym() */ 

फ़ाइल 2: dyload1.c

/* gcc-4.8 -o dyload1.so dyload1.c -shared -fpic */ 
/* compile as C, C++ does name mangling -- changes function names */ 
#include <stdio.h> 

void say_hi1() { 
    puts("dy1: hi"); 
} 

इन फ़ाइलों को संकलित किया गया है और अलग से जुड़ा हुआ लेकिन main.c ./dyload1 की तलाश में जाने के लिए जानता है। तो रनटाइम पर। मुख्य रूप से कोड मानता है कि dyload1.so का प्रतीक "say_hi1" होगा। यह dlopen() के साथ dyload1.so के प्रतीकों के लिए एक हैंडल प्राप्त करता है, dlsym() का उपयोग कर प्रतीक का पता प्राप्त करता है, मानता है कि यह एक ऐसा कार्य है जो कोई तर्क नहीं लेता है और कुछ भी नहीं देता है, और इसे कॉल करता है। यह सुनिश्चित करने का कोई तरीका नहीं है कि "say_hi1" क्या है - एक पूर्व समझौता वह सब है जो हमें segfaulting से रोकता है।

जो मैंने ऊपर दिखाया है वह है dlopen() फ़ंक्शन का परिवार। पाइथन कई प्लेटफार्मों पर तैनात किया गया है, जिनमें से सभी dlopen() प्रदान नहीं करते हैं, लेकिन अधिकांश में समान गतिशील लोडिंग तंत्र होते हैं। पायथन एक सामान्य इंटरफेस में कई ऑपरेटिंग सिस्टम के गतिशील लोडिंग तंत्र को लपेटकर पोर्टेबल गतिशील लोडिंग प्राप्त करता है।

पायथन/importdl.c में यह टिप्पणी रणनीति को सारांशित करती है।

/* ./configure sets HAVE_DYNAMIC_LOADING if dynamic loading of modules is 
    supported on this platform. configure will then compile and link in one 
    of the dynload_*.c files, as appropriate. We will call a function in 
    those modules to get a function pointer to the module's init function. 
*/ 

के रूप में संदर्भित, पायथन 2.7.6 में हम इन dynload * ग फ़ाइलें:

Python/dynload_aix.c  Python/dynload_beos.c Python/dynload_hpux.c 
Python/dynload_os2.c  Python/dynload_stub.c Python/dynload_atheos.c 
Python/dynload_dl.c  Python/dynload_next.c Python/dynload_shlib.c 
Python/dynload_win.c 

वे एक इस हस्ताक्षर के साथ एक समारोह को परिभाषित:

dl_funcptr _PyImport_GetDynLoadFunc(const char *fqname, const char *shortname, 
            const char *pathname, FILE *fp) 

इन कार्यों को शामिल विभिन्न ऑपरेटिंग सिस्टम के लिए विभिन्न गतिशील लोडिंग तंत्र। 10.2 से अधिक मैक ओएस पर गतिशील लोडिंग के लिए तंत्र और अधिकांश यूनिक्स (जैसे) सिस्टम dlopen() है, जिसे पायथन/dynload_shlib.c में कहा जाता है।

dynload_win.c पर स्किमिंग, विंडोज के लिए समान कार्य लोडLibraryEx() है। इसका उपयोग बहुत समान दिखता है।

पायथन/dynload_shlib.c के नीचे आप dlopen() और dlsym() को वास्तविक कॉल देख सकते हैं।

handle = dlopen(pathname, dlopenflags); 
/* error handling */ 
p = (dl_funcptr) dlsym(handle, funcname); 
return p; 

इससे पहले, पाइथन स्ट्रिंग को उस कार्य नाम के साथ लिखता है जो इसे देखेगा। मॉड्यूल नाम शॉर्ट नाम चर में है।

PyOS_snprintf(funcname, sizeof(funcname), 
       LEAD_UNDERSCORE "init%.200s", shortname); 

अजगर बस वहाँ एक समारोह init {modulename} कहा जाता है की उम्मीद है और इसके लिए लिंकर पूछता है। यहां से शुरू हो रहा है, पाइथन सी कोड की गतिशील लोडिंग को संभव और भरोसेमंद बनाने के लिए सम्मेलनों के एक छोटे से सेट पर निर्भर करता है।

चलिए देखते हैं कि उपर्युक्त कॉल को dlsym() काम करने वाले अनुबंध को पूरा करने के लिए सी एक्सटेंशन को क्या करना चाहिए। संकलित सी पायथन मॉड्यूल के लिए, पहला सम्मेलन जो पाइथन को संकलित सी कोड तक पहुंचने की अनुमति देता है वह init {shared_library_filename}() फ़ंक्शन है।a module named spam के लिए "spam.so" नाम साझा लाइब्रेरी के रूप में संकलित है, हम इस initspam() फ़ंक्शन प्रदान कर सकते हैं: init समारोह के नाम फ़ाइल नाम से मेल नहीं खाता

PyMODINIT_FUNC 
initspam(void) 
{ 
    PyObject *m; 
    m = Py_InitModule("spam", SpamMethods); 
    if (m == NULL) 
     return; 
} 

, पायथन दुभाषिया कैसे पता नहीं कर सकते हैं इसे खोजें। उदाहरण के लिए, स्पैम का नाम बदलना। इसलिए notspam.so पर और आयात करने का प्रयास निम्नलिखित देता है।

>>> import spam 
ImportError: No module named spam 
>>> import notspam 
ImportError: dynamic module does not define init function (initnotspam) 

यदि नामकरण सम्मेलन का उल्लंघन किया गया है तो साझा पुस्तकालय में प्रारंभिक कार्य भी शामिल होने पर बस कोई बात नहीं है।

दूसरा मुख्य सम्मेलन यह है कि एक बार कहा जाता है कि init फ़ंक्शन Py_InitModule को कॉल करके स्वयं को प्रारंभ करने के लिए ज़िम्मेदार है। यह कॉल मॉड्यूल को मॉड्यूल डेटा में मॉड्यूल डेटा को मानचित्रित करने वाले दुभाषिया द्वारा रखी गई "डिक्शनरी"/हैश तालिका में जोड़ती है। यह विधि तालिका में सी कार्यों को भी पंजीकृत करता है। Py_InitModule को कॉल करने के बाद, मॉड्यूल अन्य तरीकों से खुद को प्रारंभ कर सकते हैं जैसे ऑब्जेक्ट्स जोड़ना। (पूर्व: the SpamError object in the Python C API tutorial)। (Py_InitModule वास्तव में एक मैक्रो है जो वास्तविक init कॉल बनाता है लेकिन कुछ जानकारी के साथ पकेथन के हमारे संस्करण सी एक्सटेंशन का उपयोग किया जाता है।)

यदि इनिट फ़ंक्शन का उचित नाम है लेकिन Py_InitModule() को कॉल नहीं करता है, हम इसे प्राप्त करते हैं:

SystemError: dynamic module not initialized properly 

हमारी विधियों की तालिका को स्पैम विधि कहा जाता है और ऐसा लगता है।

static PyMethodDef SpamMethods[] = { 
    {"system", spam_system, METH_VARARGS, 
    "Execute a shell command."}, 
    {NULL, NULL, 0, NULL} 
}; 

विधि तालिका ही है और समारोह हस्ताक्षर अनुबंध यह जरूरत पर जोर देता तीसरे और अंतिम कुंजी सम्मेलन आवश्यक अजगर समझ बनाने के लिए के लिए की गतिशील सी भरी हुई विधि तालिका एक अंतिम साथ struct PyMethodDef की एक सरणी है सेंटीनेल प्रविष्टि। एक PyMethodDef को निम्नानुसार/methodobject.h में परिभाषित किया गया है।

struct PyMethodDef { 
    const char *ml_name; /* The name of the built-in function/method */ 
    PyCFunction ml_meth; /* The C function that implements it */ 
    int  ml_flags; /* Combination of METH_xxx flags, which mostly 
        describe the args expected by the C func */ 
    const char *ml_doc; /* The __doc__ attribute, or NULL */ 
}; 

यहां महत्वपूर्ण हिस्सा यह है कि दूसरा सदस्य एक पीईसीफ़ंक्शन है। हम एक समारोह के पते में पारित, तो एक पीईसी समारोह क्या है? यह एक typedef है, शामिल भी/methodobject.h

typedef PyObject *(*PyCFunction)(PyObject *, PyObject *); 

PyCFunction एक समारोह है कि एक PyObject के लिए सूचक रिटर्न और उस तर्क PyObjects करने के लिए दो संकेत के लिए ले जाता है के लिए एक सूचक के लिए एक typedef है। तीन सम्मेलन के लिए एक लीमा के रूप में, विधि तालिका के साथ पंजीकृत सी कार्यों में सभी एक ही हस्ताक्षर हैं।

पायथन सी फ़ंक्शन हस्ताक्षरों के सीमित सेट का उपयोग करके गतिशील लोडिंग में अधिक कठिनाई को रोकता है। विशेष रूप से एक हस्ताक्षर अधिकांश सी कार्यों के लिए उपयोग किया जाता है। पॉइंटर्स टू सी फ़ंक्शंस जो अतिरिक्त तर्क लेते हैं उन्हें पीईसीफंक्शन पर कास्टिंग करके "स्नैक इन" किया जा सकता है। (Python C API tutorial में keywdarg_parrot उदाहरण देखें।) यहां तक ​​कि सी कार्य जो बैकअप पाइथन फ़ंक्शन जो पाइथन में कोई तर्क नहीं लेते हैं, सी में दो तर्क (नीचे दिखाए गए) लेते हैं। सभी कार्यों को कुछ वापस करने की भी उम्मीद है (जो कि कोई भी वस्तु नहीं हो सकती है)। पाइथन में एकाधिक पोजीशनल तर्क लेने वाले कार्यों को सी

में उन तर्कों को उन ऑब्जेक्ट्स को अनपैक करना होगा, इस प्रकार गतिशील रूप से लोड किए गए सी कार्यों के साथ इंटरफेसिंग के लिए डेटा अधिग्रहण और संग्रहीत किया जाता है। अंत में, यहां एक उदाहरण दिया गया है कि उस डेटा का उपयोग कैसे किया जाता है।

यहां संदर्भ यह है कि हम पाइथन "ऑपोड्स" का मूल्यांकन कर रहे हैं, निर्देश द्वारा निर्देश, और हमने फ़ंक्शन कॉल ऑपोड मारा है। (https://docs.python.org/2/library/dis.html देखें। यह एक स्कीम के लायक है।) हमने पाया है कि पायथन फ़ंक्शन ऑब्जेक्ट को सी फ़ंक्शन द्वारा समर्थित किया जाता है। नीचे दिए गए कोड में हम जांचते हैं कि पायथन में फ़ंक्शन कोई तर्क नहीं लेता है (पायथन में) और यदि ऐसा है, तो इसे कॉल करें (सी में दो तर्कों के साथ)।

पायथन/ceval.c।

if (flags & (METH_NOARGS | METH_O)) { 
    PyCFunction meth = PyCFunction_GET_FUNCTION(func); 
    PyObject *self = PyCFunction_GET_SELF(func); 
    if (flags & METH_NOARGS && na == 0) { 
     C_TRACE(x, (*meth)(self,NULL)); 
    } 

यह निश्चित रूप से सी में तर्क लेता है - बिल्कुल दो। चूंकि सब कुछ पाइथन में एक वस्तु है, इसलिए यह स्वयं तर्क प्राप्त करता है। नीचे आप देख सकते हैं कि meth को एक फ़ंक्शन पॉइंटर असाइन किया गया है जिसे तब संदर्भित किया जाता है और कहा जाता है। वापसी मूल्य एक्स में समाप्त होता है।

+2

+1 यह एक बहुत ही संपूर्ण जवाब है। – refi64

+0

यदि कोई अभी भी इसे पढ़ रहा है ... क्या यह कहना सुरक्षित है कि अंतर्निर्मित सी मॉड्यूल स्थिर रूप से पाइथन बाइनरी में जुड़े हुए हैं, जबकि कस्टम एक्सटेंशन गतिशील रूप से जुड़े हुए हैं? – SamuelN

+1

@ सैमुएलएन आप 'sys' आयात करके और sys.builtin_module_names' को चेक करके स्थिर रूप से जुड़े मॉड्यूल की सूची प्राप्त कर सकते हैं। पाइथन जैसे 'गणित' के साथ आने वाले कई प्रतीत होता है मौलिक मॉड्यूल सख्ती से "बिल्टिन" नहीं हैं और वास्तव में गतिशील रूप से लोड होते हैं। आप इसे 'गणित' आयात करके और गणित की जांच करके देख सकते हैं .__ file__'। मॉड्यूल जो अंतर्निहित और स्थिर रूप से जुड़े हुए हैं वे मॉड्यूल हैं जो 'sys' जैसे दुभाषिया के साथ बहुत कड़े रूप से एकीकृत होते हैं। (यह प्रतिक्रिया थोड़ा सा टिकाऊ है। यह थोड़ी देर के बाद से मैंने इसमें देखा है और मैं अभी भी दोबारा जांच कर रहा हूं।)। – Praxeolitic

संबंधित मुद्दे