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

Περιεχόμενα

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Υπάρχουν διάφορες εναλλακτικές λύσεις για να γράψετε τις δικές σας επεκτάσεις C, ανάλογα με το τι προσπαθείτε να κάνετε.

Cython and its relative Pyrex are compilers that accept a slightly modified form of Python and generate the corresponding C code. Cython and Pyrex make it possible to write an extension without having to learn Python’s C API.

If you need to interface to some C or C++ library for which no Python extension currently exists, you can try wrapping the library’s data types and functions with a tool such as SWIG. SIP, CXX Boost, or Weave are also alternatives for wrapping C++ libraries.

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

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

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

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

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

That depends on the object’s type. If it’s a tuple, `PyTuple_Size()` returns its length and `PyTuple_GetItem()` returns the item at a specified index. Lists have similar functions, `PyListSize()` and `PyList_GetItem()`.

For bytes, `PyBytes_Size()` returns its length and `PyBytes_AsStringAndSize()` provides a pointer to its value and its length. Note that Python bytes objects may contain null bytes so C’s `strlen()` should not be used.

Για να ελέγξετε τον τύπο ενός αντικειμένου, πρώτα βεβαιωθείτε ότι δεν είναι `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 μου, αλλά λείπουν ορισμένα αρχεία. Γιατί;

Most packaged versions of Python don’t include the `/usr/lib/python2.x/config/` directory, which contains various files required for compiling Python extensions.

For Red Hat, install the python-devel RPM to get the necessary files.

For Debian, run `apt-get install python-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).