如何使用 SIP 制作两个或多个 C++/Qt 类 的 python 模块?

How can I make a python module of two or more C++/Qt classes using SIP?

我有两个 C++/Qt classes (A.h, A.cpp, B.h, B.cpp):

class A : public QObject
{
    // bla-bla
};
class B : public A
{
    // bla-bla
};

我想在 Python classes A 和 B 中使用这样的东西:

import mymodule

class MyB(mymodule.B):
    pass

a = mymodule.A()

我可以用一个 class 制作一个模块并在 Python 中成功使用它,但我不明白用 2 个 class 或更多可以做什么。

这就是我用于构建模块的文件查找 class:

*.pro:

TEMPLATE = lib

CONFIG   += qt warn_on release

HEADERS  = A.h
SOURCES  = A.cpp
TARGET   = mymodule

DESTDIR  = /home/alex/tmp/lib

*.sip:

%Module A 0

%Import QtCore/QtCoremod.sip

class A : QObject
{
%TypeHeaderCode
#include "A.h"
%End

public:
  A();

// bla-bla
};

configure.py:

import os
import sipconfig
from PyQt4 import pyqtconfig

build_file = "A.sbf"

config = pyqtconfig.Configuration()

qt_sip_flags = config.pyqt_sip_flags

os.system(" ".join([config.sip_bin, "-c", ".", "-b", build_file, "-I", 
config.pyqt_sip_dir, qt_sip_flags, "A.sip"]))

installs = []
installs.append(["A.sip", os.path.join(config.default_sip_dir, "A")])

makefile = pyqtconfig.QtCoreModuleMakefile(
                      configuration=config,
                      build_file=build_file,
                      installs=installs)

makefile.LFLAGS.append("-L/home/alex/tmp/lib")
makefile.extra_libs = ["A"]

makefile.generate()

在 运行 Makefile 之后,我有 "A" 模块可以在 Python 中使用。 如何在一个 python 模块中制作 2 个或更多 classes?

最好在开发模块时构建一个项目,在这种情况下我将使用以下内容:

├── configure.py
├── examples
│   └── main.py
├── sip
│   ├── base.sip
│   ├── derived.sip
│   └── pyfoomodule.sip
└── src
    ├── base.cpp
    ├── base.h
    ├── derived.cpp
    ├── derived.h
    ├── foomodule_global.h
    └── FooModule.pro

base.sip和derived.sip不创建模块,它只定义了class:

base.sip

%Import QtCore/QtCoremod.sip
class Base: public QObject
{
%TypeHeaderCode
#include "base.h"
%End
public:
    Base(QObject *parent=nullptr);
    virtual QString doStuff();
};

derived.sip

%Import QtCore/QtCoremod.sip
class Derived: public Base
{
%TypeHeaderCode
#include "derived.h"
%End
public:
    Derived(QObject *parent=nullptr);
    QString doStuff();
};

并且在 pyfoomodule.sip 中创建的项目包括另一个 .sip

pyfoomodule.sip

%Module(name=PyFooModule, call_super_init=True, keyword_arguments="Optional")
%DefaultMetatype PyQt4.QtCore.pyqtWrapperType
%DefaultSupertype sip.simplewrapper
%Include base.sip
%Include derived.sip

我还创建了一个脚本,负责编译项目。

configure.py

from PyQt4.QtCore import PYQT_CONFIGURATION as pyqt_config
from distutils import sysconfig
import os, sipconfig, sys


class HostPythonConfiguration(object):
    def __init__(self):
        self.platform=sys.platform
        self.version=sys.hexversion>>8

        self.inc_dir=sysconfig.get_python_inc()
        self.venv_inc_dir=sysconfig.get_python_inc(prefix=sys.prefix)
        self.module_dir=sysconfig.get_python_lib(plat_specific=1)

        if sys.platform=='win32':
            self.data_dir=sys.prefix
            self.lib_dir=sys.prefix+'\libs'
        else:
            self.data_dir=sys.prefix+'/share'
            self.lib_dir=sys.prefix+'/lib'

class TargetQtConfiguration(object):
    def __init__(self, qmake):
        pipe=os.popen(' '.join([qmake, '-query']))

        for l in pipe:
            l=l.strip()

            tokens=l.split(':', 1)
            if isinstance(tokens, list):
                if len(tokens) != 2:
                    error("Unexpected output from qmake: '%s'\n" % l)

                name,value=tokens
            else:
                name=tokens
                value=None

            name=name.replace('/', '_')
            setattr(self, name, value)

        pipe.close()        

