Συχνές ερωτήσεις επέκτασης/ενσωμάτωσης

Μπορώ να δημιουργήσω τις δικές μου συναρτήσεις στη C;

Ναι, μπορείτε να δημιουργήσετε ενσωματωμένα (built-in) modules που περιέχουν συναρτήσεις, μεταβλητές, εξαιρέσεις και ακόμη και νέους τύπους στην C. Αυτό εξηγείται στο έγγραφο Επέκταση και Ενσωμάτωση του Διερμηνέα της Python.

Τα περισσότερα βιβλία μεσαίας ή προηγμένης Python θα καλύπτουν επίσης αυτό το θέμα.

Μπορώ να δημιουργήσω τις δικές μου συναρτήσεις στη C++;

Ναι, χρησιμοποιώντας τις δυνατότητες συμβατότητας C που βρίσκονται στη C++. Τοποθετήστε το extern "C" { ... } γύρω από την Python να περιλαμβάνει αρχεία και τοποθετήστε το extern "C" πριν από κάθε συνάρτηση που πρόκειται να κληθεί από τον διερμηνέα της Python. Τα καθολικά ή τα στατικά αντικείμενα C++ με constructors μάλλον δεν είναι καλή ιδέα.

Το να γράψει C κάποιος είναι δύσκολο· υπάρχουν άλλες εναλλακτικές;

Υπάρχουν αρκετές εναλλακτικές λύσεις για τη σύνταξη των δικών σας επεκτάσεων C, ανάλογα με το τι προσπαθείτε να κάνετε. Το Recommended third party tools προσφέρουν απλούστερες και πιο εξελιγμένες προσεγγίσεις για τη δημιουργία επεκτάσεων C και C++ για Python.

Πως μπορώ να εκτελέσω αυθαίρετες δηλώσεις Python από το C;

Η συνάρτηση υψηλότερου επιπέδου για να γίνει αυτό είναι η PyRun_SimpleString() η οποία εκτελεί ένα όρισμα συμβολοσειράς στο πλαίσιο της ενότητας __main__ και επιστρέφει 0 για επιτυχία και -1 όταν συμβαίνει μια εξαίρεση (συμπεριλαμβανομένου του SyntaxError). Εάν θέλετε περισσότερο έλεγχο, χρησιμοποιήστε PyRun_String()· δείτε τον πηγαίο κώδικα PyRun_SimpleString() στο``Python/pythonrun.c``.

Πώς μπορώ να αξιολογήσω μια αυθαίρετη έκφραση Python από τη C;

Καλέστε τη συνάρτηση PyRun_String() από την προηγούμενη ερώτηση με το σύμβολο έναρξης Py_eval_input· αναλύει μια παράσταση, την αξιολογεί και επιστρέφει την τιμή της.

Πως μπορώ να εξάγω τιμές C από ένα αντικείμενο Python;

Αυτό εξαρτάται από τον τύπο του αντικειμένου. Εάν είναι μια πλειάδα (tuple), PyTuple_Size() επιστρέφει το μήκος του και το PyTuple_GetItem() επιστρέφει το στοιχείο σε ένα καθορισμένο index. Οι λίστες έχουν παρόμοιες συναρτήσεις, PyList_Size() και PyList_GetItem().

Για bytes, PyBytes_Size() επιστρέφει το μήκος του και το PyBytes_AsStringAndSize() παρέχει έναν δείκτη στην τιμή και το μήκος του. Λάβετε υπόψη ότι τα αντικείμενα byte της Python μπορεί να περιέχουν null byte, επομένως η strlen() της C δεν πρέπει να χρησιμοποιείται.

Για να ελέγξετε τον τύπο ενός αντικειμένου, πρώτα βεβαιωθείτε ότι δεν είναι NULL, και μετά χρησιμοποιήστε τα PyBytes_Check(), PyTuple_Check(), PyList_Check(), κλπ.

Υπάρχει επίσης ένα API υψηλού επιπέδου για αντικείμενα Python που παρέχεται από τη λεγόμενη “abstract” διεπαφή (interface) – διαβάστε Include/abstract.h για περισσότερες λεπτομέρειες. Επιτρέπει τη διασύνδεση με κάθε είδους ακολουθίας Python χρησιμοποιώντας κλήσεις όπως PySequence_Length(), PySequence_GetItem(), κλπ. καθώς και πολλά άλλα χρήσιμα πρωτόκολλα όπως αριθμοί (PyNumber_Index() et al.) και αντιστοιχίσεις στον PyMapping APIs.

Πώς μπορώ να χρησιμοποιήσω την Py_BuildValue() για να δημιουργήσω μια πλειάδα (tuple) αυθαίρετου μήκους;

Δεν μπορείς. Χρησιμοποιήστε το PyTuple_Pack().

