如何使用 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。
我有两个 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。