龙空技术网

如何刷新 Python 打印函数的输出

趣学Python 98

前言:

当前我们对“python打印文本”大致比较关切,朋友们都需要剖析一些“python打印文本”的相关资讯。那么小编同时在网络上收集了一些对于“python打印文本””的相关内容,希望姐妹们能喜欢,朋友们快快来学习一下吧!

您是否想使用print() 为您的 Python 脚本构建一个紧凑的可视化进度指示器,但您的输出在您期望时没有显示出来?或者您是否将脚本的日志管道传输到另一个应用程序,但无法实时访问它们?在这两种情况下,数据缓冲都是罪魁祸首,您可以通过刷新输出来解决您的麻烦。print()

在本教程中,你将了解如何:

使用 flush参数显式刷新输出数据缓冲区print()更改单个函数、整个脚本甚至整个 Python 环境的数据缓冲确定何时需要显式刷新数据缓冲区,何时不需要刷新

通过重复运行仅略微更改的短代码片段,您将看到,如果使用其默认参数运行,则其执行在交互模式下进行行缓冲,否则进行块缓冲。print()

通过实际探索代码,您将了解所有这些含义。但在深入了解在 Python 中更改输出流缓冲之前,重新审视默认情况下它是如何发生的,并了解您可能想要更改它的原因会很有帮助。

了解 Python 缓冲区如何输出

当您对类似文件的对象进行写入调用时,Python 默认会缓冲调用 - 这是一个好主意!与随机存取存储器 (RAM) 存取相比,磁盘写入和读取操作较慢。当您的脚本通过在 RAM 数据缓冲区中批处理字符并通过单个系统调用将它们一次性写入磁盘来减少对写入操作的系统调用时,您可以节省大量时间。

要将缓冲用例置于实际上下文中,请将交通信号灯视为汽车交通的缓冲区。如果每辆车在到达时立即穿过十字路口,就会陷入拥堵。这就是为什么交通信号灯缓冲来自一个方向的交通,而另一个方向刷新的原因。

注意:数据缓冲区通常基于大小,而不是基于时间,这是流量类比的分解。在数据缓冲区的上下文中,如果一定数量的汽车排队等待,交通信号灯将切换。

但是,在某些情况下,您不希望在刷新数据缓冲区之前等待数据缓冲区填满。想象一下,有一辆救护车需要尽快通过十字路口。你不希望它在红绿灯前等待,直到有一定数量的汽车排队。

在程序中,当您需要对已执行的代码进行实时反馈时,通常希望立即刷新数据缓冲区。以下是立即刷新的几个用例:

即时反馈:在交互式环境中,例如 PythonREPL或Python 脚本写入终端的情况文件监控:在写入类似文件的对象的情况下,写入操作的输出在脚本仍在执行时被另一个程序读取(例如,监视日志文件时)

在这两种情况下,您都需要在生成的输出生成后立即读取它,而不仅仅是在组装了足够的输出来刷新数据缓冲区时。

在许多情况下,缓冲是有帮助的,在某些情况下,过多的缓冲可能是一个缺点。因此,您可以在最适合它们的位置实现不同类型的数据缓冲:

无缓冲意味着没有数据缓冲区。每个字节创建一个新的系统调用并独立写入。行缓冲意味着有一个数据缓冲区在内存中收集信息,一旦遇到换行符 (),数据缓冲区就会刷新并在一个系统调用中写入整行。\n完全缓冲(块缓冲)意味着存在特定大小的数据缓冲区,用于收集要写入的所有信息。一旦它已满,它就会刷新并在单个系统调用中继续发送其所有内容。

Python 在写入类似文件的对象时使用块缓冲作为默认值。但是,如果要写入交互式环境,它将执行行缓冲。

为了更好地理解这意味着什么,请编写一个模拟倒计时的 Python 脚本:

# countdown.pyfrom time import sleepfor second in range(3, 0, -1):    print(second)    sleep(1)print("Go!")

默认情况下,每个数字在脚本中调用时都会显示。但是,当您开发和调整倒数计时器时,您可能会遇到所有输出都被缓冲的情况。缓冲整个倒计时并在脚本结束时一次性打印所有内容会给在起跑线上等待的运动员带来很多混乱!print()

那么,如何确保在开发 Python 脚本时不会遇到数据缓冲问题呢?

为 Python 添加换行符以刷新打印输出

