脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Python - 关于Pyinstaller打包eel和pygame需要注意的坑

关于Pyinstaller打包eel和pygame需要注意的坑

2022-09-20 18:04DeadPool loves Star Python

这篇文章主要介绍了关于Pyinstaller打包eel和pygame需要注意的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

这几天我们团队用python做了一个游戏,引擎用的pygame,UI界面用的eel(一个python库,用于利用HTML开发桌面应用程序,主要是因为QT机制太过复杂,而博主Deadpool不愿做费力不讨好的事,import一个eel,便可通过HTML、CSS、JS开发桌面应用程序,这简直不要太爽,另外,关于eel的使用也许我会在后续文章中写到)

1、打开cmd / Visual Studio等等可以装包的工具下载pyinstaller

如果是python环境,那么:pip install pyinstaller;如果是conda环境,那么:conda install pyinstaller

2、找到你的python_main程序

假设项目根目录为F:\xx\xx\xx,该目录的文件管理如下:

?
1
2
3
4
5
6
- Web(包括HTML、CSS、JS)
- 游戏资源文件夹 (包括图片巴拉巴拉的)
- Class1.py (Class1中导入Class3)
- Class2.py
- Class3.py
- Python_Main.py (Python_Main中导入Class1,Class2和eel)

3、查看eel库

eel库一般在site-pakages里

关于Pyinstaller打包eel和pygame需要注意的坑

打开__main__.py文件

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#eel的__main__.py文件
from __future__ import print_function
import sys
import pkg_resources as pkg
import PyInstaller.__main__ as pyi
import os
args = sys.argv[1:]
main_script = args.pop(0)
web_folder = args.pop(0)
print("Building executable with main script '%s' and web folder '%s'...\n" %
      (main_script, web_folder))
eel_js_file = pkg.resource_filename('eel', 'eel.js')
js_file_arg = '%s%seel' % (eel_js_file, os.pathsep)
web_folder_arg = '%s%s%s' % (web_folder, os.pathsep, web_folder)
needed_args = ['--hidden-import', 'bottle_websocket',
               '--add-data', js_file_arg, '--add-data', web_folder_arg]
full_args = [main_script] + needed_args + args
print('Running:\npyinstaller', ' '.join(full_args), '\n')
pyi.run(full_args)

