Вот мой код
print("1")
print("2")
Мой желаемый результат
2
1
Я пробовал использовать символы Ansi, но вторая строка перекрывает первую строку.
print("1")
print("\033[3A2")
выход
2
Как я могу добиться желаемого результата, используя другие средства, кроме цикла, чтобы изменить порядок оператора печати?
Я предпочитаю символы Ansi, терминал(), но я также буду использовать и другие методы.
🤔 А знаете ли вы, что...
С Python можно создавать кросс-платформенные приложения для Windows, macOS и Linux.
Проблема в вашем коде заключается в том, что коды «ANSI» по-прежнему считаются символами и интерпретируются при печати/сбрасывании.
Поэтому, если вы хотите переместиться из N строк/столбцов, вам следует сначала использовать инструкцию перемещения, а затем (и только тогда/когда произошла печать) напечатать нужные символы.
По крайней мере, я так обычно делаю, и у меня никогда не было с этим проблем.
Чтобы добиться визуального эффекта печати новой строки вверху при перемещении предыдущих строк вниз, вы можете сохранить предыдущие строки в списке и печатать их в обратном порядке при печати новой строки.
Однако перед печатью новой строки вам необходимо переместить курсор вверх на любое количество предыдущих строк, используя управляющий код <ESC>[{count}A
. Используйте управляющий код <ESC>[K
для очистки от текущей позиции курсора до конца текущей строки, чтобы стереть все оставшиеся символы из более длинной предыдущей строки:
def reverse_print_factory(print=print):
def _print(line):
if lines:
print(f'\033[{len(lines)}A', end='')
lines.append(line)
for line in reversed(lines):
print(f'{line}\033[K')
lines = []
return _print
print = reverse_print_factory()
так что:
from time import sleep
for text in 'first', 'second', 'third':
print(text)
sleep(1)
выдаст следующий вывод на терминале, совместимом с VT100/ANSI:
first
и затем через секунду:
second
first
и еще через секунду:
third
second
first
Обновлено: Более эффективный (но немного менее портативный из-за необходимости контролировать стандартный ввод) подход — прокручивать область предыдущих строк вверх, чтобы вам не приходилось хранить предыдущие строки в списке.
Это можно сделать, используя управляющий код <ESC>[{start row};{end row}r
для установки прокручиваемой области, переместив курсор в верхнюю часть области с помощью управляющего кода <ESC>[{row};{column}f
, а затем используя управляющий код <ESC>M
для прокрутки вверх, эффективно перемещая строки в области вниз. .
Чтобы получить начальную строку, вы можете использовать управляющий код <ESC>[6n
для запроса позиции курсора, а затем прочитать ответ со стандартного ввода в формате <ESC>[{row};{column}R
. Это было полезно записано в функции cursorPos
в этом ответе, который я использую в следующем примере:
def reverse_print_factory(print=print):
def _print(text):
nonlocal count
if count:
print(f'\033[{start_row};{start_row + count}r\033[{start_row};1f\033M', end='')
count += 1
print(f'{text}\033[K\033[{start_row + count};1f', flush=True, end='')
start_row = int(cursorPos()[1])
count = 0
return _print
print = reverse_print_factory()
Для дальнейшего учета строк, достигающих высоты терминала (можно получить с помощью shil.get_terminal_size ), вы можете переместить строки перед начальной строкой вверх (с помощью управляющего кода <ESC>D
), чтобы освободить место для новой строки, а не перемещаться. предыдущие строки вниз, пока весь экран не будет заполнен выводом, после чего снова переместите весь экран вниз, чтобы освободить место для новой строки вверху.
Также используйте atexist.register для регистрации обработчика выхода, который сбрасывает прокручиваемую область на весь экран и выводит дополнительную новую строку, если строки достигли высоты терминала, чтобы командная строка не перезаписывала ненужную последнюю строку вывода:
import shutil
import atexit
def reverse_print_factory(print=print):
def _print(text):
nonlocal start_row, count
if count:
if 1 < start_row > height - count:
print(f'\033[1;{start_row - 1}r\033[{start_row - 1};1f\033D\033[{start_row - 1};1f', end='')
start_row -= 1
else:
print(f'\033[{start_row};{start_row + count}r\033[{start_row};1f\033M', end='')
count += 1
print(f'{text}\033[K\033[{start_row + count};1f', flush=True, end='')
def _exit():
print(f'\033[r\033[{start_row + count};1f', end='')
if 1 < start_row > height - count:
print()
height = shutil.get_terminal_size()[1]
start_row = int(cursorPos()[1])
count = 0
atexit.register(_exit)
return _print
print = reverse_print_factory()
Обратите внимание, что полный набор последовательностей управления VT100 можно найти по адресу: https://vt100.net/docs/vt100-ug/chapter3.html
Попытка попросить терминал изменить порядок строк таким образом — концептуально неправильный подход. Забудь это.
Измените бизнес-логику вашего приложения так, чтобы строки выводились в желаемом конечном порядке (сверху вниз).
Это может потребовать сохранения некоторых данных в памяти для последующего использования, а не немедленного вывода. Например. сохраните массив строк для печати, продолжайте добавлять к этому массиву и распечатывайте его один раз, когда все закончите.
Я попробовал ваш сценарий. Результат не такой простой 2
, как вы утверждаете. Вывод — это 2
на несколько строк выше (\033[3A
перемещает курсор на 3 строки вверх, почему вы выбрали 3?), искажая вывод предыдущей команды; и 1
внизу под новой подсказкой, которую я получил. Совершенно неправильно во всех отношениях.
Мне непонятно, почему вы перемещаете курсор на 3 строки вверх, а не на 1. Но, как вы, наверное, заметили, перемещение курсора вверх не означает, что новые напечатанные строки будут смещать содержимое ниже.
Если вы хотите сделать это (нажать содержимое вниз), для этого есть escape-последовательность. Но тогда, если вы находитесь в нижней части терминала, линия, которую вы нажимаете, будет потеряна навсегда во многих терминалах.
Что должно произойти, когда ваше приложение завершится? Если вы нажмете 1
, курсор окажется над ним. Так должно ли следующее приглашение оболочки и каждое дальнейшее действие продолжать его подавлять? Или перезаписать? Конечно, ни то, ни другое нехорошо. Вам нужно будет перепрыгнуть через него и вернуть курсор ниже 1
.
Я уверен, что эти 1
и 2
даны для примера; в реальной жизни у вас будут разные струны. Что, если они шире терминала и, таким образом, переносятся на несколько строк, как вы это узнаете и как вычислите количество строк, необходимых для перемещения курсора или перемещения содержимого?
Что, если выходные данные вашего приложения длиннее, чем высота терминала? Эмуляторы терминала позволяют работать только на области высотой с окно; буфер обратной прокрутки сохраняется для удобства пользователя, но недоступен и не доступен для записи приложениями. Вы абсолютно не сможете заставить его работать, если желаемый результат выше терминала.
Много-много открытых вопросов со множеством угловых случаев, вы можете бесконечно копаться в этой кроличьей норе и бесконечно усложнять свое приложение, но так и не сделать его правильно.
Нет, это абсолютно неправильный подход. Не говоря уже о том, что выходные данные не могут быть переданы в другую утилиту для дальнейшей обработки. Забудь это. Измените свое приложение так, чтобы оно выводило то, что ему действительно нужно, в конечном желаемом порядке.
Конечно, если вы пишете приложение, которое контролирует весь холст терминала и постоянно меняет его содержимое (например, текстовый редактор или игру для терминала), тогда это совсем другая история. В этом случае вам, вероятно, следует использовать существующую библиотеку обработки экрана терминала.