if __name__=="__main__":
    from argparse import ArgumentParser

    parser=ArgumentParser(description="Configure PyAnalogClock module.")
    parser.add_argument(
        '-q', '--qmake',
        dest="qmake",
        type=str,
        default="qmake-qt4",
        help="Path to qmake executable"
    )
    parser.add_argument(
        '-s', '--sip-extras',
        dest="sip_extras",
        type=str,
        default="",
        help="Extra arguments to sip"
    )
    args=parser.parse_args()

    qmake_exe=args.qmake
    if not qmake_exe.endswith('qmake-qt4'):
        qmake_exe=os.path.join(qmake_exe,'qmake')

    if os.system(' '.join([qmake_exe, '-v']))!=0:

        if sys.platform=='win32':
            print("Make sure you have a working Qt qmake on your PATH.")
        else:
            print(
                "Use the --qmake argument to explicitly specify a "
                "working Qt qmake."
            )
        exit(1)

    sip_args=args.sip_extras

    pyconfig=HostPythonConfiguration()
    py_sip_dir=os.path.join(pyconfig.data_dir, 'sip', 'PyQt4')
    sip_inc_dir=pyconfig.venv_inc_dir

    qtconfig=TargetQtConfiguration(qmake_exe)

    inc_dir=os.path.abspath(os.path.join(".","src"))
    lib_dir=inc_dir

    sip_files_dir=os.path.abspath(os.path.join(".","sip"))
    output_dir =os.path.abspath(os.path.join(".", "modules"))
    build_file="pyfoomodule.sbf"
    build_path = os.path.join(output_dir, build_file)

    if not os.path.exists(output_dir): os.mkdir(output_dir)
    sip_file = os.path.join(sip_files_dir, "pyfoomodule.sip")

    config=sipconfig.Configuration()    

    cmd=" ".join([
        config.sip_bin,
        pyqt_config['sip_flags'],
        sip_args,
        '-I', sip_files_dir,
        '-I', py_sip_dir,
        '-I', config.sip_inc_dir,
        '-I', inc_dir,
        "-c", output_dir,
        "-b", build_path,
        "-w",
        "-o",
        sip_file,
    ])

    print(cmd)
    if os.system(cmd)!=0: sys.exit(1)

    installs = []
    installs.append([os.path.join(sip_files_dir, "pyfoomodule.sip"), 
        os.path.join(config.default_sip_dir, "PyFooModule")])

    makefile=sipconfig.SIPModuleMakefile(
        config,
        build_file,
        dir=output_dir,
        installs=installs
    )

    makefile.extra_defines+=['MYMODULE_LIBRARY','QT_CORE_LIB', 'QT_GUI_LIB']
    makefile.extra_include_dirs+=[os.path.abspath(inc_dir), qtconfig.QT_INSTALL_HEADERS]
    makefile.extra_lib_dirs+=[qtconfig.QT_INSTALL_LIBS, os.path.join('..','src')]
    makefile.extra_libs+=['FooModule']

    if sys.platform=='darwin':
        makefile.extra_cxxflags+=['-F'+qtconfig.QT_INSTALL_LIBS]        
        makefile.extra_include_dirs+=[
            os.path.join(qtconfig.QT_INSTALL_LIBS,'QtCore.framework','Headers'),
            os.path.join(qtconfig.QT_INSTALL_LIBS,'QtGui.framework','Headers')
        ]

        makefile.extra_lflags+=[      
            '-F'+qtconfig.QT_INSTALL_LIBS,
            "-framework QtGui",
            "-framework QtCore",
            "-framework DiskArbitration",
            "-framework IOKit",
            "-framework OpenGL",
            "-framework AGL",
        ]

    else:
        makefile.extra_include_dirs+=[
            os.path.join(qtconfig.QT_INSTALL_HEADERS, "QtCore"),
            os.path.join(qtconfig.QT_INSTALL_HEADERS, "QtGui"),
        ]
        makefile.extra_lib_dirs+=[os.path.join('..','src','release')]
        # makefile.extra_libs+=['Qt4Core','Qt4Gui']

    makefile.generate()

    sipconfig.ParentMakefile(
        configuration = config,
        subdirs = ["src", output_dir],
    ).generate()

    os.chdir("src")    
    qmake_cmd=qmake_exe
    if sys.platform=="win32": qmake_cmd+=" -spec win32-msvc2010"
    print()
    print(qmake_cmd)
    os.system(qmake_cmd)
    sys.exit()

然后执行以下命令:

python configure.py --qmake /path/of/qmake
make 
sudo make install

最后是如何使用它的示例:

from PyQt4 import QtCore
from PyFooModule import Base, Derived

class PyDerived(Base):
    def doStuff(self):
        return "PyDerived"

if __name__ == '__main__':
    print("==============================")
    b = Base()
    pd = PyDerived()
    d = Derived()
    print("b.doStuff(): ", b.doStuff())
    print("pd.doStuff(): ", pd.doStuff())
    print("d.doStuff(): ", d.doStuff())
    print("==============================")
    print("Base is subclass of QObject: ", issubclass(Base, QtCore.QObject))
    print("PyDerived is subclass of Base: ", issubclass(PyDerived, Base))
    print("Derived is subclass of Base: ", issubclass(Derived, Base))
    print("==============================")

输出:

==============================
b.doStuff():  Base
pd.doStuff():  PyDerived
d.doStuff():  Derived
==============================
Base is subclass of QObject:  True
PyDerived is subclass of Base:  True
Derived is subclass of Base:  True
==============================

可以找到完整的示例 here