如果您在 Python REPL 中运行代码片段或直接使用 Python 解释器将其作为脚本执行,那么您不会遇到上面显示的脚本的任何问题。

在交互式环境中,标准输出流是行缓冲的。这是默认写入的输出流。只要您的输出将显示在终端中,您就会使用交互式环境。在这种情况下,数据缓冲区在遇到换行符 () 时会自动刷新:print()"\n"

交互式时,标准输出流是行缓冲的。(来源)

如果您使用其默认参数编写倒计时脚本,则每次调用的末尾隐式写入换行符:print()print()

# countdown.pyfrom time import sleepfor second in range(3, 0, -1):    print(second)    sleep(1)print("Go!")

如果您使用 Python 解释器从终端运行此脚本,那么您将获得所需的输出。数字一个接一个地出现,并从以下开始倒计时:3

每次调用首先将数字发送,然后发送换行符到缓冲区。因为您正在写入表示交互式环境的终端,所以执行行缓冲。因此,它会在每次调用结束时刷新数据缓冲区,每个号码都会通过自己的系统调用发送到终端。print()print()

如果您希望在输出中使用换行符,那么最好不要传递任何参数。在这种情况下,将使用默认值 offor。但换行符不需要出现在行尾即可触发刷新。您可以在其他地方向 yourcall 添加一个换行符:endprint()"\n"endprint()

显式添加换行符以刷新缓冲区显示/隐藏

但是,如果您不想使用换行符怎么办?如果您希望您的倒计时更紧凑并在一行上打印所有数字怎么办?

在下一节中,您将探讨如果出于该原因重构代码,可能会遇到意外的数据缓冲问题。您还将了解如何解决此问题。

设置参数(如果已禁用换行符)flush

您可能希望在一行上打印倒计时。你知道这有一个参数。您希望使用它将所有数字放在一行上,方法是将 的值从默认换行符更改为空格 ():print()endend" "

# countdown.pyfrom time import sleepfor second in range(3, 0, -1):    print(second, end=" ")    sleep(1)print("Go!")

在这个看似无害的更改之后,您的倒计时脚本突然被破坏了。它不再将数字打印为倒计时。相反,它们都同时出现:

哎呀,这不是你所期望的!您确实希望数字打印在一行上,但现在它们不再一个接一个地打印了。当脚本完成执行时,它们都会同时显示。

此意外行为的原因是块缓冲。写入的输出流仍然是您的终端,它代表一个交互式环境。因此,从技术上讲,输出流仍然是行缓冲的,就像以前一样。但是,由于您更改了 的值,因此不再写入任何换行符,因此永远不会触发行缓冲。print()end

因此,数据缓冲区仅在达到其最大阈值大小或脚本完成执行时刷新。在您的情况下,您永远不会填满缓冲区,因此在脚本完成执行后发送显示数据缓冲区内容的系统调用。

如果要更改该行为,则最快的方法是设置为强制每个调用to以刷新缓冲区:flush=Trueprint()

# countdown.pyfrom time import sleepfor second in range(3, 0, -1):    print(second, end=" ", flush=True)    sleep(1)print("Go!")

您已将 的值从默认值更改为。此更改会强制刷新输出流,与要写入的文件流具有的默认数据缓冲无关:flushFalseTrue

伟大!您的倒计时再次按预期工作,现在您可以更紧凑地将其打印到一行上。通过此更改,您可以使用 Python 来增强您的游戏,并使用它来构建很酷的动画,例如正在运行的脚本的进度条。print()

注意:请记住,更改为将输出打印到终端时仅在不使用默认值时才有效。如果保留默认值(即换行符),则即使没有显式,数据缓冲区也会在每次调用 to 后自动刷新。flushTrueendendprint()flush=True

如果从调用中删除显式和隐式换行符,则还需要设置是否希望实时显示输出。如果您保持其默认值,则 Python 将缓冲您的输出,并且只有在数据缓冲区已满或程序完成执行时才会显示。print()flush=TrueflushFalse

监视正在运行的脚本时显式刷新数据缓冲区

你已经读了很多关于在交互式环境中运行Python的信息。甚至文档,默认输出流,也提到了这一点,而没有进一步详细说明:sys.stdoutprint()

交互式时,标准输出流是行缓冲的。否则,它会像常规文本文件一样进行块缓冲。(来源)

如果您主要在交互式环境中工作,那么可能更难看到块缓冲默认应用的位置,因此可能是一个问题。

