GSL Python 化教程#
本教程介绍了 pythonizations 及其如何解决底层问题的使用方式。
设定:想象您想使用 numpy
,但被提供了一个基于 GNU 科学库(GNU Scientific Library,简称 GSL)的 C 或 C++ 库。如何让它们友好地协同工作?
GSL 是用 C 编写的,但结构非常严谨:它有一致的命名约定,为其主要结构提供分配器/解除分配器,并且具有明确的所有权规则。由于这种结构,可以根据反射信息编写 pythonizations,最终变得非常简单,因此易于维护。
import cppyy
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 1
----> 1 import cppyy
ModuleNotFoundError: No module named 'cppyy'
为了简化,我们将使用 gsl_blas_dgemm
作为“基于GSL的C/C++库”的替代。为了让我们的生活更轻松,我们将把绑定到 GSL 和 GSLBLAS 的内容包装成单个反射字典。对于简单项目来说,这有些过头了,但如果我们想访问所有的 GSL(而不是单独的 GSL 和 GSLBLAS),这种方法是可行的。
# first, pull in all headers from the GSL installation directory (/usr/include on my system).
import glob, os
GSL_HOME = '/usr/include'
gsl_headers = [os.path.relpath(x, GSL_HOME) for x in glob.glob(GSL_HOME+'/gsl/*.h')]
接下来,我们编写一个选择文件,从 GSL 中提取所有有趣的部分。由于适当的命名约定,这个文件可以保持简单(如果 GSL 是一个 C++ 库,并且位于一个命名空间中,它可能会更简单)。然后我们运行 genreflex
命令生成字典文件并编译它,链接到 GSL 和 GSLBLAS。最后,我们准备将字典反射文件加载到 cppyy
中。
!mkdir -p .temp
%%file .temp/gsl_selection.xml
<lcgdict>
<struct pattern="gsl_*" />
<function pattern="gsl_*" />
<enum pattern="GSL*" />
<enum pattern="CBLAS*" />
</lcgdict>
Writing .temp/gsl_selection.xml
# conventional name for generated output
rfldct = 'GSLDict'
if not os.path.exists(f'{rfldct}_rflx.cpp'):
import subprocess, sys
# generate the reflection dictionary
try:
subprocess.check_output(
['genreflex', # utility installed by pip when installing cppyy
'-s', '.temp/gsl_selection.xml', # selection file (see above)
'-o', f'.temp/{rfldct}_rflx.cpp', # intermediate output file
'-I'+GSL_HOME]+ # include search path for GSL headers
gsl_headers) # headers themselves
except subprocess.CalledProcessError as e:
print(f"genreflex failed ({e.returncode:d}): {e.output}")
else:
print("genreflex done")
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
Cell In[5], line 9
7 # generate the reflection dictionary
8 try:
----> 9 subprocess.check_output(
10 ['genreflex', # utility installed by pip when installing cppyy
11 '-s', '.temp/gsl_selection.xml', # selection file (see above)
12 '-o', f'.temp/{rfldct}_rflx.cpp', # intermediate output file
13 '-I'+GSL_HOME]+ # include search path for GSL headers
14 gsl_headers) # headers themselves
15 except subprocess.CalledProcessError as e:
16 print(f"genreflex failed ({e.returncode:d}): {e.output}")
File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:466, in check_output(timeout, *popenargs, **kwargs)
463 empty = b''
464 kwargs['input'] = empty
--> 466 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
467 **kwargs).stdout
File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:548, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
545 kwargs['stdout'] = PIPE
546 kwargs['stderr'] = PIPE
--> 548 with Popen(*popenargs, **kwargs) as process:
549 try:
550 stdout, stderr = process.communicate(input, timeout=timeout)
File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:1026, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
1022 if self.text_mode:
1023 self.stderr = io.TextIOWrapper(self.stderr,
1024 encoding=encoding, errors=errors)
-> 1026 self._execute_child(args, executable, preexec_fn, close_fds,
1027 pass_fds, cwd, env,
1028 startupinfo, creationflags, shell,
1029 p2cread, p2cwrite,
1030 c2pread, c2pwrite,
1031 errread, errwrite,
1032 restore_signals,
1033 gid, gids, uid, umask,
1034 start_new_session, process_group)
1035 except:
1036 # Cleanup if the child failed starting.
1037 for f in filter(None, (self.stdin, self.stdout, self.stderr)):
File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:1955, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
1953 err_msg = os.strerror(errno_num)
1954 if err_filename is not None:
-> 1955 raise child_exception_type(errno_num, err_msg, err_filename)
1956 else:
1957 raise child_exception_type(errno_num, err_msg)
FileNotFoundError: [Errno 2] No such file or directory: 'genreflex'
if not os.path.exists('%s.so' % rfldct):
# get command line arguments for compiler from cling
try:
clingflags = subprocess.check_output(
['cling-config', # utility installed by pip when installing cppyy
'--cppflags'])
except subprocess.CalledProcessError as e:
print('cling-config failed (%d):' % e.returncode, e.output)
raise
else:
print('cling-config done')
# compile generated file
try:
subprocess.check_output(
['g++', # C++ compiler
'-fPIC', # require position independent code
'-shared', # generate shared library
'-o', '%s.so'%rfldct, # output file
'-I'+GSL_HOME, # include search path for GSL headers
'%s_rflx.cpp'%rfldct]+ # intermediate file to compile
clingflags.split()+ # extra flags provided by cling
['-lgsl', '-lgslcblas']) # link in GSL and GSLBLAS
except subprocess.CalledProcessError as e:
print('compilation failed (%d):' % e.returncode, e.output)
else:
print('compilation done')
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
Cell In[6], line 4
1 if not os.path.exists('%s.so' % rfldct):
2 # get command line arguments for compiler from cling
3 try:
----> 4 clingflags = subprocess.check_output(
5 ['cling-config', # utility installed by pip when installing cppyy
6 '--cppflags'])
7 except subprocess.CalledProcessError as e:
8 print('cling-config failed (%d):' % e.returncode, e.output)
File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:466, in check_output(timeout, *popenargs, **kwargs)
463 empty = b''
464 kwargs['input'] = empty
--> 466 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
467 **kwargs).stdout
File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:548, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
545 kwargs['stdout'] = PIPE
546 kwargs['stderr'] = PIPE
--> 548 with Popen(*popenargs, **kwargs) as process:
549 try:
550 stdout, stderr = process.communicate(input, timeout=timeout)
File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:1026, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
1022 if self.text_mode:
1023 self.stderr = io.TextIOWrapper(self.stderr,
1024 encoding=encoding, errors=errors)
-> 1026 self._execute_child(args, executable, preexec_fn, close_fds,
1027 pass_fds, cwd, env,
1028 startupinfo, creationflags, shell,
1029 p2cread, p2cwrite,
1030 c2pread, c2pwrite,
1031 errread, errwrite,
1032 restore_signals,
1033 gid, gids, uid, umask,
1034 start_new_session, process_group)
1035 except:
1036 # Cleanup if the child failed starting.
1037 for f in filter(None, (self.stdin, self.stdout, self.stderr)):
File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:1955, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
1953 err_msg = os.strerror(errno_num)
1954 if err_filename is not None:
-> 1955 raise child_exception_type(errno_num, err_msg, err_filename)
1956 else:
1957 raise child_exception_type(errno_num, err_msg)
FileNotFoundError: [Errno 2] No such file or directory: 'cling-config'
# load the generated dictionary
cppyy.load_reflection_info(rfldct)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[7], line 2
1 # load the generated dictionary
----> 2 cppyy.load_reflection_info(rfldct)
NameError: name 'cppyy' is not defined