Как расширить Python и сделать C -пакет? - PullRequest
2 голосов
/ 09 мая 2020

Я встроил и расширил Python 2.7 в свое приложение C некоторое время go. Поздно в поезде я привожу его к Python 3, и для меня изменилось множество инициализаций для регистрации модуля. подмодули, чтобы я мог выполнить:

from foo.bar import bas

Я добавил / добавил модуль «верхнего уровня» к PyEval_GetBuiltins(), что могло быть неправильным в Py 2, но это сработало. Теперь в Py 3 я получаю это исключение в приведенном выше коде:

Traceback (most recent call last):
  File "foo.py", line 1, in <module>
ModuleNotFoundError: No module named 'foo.bar'; 'foo' is not a package

Просматривая документы, я нашел пример с PyImport_ExtendInittab. У меня есть два вопроса по этому поводу:

1) Что означает Inittab? Do c говорит, что означает, но это название немного раздражает. Что такое Inittab? Разве это не должно называться PyImport_ExtendBuiltins, я бы понял.

2) Я могу найти только примеры, когда добавляются простые модули. Возможно ли создание пакета с подмодулями и с PyImport_ExtendInittab?

Большое спасибо!

Ответы [ 2 ]

2 голосов
/ 19 мая 2020

Я не знаю, является ли то, что вы пытаетесь вытащить сюда (вложенные модули расширения), OK , в любом случае рекомендуемый способ структурирования кода - через [Python 3.Docs ]: Модули - Пакеты .
Однако я сделал это (воспроизвести проблему, исправить ее) в качестве личного упражнения.

1. Введение

Перечисление 2 соответствующих страниц:

Среда:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q061692747]> tree /a /f
Folder PATH listing for volume SSD0-WORK
Volume serial number is AE9E-72AC
E:.
|   test00.py
|
+---py2
|       mod.c
|
\---py3
        helper.c
        mod.c


2. Python 2

Фиктивный модуль пытается воспроизвести поведение, указанное в вопросе.

мод. c:

#include <stdio.h>
#include <Python.h>

#define MOD_NAME "mod"
#define SUBMOD_NAME "submod"


static PyObject *pMod = NULL;
static PyObject *pSubMod = NULL;

static PyMethodDef modMethods[] = {
    {NULL}
};


PyMODINIT_FUNC initmod() {
    if (!pMod) {
        pMod = Py_InitModule(MOD_NAME, modMethods);
        if (pMod) {
            PyModule_AddIntConstant(pMod, "i", -69);
            pSubMod = Py_InitModule(MOD_NAME "." SUBMOD_NAME, modMethods);
            if (pSubMod) {
                PyModule_AddStringConstant(pSubMod, "s", "dummy");
                if (PyModule_AddObject(pMod, SUBMOD_NAME, pSubMod) < 0) {
                    Py_XDECREF(pMod);
                    Py_XDECREF(pSubMod);
                    return;
                }
            }
        }
    }
}

Вывод :

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q061692747\py2]> sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***

[prompt]> "f:\Install\pc032\Microsoft\VisualCForPython2\2008\Microsoft\Visual C++ for Python\9.0\vcvarsall.bat" x64
Setting environment for using Microsoft Visual Studio 2008 x64 tools.

[prompt]> dir /b
mod.c

[prompt]> cl /nologo /MD /DDLL /I"c:\Install\pc064\Python\Python\02.07.17\include" mod.c  /link /NOLOGO /DLL /OUT:mod.pyd /LIBPATH:"c:\Install\pc064\Python\Python\02.07.17\libs"
mod.c
   Creating library mod.lib and object mod.exp

[prompt]> dir /b
mod.c
mod.exp
mod.lib
mod.obj
mod.pyd
mod.pyd.manifest

[prompt]> "e:\Work\Dev\VEnvs\py_pc064_02.07.17_test0\Scripts\python.exe"
Python 2.7.17 (v2.7.17:c2f86d86e6, Oct 19 2019, 21:01:17) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>>
>>> [item for item in sys.modules if "mod" in item]
[]
>>> import mod
>>>
>>> [item for item in sys.modules if "mod" in item]  # !!! NOTICE the contents !!!
['mod.submod', 'mod']
>>>
>>> mod
<module 'mod' from 'mod.pyd'>
>>> mod.i
-69
>>> mod.submod
<module 'mod.submod' (built-in)>
>>> mod.submod.s
'dummy'
>>>
>>> from mod.submod import s
>>> s
'dummy'
>>>

Как видно, импорт модуля с подмодулями добавляет подмодули в sys.path (не смотрел, но я 99,99% уверен, что это выполняется Py_InitModule )


3. Python 3

Преобразование в Python 3 . Так как это шаг 1 st , относитесь к 2 прокомментированным строкам, так как их там не было.

mod. c:

#include <stdio.h>
#include <Python.h>
//#include "helper.c"

#define MOD_NAME "mod"
#define SUBMOD_NAME "submod"


static PyObject *pMod = NULL;
static PyObject *pSubMod = NULL;

static PyMethodDef modMethods[] = {
    {NULL}
};

static struct PyModuleDef modDef = {
    PyModuleDef_HEAD_INIT, MOD_NAME, NULL, -1, modMethods,
};

static struct PyModuleDef subModDef = {
    PyModuleDef_HEAD_INIT, MOD_NAME "." SUBMOD_NAME, NULL, -1, modMethods,
};