注意:大多数情况下,你会希望Python 缓冲输出流,因为这会减少系统调用次数并提高性能。但在某些情况下,块缓冲会对您不利。

假设您有一个长时间运行的 Python 脚本,该脚本将日志记录输出写入日志文件。这样的文件对象不表示交互式环境,因此对它的写入调用将被块缓冲。

如果您仅将日志文件用作将来某个时候可能会访问的历史存储,那么 Python 的默认数据缓冲完全没问题。但是,如果在写入日志时监视日志,则在日志发生时不会看到输出。对于需要立即响应的监视脚本来说,这可能是一个问题,例如,当任务遇到错误时。

再看看倒计时脚本的初始版本,其中没有更改以下任何默认参数:print()

# countdown.pyfrom time import sleepfor second in range(3, 0, -1):    print(second)    sleep(1)print("Go!")

在此版本的脚本中,每个数字打印在单独的行上。当您运行此脚本以在终端中显示输出时,它会进行行缓冲,并在脚本中发生调用时立即打印数字。print()

现在,如果您使用的是Linux或macOS等UNIX系统,则无需让Python直接将输出打印到终端,而是将标准输出通过管道传输到cat,如果您使用的是Windows PowerShell,则需要回显输出。

注意:Bothandare UNIX 实用程序。Windows PowerShell使用这些名称作为具有类似用例的命令的别名catecho

cat是获取内容的别名。echo是写入输出的别名。

但是,Windows PowerShell 命令的工作方式与别名命名所引用的 UNIX 实用工具程序不同。Using如下所述不适用于Windows PowerShell,但usingworks。catecho

这些命令行实用程序将用作任何将读取其输出的监视脚本的替代示例。继续并尝试适用于您的操作系统的正确命令:countdown.py

窗户Linux + macOS

PS> python countdown.py | echo

在这种情况下,标准输出不指向终端,因此不被视为交互式环境。Python 对发送的数据的缓冲返回到默认的块缓冲。print()

因此,当脚本中出现数字时,您的倒计时不可用,而仅在 Python 脚本执行结束时数据缓冲区刷新时可用:cat

将标准输出缓冲重定向到 macOS 上的另一个程序。

即使你在每次调用结束时保留了默认的换行符,Python 也没有刷新缓冲区!脚本完成执行后,所有输出都会立即显示。只有这样,Python 才会刷新缓冲输出,然后才能将其提供给管道中的下一个程序,在这种情况下。只有这样才能将输出显示给您。print()catcat

当然,如果你想监控正在发生的输出,那么你就不能使用它!幸运的是,解决方案是再次设置,从而强制每个调用toto刷新数据缓冲区并继续发送输出:countdown.pyflush=Trueprint()

# countdown.pyfrom time import sleepfor second in range(3, 0, -1):    print(second, flush=True)    sleep(1)print("Go!")

通过此更改,您可以在循环内调用时显式刷新标准输出数据缓冲区。现在,当数据缓冲区刷新时,这些数字将立即可用于管道中的下一个程序,这在执行过程中会发生多次:print()for

刷新缓冲区,同时将标准输出重定向到 macOS 上的另一个程序。

现在,您可以为长时间运行的 Python 任务设置自动监视脚本,该脚本可以实时访问脚本发送到输出流的输出。但是,如果您已经接管了监视包含数千个调用的脚本的任务,该怎么办?您是否必须更改函数的每一次出现?print()print()

你可以这样做,这不是一个坏方法。设置参数 to 可能是指示要刷新数据缓冲区的最明确方法。但是,如果您只需要进行更改来测试某些内容,则有比这更快的方法。flushTrue

更改环境变量PYTHONUNBUFFERED

如果您不想更改任何代码,但需要在没有数据缓冲区的情况下执行脚本,则可以使用快速命令选项。

再次考虑倒计时代码的早期版本示例,其中您没有显式刷新 Python函数中的数据缓冲区:print()

# countdown.pyfrom time import sleepfor second in range(3, 0, -1):    print(second)    sleep(1)print("Go!")

就像以前一样,您需要将此脚本的输出通过管道传输到监视脚本。您将再次用作监视脚本的替身,具体取决于您的操作系统。catecho

要在不对源代码应用任何更改的情况下运行无缓冲,可以在管道输出之前使用 -u命令选项执行 Python:countdown.pycatecho

窗户Linux + macOS

PS> python -u countdown.py | echo

