Если вам уже приходилось изучать язык Python, вы, вероятно, знаете, что подобная совместимость по интерфейсам обычно называется полиморфизмом - неважно, чем является объект, и неважно, что делают его методы, - важно, чтобы этот объект предоставлял ожидаемый интерфейс. Такое либеральное отношение к типам данных объясняет значительную долю гибкости и выразительности программного кода на языке Python. Ниже демонстрируется способ, позволяющий сценариям переопределять собственные потоки ввода-вывода. В примере 3.9 приводится вспомогательный модуль, демонстрирующий эту идею.
Пример 3.9. PP4E\System\Streams\redirect.py
объекты, похожие на файлы, один из которых сохраняет в строке текст, отправленный в стандартный поток вывода, а другой обеспечивает ввод текста из строки в стандартный поток ввода; функция redirect вызывает переданную ей функцию, для которой стандартные потоки вывода и ввода будут связаны с объектами, похожими на файлы;
import sys # импортировать встроенный модуль
class Output: # имитирует выходной файл
def __init__(self):
self.text = ‘’ # при создании строка пустая
def write(self, string): # добавляет строку байтов
self.text += string
def writelines(self, lines): # добавляет все строки в список
for line in lines: self.write(line)
class Input: # имитирует входной файл
def __init__(self, input=’’): # аргумент по умолчанию
self.text = input # сохранить строку при создании
def read(self, size=None): # необязательный аргумент
if size == None: # прочитать N байт или все
res, self.text = self.text, ‘’ else:
res, self.text = self.text[:size], self.text[size:] return res def readline(self):
eoln = self.text.find(‘\n’) # найти смещение следующего eoln
if eoln == *1: # извлечь строку до eoln
res, self.text = self.text, ‘’ else:
res, self.text = self.text[:eoln+1], self.text[eoln+1:] return res
def redirect(function, pargs, kargs, input): # перенаправляет stdin/out savestreams = sys.stdin, sys.stdout # вызывает объект функции sys.stdin = Input(input) # возвращает текст в stdout
sys.stdout = Output() try:
result = function(*pargs, **kargs) # вызвать функцию с аргументами output = sys.stdout.text
finally: # восстановить, независимо от
sys.stdin, sys.stdout = savestreams # того, было ли исключение return (result, output) # вернуть результат,
# если исключения не было
В этом модуле определены два класса, маскирующиеся под настоящие файлы:
Output
Предоставляет интерфейс (он же протокол) метода записи, предполагаемый у выходных файлов, но сохраняет всю записываемую информацию в строке, хранящейся в памяти.
Input
Предоставляет интерфейс, предполагаемый у входных файлов, но возвращает входные данные по требованию, извлекая их из хранящейся в памяти строки, переданной при создании объекта.
Функция redirect в конце этого файла объединяет эти два объекта, чтобы выполнить единственную функцию, для которой стандартные потоки ввода и вывода будут перенаправлены в объекты Python. Функции, которая вызывается функцией redirect, не требуется ни знать, ни заботиться о том, что вызываемые ею функции print и input или методы stdin и stdout в действительности будут иметь дело с нашими объектами, а не с настоящим файлом, каналом или пользователем.
Чтобы продемонстрировать, как действует эта функция, импортируем и вызовем функцию interact, лежащую в основе сценария teststreams, представленного в примере 3.5, который прежде мы запускали из командной строки (для использования вспомогательной функции перенаправления нужно действовать на языке функций, а не файлов). При непосредственном вызове функция читает данные с клавиатуры и выводит результаты на экран, как если бы она выполнялась как программа без перенаправления:
C:\...\PP4E\System\Streams> python >>> from teststreams import interact >>> interact()
Hello stream world Enter a number>2
2 squared is 4 Enter a number>3
3 squared is 9 Enter a number^Z Bye
>>>
Теперь вызовем эту функцию под управлением функции перенаправления в redirect.py и передадим ей некоторый готовый входной текст. В этом случае на вход функции interact поступит переданная строка ('4\n5\n6\n ’ - три строки с явными символами конца строки), а результатом выполнения функции будет кортеж, содержащий возвращаемое значение и строку с текстом, который был записан в стандартный поток вывода:
>>> from redirect import redirect
>>> (result, output) = redirect(interact, (), {}, '4\n5\n6\n')
>>> print(result)
None
>>> output
‘Hello stream world\nEnter a number>4 squared is 16\nEnter a number>5 squared is 25\nEnter a number>6 squared is 36\nEnter a number>Bye\n’
На выходе получится одна длинная строка, содержащая весь текст, записанный в стандартный поток вывода. Чтобы улучшить внешний вид строки, ее можно передать функции print или разбить на отдельные строки с помощью строкового метода splitlines:
>>> for line in output.splitlines(): print(line)