PyMODINIT_FUNC PyInit_mod() {
    if (!pMod) {
        pMod = PyModule_Create(&modDef);
        if (pMod) {
            PyModule_AddIntConstant(pMod, "i", -69);
            pSubMod = PyModule_Create(&subModDef);
            if (pSubMod) {
                PyModule_AddStringConstant(pSubMod, "s", "dummy");
                if (PyModule_AddObject(pMod, SUBMOD_NAME, pSubMod) < 0) {
                    Py_XDECREF(pMod);
                    Py_XDECREF(pSubMod);
                    return NULL;
                }
                //addToSysModules(MOD_NAME "." SUBMOD_NAME, pSubMod);
            }
        }
    }
    return pMod;
}

Вывод :

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q061692747\py3]> sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***

[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2017\VC\Auxiliary\Build\vcvarsall.bat" x64
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.23
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'

[prompt]> dir /b
helper.c
mod.c

[prompt]> cl /nologo /MD /DDLL /I"c:\Install\pc064\Python\Python\03.07.06\include" mod.c  /link /NOLOGO /DLL /OUT:mod.pyd /LIBPATH:"c:\Install\pc064\Python\Python\03.07.06\libs"
mod.c
   Creating library mod.lib and object mod.exp

[prompt]> dir /b
helper.c
mod.c
mod.exp
mod.lib
mod.obj
mod.pyd

[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe"
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>>
>>> [item for item in sys.modules if "mod" in item]
[]
>>> import mod
>>>
>>> [item for item in sys.modules if "mod" in item]  # !!! NOTICE the contents !!!
['mod']
>>>
>>> mod
<module 'mod' from 'e:\\Work\\Dev\\StackOverflow\\q061692747\\py3\\mod.pyd'>
>>> mod.i
-69
>>> mod.submod
<module 'mod.submod'>
>>> mod.submod.s
'dummy'
>>>
>>> from mod.submod import s
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'mod.submod'; 'mod' is not a package
>>> ^Z


[prompt]>

Как видно, вложенный импорт невозможен. Это потому, что mod.submod отсутствует в sys.modules . В общем, «вложенные» подмодули расширений больше не могут быть импортированы через модуль, который содержит их функцию инициализации. единственный вариант - импортировать их вручную.
В качестве примечания: я думаю, что это Python 3 ограничение существует не просто так, поэтому то, что будет ниже, похоже на игру с огнем .

Декомментируйте 2 строки из мод. c.

помощник. c:

int addToSysModules(const char *pName, PyObject *pMod) {
    PyObject *pSysModules = PySys_GetObject("modules");
    if (!PyDict_Check(pSysModules)) {
        return -1;
    }
    PyObject *pKey = PyUnicode_FromString(pName);
    if (!pKey) {
        return -2;
    }
    if (PyDict_Contains(pSysModules, pKey)) {
        Py_XDECREF(pKey);
        return -3;
    }
    Py_XDECREF(pKey);
    if (PyDict_SetItemString(pSysModules, pName, pMod) == -1)
    {
        return -4;
    }
    return 0;
}

Выход :

[prompt]> cl /nologo /MD /DDLL /I"c:\Install\pc064\Python\Python\03.07.06\include" mod.c  /link /NOLOGO /DLL /OUT:mod.pyd /LIBPATH:"c:\Install\pc064\Python\Python\03.07.06\libs"
mod.c
   Creating library mod.lib and object mod.exp

[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe"
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> import sys
>>>
>>> [item for item in sys.modules if "mod" in item]
[]
>>> import mod
>>>
>>> [item for item in sys.modules if "mod" in item]  # !!! NOTICE the contents :) !!!
['mod.submod', 'mod']
>>>
>>> from mod.submod import s
>>> s
'dummy'
>>>


4. Заключительные примечания

Как я уже говорил выше, это больше похоже на временное решение. Более чистым решением было бы лучше организовать модули с помощью пакетов.

Поскольку это сделано для демонстрационных целей и чтобы код был как можно более простым, я не всегда проверял Python C API коды возврата функций. Это может привести к трудностям поиска ошибок (даже сбоям), и никогда не следует выполнять (особенно в производственном коде).

Я не очень уверен, что PyImport_ExtendInittab эффект действительно так, как я не играл с ним, но [Python 3.Docs]: Импорт модулей - int PyImport_ExtendInittab (struct _inittab * newtab) состояния ( выделение мое) :

Это должно вызываться перед Py_Initialize () .

Итак, вызов его в нашем контексте отсутствует вопроса.

Также упоминается это (старое) обсуждение (не уверен, содержит ли оно релевантную информацию, но все же) [Python .Mail]: [Python -Dev] вложенные модули расширения ? .

0 голосов
/ 14 мая 2020

Без минимально воспроизводимого примера трудно сказать, что не так и что конкретно вы ищете в ответе. Тем не менее, я попытаюсь оказать некоторую помощь.

from foo.bar import bas

Для того, чтобы вышеперечисленное работало, вам нужен файл bar.py в папке с именем foo , и bar.py должен содержать функцию bas(). Кроме того, папка foo должна содержать пустой файл __ init __. Py .

Теперь, если вы хотите где-то вызвать скомпилированный файл C, то, вероятно, самый простой способ выполнить sh это будет использовать os.system() или subprocess.call() и вызвать файл, как если бы вы вызывали его из командной строки.

Предполагая, что файл make находится в том же каталоге:

import os
import subprocess

os.system("make run")

# or
subprocess.run("make run".split())

Где make run запускает ваш C файл по желанию (объявлен в вашем make-файле). Также не стесняйтесь передавать аргументы ключевого слова, используя python f-строки.

Надеюсь, это поможет.

...