Упакованное приложение PyInstaller отлично работает в консольном режиме, сбой в оконном режиме

Я создаю довольно сложное приложение, используя Python и PySide. Наконец-то приближается день релиза, поэтому я хочу собрать это приложение в виде исполняемого файла.

Тем не менее, у меня есть странная проблема на моих руках. Я использовал PyInstaller (кстати, используя версию 2) в прошлом, и со мной этого никогда не случалось.

По сути, когда я создаю приложение с флагом --console, оно работает нормально, но открывает окно консоли. Когда я создаю приложение с флагом окна (-w), оно не работает нормально. Он запускается и все, но есть все эти странные глюки. Например, при загрузке текстового файла часто возникает ошибка BadFileDescriptor (которой не бывает в консольном режиме), а приложение вылетает после выполнения определенной задачи. Хуже то, что задача представляет собой цикл, и в первый раз она выполняется нормально, но когда снова начинает работать, происходит сбой.

Когда я просматривал файл минидампа, были некоторые ошибки о нарушении доступа к памяти файла QtGui4.dll. Опять же, этого не происходит в режиме консоли.

У кого-нибудь есть идеи?


person Bo Milanovich    schedule 17.11.2012    source источник
comment
В вашей программе используются операторы print или sys.stdout.write? Читаю здесь (к сожалению, итальянский для вас) ошибка плохого дескриптора файла может быть вызвана этим, и вы можете решить ее, удалив / закомментировав print или перенаправив sys.stdout или используя вместо этого logging. Кажется, что когда вы запускаете приложение в оконном режиме, это stdout буфер фиксированного размера, что может вызвать нарушения доступа к памяти.   -  person Bakuriu    schedule 17.11.2012
comment
Он использует операторы печати. Можете ли вы рассказать мне больше о том, как перенаправить sys.stdout? Я имею в виду, можно ли легко заменить все операторы печати в программе одной строкой кода?   -  person Bo Milanovich    schedule 17.11.2012
comment
Вы можете разместить что-то вроде import sys; import tempfile; sys.stdout = tempfile.TemporaryFile(); sys.stderr = tempfile.TemporaryFile() (надеюсь, с лучшим форматированием) в самом начале выполнения вашей программы, тогда все print будут перенаправлены во временный файл, который будет удален при выходе из вашей программы. Для более долгосрочного решения я бы подумал об использовании logging.   -  person Bakuriu    schedule 17.11.2012
comment
Большое спасибо! Я попробую это. У меня уже включено ведение журнала, но большинство моих утверждений были как print, так и logging. Спасибо еще раз.   -  person Bo Milanovich    schedule 17.11.2012
comment
@Bakuriu, хм, у меня почему-то не работает: у меня пока так: sys.stdout = tempfile.TemporaryFile() sys.stderr = tempfile.TemporaryFile() Но почему-то я все еще вижу отпечатки в консоли PyCharm, а приложение все равно не работает. --- РЕДАКТИРОВАТЬ, неважно, у меня было reload(sys) в одной части моего кода. Теперь это работает!   -  person Bo Milanovich    schedule 17.11.2012
comment
Поскольку это решило вашу проблему, я переписал все в ответ.   -  person Bakuriu    schedule 17.11.2012


Ответы (1)


Ошибка BadFileDescriptor и, как следствие, нарушение доступа к памяти вызваны тем, что stdout приложений в оконном режиме представляет собой буфер фиксированного размера. Таким образом, если вы пишете в stdout, напрямую через print или sys.stdout, через некоторое время вы увидите эти ошибки.

Вы можете исправить это:

  1. Удаление/комментирование написанного на stdout
  2. Использование logging вместо печати на стандартный вывод
  3. Перенаправление stdout в начале выполнения вашего приложения. Это решение требует изменения меньшего количества кода, хотя я думаю, что перемещение операторов отладки в logging было бы лучшим выбором.

Чтобы перенаправить stdout, вы можете использовать такой код:

import sys
import tempfile
sys.stdout = tempfile.TemporaryFile()
sys.stderr = tempfile.TemporaryFile()

Непосредственно перед выполнением вашей программы. Вы также можете использовать какой-либо пользовательский объект, чтобы поместить вывод в файлы журнала или что-то еще, важно то, что вывод не должен заполнять буфер фиксированного размера.

Например, вы можете сделать что-то подобное, чтобы воспользоваться преимуществами модуля logging, не меняя слишком много кода:

import sys
import logging

debug_logger = logging.getLogger('debug')
debug_logger.write = debug_logger.debug    #consider all prints as debug information
debug_logger.flush = lambda: None   # this may be called when printing
#debug_logger.setLevel(logging.DEBUG)      #activate debug logger output
sys.stdout = debug_logger

Недостатком этого подхода является то, что print выполняет больше вызовов stdout.write для каждой строки:

>>> print 'test'
DEBUG:debug:test
DEBUG:debug:

Если вы хотите, вы, вероятно, можете избежать такого поведения, написав настоящую функцию write, которая вызывает the_logger.debug только с «полными строками».

В любом случае, я думаю, что такое решение должно быть только временным и использоваться только перед переносом prints на вызовы logging.debug.

(Очевидно, что регистраторы должны писать в файл, а не в stdout, чтобы избежать ошибок.)

person Bakuriu    schedule 17.11.2012
comment
Использовал второй метод с многопроцессорной обработкой, и он жаловался на отсутствие flush() - мне также пришлось добавить фиктивную (содержащую только «проход») процедуру сброса в debug_logger. - person Andris; 26.04.2016
comment
@Andris Отредактировано, чтобы включить его, на всякий случай. - person Bakuriu; 26.04.2016
comment
В современных Windows иногда Windows просто приостанавливает процесс, ожидая очистки стандартного вывода, что приводит к сбою вашей программы. Вздох. - person std''OrgnlDave; 05.08.2017