标准流
sys 模块提供了Python的标准输入,输出和错误流
import sys
sys.stdin, sys.stdout, sys.stderr
(<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>,
<ipykernel.iostream.OutStream at 0x7f734b0c24e0>,
<ipykernel.iostream.OutStream at 0x7f735362a0f0>)
标准流是预先打开的Python 文件对象。print() 和input() 实际上只是标准输出流和输入流的接口,因此它们和sys.stdin() /sys.stdout() 类似:
print('Hello, world!')
sys.stdout.write('Hello, world!' + '\n')
Hello, world!
Hello, world!
input('Your name >>> ')
print('Your name >>> ');sys.stdin.readline()[:-1]
Your name >>> Tom
Your name >>>
重定向流到文件或程序
重定向的常见作用
- 通过重定向标准输入流到不同的程序,可以将一个测试脚本应用于任意的输出
- 重定向标准输出流使得我们能够保存及后续分析一个程序的输出
示例:teststreams.py
"read numbers till eof and show squares"
def interact():
"与用户交互计算平方"
print('Hello, stream world!')
while True:
try: reply_str = input('Enter a number >>> ')
except EOFError: break
else:
if reply_str == 'exit': break
reply_int = int(reply_str)
print('%s squared is %d' % (reply_str, reply_int ** 2))
print(':) Bye')
if __name__ == '__main__':
interact()
Hello, stream world!
Enter a number >>> 11
11 squared is 121
Enter a number >>> 6
6 squared is 36
Enter a number >>> exit
:) Bye
- 它从标准输出流中读入数字,直到读入文件结束符(在Windows下是
CTRL + Z ,在Unix下是CTRL + D ) - EOFError: 读取到文件结束符
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/Python/Python项目/pp4e/system$ cat input.txt
11
6
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/Python/Python项目/pp4e/system$ python test_streams.py < input.txt
Hello, stream world!
Enter a number >>> 11 squared is 121
Enter a number >>> 6 squared is 36
Enter a number >>> :) Bye
- 可以利用shell语法
< filename 把标准输入流重定向到文件输入
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/Python/Python项目/pp4e/system$ python test_streams.py < input.txt > output.txt
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/Python/Python项目/pp4e/system$ cat output.txt
Hello, stream world!
Enter a number >>> 11 squared is 121
Enter a number >>> 6 squared is 36
Enter a number >>> :) Bye
- 也可以利用shell语法将标准输出流重定向到文件中
用管道(pipe)链接程序
在两个命令之间使用shell字符| ,可以将一个程序的标准输出发送到另一个程序的标准输入。
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/Python/Python项目/pp4e/system$ python test_streams.py < input.txt | more
Hello, stream world!
Enter a number >>> 11 squared is 121
Enter a number >>> 6 squared is 36
Enter a number >>> :) Bye
使用stdin 和stdout 实现脚本程序的通信,可以简化复用过程
示例:sorter.py
import sys
lines_list = sys.stdin.readlines()
lines_list.sort()
for line_str in lines_list: print(line_str, end='')
示例:adder.py
sum_int = 0
while True:
try: data_str = input()
except EOFError: break
else:
sum_int += int(data_str)
print(sum_int)
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/Python/Python项目/pp4e/system$ cat data.txt
2007
0000
1106
1234
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/Python/Python项目/pp4e/system$ python sorter.py < data.txt
0000
1106
1234
2007
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/Python/Python项目/pp4e/system$ python adder.py < data.txt
4347
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/Python/Python项目/pp4e/system$ cat data.txt | python sorter.py | python adder.py
4347
adders和sorters的替代编码之选
- sorter.py使用
readlines() 一次性从stdin 读取所有输出 - adder.py每次只读取一行
一些平台会并行启动用管道链接的程序,在这种系统之下,如果数据量过大,按行读取将会更加优雅
示例:adder2.py
import sys
sum_int = 0
while True:
line_str = sys.stdin.readline()
if not line_str: break
sum_int += int(line_str)
print(sum_int)
我们可以利用Python最新的file iterator来实现它,在file对象上迭代,for 循环每次自动抓取一行
示例:adder3.py
import sys
sum_int = 0
for line_str in sys.stdin: sum_int += int(line_str)
print(sum_int)
在Python 2.4以后的脚本还可以更精简:使用内部的sorted() 函数生成器表达式、文件迭代器。
示例:sorter_small.py
import sys
for line_str in sorted(sys.stdin): print(line_str, end='')
示例:adder_small.py
import sys
print(sum(int(line_str) for line_str in sys.stdin))
重定向流和用户交互
之前,我们曾用Python实现了more 分页程序。
然而,more.py还隐藏着不易察觉的缺点:如果使用管道重定向标准输出流到more,且输出足够长导致more产生分页,向用户提示是否分页,脚本会直接报错EOFError
原因在于more.py错误地使用stdin :
当stdin 流被重定向到文件或者管道时,我们无法再用它读取用户输入,此时它只能获取输入源的文本。而且,stdin 在程序启动前就被重定向,因此无法获取重定向前的stdout 。
因此,就需要特殊的接口从键盘而非stdin 直接读取用户输入。
这里给出Windows平台下的示例
示例:more_plus.py
"""
分隔字符串或文本文件并交互的进行分页
"""
def get_reply():
"读取用户交互式的回复键,即使stdin重定向到某个文件或者管道"
import sys
if sys.stdin.isatty():
return input('More? ')
else:
if sys.platform[:3] == 'win':
import msvcrt
msvcrt.putch(b'More? ')
key = msvcrt.getche()
msvcrt.putch(b'\n')
return key
else:
assert False, 'platform not supported'
def more(text_str, num_lines=15):
lines = text_str.splitlines()
while lines:
chunk = lines[: num_lines]
lines = lines[num_lines: ]
for line in chunk: print(line)
if lines and get_reply() not in [b'y', b'Y']: break
if __name__ == '__main__':
import sys
if len(sys.argv) == 1: more(sys.stdin.read())
else: more(open(sys.argv[1]).read())
回顾一下,我们总共用了4种方式使用more
- 加载和函数调用
- 命令行参数传递文件名
- 重定向
stdin 到文件 - 通过管道将内容输出到
stdin
重定向流到Python对象
在Python里,任何在方法上与文件类似的对象都可以充当标准流。它和对象的数据类型无关,而取决于接口(有时被称为协议)。
示例:redirect.py
"将函数运行的结果重定向"
import sys
class Output:
"类似文件的类,提供了write等方法"
def __init__(self):
self.text = ''
def write(self, text_str):
self.text += text_str
def wirtelines(self, lines):
for line_str in lines:
self.write(line_str)
class Input:
"类似文件的类,提供了read等方法"
def __init__(self, input_str=''):
self.text = input_str
def read(self, size=None):
if not size:
r_text, self.text = self.text, ''
else:
r_text, self.text = self.text[:size], self.text[size:]
return r_text
def readline(self):
n = self.text.find('\n')
if n == -1:
r_text, self.text = self.text, ''
else:
r_text, self.text = self.text[:n + 1], self.text[n + 1:]
return r_text
def redirect(function, pargs_tuple, kargs_dict, input_str):
"将函数运行的结果重定向"
save_streams = sys.stdin, sys.stdout
sys.stdin = Input(input_str=input_str)
sys.stdout = Output()
try:
r_result = function(*pargs_tuple, **kargs_dict)
r_output_str = sys.stdout.text
finally:
sys.stdin, sys.stdout = save_streams
return r_result, r_output_str
-
Output :提供了作为输出文件所需的write 接口(又称protocol ),但是它将所有的输出保存到一个内存字符串。 -
Input :提供了作为输入文件所需的read 接口,在对象构造时它被传入一个内存字符串,在被调用时它将作为输出。 -
redirect() :绑定了这两个对象,将输入和输出重定向到Python类对象,运行了一个简单的函数。所传入的函数function 不必在意它的print() 和input() ,以及stdin 和stdout 等调用在和一个类而非实际文件或管道等打交道。
>>> from test_streams 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 >>> 4
4 squared is 16
Enter a number >>> :) Bye
>>> from redirect import redirect
>>> result, output_str = redirect(interact, (), {}, '2\n3\n4\n')
>>> result
>>> print(result)
None
>>> output_str
'Hello, stream world!\nEnter a number >>> 2 squared is 4\nEnter a number >>> 3 squared is 9\nEnter a number >>> 4 squared is 16\nEnter a number >>> :) Bye\n'
>>> print(output_str)
Hello, stream world!
Enter a number >>> 2 squared is 4
Enter a number >>> 3 squared is 9
Enter a number >>> 4 squared is 16
Enter a number >>> :) Bye
io.StringIO 和io.BytesIO 工具类
标准类工具io.StringIO 提供了一个对象,它将一个文件对象接口和内存字符串相映射。
>>> from io import StringIO
>>> buff = StringIO()
>>> buff.write('BeacherHou\n')
11
>>> buff.write('侯宇泽\n')
4
>>> buff.getvalue()
'BeacherHou\n侯宇泽\n'
>>>
>>> buff = StringIO('BeacherHou\n侯宇泽\n')
>>> buff.getvalue()
'BeacherHou\n侯宇泽\n'
io.StringIO 对象实例可以指定给stdin 和stdout 以及重定向给print 和input 调用,并像真实文件对象那样传给任何代码。再次强调, Python 中的对象接口( interface ),并不是具体的数据类型。
>>> from io import StringIO
>>> import sys
>>>
>>>
>>> buff = StringIO()
>>> save_stdout = sys.stdout
>>> sys.stdout = buff
>>> print('BeacherHou', '侯宇泽')
>>> sys.stdout = save_stdout
>>>
>>> buff.getvalue()
'BeacherHou 侯宇泽\n'
标准类工具io.BytesIO ,它和StringIO 相似,只不过是将文件操作映射到内存字节缓冲区,而非str字符串。
>>> from io import BytesIO
>>> buff = BytesIO()
>>> buff.write(b'BeacherHou\n')
11
>>> buff.getvalue()
b'BeacherHou\n'
>>>
>>> buff = BytesIO(b'BeacherHou\n')
>>> buff.getvalue()
b'BeacherHou\n'
捕获stderr 流
同样可行,比如,将stderr 流重定向到一个类对象比如Output 或StringIO 上,便可以让你的脚本拦截打印到标准错误的文本。
print 调用中的重定向语法
Python内部的print 函数同样可以拓展为显式指定一个文件,所有的输出将发送到该文件上。
print('BeacherHou', file=afile)
其他重定向选项:重访os.popen 和子进程
os.popen 和subprocess 工具成为重定向子程序流的又一方式。它们的效果类似于shell的重定向流到程序的命令行管道语法(实际上,它们名字的含义是“管道开放”),但它们是在脚本的内部运行,对管道流提供了类似file的接口。它们和redirect 函数类似,但它们是基于运行的程序(而非调用函数),命令行的流在子程序中被当做文件(而非绑定到类文件上)。这些工具对脚本所启动的程序的流(包括标准输出流、标准输入流等)进行重定向,而非重定向脚本本身的流。
用os.popen 重定向输入或输出
事实上,通过传入不同的模式标志,可以在调用的脚本中重定向一个子程序的输入或输出流到文件,可以从close 方法中获取程序的退出状态码(None 意味着“没有错误”)。
示例:hello_out.py
print('Hello stream world')
示例:hello_in.py
user_str = input()
open('hello_in.txt', 'w').write('Hello ' + user_str + '\n')
beacherhou@alone-Vostro-14-5401:~/media/Coding/code_obsidian_知识库/Python编程_Markdown笔记/pp4e/system$ python hello_out.py
Hello stream world
beacherhou@alone-Vostro-14-5401:~/media/Coding/code_obsidian_知识库/Python编程_Markdown笔记/pp4e/system$ python hello_in.py
BeacherHou
beacherhou@alone-Vostro-14-5401:~/media/Coding/code_obsidian_知识库/Python编程_Markdown笔记/pp4e/system$ cat hello_in.txt
Hello BeacherHou
之前学过,Python可以读取其他脚本或程序的输出。
>>> import os
>>> output = os.popen('python hello_out.py')
>>> output.read()
'Hello stream world\n'
>>> output.close()
>>> print(output.close())
None
Python脚本同样可以为生成程序的标准输入流提供输入,传入一个"w"模式参数来替代默认的"r",会把返回对象连接到生成程序的输入流上。
>>> pipe = os.popen('python hello_in.py', 'w')
>>> pipe.write('BeacherHou\n')
11
>>> pipe.close()
>>> open('hello_in.txt').read()
'Hello BeacherHou\n'
调用popen 同样可以支持该功能的平台上将命令字符串作为一个独立的进程运行。技巧在于,它接受一个可选的第三参数用来控制文本的缓冲。
利用subprocess 重定向输入输出
我们已经知道,该模块可以模拟os.popen 的功能,同时它还能够实现双向流的通信(访问一个程序的输入和输出),将一个程序的输出发送到另一个程序的输入。
例如,该模块提供了多种衍生子程序并获取它们的标准输出和退出状态的方式。
>>> from subprocess import Popen, PIPE, call
>>>
>>> output = call('python hello_out.py', shell=True)
Hello stream world
>>> output
0
>>>
>>> pipe = Popen('python hello_out.py', shell=True, stdout=PIPE)
>>> pipe.communicate()
(b'Hello stream world\n', None)
>>> pipe.returncode
0
>>>
>>> pipe = Popen('python hello_out.py', shell=True, stdout=PIPE)
>>> pipe.stdout.read()
b'Hello stream world\n'
>>> pipe.wait()
0
重定向、连接到派生程序的输出流
>>> pipe = Popen('python hello_in.py', shell=True, stdin=PIPE)
>>> pipe.stdin.write(b'BeacherHou\n')
11
>>> pipe.stdin.close()
>>> pipe.wait()
0
>>> open('hello_in.txt').read()
'Hello BeacherHou\n'
之前写过的程序:writer.py
print("Help! Help! I'm being repressed!")
print(42)
也是之前写过的程序:reader.py
print('Got this: "%s"' % input())
import sys
data = sys.stdin.readline()[:-1]
print('The meaning of life is', data, int(data) * 2)
事实上,我们可以利用subprocess 模块来获取派生程序的输入和输出流。
>>> pipe = Popen('python reader.py', shell=True, stdin=PIPE, stdout=PIPE)
>>> pipe.stdin.write(b'BeacherHou\n')
11
>>> pipe.stdin.write(b'12\n')
3
>>> pipe.stdin.close()
>>> pipe.stdout.read()
b'Got this: "BeacherHou"\nThe meaning of life is 12 24\n'
>>> pipe.wait()
0
当与程序反复交互时需谨慎对待:如果读写是交替发生的,缓冲输出流可能会导致死锁,可能需要使用类似Pexpect 等工具作为变通方案。
最后,即使控制更多的外部流也是可能的。
先使用shell语法:
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/code_obsidian_知识库/Python编程_Markdown笔记/pp4e/system$ python writer.py | python reader.py
Got this: "Help! Help! I'm being repressed!"
The meaning of life is 42 84
也可以用subprocess 模块:
>>> pipe_1 = Popen('python writer.py', shell=True, stdout=PIPE)
>>> pipe_2 = Popen('python reader.py', shell=True, stdin=pipe_1.stdout, stdout=PIPE)
>>> pipe_2.communicate()
(b'Got this: "Help! Help! I\'m being repressed!"\nThe meaning of life is 42 84\n', None)
>>> pipe_2.returncode
0
也可以用os.popen 来实现,但是由于它的管道只能读或写(单工),我们无法在代码中获取第二个脚本的输出:
>>> import os
>>> pipe_1 = os.popen('python writer.py', 'r')
>>> pipe_2 = os.popen('python reader.py', 'w')
>>> pipe_2.write(pipe_1.read())
36
>>> pipe_2.close()
Got this: "Help! Help! I'm being repressed!"
The meaning of life is 42 84
>>> output = pipe_2.close()
>>> output
>>> print(output)
None
:)学完博客后,是不是有所启发呢?如果对此还有疑问,欢迎在评论区留言哦。 如果还想了解更多的信息,欢迎大佬们关注我哦,也可以查看我的个人博客网站BeacherHou
|