美文网首页python热爱者Python新世界Python自动化运维
如何用 Python 调用其他程序和代码

如何用 Python 调用其他程序和代码

作者: 轻松学Python111 | 来源:发表于2018-09-10 16:26 被阅读2次

    Python 常被称为是胶水语言,主要原因就是其对于系统命令、各种语言的代码和程序都具有良好的调用能力。这里列举了一些在 Windows 系统中,常见的接口与调用。在 Linux 中,实现起来都非常简单直接,符合直觉。在 Windows 中,程序、代码、动态链接库的调用相对比较复杂,因此 Python 的这种能力也格外地有用。

    大家可以加下我这个Python学习qun:548377875 我会每天更新一些东西,有时间也会帮大家解答的

    用 Python 调用 Python 包

    这节算是个凑数的热身,但也是实际使用 Python 开发时,每日必不可少的工作。

    from __future__ import (absolute_import, division, print_function,
                           unicode_literals)
    import python_package_example1
    import python_package_example2 as exp2
    from python_package_example3 import sub31, sub32, sub33
    from python_package_example4.sub import code1, code2
    
    

    推荐尽量使用绝对路径调用,尽量起一个简短明确的别名。

    示例

    这里举一个在开源软件中实际应用的例子,是 matplotlib 中 imread 函数调用 pillow 图像读取代码的实例。

    试着引入一个包来读图像,不成功就返回空。

    def pilread(fname):
       """try to load the image with PIL or return None"""
       try:
           from PIL import Image
       except ImportError:
           return None
       with Image.open(fname) as image:
           return pil_to_array(image)
    
    

    参见 GitHub 源码

    邪恶的小技巧

    • 引入一个包,再删除(matplotlib)
    # Get the version from the _version.py versioneer file. For a git checkout,
    # this is computed based on the number of commits since the last tag.
    from ._version import get_versions
    __version__ = str(get_versions()['version'])
    del get_versions
    
    

    参见 GitHub 源码

    • 手动版本检验,出错手动报错(matplotlib)
    _python27 = (sys.version_info.major == 2 and sys.version_info.minor >= 7)
    _python34 = (sys.version_info.major == 3 and sys.version_info.minor >= 4)
    if not (_python27 or _python34):
       raise ImportError("Matplotlib requires Python 2.7 or 3.4 or later")
    
    

    参见 GitHub 源码

    用 Python 调用 exe

    话说有这么一个程序 cos_exe 可以求一个角度的余弦。你想调用这个程序,怎么做?

    可以使用 subprosess 包(无需安装)。

    文档参见:17.5. subprocess - Subprocess management - Python 3.5.4 documentation

    # test_cos_exe.py
    
    import subprocess
    import unittest
    
    class TestCosExe(unittest.TestCase):
      """docstring for TestCosExe"""
    
      def test_result(self):
          subprocess.check_output([
              '..\\..\\tcc.exe',
              str('cos_exe.c')])
          result = subprocess.check_output([
              str('cos_exe.exe'),
              str('45')])
          self.assertEqual(result, b'0.70711')
    
    if __name__ == '__main__':
      unittest.main()
    
    
    // cos_exe.c
    
    #include <stdio.h>
    #include <math.h>
    
    inline float to_radians(float radians) {
      return radians * (M_PI / 180.0);
    }
    
    int main(int argc, char const *argv[]) {
      float angle;
      if (argc == 2) {
          /* code */
          sscanf(argv[1], "%f", &angle);
      }
      else {
          printf("Wrong argument. Should be: \"%s 45\"\n", argv[0]);
          scanf("%f", &angle);
          printf("%0.5f", cos(to_radians(angle)));
          sleep(2000);
          return -1;
      }
    
      printf("%0.5f", cos(to_radians(angle)));
    
      return 0;
    }
    
    

    subprocess 可以调用一个 exe 程序,然后把标准输出和错误输出都接收回来,以字符串变量的形式存储下来。

    如果程序复杂,也可以保存成文件,然后读取文件中的数据。也可以直接存成图像、声音等多媒体文件。但要注意这种使用方式会给程序带来很多额外的依赖关系,对程序的稳定性与健壮性损害较大。

    示例

    同样也举一个 matplotlib 中的例子。matplotlib 中对于 latex 的支持就是通过系统调用实现的。

    在查看到当前目录下不存在同名 dvi 文件后,matplotlib 调用系统中的 latex 编译 tex 源文件。如果调用中出现错误,则代理输出。

    if (DEBUG or not os.path.exists(dvifile) or
          not os.path.exists(baselinefile)):
      texfile = self.make_tex_preview(tex, fontsize)
      command = [str("latex"), "-interaction=nonstopmode",
                 os.path.basename(texfile)]
      _log.debug(command)
      try:
          report = subprocess.check_output(command,
                                           cwd=self.texcache,
                                           stderr=subprocess.STDOUT)
      except subprocess.CalledProcessError as exc:
          raise RuntimeError(
              ('LaTeX was not able to process the following '
               'string:\n%s\n\n'
               'Here is the full report generated by LaTeX:\n%s '
               '\n\n' % (repr(tex.encode('unicode_escape')),
                         exc.output.decode("utf-8"))))
      _log.debug(report)
    
    

    参见 GitHub 源码

    用 Python 调用 dll

    Python 调用简单的 C 语言编译的 DLL,怎么做?

    可以简单地使用 ctypes 包(无需安装)。

    import ctypes
    user32 = ctypes.windll.LoadLibrary('user32.dll') # load dll
    assert(user32.MessageBoxA(0, b'Ctypes is cool!', b'Ctypes', 0))
    # call message box function
    
    

    (示例代码来源久远,已不可考,首次使用在该文中:Some Notes on Python and PyGame

    相当简单直接。其实只需要用 dependency walker 找到 dll 文件中的函数名,就可以使用 ctypes 包直接调用。(http://dependencywalker.com/)

    这里其实揭示了 dll 文件的本质,就是一组二进制代码,函数名就是代码的入口定位符号。

    用 ctypes 调用 dll 需要查看 dll 本身的手册,以便了解相关函数的功能和参数。ctypes 本身只是一层接口,不提供相关功能。

    示例

    OpenCV 里面,使用 ctypes 调用系统调用,来获得当前进程的 exe 文件名和起始地址。

    def getRunningProcessExePathByName_win32(name):
      from ctypes import windll, POINTER, pointer, Structure, sizeof
      from ctypes import c_long , c_int , c_uint , c_char , c_ubyte , c_char_p , c_void_p
    
      class PROCESSENTRY32(Structure):
          _fields_ = [ ( 'dwSize' , c_uint ) ,
                      ( 'cntUsage' , c_uint) ,
                      ( 'th32ProcessID' , c_uint) ,
                      ( 'th32DefaultHeapID' , c_uint) ,
                      ( 'th32ModuleID' , c_uint) ,
                      ( 'cntThreads' , c_uint) ,
                      ( 'th32ParentProcessID' , c_uint) ,
                      ( 'pcPriClassBase' , c_long) ,
                      ( 'dwFlags' , c_uint) ,
                      ( 'szExeFile' , c_char * 260 ) ,
                      ( 'th32MemoryBase' , c_long) ,
                      ( 'th32AccessKey' , c_long ) ]
    
      class MODULEENTRY32(Structure):
          _fields_ = [ ( 'dwSize' , c_long ) ,
                      ( 'th32ModuleID' , c_long ),
                      ( 'th32ProcessID' , c_long ),
                      ( 'GlblcntUsage' , c_long ),
                      ( 'ProccntUsage' , c_long ) ,
                      ( 'modBaseAddr' , c_long ) ,
                      ( 'modBaseSize' , c_long ) ,
                      ( 'hModule' , c_void_p ) ,
                      ( 'szModule' , c_char * 256 ),
                      ( 'szExePath' , c_char * 260 ) ]
    
      TH32CS_SNAPPROCESS = 2
      TH32CS_SNAPMODULE = 0x00000008
    
      ## CreateToolhelp32Snapshot
      CreateToolhelp32Snapshot= windll.kernel32.CreateToolhelp32Snapshot
      CreateToolhelp32Snapshot.reltype = c_long
      CreateToolhelp32Snapshot.argtypes = [ c_int , c_int ]
      ## Process32First
      Process32First = windll.kernel32.Process32First
      Process32First.argtypes = [ c_void_p , POINTER( PROCESSENTRY32 ) ]
      Process32First.rettype = c_int
      ## Process32Next
      Process32Next = windll.kernel32.Process32Next
      Process32Next.argtypes = [ c_void_p , POINTER(PROCESSENTRY32) ]
      Process32Next.rettype = c_int
      ## CloseHandle
      CloseHandle = windll.kernel32.CloseHandle
      CloseHandle.argtypes = [ c_void_p ]
      CloseHandle.rettype = c_int
      ## Module32First
      Module32First = windll.kernel32.Module32First
      Module32First.argtypes = [ c_void_p , POINTER(MODULEENTRY32) ]
      Module32First.rettype = c_int
    
      hProcessSnap = c_void_p(0)
      hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS , 0 )
    
      pe32 = PROCESSENTRY32()
      pe32.dwSize = sizeof( PROCESSENTRY32 )
      ret = Process32First( hProcessSnap , pointer( pe32 ) )
      path = None
    
      while ret :
          if name + ".exe" == pe32.szExeFile:
              hModuleSnap = c_void_p(0)
              me32 = MODULEENTRY32()
              me32.dwSize = sizeof( MODULEENTRY32 )
              hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, pe32.th32ProcessID )
    
              ret = Module32First( hModuleSnap, pointer(me32) )
              path = me32.szExePath
              CloseHandle( hModuleSnap )
              if path:
                  break
          ret = Process32Next( hProcessSnap, pointer(pe32) )
      CloseHandle( hProcessSnap )
      return path
    
    

    参见 GitHub 源码

    用 Python 调用 C/C++ 代码

    推荐使用 Cython 生成并调用 pyd。

    其实 Python 基本实现就是 C 写的。#include <Python.h> 之后,其实就可以直接写 Python 的扩展了。例如 OpenCV 就是这么实现的,参见 GitHub 源码。但这需要对 Python 内部底层模型比较详细的了解,相对比较麻烦,工程量也比较大。而 Cython 的打包方式,相对简单直接得多。

    cos_pyd.pyx

    # distutils: language = c
    # distutils: sources =
    # distutils: include_dirs =
    # distutils: extra_compile_args =
    # distutils: extra_link_args =
    # distutils: libraries =
    
    cdef extern from 'math.h':
    double M_PI
    
    cdef extern from 'cos_exe.c':
    int cos_c(double alpha, double * cos_of_alpha)
    
    def cos_pyd(double alpha):
    cdef double cos_of_alpha
    cos_c(alpha, &cos_of_alpha)
    return cos_of_alpha
    
    

    pyx 是整个调用工程的组织文件,其中面向 distutils 声明了整个项目的组织参数。包含了所需的头文件与源代码文件。更是写清了对于 Python 这个 Python 包的组织。

    // source.c
    #include<math>
    
    int cos_c(double alpha, double * cos_of_alpha){
     double cos_of_alpha = 0;
     cos_of_alpha = cos(alpha);
     return 0;
    }
    
    

    这个 source.c 是示例的一部分,简单写清了输入输出,完成一个简单的功能。也可以拓展为多输入、多输出。

    # cos_build_and_app.py
    from distutils.core import setup
    from Cython.Build import cythonize
    
    setup(ext_modules=cythonize('cos_pyd.pyx'),
    script_args='build_ext -i -t .'.split(' '))
    
    import cos_pyd as c
    
    if __name__ == '__main__':
    result = c.cos_pyd(45)
    print('The cos of 45 is {0:.5f}.'.format(result))
    
    

    这个 py 文件,实现了 pyd 工程的编译和调用两个步骤。

    在 import 语句以前的上半部分,完成了一个 pyd 包的编译过程。编译后要保存好 pyd 文件,以后在使用的时候,需要把 pyd 放在 python 路径下。

    从 import 语句开始,就是一个典型 python 包的使用方法了。通过使用 pyd 就可以直接实现各种功能。

    也可以将 pyd 当作一种打包方式,打包发布供二次开发的代码。

    复杂工程的调用

    再复杂的工程,其实都可以用 C/C++ 封装起来,做成一个工程,然后用 Cython 编译成 pyd,都可以做成 python 包,被调用。简单的,其实可以直接用 C 打包,用 ctypes 调用。稍复杂的,再用 Cython。

    用 Python 调用 Java 代码

    简单的,可以使用 Jpype 包。

    安装:conda install -c conda-forge jpype1

    考虑到网络问题,可以从这里下载 whl:Unofficial Windows Binaries for Python Extension Packages,然后使用 pip install *.whl 安装。

    使用

    from jpype import *
    
    startJVM("C:/Program Files/Java/jdk1.8.0_144/jre/bin/server/jvm.dll", "-ea")
    
    # Java code here
    # Create a java.lang.String object
    javaPackage = JPackage("java.lang")
    javaClass = javaPackage.String
    javaObject = javaClass("Hello, Jpype")
    
    # print the object
    java.lang.System.out.println(javaObject)
    # call String.length() and Integer.toString() methods
    java.lang.System.out.println("This string's length: " +
    javaPackage.Integer.toString(javaObject.length()))
    
    shutdownJVM()
    
    

    (示例代码来源于:https://www.quora.com/Can-we-write-C-and-Java-in-Python,有修改)

    从调用方式中可以看出,其实也是调用 jvm 的 dll。

    复杂的,可以尝试使用 pyjnius。

    用 Python 调用 MATLAB 代码

    有原生工具,但本质上就是调用 matlab engine 的 dll。

    安装

    cd "matlabroot\extern\engines\python"
    python setup.py install
    
    

    调用

    import matlab.engine
    eng = matlab.engine.start_matlab()
    tf = eng.isprime(37)
    print(tf)
    # True
    
    

    具体请参考文档

    用 Python 调用 R 代码

    跟调用 exe 一样,只不过这个 exe 是 Rscript。

    # run_max.py
    import subprocess
    
    # 声明要调用的命令和参数
    command = 'Rscript'
    path2script = 'path/to your script/max.R'
    
    # 把参数(这里是数据)放在一个列表里
    args = [str(11), str(3), str(9), str(42)]
    
    # 生成要执行的命令
    cmd = [command, path2script] + args
    
    # check_output 执行命令,保存结果
    return_str = subprocess.check_output(cmd, universal_newlines=True)
    
    print('The maximum of the numbers is:', return_str)
    
    

    (示例代码来源于:https://www.mango-solutions.com/blog/integrating-python-and-r-part-ii-executing-r-from-python-and-vice-versa,有修改)

    总结

    Python 的开发,是以 C/C++ 为基础的,所以针对 C/C++ 的调用最为方便。其它程序、动态链接库、代码的调用,都可以通过 EXE、DLL、C/C++ 三种渠道之一实现。Python 还是比较适合粘合各种程序与代码的。

    当然,也就是比 Julia 差一点,(逃

    相关文章

      网友评论

        本文标题:如何用 Python 调用其他程序和代码

        本文链接:https://www.haomeiwen.com/subject/uwfdgftx.html