Python编程 | 系统编程 | 脚本运行上下文 | 标准流

文章目录

  • 标准流
    • 重定向流到文件或程序
      • 重定向的常见作用
      • 用管道(*pipe*)链接程序
      • adders和sorters的替代编码之选
      • 重定向流和用户交互
    • 重定向流到*Python*对象
    • `io.StringIO`和`io.BytesIO`工具类
    • 捕获`stderr`流
    • `print`调用中的重定向语法
    • 其他重定向选项:重访`os.popen`和子进程
      • 用`os.popen`重定向输入或输出
      • 利用`subprocess`重定向输入输出

标准流

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: breakelse:if reply_str == 'exit': breakreply_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

使用stdinstdout实现脚本程序的通信,可以简化复用过程

示例:sorter.py

import syslines_list = sys.stdin.readlines()
lines_list.sort()
for line_str in lines_list: print(line_str, end='')

示例:adder.py

sum_int = 0while True:try: data_str = input()except EOFError: breakelse: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 syssum_int = 0
while True:line_str = sys.stdin.readline()if not line_str: breaksum_int += int(line_str)
print(sum_int)
  • int()可以接受两端带有空白的数字

我们可以利用Python最新的file iterator来实现它,在file对象上迭代,for循环每次自动抓取一行

示例:adder3.py

import syssum_int = 0
for line_str in sys.stdin: sum_int += int(line_str)
print(sum_int)

Python 2.4以后的脚本还可以更精简:使用内部的sorted()函数生成器表达式、文件迭代器。

示例:sorter_small.py

import sysfor line_str in sorted(sys.stdin): print(line_str, end='')

示例:adder_small.py

import sysprint(sum(int(line_str) for line_str in sys.stdin))

重定向流和用户交互

之前,我们曾用Python实现了more分页程序。

然而,more.py还隐藏着不易察觉的缺点:如果使用管道重定向标准输出流到more,且输出足够长导致more产生分页,向用户提示是否分页,脚本会直接报错EOFError

原因在于more.py错误地使用stdin

  • 一方面,它通过调用inputstdin读取用户的标准输入流

  • 另一方面,它又从stdin中读取输入文本

stdin流被重定向到文件或者管道时,我们无法再用它读取用户输入,此时它只能获取输入源的文本。而且,stdin程序启动前就被重定向,因此无法获取重定向前的stdout

因此,就需要特殊的接口从键盘而非stdin直接读取用户输入。

  • Windows下,提供了Python标准库msvcrt

  • Unix下,可以读取设备文件/dev/tty

这里给出Windows平台下的示例

示例:more_plus.py

#!/usr/bin/env python
"""
分隔字符串或文本文件并交互的进行分页
"""def get_reply():"读取用户交互式的回复键,即使stdin重定向到某个文件或者管道"import sysif sys.stdin.isatty():return input('More? ')else:if sys.platform[:3] == 'win':import msvcrtmsvcrt.putch(b'More? ')key = msvcrt.getche()msvcrt.putch(b'n')return keyelse: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']: breakif __name__ == '__main__':import sysif len(sys.argv) == 1: more(sys.stdin.read())else: more(open(sys.argv[1]).read())

回顾一下,我们总共用了4种方式使用more

  1. 加载和函数调用
  2. 命令行参数传递文件名
  3. 重定向stdin到文件
  4. 通过管道将内容输出到stdin

重定向流到Python对象

Python里,任何在方法上与文件类似的对象都可以充当标准流。它和对象的数据类型无关,而取决于接口(有时被称为协议)。

  • 任何提供了类似于文件read方法的对象都可以指定给sys.stdin,以从该对象的read方法读取输入

  • 任何定义了类似于文件write方法的对象都可以指定给sys.stdout,所有标准输出都将发送到该对象方法上

示例:redirect.py

"将函数运行的结果重定向"import sysclass Output:"类似文件的类,提供了write等方法"def __init__(self):self.text = ''def write(self, text_str):self.text += text_strdef wirtelines(self, lines):for line_str in lines:self.write(line_str)class Input:"类似文件的类,提供了read等方法"def __init__(self, input_str=''):self.text = input_strdef 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_textdef 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_textdef redirect(function, pargs_tuple, kargs_dict, input_str):"将函数运行的结果重定向"save_streams = sys.stdin, sys.stdoutsys.stdin = Input(input_str=input_str)sys.stdout = Output()try:r_result = function(*pargs_tuple, **kargs_dict)r_output_str = sys.stdout.textfinally:sys.stdin, sys.stdout = save_streamsreturn r_result, r_output_str
  • Output:提供了作为输出文件所需的write接口(又称protocol),但是它将所有的输出保存到一个内存字符串

  • Input:提供了作为输入文件所需的read接口,在对象构造时它被传入一个内存字符串,在被调用时它将作为输出。

  • redirect():绑定了这两个对象,将输入和输出重定向到Python类对象,运行了一个简单的函数。所传入的函数function不必在意它的print()input(),以及stdinstdout等调用在和一个类而非实际文件或管道等打交道。

