ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python print()函数完全指南:从基础参数到高阶玩法

Python print()函数完全指南:从基础参数到高阶玩法 每个学Python的人跟print()函数的第一次见面几乎都发生在同一天——你在终端敲下print(Hello, world)屏幕应声吐出那行字然后你觉得自己已经会编程了。但我要泼一盆冷水print()是Python里最容易被低估的内建函数。我面试过不少候选人聊到print(a, b, sep-)输出什么能脱口而出的人寥寥无几再问flush参数是干嘛的基本就卡壳了。这篇文章我打算把Python print()函数从里到外拆个干净完整签名、四个关键参数的底层逻辑、格式化输出怎么选、能直接拿去用的高阶玩法还有我这些年踩过的坑。不管你是刚入门Python的小白还是写了几年代码想补基本功的老手这篇文章都能让你对print()有个全新认识。1. print()真的只是一行输出吗先看它的完整签名1.1 从Python 2到Python 3print到底经历了什么很多新入门的朋友可能不知道print在Python 2里压根不是函数而是一条语句。那时候你写print hello对连括号都不用加。到了Python 3它被正式改成函数必须写成print(hello)。这个改动当年劝退了一大批从Python 2迁移过来的老开发者但站在今天的视角看这个设计决策非常明智——只有变成函数才能顺理成章地给print扩展新能力。你想想如果print是一条语句sep、end、file、flush这些参数就没地方塞了。这里顺便提一个很多面试官爱问的冷知识在Python 2里print(hello, world)的括号会被当成元组输出结果是(hello, world)两个元素的元组而不是连着打印两个字符串。Python 3修正了这个含糊的语义。如果你现在还在维护Python 2的老代码又想让print具备类似函数的扩展能力可以在文件头部加一行fromfutureimport print_function这样print就会被升级成函数版。这个历史包袱告诉我们一件事print()的设计不是拍脑袋定的它背后有一套完整的演进逻辑。理解了这个你再看下面这堆参数就会觉得顺理成章。1.2 完整参数拆解*objects、sep、end、file、flush先看官方签名一目了然print(*objects, sep , end\n, filesys.stdout, flushFalse)翻译成人话就是*objects可变位置参数。你想打印几个对象就传几个print会把它们挨个写出去对象之间用sep指定的内容隔开。这是最灵活的一个参数也是print能同时输出多个值的原因。sep全称separator分隔符。默认是单个空格。控制的是多个对象之间的间隔最后一个对象后面不会加它。end结尾符。默认是换行符\n。控制的是整条print语句结束后追加的字符。file输出目标。默认是sys.stdout也就是标准输出流通常就是我们看到的终端。你可以把它换成任何带write方法的对象最常见的是文件对象。flush是否强制刷新缓冲区。默认是False意味着print先把内容写进缓冲区攒够了再一次性刷出去。很多人用了好几年print可能一次都没主动设置过这五个参数——全靠默认值当然也能跑但很多实际场景下默认值恰恰是麻烦的根源。比如你想打印一行数据不要换行比如你想把日志实时写到文件里比如你想让多个字符串之间用逗号隔开这些场景都要求你对参数有真正的掌控力。2. sep、end、file、flush四个参数怎么用实战演示2.1 sep参数多个输出对象之间的胶水sep是我最常用的参数之一。别看它就是控制一个分隔符用好了能让代码干净不少。举几个例子# 打印日期用短横线分隔 print(2025, 01, 15, sep-) # 输出2025-01-15 # 打印IP地址用点分隔 print(192, 168, 1, 1, sep.) # 输出192.168.1.1 # 打印路径用斜杠分隔 print(usr, local, bin, sep/) # 输出usr/local/bin # 分隔符也可以是空字符串实现无缝拼接 print(Hello, World, sep) # 输出HelloWorld看到规律了吗当你需要把多个片段按固定格式拼起来的时候比手动字符串拼接干净得多。以前我写日期输出都是这样print(f{year}-{month}-{day})现在直接print(year, month, day, sep-)就完事少写一对花括号可读性还更好。另外注意一点sep只在多个对象之间生效如果你只打印一个对象sep不会在末尾额外加东西。这是很多初学同学容易混淆的地方。2.2 end参数控制结尾告别强制换行print的默认行为是每行输出后自动换行。但有时候我们偏偏不想要这个换行比如循环里打印一行星号拼图形或者打印一组数据想让它们待在同一行。这时候end参数就登场了。for i in range(5): print(i, end ) # 输出0 1 2 3 4把end从默认的\n改成空格输出就全部待在了一行。同理你可以改成任意的字符比如逗号、竖线、空字符串。一个特别常见的应用是打印表格行内数据或者打印loading动画import time for i in range(10): print(Loading, . * (i % 4), end\r) time.sleep(0.3)这里用\r回车符把光标拉回当前行的行首配合end不换行就能实现一个极简的动态loading效果。注意\r这个转义符在各平台行为略有差异但在主流终端里基本都按回到行首处理。真正要做到完美进度条还得加上flush参数这个我们等会儿细说。2.3 file参数把print的输出送到文件print默认把内容写到stdout但实际问题里我们经常需要把输出写到文件里。最简单的做法with open(output.log, w, encodingutf-8) as f: print(这是一条日志, filef) print(这是另一条, filef)核心逻辑是file参数接收的是一个对象这个对象只要有write方法就行。文件对象显然满足所以直接传进去就可以。这里有个容易踩的坑如果with块结束前文件没有正确关闭内容可能没有完全落盘——因为文件对象内部也有缓冲区。但用with语句管理上下文Python会在退出时自动flush并关闭基本不需要操心。file参数的用处不止于此。常见的一个技巧是把错误信息输出到标准错误流sys.stderrprint(出错了, filesys.stderr)这样做的好处是在Linux终端下你可以用2error.log把错误单独重定向到文件而正常输出仍然保留在终端。日志系统里这个区分非常关键。更进阶的玩法你可以自己定义一个类只要实现write方法print就能把内容打印到任何地方class Sink: def write(self, text): # 这里可以做任何事比如把text发到网络 print(f[sink] {text}, end) print(hello, world, fileSink())输出是[sink] hello world自定义输出目标在写框架、做辅助测试的时候非常好用。这也是理解file参数精髓的关键——它让print从打印到屏幕扩展为输出到任意目标。2.4 flush参数理解缓冲区解决日志不显示的问题flush是这四个参数里最容易被忽略、却最容易出问题的一个。要理解它先要知道缓冲机制是怎么回事。程序调用print时内容并不是立刻写到屏幕或文件里而是先进入一个缓冲区。缓冲区满了或者遇到换行符或者程序正常退出系统才会把内容实际刷出去。这样设计是为了效率——频繁写底层文件系统是非常慢的攒一批再写能大幅提升性能。但正是这个机制在很多场景下坑了人。最典型的例子你在脚本里print了一行提示然后程序卡在了某个耗时操作这时候终端上的表现是——什么提示都没有。你以为程序挂了实际上print的内容还窝在缓冲区里没出来。解决办法就是加flushTrueprint(开始处理数据请稍候..., flushTrue)这会强制print立刻把内容写入目标不经过缓冲等待。类似的场景还有程序崩溃前看不到最后一行日志、进度条不动、输出被重定向到管道文件时内容攒着不出来等。分布式训练、长任务跑批、实时日志监控这些场景几乎必须用到flush。flush参数和sys.stdout.flush()是等效的print(消息) sys.stdout.flush()这两行的效果和print(消息, flushTrue)完全一样。我个人更推荐直接用flushTrue写起来更简洁也不用额外import sys。但也有副作用频繁flush会让输出性能明显下降因为每次都在强制进行真实的I/O操作。所以如果只是最终一次性输出结果、或者对实时性没要求保持默认的False就行别无脑加flush。3. print()的6种高阶玩法面试加分的那种3.1 格式化输出的三种姿势f-string永远是首选print支持多种字符串格式化方式。多写几年Python你会逐渐体会到它们之间的代差。假设要输出name和age两个变量name Alice age 30 # 老旧但偶尔遇到的%格式化 print(Name: %s, Age: %d % (name, age)) # 过渡时代的str.format print(Name: {}, Age: {}.format(name, age)) print(Name: {n}, Age: {a}.format(nname, aage)) # 现代推荐的f-stringPython 3.6 print(fName: {name}, Age: {age})%格式化是从C语言的printf借来的思路用%s、%d这类占位符参数一多位置一错人就看晕了。str.format相比有所进步支持花括号占位和命名参数但写法仍然略显啰嗦。f-string是Python 3.6引入的语法糖直接在字符串前加f花括号里写表达式可读性和性能都是三者里最好的。我现在的原则很简单新代码永远用f-string除非是维护老项目否则我不用回头去写前两种。f-string里还能嵌各种格式说明符做对齐、补零、千分位非常顺手num 1234.5678 print(f金额: {num:.2f}) # 保留两位小数 print(f{num:,.2f}) # 千分位1,234.57 print(f{left:10}向右补空格) # 左对齐 print(f{num:08.2f}) # 补零01234.57这种实战里用得飞起的能力恰恰是很多人没花时间研究的盲区。建议你把f-string的格式说明符当成一门小的必修课花半小时看一遍后续省下的时间远超投入。3.2 30行代码实现动态进度条动态进度条是print高阶玩法的经典案例。它把end、flush、formatting三样全占了。看代码import time total 50 for i in range(total 1): percent i / total * 100 filled █ * i # 已完成的填充 empty * (total - i) # 未完成的空白 # \r把光标拉回行首end取消默认换行flush确保立刻显示 print(f\r[{filled}{empty}] {percent:.0f}%, end, flushTrue) time.sleep(0.05) print()跑起来的效果是同一行不断刷新的进度条。三个关键点缺一不可\r回车符把光标移动到当前行开头否则每次print都会换新行进度条就变成了一列乱糟糟的输出。end取消print自动追加的换行符让下一次print接着当前行输出。flushTrue让每次进度都立刻渲染到终端而不是攒在缓冲区。我实测过去掉flush进度条会卡住不动直到循环结束才一次性蹦出来完全失去动态的意义。进度条这个例子看起来小但把print的核心参数全串起来了。你自己写一遍对print的理解会有质的提升。3.3 用ANSI转义序列打印彩色文字终端里带颜色的输出靠的是ANSI转义序列。核心原理终端识别到形如\033[31m的转义序列就把后续文字染成红色遇到\033[0m再恢复默认。用print打印这些序列就能实现彩色输出print(\033[31m 这是一条红色消息 \033[0m) print(\033[32m 这是绿色消息 \033[0m) print(\033[33m 这是黄色消息 \033[0m)常见颜色码有31红、32绿、33黄、34蓝、35紫、36青。加上样式码还能组合出加粗、下划线等效果。我的习惯是在调试脚本里给错误信息打红色成功提示打绿色一眼就能从满屏日志中抓住重点。踩过的一个坑是Windows老版本控制台如cmd对ANSI转义的支持时好时坏经常打出\033[31m这种乱码而不是彩色。解决思路有两个一是升级到Windows Terminal二是安装colorama库并初始化一下from colorama import init, Fore init() print(Fore.RED 红色消息 Fore.RESET)colorama底层做了兼容处理能让ANSI在Windows下也正常生效。跨平台脚本来一套还是很省心的。3.4 控制自定义对象的打印输出str__与__repr当你print一个自定义类的实例时输出默认长这样class Point: def __init__(self, x, y): self.x x self.y y p Point(3, 4) print(p) # 输出__main__.Point object at 0x7f8c1a3b5d90这种内存地址学输出对调试毫无帮助。要改变打印呈现需要实现特殊方法。print语句内部会尝试调用对象的__str__方法如果没实现再回退到__repr__。所以给类补上这两个方法class Point: def __init__(self, x, y): self.x x self.y y def __repr__(self): return fPoint({self.x}, {self.y}) def __str__(self): return f({self.x}, {self.y}) p Point(3, 4) print(p) # (3, 4) 调用__str__ print(repr(p)) # Point(3, 4) 调用__repr__你可能会问这俩方法有啥区别__str__面向最终用户追求好看易懂print默认用它repr__面向开发者追求无歧义、可还原能精确复现这个对象的状态甚至在交互式终端里输入repr(p)能直接看到完整构造信息。在设计自己的类时我的建议是至少实现__repr因为我们调试的时候到处都靠它。还有一个小技巧容器类列表、字典打印元素时用的是__repr__而不是__str__。所以就算你只实现了__str__print([p])依然会显示成Point(3, 4)而不是(3, 4)。这是很多人没意识到的细节。3.5 玩转输出重定向contextlib.redirect_stdout有时候你想捕获print的输出不让它直接打到屏幕上而是存起来用于日志或者测试断言。方式之一是直接改sys.stdout但改全局变量容易出脏问题。Python提供了一个更优雅的上下文管理器contextlib.redirect_stdoutimport io from contextlib import redirect_stdout buf io.StringIO() with redirect_stdout(buf): print(这段不会出现在终端) print(这两行都被捕获了) content buf.getvalue() print(捕获的内容, content)这段代码把print的输出临时重定向到buf里with块结束后print恢复正常。在单元测试里想断言某个函数的print输出这是最干净的手段。另一个场景是调用第三方库时它不停地往终端打日志你又没有日志开关可以用这招把它的print吞掉。这里注意一点redirect_stdout只对Python层面的print生效如果第三方库用的是C扩展直接往stdout写或者调用了os.writeredirect_stdout是管不住的。这种极端情况需要用更底层的文件描述符重定向方案属于另一个话题了。4. 开发中常见的print()翻车现场4.1 问题速查表踩过不少坑整理成一张速查表希望对你有用现象根本原因解决方案多个print输出挤在同一行不想要换行时用了默认end显式设置end 或end输出间隔异常多了奇怪的空格没理解sep是对象之间的分隔符根据需求设置sep日志打到文件后长时间不显示缓冲区没有刷新加flushTrue或定期调用flush()print大量数据时程序明显卡顿每次print都触发高频I/O拼接成一个大字符串一次print输出中文跑到Windows终端是乱码终端/代码编码不一致设置PYTHONIOENCODINGutf-8或改用UTF-8终端自定义对象打印一串难懂的内存地址没实现__str__或__repr__在类中实现这两个方法重定向输出后无内容缓冲区没刷新或重定向顺序错误检查flush确认重定向生效后再print这些坑我以前基本全踩过一遍。尤其是第一个刚学print的时候我以为默认就会换行但一旦需要不换行就完全懵了后来才恍然大悟原来换行是end默认值给的不是print的天赋异禀。4.2 性能陷阱大量print为什么慢print慢不慢这个问题得分场景。从性能角度看print本质是系统调用级别的写操作而系统调用是有开销的。如果在一个一百万次的循环里每次print一个短字符串程序会出奇地慢。我做过一个快速对比import sys import time # 方案一每次循环都print start time.time() for i in range(20000): print(i) print(方案一耗时, time.time() - start) # 方案二拼成一个大字符串只调用一次print start time.time() sys.stdout.write(\n.join(str(i) for i in range(20000)) \n) print(方案二耗时, time.time() - start)在同一台机器上的实测方案二往往比方案一快一个数量级原因就是大幅减少了系统调用的次数。在数据处理、日志导出这种场景如果非要走print尽量把多条内容拼成一个整体再输出。但也要注意一个权衡如果内容大到几个GB一次性拼成字符串会撑爆内存。这时候更合适的方案是改用logging或直接写文件流式地一批一批输出而不是靠print硬扛。print天生适合交互式的少量输出大批量生产数据它并不是最优解。4.3 中文乱码和编码问题中文乱码是print最常见的国际难题。核心在于print输出的字节在终端里展示时终端用的编码和Python字符串的编码对不上就会出现乱码。最常见的两个场景在Linux终端使用UTF-8编码正常情况下没问题。但如果你的Python脚本文件不是UTF-8保存的或者环境变量LANG被改成了别的编码print中文就可能乱。在Windows的cmd/PowerShell环境老版本控制台默认编码可能是GBK而Python 3字符串内部是Unicode一旦输出宽字符就可能乱。解决思路我按优先级排列# Linux/macOS 下可以设置环境变量强制Python的stdout用UTF-8 export PYTHONIOENCODINGutf-8文件输出时则显式指定编码with open(out.txt, w, encodingutf-8) as f: print(中文内容, filef)新版Python在Windows上通常能通过UTF-8模式Python 3.7的UTF-8 Mode或者3.15开始默认缓解这些问题但我仍然建议在涉及文件写入时养成显式指定encoding的习惯因为代码拷贝到不同的服务器上默认编码很可能就不是你想的那样。5. 一点过来人的经验与扩展建议5.1 临时调试用print长期运行请换loggingprint不是万能的。开发阶段拿来快速看变量、跟踪流程print简直是神器。但一旦脚本要部署到生产环境长期运行print就暴露出短板没有日志级别info/warning/error、没有时间戳、没有文件滚动、不容易按天切分。这时候应该用标准库logging模块配置好后可以同时往终端和文件输出还能控制日志级别。我现在的个人习惯是分三个阶段快速验证想法直接print怎么快怎么来。脚本逻辑基本稳定把关键输出改成logging保留debug级别的细节日志。上线前检查去掉冗余print保留必要的warn/error级别记录。曾经为了排查一个线上问题我找遍了一台服务器上的print输出结果发现关键日志被一堆无关print淹没了。从那以后我在所有正式代码里都贯彻logging优先的原则。print只属于开发调试不该长期留在生产路径里。5.2 比print更稳的替代方案如果你觉得print在某些场景确实不够用Python生态里还有几个优秀替补pprint专门美化输出列表、字典、嵌套结构。print一个深嵌套的字典会挤成一坨pprint会按层级缩进排版肉眼可读性高出一大截。logging上面聊过生产环境日志输出的正道。tqdm一个专业的进度条库几十行代码手写进度条在tqdm那里一行就能搞定还自带剩余时间估算和文件传输模式。简单脚本可以手写复杂任务的进度显示直接用tqdm。reprlib打印超长对象时自动截断。比如你要打印一个有10万个元素的列表print会一次性倒出来刷屏reprlib能只显示前几百个。这些工具都不是要替代print的主战场但在合适的场景里用对了你的代码质量和个人效率都能上一个台阶。最后再分享一个小技巧在写临时调试print的时候养成带标记的习惯比如print( DEBUG:, x)这样调试完用编辑器全局搜索 DEBUG就能一秒清掉所有遗留的调试代码再也不用担心删不干净或者误删正常输出。这套土办法我用了很多年简单但省心。深入掌握print()不是浪费时间的炫技它可能在关键时刻帮你省下几个小时排查问题的精力。
返回列表