почему пользовательская сборка Python 2.7.x в возвышенном тексте 3.x не работает соответственно - PullRequest
0 голосов
/ 27 апреля 2018
def a(x):
    assert x>0,'invalid argument'
    print 'wow'

a(2)
a(0)

это должно сначала вывести «wow», а затем вызвать исключение, но вместо этого оно печатает. «Wow» разделяется как «wo» перед «assert x> 0» и после «AssertionError» в третьей последней строке, и это продолжает изменяться непредсказуемо, но не один раз перед «трассировкой»:

Traceback (most recent call last):
  File "E:\Books\Python\think python\assert.py", line 6, in <module>
    a(0)
  File "E:\Books\Python\think python\assert.py", line 2, in a
wo    assert x>0,'invalid argument'
AssertionErrorw
: invalid argument
[Finished in 0.1s with exit code 1]

Моя возвышенная сборка:

{
    "cmd": ["C:\\Python27\\python", "-u", "$file"],
    "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
    "selector": "source.python",
    "shell": true
}

1 Ответ

0 голосов
/ 27 апреля 2018

Когда Sublime запускает внешнюю программу (например, когда вы запускаете сборку и она запускает python), она запускает два фоновых потока, которые захватывают выходные данные дескрипторов stdout и stderr программы, которую она запускает чтобы он мог отображать его на панели сборки.

Sublime гарантирует, что только один из этих двух потоков может добавлять данные на панель вывода в любой точке, но данные, захваченные каждым потоком, не буферизуются каким-либо образом, поэтому, как только какие-либо данные появляются на дескрипторе вывода, они помещаются в очередь. для добавления на панель вывода.

Причина, по которой вы видите это, заключается в том, что оператор print отправляет вывод в stdout, а исключения записываются в stderr. Из-за природы многопоточных программ не определено, какой из двух потоков может сначала захватить его вывод, что означает, что выходные данные двух дескрипторов смешиваются явно случайным образом.

Я полагаю, что Sublime работает таким образом, потому что, если он буферизует вывод в строки по умолчанию, программы, которые генерируют частичные строки за один раз, могут вообще не работать.

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

Также возможно создать собственную пользовательскую сборку target, которая имитирует то, что делает exec, за исключением строковой буферизации, в случае, если такая вещь является серьезным требованием.


В качестве дополнительного пояснения к вышесказанному (и к примеру запуска программы с буферизацией строки) приведен пример программы на Python, которая более наглядно демонстрирует, что здесь происходит. Раз в секунду в течение 10 секунд генерируются значения от . до stdout и от - до stderr, а затем, по истечении 10 секунд, он отправляет новую строку для каждого дескриптора по очереди.

import sys
import time

for _ in range(0,10):
    sys.stdout.write(".")
    sys.stderr.write("-")
    sys.stdout.flush()
    sys.stderr.flush()
    time.sleep(1)

sys.stdout.write("\n")
sys.stderr.write("\n")

Когда я запускаю программу со значением по умолчанию Python.sublime-build, я получаю следующее:

.--.-..-.--.-.-.-.-.

[Finished in 10.1s]

Это показывает, что выходные данные stderr и stdout не только смешиваются вместе, но и странным образом неопределенным образом, вызванным многопоточностью захвата.

Например, вы можете ожидать, что результат будет выходить как чередующийся ряд, как .-.-.-.-.-.-.-.-.-.-, но это не так; хотя в каждом элементе по 10 элементов, они отображаются не в том порядке, в котором мы их распечатали, поскольку порядок, в котором запланирован запуск каждого потока, и уведомление о том, что есть выход для добавления на панель, неопределенны.

Как я упоминал выше, вы можете создать свою собственную цель сборки, которая выполняет то же, что и команда exec, за исключением того, что она хранит данные в промежуточном буфере и только выдает их в строки панели вывода за раз вместо Похоже, это остановит это.


Важное примечание : Выполнение такой буферизации строк не останавливает чередование данных от stderr и stdout, а просто гарантирует, что данные чередуются целыми строками в время вместо того, чтобы перебирать всех "чужих сундуков" по средней линии. Чтобы по-настоящему остановить чередование, вам нужно отправить каждый маркер в другое место (например, на две панели или отправить stderr на консоль).


Полный код для чего-то подобного слишком длинен, чтобы добавить его к SO-ответу, поэтому для демонстрации я поместил код в в этой сущности . Редакция 1 - это базовая версия файлов, а редакция 2 - модифицированные версии для буферизации, поэтому вы можете увидеть различия между двумя , чтобы лучше понять изменения.

Команда по умолчанию exec захватывает выходные данные из stderr и stdout и отправляет их непосредственно на панель вывода после получения; когда данные поступают на панель вывода, команда нормализует символы конца строки в соответствии с тем, что Sublime ожидает увидеть (для компьютеров под управлением Windows / Mac), а затем добавляет их на панель.

Модифицированная версия считывает данные во временный буфер и сразу же выполняет нормализацию новой строки, а затем только вперед по всем накопленным строкам, оставляя оставшиеся данные в следующий раз. После завершения вывода все оставшиеся данные отправляются в буфер, так что все отображается.

Чтобы использовать новую цель, в файле sublime-build должен быть добавлен аргумент target, чтобы указать Sublime использовать команду, отличную от exec. В Gist есть пример этого для значения по умолчанию Python.sublime-build, а также дополнительный ключ для добавления, позволяющий функциональности Cancel Build работать с пользовательской целью.

С измененной сборкой на выходе программа выглядит так:

----------
..........
[Finished in 10.1s]

Обратите внимание, что побочным эффектом этого является то, что теперь вы вообще ничего не видите на панели сборки в течение 10 секунд, а затем весь вывод отображается сразу, так как он не может появиться, пока не завершится; Подобные вещи, вероятно, являются причиной, по которой Sublime не делает этого по умолчанию.

Как упомянуто выше, это не останавливает чередование данных, оно просто контролирует, как это происходит, так что происходит строка за раз. До сих пор не определено, какая из строк stdout и stderr появится первой.

Если хотите, вы можете изменить код, начиная со строки 169, следующим образом:

# Accumulate data into our buffer and only let full lines
# through to the output panel.
if self.listener:
    pBuffer += data
    nPos = pBuffer.rfind("\n")
    if nPos >= 0:
        self.listener.on_data(self, pBuffer[:nPos+1])
        pBuffer = pBuffer[nPos+1:]

На это:

# Accumulate data into our buffer and only let full lines
# through to the output panel; stderr data is held until the
# handle is closed below.
if self.listener:
    pBuffer += data
    if execute_finished:
        nPos = pBuffer.rfind("\n")
        if nPos >= 0:
            self.listener.on_data(self, pBuffer[:nPos+1])
            pBuffer = pBuffer[nPos+1:]

Это изменяет все так, что данные для stderr сохраняются в буфере, но не передаются на панель вывода до завершения программы.

Это обеспечит отсутствие чередования, но также не даст вам увидеть ошибки в том месте, где они произошли. Это также делает невозможным видеть ошибки такими, как они возникают, если ваша программа долго работает, и может привести к увеличению использования памяти, если в буфере выводится много ошибок, но они не отображаются.

Как таковой, я бы не рекомендовал это, если вы не делаете что-то вроде запуска недолговечных программ, где менее навязчиво, чтобы в конце появлялись ошибки.

...