命令选项禁用输出流、标准输出 (stdout) 和标准错误 (stderr) 的数据缓冲区。因此,您可以观察到这些数字在脚本中出现后立即一次传递到一个:-ucat

将标准输出无缓冲重定向到 macOS 上的另一个程序。

此方法不需要对代码进行任何更改,但无需缓冲即可写入标准输出,即使您将输出管道传输到其他程序也是如此。print()

命令选项更改当前脚本执行的PYTHONUNBUFFERED环境变量,因此在无缓冲的情况下运行脚本。-u

作为使用命令选项的替代方法,您可以在当前环境中将环境变量显式更改为非空字符串。空字符串是默认值,因此如果变量具有任何其他值,则 Python 将在无缓冲的情况下执行当前环境中的所有脚本运行。-uPYTHONUNBUFFERED

注意:显式或通过命令选项更改环境变量意味着输出流(标准输出标准错误)都将取消缓冲。因此,如果您希望在两个输出流上出现不同的行为,这可能不是正确的方法。PYTHONUNBUFFERED-u

在运行脚本之前,您需要更改环境中的值,以使此更改生效:PYTHONUNBUFFERED

窗户Linux + macOS

PS> $env:PYTHONUNBUFFERED = 'True'

使用此命令,您可以将环境变量设置为非空字符串,这会导致当前环境中的所有 Python 脚本运行都在无缓冲的情况下运行。若要撤消此更改,请再次运行该命令,但改为将变量设置为空字符串。PYTHONUNBUFFERED

用于更改打印的签名functools.partial

最后,您将探索在工作中可能遇到的另一种情况。假设您的团队向您介绍了一个大型代码库,并且您的任务是对现有日志文件设置自动监视。

负责监视的脚本用于将日志记录信息发送到输出流。但是,实时监视不会以当前形式工作,因为输出会被缓冲。在这个长脚本中有很多调用。您可以在代码库中进行编辑,但您希望尽可能少地接触原始脚本。print()print()

若要快速更改 的默认值,可以在要监视的脚本上下文中更改 的函数签名:flushprint()

# script_to_monitor.pyimport functoolsprint = functools.partial(print, flush=True)# ...

通过在脚本顶部添加这两行代码,您更改了内置 setto 的函数签名。您使用和覆盖更改的签名来执行此操作。此脚本中的所有后续调用都将使用该函数的更改版本,并刷新数据缓冲区,即使输出流不是终端也是如此。print()flushTruefunctools.partialprint()print()

如果您正在编写自己的脚本,那么最好明确说明何时应取消缓冲输出流。您可以通过以下任一方式执行此操作:

添加到要刷新缓冲区的所有调用flush=Trueprint()定义用于对标准输出进行无缓冲写入的新函数,而不是覆盖内置函数print()

您可以在开发过程中解决第一种方法,方法是注意需要在无缓冲的情况下运行哪些日志记录调用。在进一步开发倒计时脚本时,您已经对此进行了实验。print()

如果要将第二种方法应用于倒计时示例代码,则可以使用 as 部分定义一个新函数并调用它。然后你可以使用它来代替你想要无缓冲输出的内置位置:print()unbuffered_print()print()

# countdown.pyimport functoolsfrom time import sleepunbuffered_print = functools.partial(print, flush=True)for second in range(3, 0, -1):    unbuffered_print(second)    sleep(1)print("Go!")

使用此方法,可以继续使用无缓冲调用和缓冲调用。您还预先定义将对标准输出使用无缓冲写入。您甚至可以为函数指定一个描述性名称,这可能会使其他开发人员比添加参数更快地掌握您正在执行的操作。print()flush=True

结论

祝贺!您已经完成了本教程的结尾,并学到了很多关于 Python 中的输出流缓冲的知识,以及如何更改这方面的默认行为。print()

在本教程中,你已了解如何:

使用 flush参数显式刷新输出数据缓冲区print()更改单个函数、整个脚本甚至整个 Python 环境的数据缓冲确定何时需要显式刷新数据缓冲区,何时不需要刷新

通过重复运行仅稍作更改的短代码片段,您会看到它在交互模式下执行行缓冲,否则执行块缓冲。您还了解了何时可能需要更改该默认行为,以及如何执行此操作。print()

您是否使用有关刷新 Python 的知识为您的终端构建动画?它是否帮助您设置了实时监视脚本?或者您是否有本教程未涵盖的其他用例?在下面的评论中分享您的经验!print()

标签: #python打印文本