最近在一个Python新手群里有人问了个特别基础的问题“想写一个打印购物小票的程序应该从哪开始”底下有人直接甩了个print(hello)有人让他上数据库还有人发了份带图形界面的完整代码提问者反而更懵了。我特别理解这种卡壳状态——语法书啃完了循环、列表、函数都认识但一到“写点啥”就无从下手。购物小票这个案例其实被严重低估了代码量不大却能把变量、列表、循环、格式化输出、文件操作、时间处理这些入门核心知识点全部串起来难度刚刚好一个下午能跑通一个基础版之后还能一路玩出图形界面、PDF存档、真实打印机对接。这篇文章就按我实际做过的路径来写从需求拆解、代码落地到踩坑修复和扩展方向给卡在“不知道写什么程序”的初学者一条能直接照走的路线。1. 先从需求分析入手一张小票背后其实是五个任务先别急着打开编辑器。拿到“打印购物小票”这种题目真正的第一步是把它拆成一个一个能落地的功能点。很多新手容易栽在这里需求越模糊代码越乱。1.1 一张小票的完整信息结构拿便利店的小票举例。收银员扫完一轮商品之后打出来的纸条上通常包含这几个区域归属信息店名、门店编号、单号。这是用来区分“这张票是哪里开的”。时间信息买单时刻精确到秒。这个看似无关紧要最后对账、售后时全靠它。商品明细每一行至少要有商品名称、单价、数量、小计单价乘以数量。这是小票的核心。金额汇总总件数、商品合计、应收金额如果涉及优惠还要有优惠金额、实付金额。附加信息会员号、活动提示、二维码、售后电话属于扩展内容。把这些信息映射到Python代码里立刻能拆出五类任务小票区域对应代码任务涉及的知识点商品明细数据怎么存列表、字典、类金额汇总金额怎么算算术表达式、循环求和时间信息当前时间怎么取datetime模块文本排版列怎么对齐格式化字符串结果输出怎么给人看print、文件写入、GUI也就是说“打印购物小票”这六个字背后已经覆盖了入门阶段80%的高频语法。你写完这个小程序相当于把零散知识全部串了一遍。1.2 数据结构选择为什么先用列表加字典数据存储是第一关。我见过的初学者有两种极端一种是什么都往变量里塞name1、price2、name3写一大串另一种是一上来就要上MySQL把练手项目硬生生做成环境配置项目。我的建议非常明确第一版就用“列表套字典”。products [ {name: 可乐, price: 3.50, qty: 2}, {name: 薯片, price: 6.80, qty: 1}, ]这样选有三个具体理由。第一语义清晰。products[0][name]一眼就知道是第一个商品的名称比零散变量好维护得多。第二扩展自然。字典的键值结构后面转成JSON格式存文件时基本零成本因为JSON本身就是键值对。第三方便推导。把数据结构和真实场景对应起来——一个购物车就是商品列表一个商品就是名称、单价、数量三个属性这个建模习惯会跟着你进入类和面向对象。等到程序更复杂时字段多了、行为多了再自然过渡到类。到时你会深刻体会“数据先建模代码后落地”这句话。1.3 版本规划先跑通再谈完美我见过很多人写小票程序第一版就想把图形界面、数据库、打印机全部接上结果写了三天连基础文本输出都没跑通。练手项目必须先做减法。按我的习惯这个案例会分三个版本推进版本数据来源输出形式核心目标基础版内存列表 / 手动输入终端文本跑通语法链路进阶版文件读写 / 图形界面PDF、GUI展示工程化思维扩展版数据库 扫码 真实打印机纸质小票贴近业务场景下面先把基础版完整落地。基础版是根后面每一层扩展都是在它上面长出来的。2. 核心代码落地写一个能真正打印出来的购物小票基础版目标很纯粹程序运行后能把商品列表变成一份排版整齐的小票文本。这里不需要炫技但每一步都要让人看懂。2.1 从数据类到购物车类我习惯先定义商品这个最小单位再用购物车管理所有商品。这样做的好处是后续加优惠、加折扣、加会员功能时不会把所有逻辑都堆在主流程里。from datetime import datetime class Product: 单个商品 def __init__(self, name: str, price: float, qty: int): self.name name self.price price self.qty qty self.subtotal price * qty # 小计金额 class ShoppingCart: 购物车管理商品列表并计算汇总 def __init__(self, store_name我的小店): self.store_name store_name self.items [] def add(self, product: Product): self.items.append(product) property def total_quantity(self) - int: 总件数 return sum(item.qty for item in self.items) property def total_price(self) - float: 总金额 return sum(item.subtotal for item in self.items)这里有几个细节值得说一下。subtotal在Product初始化时就算好是因为单件商品的小计金额只需要算一次后面循环使用没必要每次取都重算一遍。property把方法伪装成属性调用时不用加括号代码读起来更像是“拿到一个值”而不是“执行一个动作”。这是Python里非常实用的习惯写多了自然会上手。sum(item.qty for item in self.items)用到了生成器表达式如果你还不太熟悉可以先把它当作“对列表里的每个商品的qty求和”这种紧凑写法。整个购物车类只负责两件事装商品、算汇总。展示和打印的职责留到下一步。2.2 格式化输出小票好看的关键数据准备好了接下来是重头戏——怎么把文本排成小票的样子。我先把打印函数写出来def print_receipt(cart: ShoppingCart): 根据购物车内容生成并打印小票 print( * 38) print(cart.store_name.center(38)) print(时间{}.format(datetime.now().strftime(%Y-%m-%d %H:%M:%S))) print(- * 38) print(f{商品:8}{单价:8}{数量:6}{小计:9}) for item in cart.items: line f{item.name:8}{item.price:8.2f}{item.qty:6}{item.subtotal:9.2f} print(line) print(- * 38) print(f总件数: {cart.total_quantity} 应收: {cart.total_price:.2f} 元) print( * 38)格式化字符串里的: 8、: 8就是对齐控制轻易别跳过。8表示左对齐宽度8个字符。商品名放左边。8.2f表示右对齐宽度8个字符浮点数保留两位小数。单价、小计这类数字放右边。center(38)让店名在38个字符的宽度中居中显示。你可以把格式化占位符理解成Excel单元格里的对齐设置单元格宽度定了靠左还是靠右一清二楚。整个小票的观感好不好全看这些细节。2.3 交互式录入让程序更像一个工具如果商品数据只能靠改代码来写那这个程序充其量是个“脚本”谈不上“工具”。我给它加上交互输入让用户边运行边录入商品。这里用了一个while循环加try/exceptdef build_cart_from_input() - ShoppingCart: cart ShoppingCart(路边的杂货铺) print(输入商品信息格式名称,价格,数量。输入 q 结束。) while True: line input( ).strip() if line.lower() q: break try: name, price, qty [part.strip() for part in line.split(,)] cart.add(Product(name, float(price), int(qty))) except (ValueError, IndexError): print(格式不正确请按名称,价格,数量 重新输入。) continue return cart if __name__ __main__: cart build_cart_from_input() print_receipt(cart)实测运行效果是这样的 路边的杂货铺 时间2025-01-12 14:23:09 -------------------------------------- 商品 单价 数量 小计 可乐 3.50 2 7.00 薯片 6.80 1 6.80 -------------------------------------- 总件数: 3 应收: 13.80 元 第一版到这一步就能用了。基础语法链路——输入、存储、计算、格式化、输出——全部跑通。但先别高兴太早我实际跑完这版后紧接着就碰到了三个问题而且没有一个是在课本上能直接翻到答案的。3. 从能跑到好用中英文对齐、金额精度、编码三座大山纸上代码跑起来之后真正的“实战”才刚刚开始。这三个坑几乎每个写小票程序的人都会遇到区别只是早踩晚踩。3.1 中英文混排看似对齐实际错位第一次测试我用的是“可乐”“薯片”这类中文商品打印结果当场裂开。看起来应该是这样商品 单价 数量 小计 可口可乐 3.50 2 7.00实际却是这样商品 单价 数量 小计 可口可乐 3.50 2 7.00根本原因在于Python格式化字符串的宽度计算方式。f{name:8}中的宽度8按字符个数计算而中文在终端显示时通常占2个英文字符的宽度。也就是说“可口可乐”四个字实际占了8个英文字符宽度但Python按4个字符计算于是后面的列全都提前顶出来了。解决办法是写一个基于“显示宽度”的对齐函数def display_width(text: str) - int: 计算字符串在终端中的显示宽度中文按2算 width 0 for ch in text: if ord(ch) 127: width 2 else: width 1 return width def align_left(text: str, width: int) - str: 按显示宽度左对齐 pad max(0, width - display_width(text)) return text * pad def align_right(text: str, width: int) - str: 按显示宽度右对齐 pad max(0, width - display_width(text)) return * pad text然后print_receipt内部的打印行改为for item in cart.items: name_col align_left(item.name, 10) price_col align_right(f{item.price:.2f}, 8) qty_col align_right(str(item.qty), 6) amount_col align_right(f{item.subtotal:.2f}, 9) print(f{name_col}{price_col}{qty_col}{amount_col})这个方案在终端、TXT文件、大部分场景都通用。如果你追求更严谨的宽度判定可以用标准库的unicodedata.east_asian_width来做字符分类但绝大多数项目里用ord(ch) 127已经足够。3.2 金额精度浮点数做财务计算的隐患第二个坑藏得很深表面看不出问题直到你多买几件商品之后。有次我测试“三件9.9元商品”实际相加是29.7元程序给出的结果却是29.699999999999996。我盯着屏幕愣了半天才反应过来是浮点数精度问题。Python里的浮点数遵循IEEE 754标准二进制无法精确表示所有的十进制小数。0.1在二进制里就是一个无限循环小数。所以 0.1 0.2 0.30000000000000004单个计算误差看起来微乎其微但购物车里几十件商品累加误差就会积累体现在应收金额上就是一分钱、几毛钱的差。我用的修复方案是把金额计算从float换成Decimalfrom decimal import Decimal class Product: def __init__(self, name: str, price: Decimal, qty: int): self.name name self.price price self.qty qty self.subtotal price * qty构建商品时一定要用字符串传给DecimalProduct(可乐, Decimal(3.50), 2)注意不要写Decimal(3.50)——这样做等于先把浮点数算了一遍精度已经丢了再接Decimal已经晚了。关于金额计算的行业经验我再多说一句很多收银系统干脆用“分”作为最小单位商品价格存整数比如12.90元就存1290。这样计算不仅精确而且绕开了浮点数问题速度还快。你可以任选一种但千万别用裸float。3.3 编码问题终端、文件与打印机的三重考验第三个坑出现在我尝试把小票保存成文件时。刚开始写文件用的是最常见的方式with open(receipt.txt, w) as f: f.write(receipt_text)结果生成的TXT文件用记事本打开正常用我的代码编辑器打开却显示成乱码。原因很简单Windows下open()默认使用系统编码GBK而编辑器默认按UTF-8读文件。老老实实把编码写明确是唯一正解with open(receipt.txt, w, encodingutf-8) as f: f.write(receipt_text)终端编码问题也值得注意。不少人在Windows自带的控制台里跑Python脚本遇到打印中文乱码第一反应是改代码其实更可能是控制台代码页和输出编码不一致。新版Windows Terminal一般没有这个问题但旧版cmd仍可能踩到。一个兜底方案是在脚本开头加import sys sys.stdout.reconfigure(encodingutf-8)还有一个坑在真实打印机上如果你最终要把小票发给热敏打印机而打印机的固件只支持GBK编码那么文本发送前就需要编码成GBK而不是UTF-8。这一点不同品牌差异很大项目真正对接硬件时一定要先查清楚打印机手册不要拿UTF-8文本直接发。这三个坑处理完之后基础版才算真正“稳了”。再往后就轮到我想让程序变得更像样了。4. 进阶扩展图形界面、PDF存档与真实打印机的接入基础版可以反复运行、打印文本小票但它的交付形态还停留在“命令行工具”。如果想做出点能展示的东西下面三个方向是性价比最高的延伸按需选择即可。4.1 用tkinter把命令行变成可视化收银界面tkinter是Python标准库自带的GUI框架不需要额外安装包拿来把命令行程序包装成一个小收银台非常合适。我的实践是先做一个极简版左边是输入区填商品名、单价、数量点“添加商品”后显示在中间的表格里点“生成小票”就把格式化好的小票文本渲染到右侧文本框。核心代码思路如下import tkinter as tk from tkinter import ttk class ReceiptApp: def __init__(self, root): self.root root self.root.title(购物小票生成器) self.cart ShoppingCart() self.name_var tk.StringVar() self.price_var tk.StringVar() self.qty_var tk.StringVar() frame ttk.Frame(root, padding10) frame.grid(row0, column0) ttk.Label(frame, text商品名).grid(row0, column0) ttk.Entry(frame, textvariableself.name_var).grid(row0, column1) ttk.Label(frame, text单价).grid(row1, column0) ttk.Entry(frame, textvariableself.price_var).grid(row1, column1) ttk.Label(frame, text数量).grid(row2, column0) ttk.Entry(frame, textvariableself.qty_var).grid(row2, column1) ttk.Button(frame, text添加商品, commandself.add_item).grid(row3, column0, columnspan2, pady5) self.output tk.Text(root, width46, height18) self.output.grid(row1, column0, padx10, pady10) def add_item(self): try: name self.name_var.get().strip() price float(self.price_var.get()) qty int(self.qty_var.get()) self.cart.add(Product(name, price, qty)) self.name_var.set() self.price_var.set() self.qty_var.set() self.refresh_print() except ValueError: self.output.insert(tk.END, 输入格式有误请检查。\n) def refresh_print(self): self.output.delete(1.0, tk.END) # 因为print_receipt默认打往标准输出这里可以改用返回字符串的版本这里有个实用细节基础版的print_receipt是直接把内容print到控制台的但GUI里很难捕获控制台输出。更优雅的做法是让函数返回小票文本返回str由调用方决定打印到控制台、写入文件还是在GUI里显示。第一次写GUI的人很容易被这个环节卡住提前设计好函数职责可以少走弯路。4.2 把小票导出成PDF给电子小票一份标准格式电商发货单、线上售后凭证场景里PDF是比TXT更靠谱的格式。我这里选择reportlab库三行代码就能把小票变成PDFfrom reportlab.lib.pagesizes import A4 from reportlab.pdfgen import canvas def export_receipt_to_pdf(receipt_lines, filenamereceipt.pdf): c canvas.Canvas(filename, pagesizeA4) y 800 for line in receipt_lines: c.drawString(50, y, line) y - 20 c.save()如果小票内容有商品表格用reportlab的Table类会更美观用drawString一行行画只能做线性排列。实际使用中我会把之前的print_receipt改造成两版一版继续输出纯文本用于终端一版把商品明细转成Table数据用于PDF。数据源相同只是展示形态不同这正好体现了“数据和展示分离”的思路。4.3 对接真实打印机从屏幕到纸质小票最硬核的扩展是让小票真正从打印机里出来。按硬件类型分两条路线普通喷墨/激光打印机把生成的小票文本写入临时文件通过系统打印命令发送。Windows下也可以用win32print库调驱动打印适合打印A4纸格式的订单、发货单。POS热敏小票打印机58mm/80mm这类打印机通常走串口、网口或USB虚拟串口指令协议多为ESC/POS。我常用的控制代码如下import serial def print_to_pos(portCOM3, textNone, max_width32): with serial.Serial(port, 9600, timeout2) as printer: printer.write(b\x1b\x40) # 初始化打印机 printer.write(text.encode(gbk)) # 注意编码要和打印机匹配 printer.write(b\n\n) printer.write(b\x1d\x56\x42\x00) # 走纸并自动切纸这里有两个容易踩坑的点。第一波特率。很多旧款串口打印机是9600但新一些的机型可能是115200搞错的话会打出一堆乱码或完全没有反应。第二切纸指令。\x1d\x56\x42\x00是常见的自动切纸指令但个别品牌有自己私有的指令集务必要以打印机说明书为准。我当时就是没查手册拿通用指令驱动一台老式打印机结果切纸功能完全失效后来翻说明书才发现那台机器支持的是另一套指令。对接真实打印机的完整方案弹性很大一条线做下来你等于把Python串口通信、字节编码、硬件交互全学了一遍已经超出“打印小票”本身的价值了。回想这次从需求拆解到最终落地的全过程这个案例最打动我的地方不在于“打印”这个动作而在于它把编程里最抽象的几件事——数据结构、函数职责、文本格式化、异常处理——全部变成了看得见摸得着的东西。每次带新人我都会推荐他先从这种“小到可以半天跑通大到可以不断扩展”的项目入手。你写购物小票时养成的习惯比如先设计输出样式再写处理逻辑比如所有文件读写都显式指定编码以后放到任何项目里都不过时。如果你的基础版已经能稳定打印下一步不妨从GUI和文件存档两个方向里挑一个继续把这个小项目往厚坐深。
企业数字化 ERP 产品动态
相关推荐
搞懂域名服务器后,ppt模板免费下载素材教学到底多少钱才值 搞懂域名服务器后,ppt模板免费下载素材教学到底多少钱才值 域名注册选错了,服务器带宽不够用,SSL证书没配好,这“三座大山”压得多少独立站长喘不过气。很多人以为网站难搞,其实最难的是在技术迷雾里算清账:做一个能正常跑起来的站,到底要投入… · 2026/9/27 1:05:02
MASM32 Windows汇编实战:从环境配置到逆向调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:05:02
ax调度是什么?一文看懂Wi-Fi 6的OFDMA、MU-MIMO与TWT机制 1. “ax”到底是谁:从802.11ax到Wi-Fi 6的来龙去脉前两天有人在评论区问我:老看到“ax调度”这个词,到底是在调度个啥?我愣了一下,意识到自己天天挂在嘴边的802.11ax,在外人眼里就是两个字母加一个陌生的后… · 2026/9/27 1:05:02
MCTP over SMBus/I2C实战:从协议原理到BMC联调排错 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:50:18
Ubuntu ARM64 安装向日葵远程控制实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:50:18
C语言二级指针详解:从内存模型到实战应用与避坑指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:50:18
DeepSeek网页版从入门到进阶:LLM、Agent与API接入全指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:50:18
iUnlocktool DFU模式实操指南:驱动识别异常与设备管理器排查 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:50:18
MIPI DSI时钟换算全解析:从pixel clock到phy-bit-clock /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:50:12
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01