退出

  • 文章收藏

  • 消息

  • 修改资料

  • 该文章原文为《Electron as GUI of Python Applications》, 原文链接在文末标明,本站保留译文版权。

    这篇文章展示了如何用 Electron 作为 Python 应用的 GUI 部分(是我之前文章的一个更新)。前后端之间的通信通过 zerorpc 来实现。完整的代码发布在 Github

    原文及争论

    注意

    这篇文章是我几年前所写的一篇文章的更新版本。如果你没有读过那篇文章,你也没有必要去读。

    争论

    我没有想到之前那篇文章吸引了那么多的读者。有些人还将它发布在 Haker News 和 Reddit 上。同样也有很多对它的批评(criticisms)。针对这些争论,我想分享一下我的回复。

    你不知道 Tkinter, GTK, QT(PySide 和 PyQT), wxPython, Kivy, thrust, ...?

    当然,至少我知道它们的存在,而且试过它们中间的几个。我仍然认为 QT 是它们中间最好的一个。另外,pyotherside 也是一个活跃的 Python 绑定。我只是在这里提供另外一种“Web技术驱动”的方法。

    ...还有 cefpython。

    或多或少,相比起 Electron,它处在更底层的位置。例如,PySide 就是基于它的。

    我可以直接用 Javascript 来写!

    是的。除非有些库——例如 numpy——JS 里没有。另外,我们关注的是如何用 Electron / Javascript / web 技术来改善Python应用。

    我可以用 QT WebEngine。

    那就去试试吧。不过既然你都用了“web 引擎”,干嘛不给 Electron 一个机会呢?

    你有两个运行时!

    是的。一个 Javascript 的,一个 Python 的。没办法,Python 和 Javascript 都是动态语言,很多时候都需要运行时的支持。

    架构和选项

    在之前的文章中,我展示了一个架构的示例:通过 Python 构建一个本地服务器,而Electron 则作为一个本地浏览器。

    start
     |
     V
    +------------+
    |            | start
    |            +-------------> +-------------------+
    |  electron  | sub process   |                   |
    |            |               | python web server |
    | (basically |     http      |                   |
    |  browser)  | <-----------> | (business logic)  |
    |            | communication |                   |
    |            |               | (all html/css/js) |
    |            |               |                   |
    +------------+               +-------------------+
    

    这是一个不太优雅(not-so-efficient)的解决办法。

    让我们重新思考一下我们的核心需求:我们有一个Python应用,和一个Node.js应用(Electron)。如何将它们结合起来,并让它们能够彼此通信?

    我们事实上需要一种跨进程通信(IPC, interprocess Communication)机制。这是无法避免的,除非 Python 和 javascript 互相可以外部调用。

    HTTP 只是流行的IPC方式中的一种,而且仅仅是在写上篇文章的时候第一个跑到我脑子里而已。

    我们有更多的选择。

    我们可以(而且应该)使用 socket。然后,在此基础上,我们需要一个抽象的消息层(messaging layer),可以用 ZeroMq 部署,因为它是最好的消息库(messaging libraries)之一。更进一步,我们需要再原始数据之上定义一些 schema,这可以由zerorpc实现。

    (幸运的是,zerorpc符合我们的需求,因为它支持 Python 和 Node.js。如果需要支持更多语言,你可以看看 gRPC。)

    因此,再这篇文章中,我将展示一个用 zerorpc 通信的例子,这个例子将比我之前展示的方法更高效。

    start
     |
     V
    +--------------------+
    |                    | start
    |  electron          +-------------> +------------------+
    |                    | sub process   |                  |
    | (browser)          |               | python server    |
    |                    |               |                  |
    | (all html/css/js)  |               | (business logic) |
    |                    |   zerorpc     |                  |
    | (node.js runtime,  | <-----------> | (zeromq server)  |
    |  zeromq client)    | communication |                  |
    |                    |               |                  |
    +--------------------+               +------------------+

    准备

    注意,这个示例可以成功在以下环境运行:Windows 10,Python 3.6,Electron 1.7,Node.js v6。

    我们需要用到:python 应用,pip,node,npm,并能通过命令行使用。为了使用zerorpc,我们还需要C/C++编译器(cc 和 c++ 的命令行工具,以及 Windows 上的 MSVC)。

    这个project的架构是:

    .
    |-- index.html
    |-- main.js
    |-- package.json
    |-- renderer.js
    |
    |-- pycalc
    |   |-- api.py
    |   |-- calc.py
    |   `-- requirements.txt
    |
    |-- LICENSE
    `-- README.md

    正如上图所示,Python应用被放置在一个子文件夹(subfolder)里。在这个例子中,Python 应用 pycalc/calc.py 提供以下功能:calc(text),可以接受一段文本,例如 1 + 1/2,并返回结果,例如1.5。pycalc/api.py就是我们的目标。

    index.html, main.js, package.json 和 renderer.js 都是从 electron-quick-start 修改而来。

    Python的部分

    首先,我们已经可以运行Python应用,那么Python环境应该是没问题的。我强烈建议你们使用 virtualenv 来开发 Python 应用。

    试着安装 zerorpc 和 pyinstaller。

    pip install zerorpc
    pip install pyinstaller
    

    如果成功的话,上述命令应该不会出现问题。否则请到网上寻找解决办法(guide)。

    Node.js / Electron 的部分

    第二步,试着配置 Node.js 和 Electron 环境。我假设 node 和 npm 已经能通过 command  line 使用,并且是最新版本的。

    我们需要配置 package.json,  特别是 main 这个入口:

    {
      "name": "pretty-calculator",
      "main": "main.js",
      "scripts": {
        "start": "electron ."
      },
      "dependencies": {
        "zerorpc": "git+https://github.com/fyears/zerorpc-node.git"
      },
      "devDependencies": {
        "electron": "^1.7.6",
        "electron-packager": "^9.0.1"
      }
    }

    清除缓存:

    # On Linux / OS X
    # clean caches, very important!!!!!
    rm -rf ~/.node-gyp
    rm -rf ~/.electron-gyp
    rm -rf ./node_modules
    # On Window PowerShell (not cmd.exe!!!)
    # clean caches, very important!!!!!
    Remove-Item "$($env:USERPROFILE)\.node-gyp" -Force -Recurse -ErrorAction Ignore
    Remove-Item "$($env:USERPROFILE)\.electron-gyp" -Force -Recurse -ErrorAction Ignore
    Remove-Item .\node_modules -Force -Recurse -ErrorAction Ignore

    (译者注:如果在 Windows 中执行上述命令,提示 Ignore 不是有效的枚举值,可以将该值更改为 SilentlyContinue 后执行。因为 Ignore 是在 Powershell 3.0 加入的。参考微软官方文档

    然后运行npm:

    # 1.7.6 is the version of electron
    # It's very important to set the electron version correctly!!!
    # check out the version value in your package.json
    npm install --runtime=electron --target=1.7.6
    
    # verify the electron binary and its version by opening it
    ./node_modules/.bin/electron

    Npm install 将会从我的复刻安装 zerorpc-node,这样就可以不用从源代码编译了。

    (如果需要,在项目文件夹中添加 ./.npmrc 文件夹)

    现在所有的库都应该部署完毕了。

    可选:从源码编译

    如果上面的安装方式出现了错误,即使你设置了正确的 electron 版本,我们也许只能从源码来编译了。

    讽刺的是,为了编译 Node.js C/C++  native code,我们必须配置 python2,不管你的 Python 应用用的是什么版本。你可以查看官方说明

    特别是,如果你的工作环境是 Windows,用管理员权限打开 PowerShell,运行 npm install --global --production windows-build-tools  来在 %USERPROFILE%\.windows-build-tools\python27 中安装一个独立的 Python 2.7 以及其他所需的 VS 库。我们只需要使用这一次。

    接下来,按照上面所说的方法清理缓存。

    设定好 npm 的版本,并且安装好所需的库。

    配置环境变量:

    # On Linux / OS X:
    
    # env
    export npm_config_target=1.7.6 # electron version
    export npm_config_runtime=electron
    export npm_config_disturl=https://atom.io/download/electron
    export npm_config_build_from_source=true
    
    # may not be necessary
    #export npm_config_arch=x64
    #export npm_config_target_arch=x64
    
    npm config ls
    # On Window PowerShell (not cmd.exe!!!)
    
    $env:npm_config_target="1.7.6" # electron version
    $env:npm_config_runtime="electron"
    $env:npm_config_disturl="https://atom.io/download/electron"
    $env:npm_config_build_from_source="true"
    
    # may not be necessary
    #$env:npm_config_arch="x64"
    #$env:npm_config_target_arch="x64"
    
    npm config ls

    接下来安装:

    # in the same shell as above!!!
    # because you want to make good use of the above environment variables
    
    # install everything based on the package.json
    npm install
    
    # verify the electron binary and its version by opening it
    ./node_modules/.bin/electron

    (如果需要,在项目文件夹中添加 ./.npmrc 文件夹)

    核心功能

    Python 部分

    我们要再 Python 端建立一个 ZeroMQ  服务器。

    将 calc.py 放到 pycalc/ 文件夹中。然后在这个路径下再创建一个 pycalc/api.py。查看 zerorpc-python 作为参考。

    from __future__ import print_function
    from calc import calc as real_calc
    import sys
    import zerorpc
    
    class CalcApi(object):
        def calc(self, text):
            """based on the input text, return the int result"""
            try:
                return real_calc(text)
            except Exception as e:
                return 0.0
        def echo(self, text):
            """echo any text"""
            return text
    
    def parse_port():
        return 4242
    
    def main():
        addr = 'tcp://127.0.0.1:' + parse_port()
        s = zerorpc.Server(CalcApi())
        s.bind(addr)
        print('start running on {}'.format(addr))
        s.run()
    
    if __name__ == '__main__':
        main()

    为了测试,在终端运行 python pycalc/api.py。然后打开另一个终端,运行下面的命令,查看结果:

    zerorpc tcp://localhost:4242 calc "1 + 1"
    ## connecting to "tcp://localhost:4242"
    ## 2.0

    结束调试后,记得终止 Python 程序。

    事实上,这只是另外一个服务器,通过构建于 TCP 之上的 zeromq 进行通信,而不是构建于 HTTP 之上的传统 web 服务器。

    Node.js / Electron 部分

    基本思想:在主进程(main process)中,引用 Python 作为子进程(child process),并构建窗口。在渲染进程中,使用 Node.js 运行时和 zerorpc 库来 Python 子进程通信。所有的 HTML / Javascript / CSS 都由 Electron 管理,而不是 Python web 服务器(像我之前那个例子里一样)。

    在 main.js 中,这些是默认的一些初始代码,没什么特别的:

    // main.js
    
    const electron = require('electron')
    const app = electron.app
    const BrowserWindow = electron.BrowserWindow
    const path = require('path')
    
    let mainWindow = null
    const createWindow = () => {
      mainWindow = new BrowserWindow({width: 800, height: 600})
      mainWindow.loadURL(require('url').format({
        pathname: path.join(__dirname, 'index.html'),
        protocol: 'file:',
        slashes: true
      }))
      mainWindow.webContents.openDevTools()
      mainWindow.on('closed', () => {
        mainWindow = null
      })
    }
    app.on('ready', createWindow)
    app.on('window-all-closed', () => {
      if (process.platform !== 'darwin') {
        app.quit()
      }
    })
    app.on('activate', () => {
      if (mainWindow === null) {
        createWindow()
      }
    })

    我们要加上一些代码来运行 Python 子进程:

    // add these to the end or middle of main.js
    
    let pyProc = null
    let pyPort = null
    
    const selectPort = () => {
      pyPort = 4242
      return pyPort
    }
    
    const createPyProc = () => {
      let port = '' + selectPort()
      let script = path.join(__dirname, 'pycalc', 'api.py')
      pyProc = require('child_process').spawn('python', [script, port])
      if (pyProc != null) {
        console.log('child process success')
      }
    }
    
    const exitPyProc = () => {
      pyProc.kill()
      pyProc = null
      pyPort = null
    }
    
    app.on('ready', createPyProc)
    app.on('will-quit', exitPyProc)

    在 index.html 中,我们有一个 <input> 来接收用户输入,还有一个 <div> 负责输出:

    <!-- index.html -->
    <!DOCTYPE html>
    <html>
     <head>
     <meta charset="UTF-8">
     <title>Hello Calculator!</title>
     </head>
     <body>
     <h1>Hello Calculator!</h1>
     <p>Input something like <code>1 + 1</code>.</p>
     <p>This calculator supports <code>+-*/^()</code>,
     whitespaces, and integers and floating numbers.</p>
     <input id="formula" value="1 + 2.0 * 3.1 / (4 ^ 5.6)"></input>
     <div id="result"></div>
     </body>
     <script>
     require('./renderer.js')
     </script>
    </html>

    在 renderer.js 中,我们有一些用来初始化 zerorpc 客户端的代码,以及查看输入变化的代码。当用户输入一些公式的时候,JS 会发送这些文字到 Python 后端,并返回计算结果。

    // renderer.js
    
    const zerorpc = require("zerorpc")
    let client = new zerorpc.Client()
    client.connect("tcp://127.0.0.1:4242")
    
    let formula = document.querySelector('#formula')
    let result = document.querySelector('#result')
    formula.addEventListener('input', () => {
      client.invoke("calc", formula.value, (error, res) => {
        if(error) {
          console.error(error)
        } else {
          result.textContent = res
        }
      })
    })
    formula.dispatchEvent(new Event('input'))

    运行

    运行下面的命令,神奇的事发生了:

    ./node_modules/.bin/electron .

    Awesome!

    如果出现错误,类似动态链接错误,试着清除缓存,然后重新安装库:

    rm -rf node_modules
    rm -rf ~/.node-gyp ~/.electron-gyp
    
    npm install

    打包

    有人问我应该怎么打包。这很简单:运用如何打包 Python 应用以及 Electron 应用的知识。

    Python 部分

    使用 PyInstaller。

    在终端运行以下命令:

    pyinstaller pycalc/api.py --distpath pycalcdist
    
    rm -rf build/
    rm -rf api.spec

    如果一切正常的话,会生成 pycalcdist/api/ 文件夹,包含了一个 exe 执行文件。这是完全独立的 Python exe,可以移植到其它地方。

    注意:你必须生成这个独立的 exe 执行文件,因为其它电脑上可能没有运行这些代码所需的 python 环境和所需的库。直接把 Python 代码复制到别的地方是不行的。

    Node.js / Electron 部分

    这有点难,因为我们将 Python 打包成了 exe。

    在上面的代码中,我写过:

     // part of main.js
      let script = path.join(__dirname, 'pycalc', 'api.py')
      pyProc = require('child_process').spawn('python', [script, port])

    然而,当我们把 Python 打包后,我们就不能再对 Python 代码使用 spawn 方法。取而代之的是,我们必须使用 execFile 方法来运行 exe 文件。
    Electron 不知道应用是否已经被分发出去了,所以在 mail.js 里,我加上了这段函数:

    // main.js
    
    const PY_DIST_FOLDER = 'pycalcdist'
    const PY_FOLDER = 'pycalc'
    const PY_MODULE = 'api' // without .py suffix
    
    const guessPackaged = () => {
      const fullPath = path.join(__dirname, PY_DIST_FOLDER)
      return require('fs').existsSync(fullPath)
    }
    
    const getScriptPath = () => {
      if (!guessPackaged()) {
        return path.join(__dirname, PY_FOLDER, PY_MODULE + '.py')
      }
      if (process.platform === 'win32') {
        return path.join(__dirname, PY_DIST_FOLDER, PY_MODULE, PY_MODULE + '.exe')
      }
      return path.join(__dirname, PY_DIST_FOLDER, PY_MODULE, PY_MODULE)
    }

    然后修改 createPyProc 方法:

    // main.js
    // the improved version
    const createPyProc = () => {
      let script = getScriptPath()
      let port = '' + selectPort()
    
      if (guessPackaged()) {
        pyProc = require('child_process').execFile(script, [port])
      } else {
        pyProc = require('child_process').spawn('python', [script, port])
      }
    
      if (pyProc != null) {
        //console.log(pyProc)
        console.log('child process success on port ' + port)
      }
    }

    关键点是,检查 *dist 文件夹是否被生成。如果生成了,那么说明我们在生产模式下,那么就直接运行 exe 文件;如果没有,我们就需要对 Python 代码用 spawn 方法,在 Python shell 里面运行。

    最后,运行 Electron-Packager 来生成最终的应用。我们需要排除一些文件夹,例如,pycalc/ 已经不需要了。应用的名称,平台等等需要在 package.json 里面配置。请参阅相关文档。

    # we need to make sure we have bundled the latest Python code
    # before running the below command!
    # Or, actually, we could bundle the Python executable later,
    # and copy the output into the correct distributable Electron folder...
    
    ./node_modules/.bin/electron-packager . --overwrite --ignore="pycalc$" --ignore="\.venv" --ignore="old-post-backup"
    ## Packaging app for platform win32 x64 using electron v1.7.6
    ## Wrote new app to ./pretty-calculator-win32-x64

    最后,我们就得到了最终生成的应用!对我来说,结果在 ./pretty-calculator-win32-x64 里面。在我的电脑上,大小是 170MB 左右。我也试着压缩了一下,生成的 .7z 文件是 43.3MB 左右。

    将最后生成的文件移植到其它电脑上看看结果吧!

    支付宝 微信 BTC
    支付宝扫一扫,向我打赏
    来源:Github

    声明:本站原创文章采用 BY-NC-SA 创作共用协议,转载时请以链接形式标明本文地址;非原创(转载)文章版权归原作者所有。 ©查看版权声明

  • 白銀の魔法師
  • 所有的信徒都别无二致,所有的信仰都一文不值
  • 发表评论

    你目前的身份是游客,请输入昵称和邮箱! 输入资料 关闭

    1条评论

    1. Original work一
      free live wallpapers for android phone free download of sexy videos android free widgets games apps for android download www gemas dawnlod com
      http://games.android.telrock.net/?mail.perla
      android market share downloads games free for mobile the sexy photo asusa zenfone 2 best android apps for dating