electron是node开发桌面应用的, 比如git for windows就是electron开发的, 桌面应用可以比网页还漂亮. 而python开发的界面真的很丑, 很多做python web开发的人也比较抵触做python桌面应用, 但是我们又没钱顾一个专门做桌面应用GUI的程序员, 所以electron就比较合适了. 我们可以用web前端的技术来做GUI.
安装全局依赖 
安装nodejs 
安装python3.5(作为开发环境) 
安装node-gyp(npm install -g node-gyp) 
安装windows下的编译环境(npm install --global --production windows-build-tools) 
 
你会发现安装windows-build-tools同时会把python2安装到你的%USERPROFILE%\.windows-build-tools\python27下, 想知道’USERPROFILE’指向哪里, 只需要在powershell中输入$env:USERPROFILE即可. 现在就需要让node-gyp知道你的python在哪里, 设置node-gyp --python /path/to/python2.7, 或者你用npm的方式调用, 需要设置npm config set python /path/to/executable/python2.7.
初始化项目 
创建一个项目文件夹 
初始化项目npm init 
创建文件夹py 
在文件夹py内部创建虚拟环境python -m venv .env 
在虚拟环境中安装依赖zerorpc和pyinstaller(python 方面比较简单, 不详细介绍) 
 
安装node依赖 
编辑package.json文件, 可以参考我的: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 {   "name" :  "python-electron" ,    "version" :  "1.0.0" ,    "description" :  "" ,    "main" :  "index.js" ,    "author" :  "" ,    "license" :  "ISC" ,    "scripts" :  {      "start" :  ".\\node_modules\\.bin\\electron ." ,      "rebuild" :  ".\\node_modules\\.bin\\electron-rebuild.cmd"    } ,    "devDependencies" :  {      "electron" :  "^1.7.10" ,      "electron-rebuild" :  "^1.6.0"    } ,    "dependencies" :  {      "zerorpc" :  "^0.9.7"    }  } 
配置npm 
在项目根目录下创建.npmrc, 然后输入如下内容: 
 