>>> 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, (), {}, '2n3n4n')
>>> result
>>> print(result)
None
>>> output_str
'Hello, stream world!nEnter a number >>> 2 squared is 4nEnter a number >>> 3 squared is 9nEnter a number >>> 4 squared is 16nEnter a number >>> :) Byen'
>>> 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.StringIOio.BytesIO工具类

标准类工具io.StringIO提供了一个对象,它将一个文件对象接口和内存字符串相映射。

>>> from io import StringIO										# 在字符串中保存写入的文本
>>> buff = StringIO()
>>> buff.write('BeacherHoun')
11
>>> buff.write('侯宇泽n')
4
>>> buff.getvalue()
'BeacherHoun侯宇泽n'
>>> 
>>> buff = StringIO('BeacherHoun侯宇泽n')					# 从字符串中读取输入值
>>> buff.getvalue()
'BeacherHoun侯宇泽n'

io.StringIO对象实例可以指定给stdinstdout以及重定向给printinput调用,并像真实文件对象那样传给任何代码。再次强调, Python 中的对象接口( interface ),并不是具体的数据类型。

>>> from io import StringIO
>>> import sys
>>> 
>>> 
>>> buff = StringIO()
>>> save_stdout = sys.stdout
>>> sys.stdout = buff
>>> print('BeacherHou', '侯宇泽')				# 或者输出“print(..., file=buff)”
>>> sys.stdout = save_stdout				# 重新存储原始数据流
>>> 
>>> buff.getvalue()
'BeacherHou 侯宇泽n'

标准类工具io.BytesIO,它和StringIO相似,只不过是将文件操作映射到内存字节缓冲区,而非str字符串。

>>> from io import BytesIO
>>> buff = BytesIO()
>>> buff.write(b'BeacherHoun')
11
>>> buff.getvalue()
b'BeacherHoun'
>>> 
>>> buff = BytesIO(b'BeacherHoun')
>>> buff.getvalue()
b'BeacherHoun'

捕获stderr

同样可行,比如,将stderr流重定向到一个类对象比如OutputStringIO上,便可以让你的脚本拦截打印到标准错误的文本。

print调用中的重定向语法

Python内部的print函数同样可以拓展为显式指定一个文件,所有的输出将发送到该文件上。

print('BeacherHou', file=afile)				# “afile”是一个文件对象,而非一个字符串名称

其他重定向选项:重访os.popen和子进程

os.popensubprocess工具成为重定向子程序流的又一方式。它们的效果类似于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 worldn'
>>> output.close()
>>> print(output.close())
None

Python脚本同样可以为生成程序的标准输入流提供输入,传入一个"w"模式参数来替代默认的"r",会把返回对象连接到生成程序的输入流上。

>>> pipe = os.popen('python hello_in.py', 'w')
>>> pipe.write('BeacherHoun')
11
>>> pipe.close()
>>> open('hello_in.txt').read()
'Hello BeacherHoun'

调用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 worldn', None)				# (stdout, stderr)
>>> pipe.returncode
0
>>> 
>>> pipe = Popen('python hello_out.py', shell=True, stdout=PIPE)
>>> pipe.stdout.read()
b'Hello stream worldn'
>>> pipe.wait()
0

重定向、连接到派生程序的输出流

>>> pipe = Popen('python hello_in.py', shell=True, stdin=PIPE)
>>> pipe.stdin.write(b'BeacherHoun')
11
>>> pipe.stdin.close()
>>> pipe.wait()
0
>>> open('hello_in.txt').read()
'Hello BeacherHoun'

之前写过的程序: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'BeacherHoun')
11
>>> pipe.stdin.write(b'12n')
3
>>> pipe.stdin.close()
>>> pipe.stdout.read()
b'Got this: "BeacherHou"nThe meaning of life is 12 24n'
>>> 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 84n', 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

Published by

风君子

独自遨游何稽首 揭天掀地慰生平