在 Windows 上防止 Python print() 自动换行转换为 CRLF

han*_*dle 6 python windows newline eol

我想通过 Windows CMD(控制台)从 Python 中使用类似 UNIX EOL (LF) 的管道文本。然而,Python 似乎会自动将单个换行符转换为 Windows 风格的行尾 (EOL)字符(即\r\n, <CR><LF>, 0D 0A, 13 10):

#!python3
#coding=utf-8
import sys
print(sys.version)
print("one\ntwo")
# run as py t.py > t.txt
Run Code Online (Sandbox Code Playgroud)

结果是

3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)]
one
two
Run Code Online (Sandbox Code Playgroud)

或十六进制... 6F 6E 65 0D 0A 74 77 6F 0D 0A

第二个 EOL 是因为 print()默认为end='\n',但它也会进行转换。

print没有newline像 for 那样的参数或属性open(),那么如何控制它呢?

han*_*dle 3

请参阅此答案: https ://stackoverflow.com/a/34997357/1619432


print()通常写入到sys.stdout. 以下是非交互模式的文档摘录:

  • stdout 用于 print() 的输出

  • sys.stdout:解释器用于标准...输出的文件对象

  • 这些流是常规文本文件,类似于 open() 函数返回的文件。

  • Windows 上的字符编码是 ANSI

  • 标准流...像常规文本文件一样进行块缓冲。

  • 注意
    要向标准流写入或读取二进制数据,请使用底层二进制缓冲区对象。例如,要将字节写入 stdout,请使用 sys.stdout.buffer.write(b'abc')。

让我们先尝试一下这种直接方法:

import sys
print("one\ntwo")
sys.stdout.write('three\nfour')
sys.stdout.buffer.write(b'five\nsix')
Run Code Online (Sandbox Code Playgroud)

结果是

five\n
sixone\r\n
two\r\n
three\r\n
four
Run Code Online (Sandbox Code Playgroud)

缓冲区写入似乎按预期工作,尽管它“扰乱”了输出顺序。

在直接写入缓冲区之前进行刷新有助于:

import sys
print("one\ntwo")
sys.stdout.write('three\nfour')
sys.stdout.flush()
sys.stdout.buffer.write(b'five\nsix')
Run Code Online (Sandbox Code Playgroud)

结果是

one\r\n
two\r\n
three\r\n
fourfive\n
six
Run Code Online (Sandbox Code Playgroud)

但它仍然没有“修复” print()。回到文件对象/流/文本文件(Python 数据模型中 IO 对象的简短信息):

https://docs.python.org/3/glossary.html#term-text-file

能够读取和写入 str 对象的文件对象。通常,文本文件实际上访问面向字节的数据流并自动处理文本编码。文本文件的示例是以文本模式(“r”或“w”)打开的文件、sys.stdin、sys.stdout 和 io.StringIO 的实例。

那么(如何)可以重新配置或重新打开 sys.stdout文件来控制换行行为?到底是什么?

>>> import sys
>>> type(sys.stdout)
<class '_io.TextIOWrapper'>
Run Code Online (Sandbox Code Playgroud)

文档:类io.TextIOWrapper(缓冲区,编码=无,错误=无,换行=无,line_buffering = False,write_through = False)

换行符控制如何处理行结尾。它可以是 None、''、'\n'、'\r' 和 '\r\n'。
它的工作原理如下:
从流中读取输入时,如果换行符为 None,则启用通用换行符模式。输入中的行可以以“\n”、“\r”或“\r\n”结尾,这些行在返回给调用者之前会被转换为“\n”。
如果是 '',则启用通用换行模式,但行结尾会以未翻译的形式返回给调用者。
如果它具有任何其他合法值,则输入行仅由给定字符串终止,并且行结尾将以未翻译的形式返回给调用者。

将输出写入流时,如果换行符为 None,则写入的任何 '\n' 字符都将转换为系统默认行分隔符os.linesep 。 如果换行符是 '' 或 '\n',则不会进行任何翻译。 如果换行符是任何其他合法值,则写入的任何“\n”字符都将转换为给定字符串。

让我们来看看:

>>> sys.stdout.newline = "\n"
>>>
Run Code Online (Sandbox Code Playgroud)

好的,那么呢

import sys
sys.stdout.newline = '\n'
print("one\ntwo")
Run Code Online (Sandbox Code Playgroud)

不起作用:

one\r\n
two\r\n
Run Code Online (Sandbox Code Playgroud)

因为该属性不存在:

>>> sys.stdout.newline
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: '_io.TextIOWrapper' object has no attribute 'newline'
Run Code Online (Sandbox Code Playgroud)

我应该早点检查一下..

>>> vars(sys.stdout)
{'mode': 'w'}
Run Code Online (Sandbox Code Playgroud)

所以实际上,我们没有任何newline属性需要重新定义。

有什么好用的方法吗?

>>> dir(sys.stdout)
['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', 
'__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', 
'__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', 
'__init__', '__init_subclass__', '__iter__', '__le__', '__lt__',
'__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', 
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 
'_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', 
'_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 
'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode', 
'name', 'newlines', 'read', 'readable', 'readline', 'readlines',
'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 
'writelines']
Run Code Online (Sandbox Code Playgroud)

并不真地。

但我们至少可以替换缓冲区末端的默认接口,指定所需的换行符:

import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, newline='\n' )
print("one\ntwo")
Run Code Online (Sandbox Code Playgroud)

最终结果是

one\n
two\n
Run Code Online (Sandbox Code Playgroud)

要恢复,只需重新分配sys.stdout到您制作的副本即可。或者,显然不建议使用内部保存sys.__stdout__来做到这一点。

警告:请参阅下面eryksun的评论,这需要小心。使用他的解决方案(下面的链接):


似乎也可以重新打开文件,请参阅使用 io.TextIOWrapper 包装打开的流以获取灵感,并通过此答案/sf/answers/2449815021/了解实现。


如果您想仔细查看,请查看 Python (CPython) 源代码: https://github.com/python/cpython/blob/master/Modules/_io/textio.c


还有os.linesep,让我们看看它是否真的是 Windows 的“\r\n”:

>>> import os
>>> os.linesep
'\r\n'
>>> ",".join([f'0x{ord(c):X}' for c in os.linesep])
'0xD,0xA'
Run Code Online (Sandbox Code Playgroud)

这可以重新定义吗?

#!python3
#coding=utf-8
import sys, os
saved = os.linesep
os.linesep = '\n'
print(os.linesep)
print("one\ntwo")
os.linesep = saved
Run Code Online (Sandbox Code Playgroud)

它可以在交互模式下,但显然不能在其他模式下:

\r\n
\r\n
one\r\n
two\r\n
Run Code Online (Sandbox Code Playgroud)