注意到print(“Building executable with main script ‘%s’ and web folder ‘%s’…\n” %

(main_script, web_folder))和print(‘Running:\npyinstaller’, ’ '.join(full_args), ‘\n’)可以知道eel的打包原理还是利用pyinstaller

4、打开cmd开始将含有eel的python_main打包起来

?
1
python -m eel F:\xx\xx\xx\Python_Main.py F:\xx\xx\xx\Web

这里暂时不加-w -f -i参数。(因为无论如何必然会出错) 报错,提示找不到eel.js文件。呼呼,我就奇怪了,eel.js文件不就在那儿摆着吗。打开eel的__init__.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
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
from __future__ import print_function   # Python 2 compatibility stuff
from builtins import range
from io import open
import gevent as gvt
import json as jsn
import bottle as btl
import bottle.ext.websocket as wbs
import re as rgx
import os
import eel.browsers as brw
import random as rnd
import sys
import pkg_resources as pkg
import socket
_eel_js_file = pkg.resource_filename('eel', 'eel.js')
_eel_js = open(_eel_js_file, encoding='utf-8').read()
    
_websockets = []
_call_return_values = {}
_call_return_callbacks = {}
_call_number = 0
_exposed_functions = {}
_js_functions = []
_mock_queue = []
_mock_queue_done = set()
# All start() options must provide a default value and explanation here
_start_args = {
    'mode':             'chrome',                   # What browser is used
    'host':             'localhost',                # Hostname use for Bottle server
    'port':             8000,                       # Port used for Bottle server (use 0 for auto)
    'block':            True,                       # Whether start() blocks calling thread
    'jinja_templates'None,                       # Folder for jinja2 templates
    'cmdline_args':     ['--disable-http-cache'],   # Extra cmdline flags to pass to browser start
    'size':             None,                       # (width, height) of main window
    'position':         None,                       # (left, top) of main window
    'geometry':         {},                         # Dictionary of size/position for all windows
    'close_callback':   None,                       # Callback for when all windows have closed
    'app_mode'True,                              # (Chrome specific option)
    'all_interfaces': False,                        # Allow bottle server to listen for connections on all interfaces
    'disable_cache': True,                          # Sets the no-store response header when serving assets
    'app': btl.default_app(),                       # Allows passing in a custom Bottle instance, e.g. with middleware
}
# == Temporary (suppressable) error message to inform users of breaking API change for v1.0.0 ===
_start_args['suppress_error'] = False
api_error_message = '''
----------------------------------------------------------------------------------
  'options' argument deprecated in v1.0.0, see https://github.com/ChrisKnott/Eel
  To suppress this error, add 'suppress_error=True' to start() call.
  This option will be removed in future versions
----------------------------------------------------------------------------------
'''
# ===============================================================================================
# Public functions
def expose(name_or_function=None):
    # Deal with '@eel.expose()' - treat as '@eel.expose'
    if name_or_function is None:
        return expose
    if type(name_or_function) == str:   # Called as '@eel.expose("my_name")'
        name = name_or_function
        def decorator(function):
            _expose(name, function)
            return function
        return decorator
    else:
        function = name_or_function
        _expose(function.__name__, function)
        return function
def init(path, allowed_extensions=['.js', '.html', '.txt', '.htm',
                                   '.xhtml', '.vue']):
    global root_path, _js_functions
    root_path = _get_real_path(path)
    js_functions = set()
    for root, _, files in os.walk(root_path):
        for name in files:
            if not any(name.endswith(ext) for ext in allowed_extensions):
                continue
            try:
                with open(os.path.join(root, name), encoding='utf-8') as file:
                    contents = file.read()
                    expose_calls = set()
                    finder = rgx.findall(r'eel\.expose\(([^\)]+)\)', contents)
                    for expose_call in finder:
                        # If name specified in 2nd argument, strip quotes and store as function name
                        if ',' in expose_call:
                            expose_call = rgx.sub(r'["\']', '', expose_call.split(',')[1])
                        expose_call = expose_call.strip()
                        # Verify that function name is valid
                        msg = "eel.expose() call contains '(' or '='"
                        assert rgx.findall(r'[\(=]', expose_call) == [], msg
                        expose_calls.add(expose_call)
                    js_functions.update(expose_calls)
            except UnicodeDecodeError:
                pass    # Malformed file probably
    _js_functions = list(js_functions)
    for js_function in _js_functions:
        _mock_js_function(js_function)
def start(*start_urls, **kwargs):
    _start_args.update(kwargs)
    if 'options' in kwargs:
        if _start_args['suppress_error']:
            _start_args.update(kwargs['options'])
        else:
            raise RuntimeError(api_error_message)
    if _start_args['port'] == 0:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind(('localhost', 0))
        _start_args['port'] = sock.getsockname()[1]
        sock.close()
    if _start_args['jinja_templates'] != None:
        from jinja2 import Environment, FileSystemLoader, select_autoescape
        templates_path = os.path.join(root_path, _start_args['jinja_templates'])
        _start_args['jinja_env'] = Environment(loader=FileSystemLoader(templates_path),
                                 autoescape=select_autoescape(['html', 'xml']))
    # Launch the browser to the starting URLs
    show(*start_urls)
    def run_lambda():
        if _start_args['all_interfaces'] == True:
            HOST = '0.0.0.0'
        else:
            HOST = _start_args['host']
        app = _start_args['app'# type: btl.Bottle
        for route_path, route_params in BOTTLE_ROUTES.items():
            route_func, route_kwargs = route_params
            app.route(path=route_path, callback=route_func, **route_kwargs)
        return btl.run(
            host=HOST,
            port=_start_args['port'],
            server=wbs.GeventWebSocketServer,
            quiet=True,
            app=app)
    # Start the webserver
    if _start_args['block']:
        run_lambda()
    else:
        spawn(run_lambda)
def show(*start_urls):
    brw.open(start_urls, _start_args)
def sleep(seconds):
    gvt.sleep(seconds)
def spawn(function, *args, **kwargs):
    gvt.spawn(function, *args, **kwargs)
# Bottle Routes
def _eel():
    start_geometry = {'default': {'size': _start_args['size'],
                                  'position': _start_args['position']},
                      'pages':   _start_args['geometry']}
    page = _eel_js.replace('/** _py_functions **/',
                           '_py_functions: %s,' % list(_exposed_functions.keys()))
    page = page.replace('/** _start_geometry **/',
                        '_start_geometry: %s,' % _safe_json(start_geometry))
    btl.response.content_type = 'application/javascript'
    _set_response_headers(btl.response)
    return page
def _static(path):
    response = None
    if 'jinja_env' in _start_args and 'jinja_templates' in _start_args:
        template_prefix = _start_args['jinja_templates'] + '/'
        if path.startswith(template_prefix):
            n = len(template_prefix)
            template = _start_args['jinja_env'].get_template(path[n:])
            response = btl.HTTPResponse(template.render())
    if response is None:
        response = btl.static_file(path, root=root_path)
    _set_response_headers(response)
    return response
def _websocket(ws):
    global _websockets
    for js_function in _js_functions:
        _import_js_function(js_function)
    page = btl.request.query.page
    if page not in _mock_queue_done:
        for call in _mock_queue:
            _repeated_send(ws, _safe_json(call))
        _mock_queue_done.add(page)
    _websockets += [(page, ws)]
    while True:
        msg = ws.receive()
        if msg is not None:
            message = jsn.loads(msg)
            spawn(_process_message, message, ws)
        else:
            _websockets.remove((page, ws))
            break
    _websocket_close(page)
BOTTLE_ROUTES = {
    "/eel.js": (_eel, dict()),
    "/<path:path>": (_static, dict()),
    "/eel": (_websocket, dict(apply=[wbs.websocket]))
}
# Private functions
def _safe_json(obj):
    return jsn.dumps(obj, default=lambda o: None)
def _repeated_send(ws, msg):
    for attempt in range(100):
        try:
            ws.send(msg)
            break
        except Exception:
            sleep(0.001)
def _process_message(message, ws):
    if 'call' in message:
        return_val = _exposed_functions[message['name']](*message['args'])
        _repeated_send(ws, _safe_json({ 'return': message['call'],
                                        'value': return_val  }))
    elif 'return' in message:
        call_id = message['return']
        if call_id in _call_return_callbacks:
            callback = _call_return_callbacks.pop(call_id)
            callback(message['value'])
        else:
            _call_return_values[call_id] = message['value']
    else:
        print('Invalid message received: ', message)
def _get_real_path(path):
    if getattr(sys, 'frozen', False):
        return os.path.join(sys._MEIPASS, path)
    else:
        return os.path.abspath(path)
def _mock_js_function(f):
    exec('%s = lambda *args: _mock_call("%s", args)' % (f, f), globals())
def _import_js_function(f):
    exec('%s = lambda *args: _js_call("%s", args)' % (f, f), globals())
def _call_object(name, args):
    global _call_number
    _call_number += 1
    call_id = _call_number + rnd.random()
    return {'call': call_id, 'name': name, 'args': args}
def _mock_call(name, args):
    call_object = _call_object(name, args)
    global _mock_queue
    _mock_queue += [call_object]
    return _call_return(call_object)
def _js_call(name, args):
    call_object = _call_object(name, args)
    for _, ws in _websockets:
        _repeated_send(ws, _safe_json(call_object))
    return _call_return(call_object)
def _call_return(call):
    call_id = call['call']
    def return_func(callback=None):
        if callback is not None:
            _call_return_callbacks[call_id] = callback
        else:
            for w in range(10000):
                if call_id in _call_return_values:
                    return _call_return_values.pop(call_id)
                sleep(0.001)
    return return_func
def _expose(name, function):
    msg = 'Already exposed function with name "%s"' % name
    assert name not in _exposed_functions, msg
    _exposed_functions[name] = function
def _websocket_close(page):
    close_callback = _start_args.get('close_callback')
    if close_callback is not None:
        sockets = [p for _, p in _websockets]
        close_callback(page, sockets)
    else:
        # Default behaviour - wait 1s, then quit if all sockets are closed
        sleep(1.0)
        if len(_websockets) == 0:
            sys.exit()
def _set_response_headers(response):
    if _start_args['disable_cache']:
        # https://stackoverflow.com/a/24748094/280852
        response.set_header('Cache-Control', 'no-store')

注意到了吗?代码中有这样两句:

?
1
2
_eel_js_file = pkg.resource_filename('eel', 'eel.js')
_eel_js = open(_eel_js_file, encoding='utf-8').read()

一不做,二不休,把eel.js的内容复制下来,然后,奥里给。将代码做如下修改(即贴即用)

?
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
from __future__ import print_function   # Python 2 compatibility stuff
from builtins import range
from io import open
import gevent as gvt
import json as jsn
import bottle as btl
import bottle.ext.websocket as wbs
import re as rgx
import os
import eel.browsers as brw
import random as rnd
import sys
import pkg_resources as pkg
import socket
eelJS = '''
eel = {
    _host: window.location.origin,
    set_host: function (hostname) {
        eel._host = hostname
    },
    expose: function(f, name) {
        if(name === undefined){
            name = f.toString();
            let i = 'function '.length, j = name.indexOf('(');
            name = name.substring(i, j).trim();
        }
        eel._exposed_functions[name] = f;
    },
    guid: function() {
        return eel._guid;
    },
    // These get dynamically added by library when file is served
    /** _py_functions **/
    /** _start_geometry **/
    _guid: ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
            (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
        ),
    _exposed_functions: {},
    _mock_queue: [],
    _mock_py_functions: function() {
        for(let i = 0; i < eel._py_functions.length; i++) {
            let name = eel._py_functions[i];
            eel[name] = function() {
                let call_object = eel._call_object(name, arguments);
                eel._mock_queue.push(call_object);
                return eel._call_return(call_object);
            }
        }
    },
    _import_py_function: function(name) {
        let func_name = name;
        eel[name] = function() {
            let call_object = eel._call_object(func_name, arguments);
            eel._websocket.send(eel._toJSON(call_object));
            return eel._call_return(call_object);
        }
    },
    _call_number: 0,
    _call_return_callbacks: {},
    _call_object: function(name, args) {
        let arg_array = [];
        for(let i = 0; i < args.length; i++){
            arg_array.push(args[i]);
        }
        let call_id = (eel._call_number += 1) + Math.random();
        return {'call': call_id, 'name': name, 'args': arg_array};
    },
    _sleep: function(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    },
    _toJSON: function(obj) {
        return JSON.stringify(obj, (k, v) => v === undefined ? null : v);
    },
    _call_return: function(call) {
        return function(callback = null) {
            if(callback != null) {
                eel._call_return_callbacks[call.call] = callback;
            } else {
                return new Promise(function(resolve) {
                    eel._call_return_callbacks[call.call] = resolve;
                });
            }
        }
    },
    _position_window: function(page) {
        let size = eel._start_geometry【'default'】.size;
        let position = eel._start_geometry['default'].position;
        if(page in eel._start_geometry.pages) {
            size = eel._start_geometry.pages【page】.size;
            position = eel._start_geometry.pages【page】.position;
        }
        if(size != null){
            window.resizeTo(size[0], size[1]);
        }
        if(position != null){
            window.moveTo(position[0], position[1]);
        }
    },
    _init: function() {
        eel._mock_py_functions();
        document.addEventListener("DOMContentLoaded", function(event) {
            let page = window.location.pathname.substring(1);
            eel._position_window(page);
            let websocket_addr = (eel._host + '/eel').replace('http', 'ws');
            websocket_addr += ('?page=' + page);
            eel._websocket = new WebSocket(websocket_addr);
            eel._websocket.onopen = function() {
                for(let i = 0; i < eel._py_functions.length; i++){
                    let py_function = eel._py_functions[i];
                    eel._import_py_function(py_function);
                }
                while(eel._mock_queue.length > 0) {
                    let call = eel._mock_queue.shift();
                    eel._websocket.send(eel._toJSON(call));
                }
            };
            eel._websocket.onmessage = function (e) {
                let message = JSON.parse(e.data);
                if(message.hasOwnProperty('call') ) {
                    // Python making a function call into us
                    if(message.name in eel._exposed_functions) {
                        let return_val = eel._exposed_functions[message.name](...message.args);
                        eel._websocket.send(eel._toJSON({'return': message.call, 'value': return_val}));
                    }
                } else if(message.hasOwnProperty('return')) {
                    // Python returning a value to us
                    if(message['return'] in eel._call_return_callbacks) {
                        eel._call_return_callbacks[message['return']](message.value);
                    }
                } else {
                    throw 'Invalid message ' + message;
                }
            };
        });
    }
}
eel._init();
if(typeof require !== 'undefined'){
    // Avoid name collisions when using Electron, so jQuery etc work normally
    window.nodeRequire = require;
    delete window.require;
    delete window.exports;
    delete window.module;
}
'''
try:
    _eel_js_file = pkg.resource_filename('eel', 'eel.js')
    _eel_js = open(_eel_js_file, encoding='utf-8').read()
except:
    _eel_js = eelJS
_websockets = []
_call_return_values = {}
_call_return_callbacks = {}
_call_number = 0
_exposed_functions = {}
_js_functions = []
_mock_queue = []
_mock_queue_done = set()
# All start() options must provide a default value and explanation here
_start_args = {
    'mode':             'chrome',                   # What browser is used
    'host':             'localhost',                # Hostname use for Bottle server
    'port':             8000,                       # Port used for Bottle server (use 0 for auto)
    'block':            True,                       # Whether start() blocks calling thread
    'jinja_templates'None,                       # Folder for jinja2 templates
    'cmdline_args':     ['--disable-http-cache'],   # Extra cmdline flags to pass to browser start
    'size':             None,                       # (width, height) of main window
    'position':         None,                       # (left, top) of main window
    'geometry':         {},                         # Dictionary of size/position for all windows
    'close_callback':   None,                       # Callback for when all windows have closed
    'app_mode'True,                              # (Chrome specific option)
    'all_interfaces': False,                        # Allow bottle server to listen for connections on all interfaces
    'disable_cache': True,                          # Sets the no-store response header when serving assets
    'app': btl.default_app(),                       # Allows passing in a custom Bottle instance, e.g. with middleware
}
# == Temporary (suppressable) error message to inform users of breaking API change for v1.0.0 ===
_start_args['suppress_error'] = False
api_error_message = '''
----------------------------------------------------------------------------------
  'options' argument deprecated in v1.0.0, see https://github.com/ChrisKnott/Eel
  To suppress this error, add 'suppress_error=True' to start() call.
  This option will be removed in future versions
----------------------------------------------------------------------------------
'''
# ===============================================================================================
# Public functions
def expose(name_or_function=None):
    # Deal with '@eel.expose()' - treat as '@eel.expose'
    if name_or_function is None:
        return expose
    if type(name_or_function) == str:   # Called as '@eel.expose("my_name")'
        name = name_or_function
        def decorator(function):
            _expose(name, function)
            return function
        return decorator
    else:
        function = name_or_function
        _expose(function.__name__, function)
        return function
def init(path, allowed_extensions=['.js', '.html', '.txt', '.htm',
                                   '.xhtml', '.vue']):
    global root_path, _js_functions
    root_path = _get_real_path(path)
    js_functions = set()
    for root, _, files in os.walk(root_path):
        for name in files:
            if not any(name.endswith(ext) for ext in allowed_extensions):
                continue
            try:
                with open(os.path.join(root, name), encoding='utf-8') as file:
                    contents = file.read()
                    expose_calls = set()
                    finder = rgx.findall(r'eel\.expose\(([^\)]+)\)', contents)
                    for expose_call in finder:
                        # If name specified in 2nd argument, strip quotes and store as function name
                        if ',' in expose_call:
                            expose_call = rgx.sub(r'["\']', '', expose_call.split(',')[1])
                        expose_call = expose_call.strip()
                        # Verify that function name is valid
                        msg = "eel.expose() call contains '(' or '='"
                        assert rgx.findall(r'[\(=]', expose_call) == [], msg
                        expose_calls.add(expose_call)
                    js_functions.update(expose_calls)
            except UnicodeDecodeError:
                pass    # Malformed file probably
    _js_functions = list(js_functions)
    for js_function in _js_functions:
        _mock_js_function(js_function)
def start(*start_urls, **kwargs):
    _start_args.update(kwargs)
    if 'options' in kwargs:
        if _start_args['suppress_error']:
            _start_args.update(kwargs['options'])
        else:
            raise RuntimeError(api_error_message)
    if _start_args['port'] == 0:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind(('localhost', 0))
        _start_args['port'] = sock.getsockname()[1]
        sock.close()
    if _start_args['jinja_templates'] != None:
        from jinja2 import Environment, FileSystemLoader, select_autoescape
        templates_path = os.path.join(root_path, _start_args['jinja_templates'])
        _start_args['jinja_env'] = Environment(loader=FileSystemLoader(templates_path),
                                 autoescape=select_autoescape(['html', 'xml']))
    # Launch the browser to the starting URLs
    show(*start_urls)
    def run_lambda():
        if _start_args['all_interfaces'] == True:
            HOST = '0.0.0.0'
        else:
            HOST = _start_args['host']
        app = _start_args['app'# type: btl.Bottle
        for route_path, route_params in BOTTLE_ROUTES.items():
            route_func, route_kwargs = route_params
            app.route(path=route_path, callback=route_func, **route_kwargs)
        return btl.run(
            host=HOST,
            port=_start_args['port'],
            server=wbs.GeventWebSocketServer,
            quiet=True,
            app=app)
    # Start the webserver
    if _start_args['block']:
        run_lambda()
    else:
        spawn(run_lambda)
def show(*start_urls):
    brw.open(start_urls, _start_args)
def sleep(seconds):
    gvt.sleep(seconds)
def spawn(function, *args, **kwargs):
    gvt.spawn(function, *args, **kwargs)
# Bottle Routes
def _eel():
    start_geometry = {'default': {'size': _start_args['size'],
                                  'position': _start_args['position']},
                      'pages':   _start_args['geometry']}
    page = _eel_js.replace('/** _py_functions **/',
                           '_py_functions: %s,' % list(_exposed_functions.keys()))
    page = page.replace('/** _start_geometry **/',
                        '_start_geometry: %s,' % _safe_json(start_geometry))
    btl.response.content_type = 'application/javascript'
    _set_response_headers(btl.response)
    return page
def _static(path):
    response = None
    if 'jinja_env' in _start_args and 'jinja_templates' in _start_args:
        template_prefix = _start_args['jinja_templates'] + '/'
        if path.startswith(template_prefix):
            n = len(template_prefix)
            template = _start_args['jinja_env'].get_template(path[n:])
            response = btl.HTTPResponse(template.render())
    if response is None:
        response = btl.static_file(path, root=root_path)
    _set_response_headers(response)
    return response
def _websocket(ws):
    global _websockets
    for js_function in _js_functions:
        _import_js_function(js_function)
    page = btl.request.query.page
    if page not in _mock_queue_done:
        for call in _mock_queue:
            _repeated_send(ws, _safe_json(call))
        _mock_queue_done.add(page)
    _websockets += [(page, ws)]
    while True:
        msg = ws.receive()
        if msg is not None:
            message = jsn.loads(msg)
            spawn(_process_message, message, ws)
        else:
            _websockets.remove((page, ws))
            break
    _websocket_close(page)
BOTTLE_ROUTES = {
    "/eel.js": (_eel, dict()),
    "/<path:path>": (_static, dict()),
    "/eel": (_websocket, dict(apply=[wbs.websocket]))
}
# Private functions
def _safe_json(obj):
    return jsn.dumps(obj, default=lambda o: None)
def _repeated_send(ws, msg):
    for attempt in range(100):
        try:
            ws.send(msg)
            break
        except Exception:
            sleep(0.001)
def _process_message(message, ws):
    if 'call' in message:
        return_val = _exposed_functions[message['name']](*message['args'])
        _repeated_send(ws, _safe_json({ 'return': message['call'],
                                        'value': return_val  }))
    elif 'return' in message:
        call_id = message['return']
        if call_id in _call_return_callbacks:
            callback = _call_return_callbacks.pop(call_id)
            callback(message['value'])
        else:
            _call_return_values[call_id] = message['value']
    else:
        print('Invalid message received: ', message)
def _get_real_path(path):
    if getattr(sys, 'frozen', False):
        return os.path.join(sys._MEIPASS, path)
    else:
        return os.path.abspath(path)
def _mock_js_function(f):
    exec('%s = lambda *args: _mock_call("%s", args)' % (f, f), globals())
def _import_js_function(f):
    exec('%s = lambda *args: _js_call("%s", args)' % (f, f), globals())
def _call_object(name, args):
    global _call_number
    _call_number += 1
    call_id = _call_number + rnd.random()
    return {'call': call_id, 'name': name, 'args': args}
def _mock_call(name, args):
    call_object = _call_object(name, args)
    global _mock_queue
    _mock_queue += [call_object]
    return _call_return(call_object)
def _js_call(name, args):
    call_object = _call_object(name, args)
    for _, ws in _websockets:
        _repeated_send(ws, _safe_json(call_object))
    return _call_return(call_object)
def _call_return(call):
    call_id = call['call']
    def return_func(callback=None):
        if callback is not None:
            _call_return_callbacks[call_id] = callback
        else:
            for w in range(10000):
                if call_id in _call_return_values:
                    return _call_return_values.pop(call_id)
                sleep(0.001)
    return return_func
def _expose(name, function):
    msg = 'Already exposed function with name "%s"' % name
    assert name not in _exposed_functions, msg
    _exposed_functions[name] = function
def _websocket_close(page):
    close_callback = _start_args.get('close_callback')
    if close_callback is not None:
        sockets = [p for _, p in _websockets]
        close_callback(page, sockets)
    else:
        # Default behaviour - wait 1s, then quit if all sockets are closed
        sleep(1.0)
        if len(_websockets) == 0:
            sys.exit()
def _set_response_headers(response):
    if _start_args['disable_cache']:
        # https://stackoverflow.com/a/24748094/280852
        response.set_header('Cache-Control', 'no-store')

ok,下面在运行一次cmd,执行之前的代码,不报错了。但是,这次又遇到了新的问题。

?
1
'utf-8' codec can't decode byte 0xce

woc。这编码怎么又出了问题。百度一下,找到了解决方案。在cmd中先输入

?
1
chcp 65001

然后再执行

?
1
python -m eel F:\xx\xx\xx\Python_Main.py F:\xx\xx\xx\Web

这下总算是成功打包了。

5、找到我们的Python_Main.exe文件

打包后的文件一般在cmd的启动路径下能够找到。如,我的Python_Main.exe文件就应该在C:\Users\Deadpool里

关于Pyinstaller打包eel和pygame需要注意的坑

发现在Deadpool文件里多出了三个文件,分别是Python_Main.spec, distbuild。打开dist后,发现有一个Python_Main文件夹,打开,便能找到Python_Main.exe,点击,弹出小黑框。接着,恭喜你,主界面显示404 Not Found。What?Woc。又是什么问题,难道我需要把Web文件夹移到这个Python_Main文件夹里?经验证答案是“是”。事实上,我们需要把所有文件都移到这个Python_Main里面,也就是前面的Web以及游戏资源文件夹。OK,问题解决。感动啊 (ಥ _ ಥ)~~~~当我满怀激情的点击了开始游戏,小黑框里又一个错误出现了:

pygame.error: Couldn't open xxx\xxx.png

百度一下,原来需要用pyinstaller打包时,pygame的load图片必须用绝对路径。ok,那就只有修改代码,重新打包。问题解决。接下来,就是如何处理掉小黑框了。现在,先让我们看看Python_Main.spec文件:

?
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
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['F:\\xx\\xx\\Python_Main.py'],
             pathex=['C:\\Users\\Deadpool'],
             binaries=[],
             datas=[('F:\\AnocondaApp\\lib\\site-packages\\eel\\eel.js', 'eel'), ('F:\\xx\\xx\\Web', 'F:\\xx\\xx\\Web')],
             hiddenimports=['bottle_websocket'],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='Python_Main',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=True, icon='')
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='Python_Main')

简单分析后,把console = True改为console = False,咦,这里还有一个icon,应该不会是游戏图标吧,试着改改,于是我让icon = ‘F:\xx\xx\Web\favicon.ico’。改完后,保存。

如何运行spec文件呢?无所不能的网友给出了答案。

?
1
2
chcp 65001
pyinstaller Python_Main.spec

ok,完成。下面,就开始享受自己的exe游戏吧!!以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/weixin_44465434/article/details/103707285

延伸 · 阅读

精彩推荐
  • PythonPython中单例模式总结

    Python中单例模式总结

    单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一...

    孟庆健4482021-01-16
  • PythonPython里的dict和set的背后小秘密

    Python里的dict和set的背后小秘密

    这篇文章主要介绍了在Python里的dict和set的背后小秘密,dict字典是Python中的重要基础知识,set与其类似,需要的朋友可以参考下...

    Python探索牛3452022-02-23
  • Python深入理解python协程

    深入理解python协程

    协程又称为微线程,协程是一种用户态的轻量级线程,它是实现多任务的另一种方式,只不过是比线程更小的执行单元。因为它自带CPU的上下文,这样只要...

    luyuze953682021-12-01
  • Python用Python给图像算法做个简单应用界面

    用Python给图像算法做个简单应用界面

    这篇文章主要介绍了用Python给图像算法做个简单应用界面,帮助大家更好的理解和学习使用python开发gui,感兴趣的朋友可以了解下...

    xlcaoyi7062021-10-24
  • Pythonpython基础之编码规范总结

    python基础之编码规范总结

    今天带大家来学习python基础知识,文中对python编码规范作了详细的介绍,对正在学习python的小伙伴们很有帮助,需要的朋友可以参考下...

    florachy5722021-11-11
  • Python总结的几个Python函数方法设计原则

    总结的几个Python函数方法设计原则

    这篇文章主要介绍了总结的几个Python函数方法设计原则,本文讲解了每个函数只做一件事、保持简单、保持简短、输入使用参数、输出使用return语句等内容...

    Python教程网5652020-07-11
  • PythonKeras 多次加载model出错的解决方案

    Keras 多次加载model出错的解决方案

    这篇文章主要介绍了Keras 多次加载model出错的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...

    licc9210522021-11-04
  • Pythonpython爬虫之利用selenium模块自动登录CSDN

    python爬虫之利用selenium模块自动登录CSDN

    这篇文章主要介绍了python爬虫之利用selenium模块自动登录CSDN,文中有非常详细的代码示例,对正在学习python的小伙伴们有很好地帮助,需要的朋友可以参考下...

    lexsaints4802021-10-18