1 2 3 4 npm_config_target="1.7.10" # electron version npm_config_runtime="electron" # 为elelctron编译 npm_config_disturl="https://atom.io/download/electron" # 资源下载地址 npm_config_build_from_source="true" # 从源码编译 
源码编译安装依赖 
npm install
这个过程会安装所有列在package.json中的库.
开发测试程序 
python部分 
这是一个简单的API, 主要功能是把前端发来的字符串经过python的eval处理. 测试程序是否能跑起来, 只需要激活你的python虚拟环境, 然后python ./py/api.py即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from  __future__ import  print_functionimport  sysimport  zerorpcclass  CalcApi (object ):    def  eval (self, text ):         """based on the input text, return the int result"""          try :             return  eval (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:{}' .format (parse_port())     s = zerorpc.Server(CalcApi())     s.bind(addr)     print ('start running on {}' .format (addr))     s.run() if  __name__ == '__main__' :    main() 
electron部分 
主线程程序, 主要是创建窗口并启动python.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 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 ()   } }) let  pyProc = null let  pyPort = null const  selectPort  = (  pyPort = 4242    return  pyPort } const  createPyProc  = (  console .log ('creating python server...' )   let  port = ''  + selectPort ()   let  script = path.join (__dirname, 'py' , 'api.py' )   let  pypath = path.join (__dirname, 'py' ,'.env' ,'scripts' ,'python.exe' )   pyProc = require ('child_process' ).spawn (pypath, [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, 就是实现了一个简单的输入框.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!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 ('./render.js' )    </script > </html > 
功能模块render.js, 这部分是响应用户操作并与python通讯的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 ("eval" , formula.value , (error, res ) =>  {     if (error) {       console .error (error)     } else  {       result.textContent  = res     }   }) }) formula.dispatchEvent (new  Event ('input' )) 
启动程序 
如果你看我写的package.json文件, 我已经写好了一个启动命令.
打包文件 
前面我们已经安装了python的打包工具pyinstaller, 所以现在我们在package.json文件中加一条命令即可:
1 "build-python":"pyinstaller ./py/api.py --clean --distpath ./pydist" 
现在只要使用命令npm run build-python即可.
于是在项目根目录下生成了一个pydist文件夹, pydist/api/目录结构如下, 虽然文件多, 但是我们只要知道, api.exe是程序入口即可. 下面可以打包到electron程序里.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 (.env) PS E:\programs\python-electron> ls .\pydist\api     目录: E:\programs\python-electron\pydist\api Mode                LastWriteTime         Length Name ----                -------------         ------ ---- d-----        2018/1/10     16:45                Include d-----        2018/1/10     16:45                tcl d-----        2018/1/10     16:45                tk d-----        2018/1/10     16:45                zmq -a----        2018/1/10     16:45          11616 api-ms-win-core-file-l1-2-0.dll -a----        2018/1/10     16:45          11616 api-ms-win-core-file-l2-1-0.dll -a----        2018/1/10     16:45          14176 api-ms-win-core-localization-l1-2-0.dll -a----        2018/1/10     16:45          12128 api-ms-win-core-processthreads-l1-1-1.dll -a----        2018/1/10     16:45          12128 api-ms-win-core-synch-l1-2-0.dll -a----        2018/1/10     16:45          11616 api-ms-win-core-timezone-l1-1-0.dll -a----        2018/1/10     16:45          12640 api-ms-win-crt-conio-l1-1-0.dll -a----        2018/1/10     16:45          15712 api-ms-win-crt-convert-l1-1-0.dll -a----        2018/1/10     16:45          12128 api-ms-win-crt-environment-l1-1-0.dll -a----        2018/1/10     16:45          13664 api-ms-win-crt-filesystem-l1-1-0.dll -a----        2018/1/10     16:45          12640 api-ms-win-crt-heap-l1-1-0.dll -a----        2018/1/10     16:45          12128 api-ms-win-crt-locale-l1-1-0.dll -a----        2018/1/10     16:45          20832 api-ms-win-crt-math-l1-1-0.dll -a----        2018/1/10     16:45          19808 api-ms-win-crt-multibyte-l1-1-0.dll -a----        2018/1/10     16:45          12640 api-ms-win-crt-process-l1-1-0.dll -a----        2018/1/10     16:45          16224 api-ms-win-crt-runtime-l1-1-0.dll -a----        2018/1/10     16:45          17760 api-ms-win-crt-stdio-l1-1-0.dll -a----        2018/1/10     16:45          17760 api-ms-win-crt-string-l1-1-0.dll -a----        2018/1/10     16:45          14176 api-ms-win-crt-time-l1-1-0.dll -a----        2018/1/10     16:45          12128 api-ms-win-crt-utility-l1-1-0.dll -a----        2018/1/10     16:44        2481852 api.exe -a----        2018/1/10     16:45           1028 api.exe.manifest -a----        2018/1/10     16:44         765325 base_library.zip -a----        2018/1/10     16:45         285696 gevent.libev.corecext.pyd -a----        2018/1/10     16:45          80896 gevent._semaphore.pyd -a----        2018/1/10     16:45          29184 greenlet.pyd -a----        2018/1/10     16:45          59904 msgpack._packer.pyd -a----        2018/1/10     16:45          75776 msgpack._unpacker.pyd -a----        2018/1/10     16:45         633152 MSVCP140.dll -a----        2018/1/10     16:45         829264 MSVCR100.dll -a----        2018/1/10     16:45         189952 pyexpat.pyd -a----        2018/1/10     16:45        3935744 python35.dll -a----        2018/1/10     16:45         138752 pywintypes35.dll -a----        2018/1/10     16:45          19968 select.pyd -a----        2018/1/10     16:45        1640960 tcl86t.dll -a----        2018/1/10     16:45        1954304 tk86t.dll -a----        2018/1/10     16:45        1004136 ucrtbase.dll -a----        2018/1/10     16:45         865792 unicodedata.pyd -a----        2018/1/10     16:45          87888 VCRUNTIME140.dll -a----        2018/1/10     16:45         124416 win32api.pyd -a----        2018/1/10     16:45          62464 win32evtlog.pyd -a----        2018/1/10     16:45          29696 win32wnet.pyd -a----        2018/1/10     16:45          55296 zmq.backend.cython.constants.pyd -a----        2018/1/10     16:45          60928 zmq.backend.cython.context.pyd -a----        2018/1/10     16:45          30720 zmq.backend.cython.error.pyd -a----        2018/1/10     16:45          77312 zmq.backend.cython.message.pyd -a----        2018/1/10     16:45         119808 zmq.backend.cython.socket.pyd -a----        2018/1/10     16:45          34816 zmq.backend.cython.utils.pyd -a----        2018/1/10     16:45          46592 zmq.backend.cython._device.pyd -a----        2018/1/10     16:45          53248 zmq.backend.cython._poll.pyd -a----        2018/1/10     16:45          26624 zmq.backend.cython._version.pyd -a----        2018/1/10     16:45          87552 _bz2.pyd -a----        2018/1/10     16:45         122368 _ctypes.pyd -a----        2018/1/10     16:45         314880 _decimal.pyd -a----        2018/1/10     16:45        1443840 _hashlib.pyd -a----        2018/1/10     16:45         146432 _lzma.pyd -a----        2018/1/10     16:45          22528 _multiprocessing.pyd -a----        2018/1/10     16:45          66048 _socket.pyd -a----        2018/1/10     16:45        2045440 _ssl.pyd -a----        2018/1/10     16:45          80896 _testcapi.pyd -a----        2018/1/10     16:45          58368 _tkinter.pyd 
打包electron部分 
这里有一些代码需要修改. 因为之前启动python程序是直接用虚拟环境中的python调用api.py文件, 现在得调用pydist/api/api.exe.
之前的代码是:
1 2 3 4 5 6 7 8 9 10 const  createPyProc  = (  console .log ('creating python server...' )   let  port = ''  + selectPort ()   let  script = path.join (__dirname, 'py' , 'api.py' )   let  pypath = path.join (__dirname, 'py' ,'.env' ,'scripts' ,'python.exe' )   pyProc = require ('child_process' ).spawn (pypath, [script, port])   if  (pyProc != null ) {     console .log ('child process success' )   } } 
改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const  NODE_ENV  = 'production' const  createPyProc  = (  console .log ('creating python server...' )   let  port = ''  + selectPort ()   if  (NODE_ENV  ==='development' ){     let  script = path.join (__dirname, 'py' , 'api.py' )     let  pypath = path.join (__dirname, 'py' , '.env' , 'scripts' , 'python.exe' )     pyProc = require ('child_process' ).spawn (pypath, [script, port])   }else {     let  exePath = path.join (__dirname, 'pydist' , 'api' ,'api.exe' )     pyProc=require ('child_process' ).execFile (exePath, [port])   }   if  (pyProc != null ) {     console .log ('child process success' )   } } 
然后, 你需要关闭dev tools, 为了方便调试, 我们在程序中调用了mainWindow.webContents.openDevTools, 同样是加一个判断即可.
1 2 3 if (NODE_ENV ==='development' ){	mainWindow.webContents .openDevTools () } 
然后, 你启动一下看是否有问题, 按道理是没有问题的.
最后就是打包electron, 用到了electron-packager, 我还是把打包命令写在了package.json中:
1 2 "pack-app": "./node_modules/.bin/electron-packager . --overwrite --ignore=py$ --ignore=\\.env --ignore=\\.vscode --ignore=old-post-backup" 
安装过程可能出现的问题 
安装electron出现网络超时 
1 2 3 4 5 /Users/chenlei/node_modules/electron/install.js:47   throw err   ^ Error: connect ETIMEDOUT 54.231.34.41:443 
你需要设置淘宝镜像, 在powershell中运行:
$env:ELECTRON_MIRROR=“http://npm.taobao.org/mirrors/electron/ ”
总是编译失败 
可能是网络原因, 你需要翻墙或者找个网络好的地方 
可能是配置有问题, 我在这里列出我的环境 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 PS E:\programs\python-electron> npm config ls ; cli configs metrics-registry = "https://registry.npm.taobao.org/" scope = "" user-agent = "npm/5.5.1 node/v8.9.3 win32 x64" ; project config E:\programs\python-electron\.npmrc npm_config_build_from_source = true npm_config_disturl = "https://atom.io/download/electron" npm_config_runtime = "electron" npm_config_target = "1.7.10" ; userconfig C:\Users\wangluobu\.npmrc msvs_version = "2015" python = "C:\\Users\\wangluobu\\.windows-build-tools\\python27" registry = "https://registry.npm.taobao.org/" ; node bin location = C:\Program Files\nodejs\v8\node.exe ; cwd = E:\programs\python-electron ; HOME = C:\Users\wangluobu ; "npm config ls -l" to show all defaults. 
结束语 
有什么问题请在下方留言. 你可以下载我打包好的程序试一试, 只不过比较大, 我放在网盘 里了.