Πώς καλώ τη μέθοδο ενός αντικειμένου από τη C;

Η συνάρτηση PyObject_CallMethod() μπορεί να χρησιμοποιηθεί για την κλήση μιας αυθαίρετης μεθόδου ενός αντικειμένου. Οι παράμετροι είναι το αντικείμενο, το όνομα της μεθόδου προς κλήση, μια συμβολοσειρά μορφής όπως αυτή που χρησιμοποιείται με τη Py_BuildValue(), και τις τιμές ορίσματος:

PyObject *
PyObject_CallMethod(PyObject *object, const char *method_name,
                    const char *arg_format, ...);

Αυτό λειτουργεί για κάθε αντικείμενο που έχει μεθόδους – είτε είναι ενσωματωμένες είτε καθορίζονται από το χρήστη. Είστε υπεύθυνοι εάν τελικά χρησιμοποιήσετε Py_DECREF() στην τιμή επιστροφής.

Για να καλέσετε, π.χ., τη μέθοδο «seek» ενός αντικειμένου αρχείου με ορίσματα 10, 0 (υποθέτοντας ότι ο δείκτης του αντικειμένου αρχείου είναι «f»):

res = PyObject_CallMethod(f, "seek", "(ii)", 10, 0);
if (res == NULL) {
        ... an exception occurred ...
}
else {
        Py_DECREF(res);
}

Σημειώστε ότι επειδή το PyObject_CallObject() πάντα θέλει μια πλειάδα (tuple) για τη λίστα ορισμάτων, για να καλέσει μια συνάρτηση χωρίς ορίσματα, να περάσει «()» για τη μορφή και να καλέσει μια συνάρτηση με ένα όρισμα, περιβάλλουν το όρισμα σε παρένθεση, π.χ. «(i)».

Πώς μπορώ να κάνω catch την έξοδο από την PyErr_Print() (ή οτιδήποτε εκτυπώνεται σε stdout/stderr);

Στον κώδικα Python, ορίστε ένα αντικείμενο που υποστηρίζει τη μέθοδο write(). Αντιστοιχίστε αυτό το αντικείμενο στα sys.stdout και sys.stderr. Καλέστε το print_error, ή απλώς επιτρέψτε στον τυπικό μηχανισμό ανίχνευσης να λειτουργήσει. Στη συνέχεια, η έξοδος θα πάει οπουδήποτε την στείλει η μέθοδος write().

Ο ευκολότερος τρόπος για να το κάνετε αυτό είναι να χρησιμοποιήσετε την κλάση io.StringIO:

>>> import io, sys
>>> sys.stdout = io.StringIO()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(sys.stdout.getvalue())
foo
hello world!

Ένα προσαρμοσμένο αντικείμενο για να κάνει το ίδιο θα μοιάζει με αυτό:

>>> import io, sys
>>> class StdoutCatcher(io.TextIOBase):
...     def __init__(self):
...         self.data = []
...     def write(self, stuff):
...         self.data.append(stuff)
...
>>> import sys
>>> sys.stdout = StdoutCatcher()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(''.join(sys.stdout.data))
foo
hello world!

Πως μπορώ να αποκτήσω πρόσβαση σε ένα module γραμμένο σε Python από τη C;

Μπορείτε να λάβετε έναν δείκτη στο αντικείμενο του module ως εξής:

module = PyImport_ImportModule("<modulename>");

Εάν το module δεν έχει εισαχθεί ακόμα (δηλαδή δεν υπάρχει ακόμα στο sys.modules), αυτό αρχικοποιεί το module· διαφορετικά απλώς επιστρέφει την τιμή του sys.modules["<modulename>"]. Σημειώστε ότι δεν εισάγει το module σε κανένα namespace – διασφαλίζει μόνο ότι έχει αρχικοποιηθεί και ότι είναι αποθηκευμένη στο sys.modules.

Μπορείτε στη συνέχεια να αποκτήσετε πρόσβαση στα χαρακτηριστικά του module (δηλαδή οποιοδήποτε όνομα ορίζεται στο module) ως εξής:

attr = PyObject_GetAttrString(module, "<attrname>");

Η κλήση PyObject_SetAttrString() για αντιστοίχιση σε μεταβλητές στο module λειτουργεί επίσης.

Πως διασυνδέομαι με αντικείμενα C++ από την Python;

Ανάλογα με τις απαιτήσεις σας, υπάρχουν πολλές προσεγγίσεις. Για να το κάνετε αυτό χειροκίνητα, ξεκινήστε διαβάζοντας το the «Extending and Embedding» document . Συνειδητοποιήστε ότι για το σύστημα χρόνου εκτελεστή Python, δεν υπάρχει μεγάλη διαφορά μεταξύ C και C++ – επομένως η στρατηγική της δημιουργίας ενός νέου τύπου Python γύρω από έναν τύπο δομής C (δείκτη) θα λειτουργήσει επίσης για αντικείμενα C++.

