Модуль readline
может быть использован для улучшения интерактивных программ командной строки, чтобы упростить их использование. Он в основном используется для обеспечения завершения текста в командной строке или "автодополнения табуляцией". Настройки, сделанные с помощью этого модуля, влияют на поведение как интерактивной подсказки переводчика, так и подсказок, предлагаемых встроенной функцией input()
.
Внимание! Ссылка для тех, кто ищет метод файлового объекта построчного чтения из файла file.readline()
.
Примечание: так как модуль readline
взаимодействует с содержимым консоли, печать отладочных сообщений затрудняет просмотр того, что происходит в примере кода.
Существует два способа настройки базовой библиотеки readline
, используя файл конфигурации или функцию parse_and_bind()
. Опции конфигурации включают связывание клавиш для вызова завершения, режимы редактирования, например vi или emacs и многие другие значения. За подробностями обращайтесь к документации библиотеки readline GNU.
Самый простой способ включить завершение табуляции - через вызов функции модуля parse_and_bind()
. Другие параметры могут быть установлены одновременно. В этом примере изменяются элементы управления редактированием для использования режима vi
вместо значения по умолчанию emacs
. Чтобы редактировать текущую строку ввода, нажмите ESC, затем используйте обычные клавиши навигации vi
, такие как j , k , l и h.
import readline readline.parse_and_bind('tab: complete') readline.parse_and_bind('set editing-mode vi') while True: line = input('Prompt ("stop" to quit): ') if line == 'stop': break print (f'ENTERED: "{line}"')
Та же самая конфигурация может быть сохранена как инструкции в файле, читаемом библиотекой за один вызов. Если myreadline.rc
содержит:
tab: complete set editing-mode vi
то файл можно прочитать с помощью функции модуля read_init_file()
:
import readline readline.read_init_file('myreadline.rc') while True: line = input('!("stop" to quit) Ввод текста: =>') if line == 'stop': break print (f'Отправлено: "{line}"')
В качестве примера того, как построить завершение командной строки, можно взглянуть на программу, которая имеет встроенный набор возможных команд и использует завершение табуляции, когда пользователь вводит инструкции.
import readline class SimpleCompleter(): def __init__(self, options): self.options = sorted(options) return def complete(self, text, state): response = None if state == 0: # Создание списка соответствий. if text: self.matches = [s for s in self.options if s and s.startswith(text)] else: self.matches = self.options[:] # Вернуть элемент состояния из списка совпадений, # если их много. try: response = self.matches[state] except IndexError: response = None return response def inputing(): line = '' while line != 'stop': line = input('!("stop" to quit) Ввод текста: => ') print (f'Отправка: {line}') # Регистрация класса 'SimpleCompleter' readline.set_completer(SimpleCompleter(['start', 'stop', 'list', 'print']).complete) # Регистрация клавиши `tab` для автодополнения readline.parse_and_bind('tab: complete') # Запрос текста inputing()
Класс SimpleCompleter
хранит список параметров, которые являются кандидатами на автозаполнение. Метод complete()
предназначен для регистрации в функции set_completer()
модуля readline
в качестве источника дополнений. Аргументами являются строка text
для завершения и значение state
, указывающее, сколько раз функция была вызвана с одним и тем же текстом. Функция вызывается многократно, состояние увеличивается каждый раз. Класс SimpleCompleter
должен вернуть строку, если есть кандидат для этого значения состояния, или None
, если нет кандидатов. Реализация метода complete()
ищет набор совпадений, когда состояние равно 0
, а затем возвращает все совпадения кандидатов по одному при последующих вызовах.
Если дважды нажать клавишу TAB , список параметров будет напечатан.
# Дважды нажата клавиша 'TAB' !("stop" to quit) Ввод текста: => # list print start stop # Введите 'li' и дважды нажмите 'TAB' !("stop" to quit) Ввод текста: => list # Введите 'pr' и дважды нажмите 'TAB' !("stop" to quit) Ввод текста: => print # Отправка: print
Функции модуля readline
можно использовать для манипулирования текстом входного буфера. В примере выполняются команды с вложенными опциями. Метод complete()
должен посмотреть на положение завершения в буфере ввода, чтобы определить, является ли оно частью первого слова или более позднего слова. Если целью является первое слово, то в качестве кандидатов используются ключи словаря опций. Если это не первое слово, то первое слово используется для поиска кандидатов из словаря опций.
Будем обрабатывать три команды верхнего уровня, две из которых имеют подкоманды:
import readline class BufferAwareCompleter(): def __init__(self, options): self.options = options self.current_candidates = [] return def complete(self, text, state): response = None if state == 0: # Создание списка соответствий. origline = readline.get_line_buffer() begin = readline.get_begidx() end = readline.get_endidx() being_completed = origline[begin:end] words = origline.split() if not words: self.current_candidates = sorted(self.options.keys()) else: try: if begin == 0: # Первое слово candidates = self.options.keys() else: # Последующие слова first = words[0] candidates = self.options[first] if being_completed: # параметры сопоставления с частью ввода self.current_candidates = [ w for w in candidates if w.startswith(being_completed) ] else: # соответствие пустой строке, используются все кандидаты self.current_candidates = candidates except IndexError as err: self.current_candidates = [] except KeyError as err: self.current_candidates = [] try: response = self.current_candidates[state] except IndexError: response = None return response def inputing(): line = '' while line != 'stop': line = input('!("stop" to quit) Ввод текста: => ') print (f'Отправка: {line}') # Регистрация класса 'BufferAwareCompleter' readline.set_completer(BufferAwareCompleter( {'list':['files', 'directories'], 'print':['byname', 'bysize'], 'stop':[], }).complete) # Регистрация клавиши `tab` для автодополнения readline.parse_and_bind('tab: complete') # Запрос текста inputing()
Если запустить программу то можно увидеть что-то подобное:
# Дважды нажата клавиша 'TAB' !("stop" to quit) Ввод текста: => list print stop # Введите 'li' и дважды нажмите 'TAB' !("stop" to quit) Ввод текста: => list # Дважды нажата клавиша 'TAB' !("stop" to quit) Ввод текста: => list directories files # Введите 'di' и дважды нажмите 'TAB' !("stop" to quit) Ввод текста: => list directories Отправка: list directories
Модуль readline
автоматически отслеживает историю ввода. Есть два разных набора функций для работы с историей. Доступ к истории текущего сеанса можно получить с помощью функций модуля get_current_history_length()
и get_history_item()
. Эта же история может быть сохранена в файл для последующей загрузки с использованием write_history_file()
и read_history_file()
. По умолчанию вся история сохраняется, но максимальная длина файла может быть установлена с помощью set_history_length()
. Длина -1
означает отсутствие ограничений.
import readline import os HISTORY_FILENAME = 'completer.hist' def get_history_items(): return [ readline.get_history_item(i) for i in range(1, readline.get_current_history_length() + 1) ] class HistoryCompleter(object): def __init__(self): self.matches = [] return def complete(self, text, state): response = None if state == 0: history_values = get_history_items() if text: self.matches = sorted(h for h in history_values if h and h.startswith(text)) else: self.matches = [] try: response = self.matches[state] except IndexError: response = None return response def inputing(): if os.path.exists(HISTORY_FILENAME): readline.read_history_file(HISTORY_FILENAME) print(f'Максимальная длина файла истории: {readline.get_history_length()}') print(f'История запуска:{get_history_items()}') try: while True: line = input('!("stop" to quit) Ввод текста: => ') if line == 'stop': break if line: print(f'Добавление "{line}" в файл истории.') finally: print(f'Конец записи истории: {get_history_items()}') readline.write_history_file(HISTORY_FILENAME) # Регистрация класса 'HistoryCompleter' readline.set_completer(HistoryCompleter().complete) # Регистрация клавиши `tab` для автодополнения readline.parse_and_bind('tab: complete') # Запрос текста inputing()
Запустим программу:
Максимальная длина файла истории: -1 История запуска:[] !("stop" to quit) Ввод текста: => print Добавление "print" в файл истории. !("stop" to quit) Ввод текста: => sort Добавление "sort" в файл истории. # Введите 'pr' и дважды нажмите 'TAB' !("stop" to quit) Ввод текста: => print # Введите 'so' и дважды нажмите 'TAB' !("stop" to quit) Ввод текста: => sort !("stop" to quit) Ввод текста: => stop Конец записи истории: ['print', 'sort', 'stop']
Когда скрипт запускается во второй раз, вся история читается из файла.
Максимальная длина файла истории: -1 История запуска:['print', 'sort', 'stop'] # Введите 's' и дважды нажмите 'TAB' !("stop" to quit) Ввод текста: => s sort stop
Функции для удаления отдельных элементов истории, а также очистки всей истории смотрите в разделе "Список истории".
Есть несколько доступных уловок/хуков для запуска действий как часть последовательности взаимодействия. Хук немедленно вызывается перед печатью подсказки, после приглашения перед чтением текста от пользователя.
import readline def startup_hook(): readline.insert_text('from startup_hook') def pre_input_hook(): readline.insert_text(' from pre_input_hook') readline.redisplay() readline.set_startup_hook(startup_hook) readline.set_pre_input_hook(pre_input_hook) readline.parse_and_bind('tab: complete') line = '' while line != 'stop': line = input('!("stop" to quit) Ввод текста: => ') print (f'Отправка: {line}')
Любой хук является потенциально хорошим местом для использования функцию модуля insert_text()
для изменения входного буфера.
!("stop" to quit) Ввод текста: => from startup_hook from pre_input_hook
Если буфер изменен внутри хука предварительного ввода, вам нужно вызвать функцию модуля redisplay()
для обновления экрана.