Για βιβλιοθήκες C++, δείτε Το να γράψει C κάποιος είναι δύσκολο· υπάρχουν άλλες εναλλακτικές;.

Πρόσθεσα ένα module χρησιμοποιώντας το αρχείο Setup και το make αποτυγχάνει· γιατί;

Το setup πρέπει να τελειώνει σε μια νέα γραμμή, αν δεν υπάρχει νέα γραμμή, η διαδικασία build αποτυγχάνει. (Για να διορθωθεί αυτό απαιτεί κάποιο κακόβουλο script shell, και αυτό το σφάλμα είναι τόσο μικρό που δεν φαίνεται να αξίζει τον κόπο.)

Πως κάνω debug μια επέκταση;

Όταν χρησιμοποιείτε το GDB με δυναμικά φορτωμένες επεκτάσεις, δεν μπορείτε να ορίσετε σημείο διακοπής στην επέκταση σας μέχρι να φορτωθεί η επέκτασής σας.

Στο αρχείο σας .gdbinit (ή διαδραστικά), προσθέστε την εντολή:

br _PyImport_LoadDynamicModule

Στη συνέχεια, όταν εκτελείτε το GDB:

$ gdb /local/bin/python
gdb) run myscript.py
gdb) continue # repeat until your extension is loaded
gdb) finish   # so that your extension is loaded
gdb) br myfunction.c:50
gdb) continue

Θέλω να κάνω compile ένα Python module στο σύστημα Linux μου, αλλά λείπουν ορισμένα αρχεία. Γιατί;

Οι περισσότερες συσκευασμένες εκδόσεις της Python παραλείπουν ορισμένα αρχεία που απαιτούνται για τη μεταγλώττιση των επεκτάσεων Python.

Για το Red Hat, εγκαταστήστε το RPM της python3-devel για να λάβετε τα απαραίτητα αρχεία.

Για το Debian, εκτελέστε το apt-get install python3-dev.

Πώς μπορώ να ξεχωρίσω την «ελλιπή εισαγωγή» από την «έγκυρη εισαγωγή»;

Μερικές φορές θέλετε να μιμηθείτε τη συμπεριφορά του διαδραστικού διερμηνέα Python, όπου σας δίνει ένα συνεχόμενο prompt όταν η είσοδος είναι ελλιπής (π.χ. πληκτρολογήσατε την αρχή μιας δήλωσης «if» ή δεν κλείσατε τις παρενθέσεις ή τα τριπλά εισαγωγικά συμβολοσειρών), αλλά σας δίνει ένα μήνυμα συντακτικού σφάλματος αμέσως όταν η εισαγωγή δεν είναι έγκυρη.

Στην Python μπορείτε να χρησιμοποιήσετε το module codeop, η οποία προσεγγίζει επαρκώς τη συμπεριφορά του parser. Το IDLE χρησιμοποιεί αυτό, για παράδειγμα.

Ο ευκολότερος τρόπος για να το κάνετε στη C είναι να καλέσετε τη PyRun_InteractiveLoop() (ίσως σε ξεχωριστό νήμα (thread)) και να αφήσετε τον διερμηνέα Python να χειριστεί την είσοδο για εσάς. Μπορείτε επίσης να ορίσετε PyOS_ReadlineFunctionPointer() για να δείξετε την δικιάς προσαρμοσμένη συνάρτηση εισαγωγής. Δείτε τα Modules/readline.c και Parser/myreadline.c για περισσότερες συμβουλές.

Πώς μπορώ να βρω απροσδιόριστα σύμβολα g++ __builtin_new ή __pure_virtual;

Για δυναμική φόρτωση module επέκτασης g++, πρέπει να κάνετε recompile την Python, να τη συνδέσετε ξανά χρησιμοποιώντας g++ (αλλάξτε το LINKCC στο Python Modules Makefile), και να συνδέσετε το module επέκτασης σας χρησιμοποιώντας g++ (π.χ. g++ -shared -o mymodule.so mymodule.o).

Μπορώ να δημιουργήσω μια κλάση αντικειμένου με ορισμένες μεθόδους που υλοποιούνται στη C και άλλες στη Python (π.χ. μέσω κληρονομικότητας);

Ναι, μπορείτε να κληρονομήσετε από ενσωματωμένες (built-in) κλάσεις όπως int, list, dict, κ.λπ.

Η βιβλιοθήκη Boost Python Library (BPL, https://www.boost.org/libs/python/doc/index.html) παρέχει ένα τρόπο για να γίνει αυτό από την C++ (δηλαδή μπορείτε να κληρονομήσετε από μια κλάση επέκτασης γραμμένη σε C++ χρησιμοποιώντας το BPL).