ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python 入门练习:圆周长与面积计算的工程化演进

Python 入门练习:圆周长与面积计算的工程化演进 “计算圆的周长和面积”几乎出现在每一本 Python 入门教材的练习部分很多初学者会把它当成一个简单的算术题输入半径套公式输出结果。但从学习效果来看这个小题其实串联了 Python 入门的多个关键知识点变量与数据类型、输入输出、类型转换、条件判断、函数封装、格式化输出甚至延伸到模块和测试。把它认真做完比盲目看十节视频课更能在脑子里建立“程序一步步执行”的感觉。这篇讨论适合刚学完 Python 基本语法、准备做第一个小项目的同学。接下来会从一个固定半径的脚本开始逐步改造成支持交互输入、负数校验、函数封装、批量计算和单元测试的完整小模块。每个版本都会讲清楚为什么这样写运行会看到什么出错该怎么排。学完后你会得到一个可以复用的小工具也会更清楚一个命令行项目的成长路径。1. 这个入门题目背后真正要理解的三件事1.1 圆的周长与面积公式在程序里如何表达圆的数学公式很小周长 C 2πr面积 S πr²。这个公式在 Python 里写出来也不复杂import math r 5 circumference 2 * math.pi * r area math.pi * r ** 2这里有一个容易忽略的细节math.pi是 Python 的math模块提供的一个浮点数常量代表圆周率精度大约到 15 到 16 位十进制有效数字。直接写3.14也可以运行但计算结果的精度会差很多。对于入门练习建议使用math.pi一方面它能保证精度另一方面让代码的意图更清晰π 是一个数学常量不是随手写的近似数字。还要注意面积公式中平方的写法。r ** 2是 Python 的乘方运算符表示 r 的二次方。写成r * r也完全没问题两个写法都常见。区别在于当 r 是一个比较复杂的表达式时r * r要重复写一遍容易出错r ** 2更紧凑。两者性能差异在入门场景可以忽略选择自己不容易看错的写法即可。不过运算符优先级需要留意。**的优先级高于*所以math.pi * r ** 2会先计算r ** 2再乘以 π。如果你在表达式中又想写乘法又想写乘方最好加上括号明确意图area math.pi * (r ** 2)这样读代码的人不会产生歧义也不会因为运算符优先级问题出现隐蔽的计算错误。1.2 Python 中“计算”不止是算术还有数据类型转换很多人在写入门代码时只盯着公式却忽略了 Python 中数据类型的转换。比如下面的代码看似没问题r input(请输入半径:) print(r * 2)如果输入5程序会输出55而不是10。原因是input()永远返回字符串字符串5乘以整数2的结果是字符串55。这个现象恰恰说明在 Python 中运算符的行为由操作数的类型决定。这就引出了本项目的第一个关键动作把用户输入的字符串转换成浮点数。用float(r)可以把5变成5.0之后再进行算术运算才是数学意义上的计算。这个坑看起来小但它是很多新手第一次面对“程序输出和预期不一致”时的经典案例。理解了它后面学文件读取、网络请求、JSON 解析时都会条件反射地检查“这里的数据到底是不是数字”。数据类型转换不仅在用户输入时出现函数参数、列表元素、字典值也可能来自不同来源。养成先确认类型、再参与运算的习惯能减少很多莫名的报错。1.3 从一行脚本到可复用函数的设计演变如果只是算一次圆的面积可以用三行代码完成。但实际开发不会只算一次用户可能输入不同半径程序可能要在多个地方调用同一个计算逻辑。这时就需要把计算部分抽出来封装成函数。def circle_calculation(radius): circumference 2 * math.pi * radius area math.pi * radius ** 2 return circumference, area封装函数的意义在于把“计算规则”和“使用场景”分开。你可以在命令行程序里调用它也可以在 Web 接口里调用它也可以在单元测试里调用它。只要计算规则不变调用方不需要关心内部细节。返回值使用元组(circumference, area)调用方可以一次性拿到两个结果也可以分别解包。从写死半径到接收用户输入再到抽出函数这一步步是很自然的演进。如果不做函数封装每次在main()里需要计算周长面积时都得复制一遍公式。一旦公式写错或者需要改用更高精度的 π所有复制点位都要改很容易漏改。函数是解决“重复代码”最基础的手段也是这个实战题最重要的学习目标。阶段代码特征是否适合复用是否方便测试写死半径公式直接写在主流程里否否交互输入版公式仍在主流程但数据来自输入否否函数封装版计算逻辑独立成函数入口只控制输入输出是是2. 环境准备如何让第一段代码顺利运行2.1 安装 Python 与确认版本在开始写代码之前先确认机器上已经安装了 Python 3。到 Python 官网下载对应操作系统的安装包Windows 安装时记得勾选Add python.exe to PATH否则后续在命令行里执行python会提示找不到命令。安装完成后打开终端或命令提示符执行python --version如果系统中存在多个 Python 版本需要用python3区分python3 --version建议使用 Python 3.8 以上版本。本文中的代码在 Python 3 环境运行没有用到高版本专属语法只要你的版本在 3.8 到当前主流版本之间运行结果基本一致。如果遇到找不到python命令先检查 PATH 环境变量而不是急着重新安装。2.2 三种运行方式与适用场景Python 代码有三种常见的运行方式适用场景不同交互式解释器在终端输入python进入一行行执行代码适合临时验证一个小语法、一个函数。缺点是代码不能长期保存上下文一清空就没有了。脚本文件用编辑器写.py文件然后执行python circle.py。这是最常见的运行方式适合完整的小程序和项目。Jupyter Notebook按单元格执行适合数据分析、教学演示但不适合作为命令行工具的交付形态。对于本文的实战推荐在项目目录创建一个circle.py文件用 VS Code 或 PyCharm 打开边写边运行。编辑器会自动高亮语法、提示错误遇到报错时定位文件行号也更方便。本项目不涉及第三方库只需要 Python 标准库因此不需要pip install任何额外依赖。如果后续扩展成 Web 接口或数据分析版本才需要安装 Flask、pandas 等包。2.3 math 模块与 π 的来源math是 Python 标准库的一部分不需要额外安装。math.pi是模块内的常量因为模块名math没有在全局命名空间中所以需要先import math才能使用。也可以这样导入from math import pi导入后可以直接写pi不必写math.pi。初学者更推荐使用import math好处是代码里能清楚看到 π 来自哪个模块避免与其他变量重名。如果项目里已经有自己的变量叫pi用math.pi就不会冲突。另外需要注意math.pi是浮点数不是高精度 Decimal。对于普通几何计算足够但如果涉及金融场景或要求超高精度的科学计算就需要考虑decimal.Decimal或其他高精度方案。本项目保持浮点数计算不做展开。3. 最小实现从固定半径的输出开始3.1 固定半径计算周长和面积第一个版本先不处理输入把半径写死在代码里目标是把计算公式跑通。import math r 5 circumference 2 * math.pi * r area math.pi * r ** 2 print(半径:, r) print(周长:, circumference) print(面积:, area)运行方式python circle.py预期输出半径: 5 周长: 31.41592653589793 面积: 78.53981633974483这个版本虽然能算出结果但问题很明显半径是写死的每次算不同半径都要改代码。不过它适合作为起点先确认公式和运行流程正确再去处理输入和校验。3.2 逐行解释代码背后的执行逻辑逐行看这段代码import math导入标准库 math提供 π。r 5定义变量保存半径。Python 不需要声明类型但变量的值决定类型这里r是整数int。circumference 2 * math.pi * r右边三个操作数中2是 intmath.pi是 floatr是 int。Python 会自动把 int 转成 float 参与运算所以circumference是 float。area math.pi * r ** 2按运算符优先级先计算r ** 2再乘 π。如果担心优先级可以写成math.pi * (r ** 2)。print(...)把结果输出到控制台。一个print打印一个变量多个打印语句会在独立行输出。这里要注意print会自动在输出末尾加换行。如果想在同一行输出多个值可以在print中使用逗号分隔例如print(r, circumference, area)输出时会用空格分隔。更规范的做法是使用 f-string后面会讲到。3.3 运行结果与浮点输出观察输出中的周长31.41592653589793是一个浮点数。Python 内部使用二进制表示浮点数因此很多十进制小数并不能被精确表示显示出来就是一长串数字。这是正常的不代表公式出错。如果要确认结果正确可以用print(2 * math.pi * 5)单独验证结果应该与前面周长一致。面积同理。如果使用在线计算器或手算最后几位可能有差异这是浮点精度问题而不是逻辑问题。入门阶段可以先接受这种长小数输出。后面会通过格式化字符串控制显示的小数位数例如保留两位小数输出31.42这是更接近实际使用习惯的展示方式。4. 交互输入版让程序接收用户输入的半径4.1 input() 返回的是字符串必须先转换修改代码如下import math r_str input(请输入圆的半径: ) r float(r_str) circumference 2 * math.pi * r area math.pi * r ** 2 print(周长:, circumference) print(面积:, area)这里input()的作用是暂停程序等待用户在控制台输入一行内容按下回车后把输入的内容作为字符串返回。因此必须通过float()转成浮点数。如果不转换在计算2 * math.pi * r_str时math.pi是 floatr_str是 str两者相乘会直接报错TypeError: cant multiply sequence by non-int of type float这个报错信息经常把新手吓到其实翻译过来就是不能用 float 去乘字符串序列。原因就是类型不匹配。建议不要为了省代码写r float(input(请输入圆的半径: ))虽然这样可以运行但调试时很难看清楚报错发生在输入还是转换。先接收字符串再转换两个变量名分别写清楚更符合入门阶段的阅读习惯。4.2 加入负数校验避免数学上没有意义的结果半径为负数时圆周长和面积在实数范围内没有物理意义。但程序不会自己判断。如果不加校验输入-5会得到负数周长和正面积这显然不是用户想要的。因此要加一个if条件import math r_str input(请输入圆的半径: ) r float(r_str) if r 0: print(半径不能为负数请重新运行程序输入非负数。) else: circumference 2 * math.pi * r area math.pi * r ** 2 print(周长:, circumference) print(面积:, area)这里体现的是一类重要工程习惯任何程序入口都可能接收非法输入。对输入做校验比让错误数据在系统里流转再排查要便宜得多。入门阶段就养成这个意识后面写接口、写数据处理脚本时能少踩很多坑。不过这种“直接退出”的交互方式体验一般。用户输错了负数程序只提示一句就结束没法继续输入。更友好的方式是让程序循环直到输入合法数据。4.3 使用 while 让用户可以连续输入多个半径如果用户想算多个圆每算一个都要重新运行程序很麻烦。可以用while True让程序循环直到用户输入一个约定好的结束符比如q或直接按回车退出。import math def main(): while True: r_str input(请输入圆的半径(输入 q 退出): ) if r_str.lower() q: break try: r float(r_str) except ValueError: print(输入不是有效数字请重新输入。) continue if r 0: print(半径不能为负数请重新输入。) continue circumference 2 * math.pi * r area math.pi * r ** 2 print(f周长: {circumference:.2f}) print(f面积: {area:.2f}) if __name__ __main__: main()这段代码加入了try/except捕获转换异常。当用户输入abc时float(abc)会抛出ValueError程序捕获后提示重新输入而不是整个程序崩溃。continue跳过本次循环剩余部分继续下一次输入。if __name__ __main__:这个写法需要理解它确保只有当这个文件被直接执行时才运行main()函数。如果以后在其他脚本里import circle则不会立刻执行程序而是只加载函数定义。这是 Python 模块化开发的常见约定几乎每个独立脚本都会使用。这个版本已经具备一个命令行小工具的基本形态有输入、有校验、有循环、有异常处理、有格式化输出。后续的优化都在这条线上扩展。5. 函数封装与格式化输出让代码像真正的工程代码5.1 封装成函数计算逻辑与入口分离前面一直在主流程里写计算现在把计算抽成独立函数让代码结构更清晰。import math def circle_calculation(radius: float) - float: 计算圆的周长和面积。 Args: radius: 圆的半径必须为非负数。 Returns: 周长和面积的元组。 Raises: ValueError: 当半径小于 0 时抛出。 if radius 0: raise ValueError(半径不能为负数) circumference 2 * math.pi * radius area math.pi * radius ** 2 return circumference, area def main(): r_str input(请输入圆的半径: ) try: r float(r_str) circumference, area circle_calculation(r) print(f周长: {circumference:.2f}) print(f面积: {area:.2f}) except ValueError as e: print(f输入错误: {e}) if __name__ __main__: main()这里有几个值得注意的细节函数注解radius: float表示参数期望是浮点数- float表示返回值类型。它只是提示不强制运行时检查但 IDE 会根据它提供更智能的补全和检查。如果项目要求兼容 Python 3.8类型注解中的tuple[float, float]写法可能不行需要改成Tuple[float, float]并导入typing。这里为了简洁返回值类型写作float也是一种可选写法。函数内对负数抛ValueError由调用方决定如何处理。这样计算函数本身保持“一旦输入合法必定返回正确结果”的语义不符合条件时快速失败。返回元组后调用方用circumference, area circle_calculation(r)解包代码读起来很直观。封装完成后main()里不再关心公式细节只负责输入、调用、输出和异常处理。职责分离后修改计算方式比如改用高精度 Decimal时不需要动入口代码。5.2 使用 f-string 和格式化宽度控制输出前面的print直接打印浮点数小数位很长。使用 f-string 可以控制显示格式print(f半径为 {r:.2f} 的圆周长是 {circumference:.2f}面积是 {area:.2f}):.2f表示保留两位小数并自动四舍五入。还可以指定宽度print(f{r:6.2f} {circumference:8.2f} {area:8.2f})6.2f表示总共占 6 个字符宽度保留两位小数。这种写法适合输出对齐的数字列。如果用户输入了很大的半径宽度不够会自动撑开不会截断数据。f-string 是 Python 3.6 引入的字符串格式化方式比%格式化和str.format()更直观。在代码中直接读取变量不易出现“参数位置错了”的问题。格式化输出是工程中的高频需求打印日志、生成报表、拼接告警信息等场景都会用到。格式化语法速查表表达式含义示例输出{r:.2f}保留两位小数3.14{r:8.2f}占 8 个字符宽度保留两位小数右对齐3.14{r:8.2f}占 8 个字符宽度左对齐3.14{r:.2f}带正负号保留两位小数3.14{r:,.2f}千分位分隔保留两位小数1,234.575.3 补充命令行参数版方便脚本调用交互输入适合人使用但如果是另一个脚本调用或者想放进自动化流程最好支持命令行参数。用sys.argv接收参数import sys def main(): if len(sys.argv) ! 2: print(用法: python circle.py 半径) return try: r float(sys.argv[1]) if r 0: print(半径不能为负数) return circumference, area circle_calculation(r) print(f{r:.2f} {circumference:.2f} {area:.2f}) except ValueError: print(请输入有效的数字且半径不能为负数) if __name__ __main__: main()运行示例python circle.py 2.5输出2.50 15.71 19.63sys.argv[0]是脚本文件名sys.argv[1]是第一个参数。如果没传参数程序会提示用法。这种模式在写自动化处理、批量任务、集成到 Shell 脚本时很实用。社区中更推荐用argparse标准库处理复杂命令行参数但对于单参数场景sys.argv足够。结合交互版本可以这样设计如果命令行给了参数就用参数计算如果没有参数就进入交互模式。这样既满足人工使用也满足脚本调用。实际项目经常采用这种“多入口兼容”的方式。6. 批量计算与单元测试从“一次计算”到“一个模块”6.1 批量计算多个半径很多入门练习只做单个值计算但实际需求往往是批量处理一组数据。比如我们有一个半径列表想一次算出所有结果。修改代码如下import math def circle_calculation(radius: float): if radius 0: raise ValueError(半径不能为负数) circumference 2 * math.pi * radius area math.pi * radius ** 2 return circumference, area def main(): radii [1.0, 2.5, 4.0, 10.3] print(f{半径:6} {周长:10} {面积:10}) for r in radii: c, s circle_calculation(r) print(f{r:6.2f} {c:10.2f} {s:10.2f}) if __name__ __main__: main()输出示例半径 周长 面积 1.00 6.28 3.14 2.50 15.71 19.63 4.00 25.13 50.27 10.30 64.72 333.29循环遍历列表对每个半径调用同一个函数这就是“复用”的直观体现。只要计算函数正确批量计算就只是遍历数据的问题。之后如果从文件读取半径列表、从数据库读取半径字段只需要替换radii的来源不需要改计算函数。如果需要从 CSV 文件读取半径一个简单的例子是import csv with open(radii.csv, newline) as f: for row in csv.reader(f): r float(row[0]) c, s circle_calculation(r) print(r, c, s)这里的核心是“数据源”和“计算逻辑”解耦。把数据导入、计算、导出三个步骤分开后续无论数据来自哪里计算函数都不需要变化。6.2 用 unittest 保护计算公式回归当代码不断扩展很容易在一次修改中不经意破坏原有公式。单元测试能早期发现问题。Python 标准库unittest可以用于这类小型验证。import math import unittest from circle import circle_calculation class TestCircleCalculation(unittest.TestCase): def test_radius_1(self): circumference, area circle_calculation(1) self.assertAlmostEqual(circumference, 2 * math.pi) self.assertAlmostEqual(area, math.pi) def test_radius_2_5(self): circumference, area circle_calculation(2.5) self.assertAlmostEqual(circumference, 2 * math.pi * 2.5) self.assertAlmostEqual(area, math.pi * 2.5 ** 2) def test_negative_radius_raises(self): with self.assertRaises(ValueError): circle_calculation(-1) if __name__ __main__: unittest.main()assertAlmostEqual用于比较浮点数它允许一定误差。直接比较浮点数相等通常不可靠因为二进制表示存在精度误差。这个测试断言了半径 1 时周长等于 2π、面积等于 π负数半径会抛出异常。运行测试python -m unittest test_circle.py输出... ---------------------------------------------------------------------- Ran 3 tests in 0.001s OK入门阶段引入测试不一定要写很多用例但至少要把“程序能跑”和“结果正确”分开。固定半径版本的输出只能靠肉眼验证单元测试则让每次修改后都能自动验证。这个小项目非常适合作为接触测试的第一个案例因为它逻辑简单预期结果容易计算。6.3 模块化后项目文件结构到这一步项目应该有清晰的文件结构。例如circle_project/ ├── circle.py # 计算函数与命令行入口 ├── test_circle.py # 单元测试 └── README.md # 使用说明circle.py中既包含可调用的circle_calculation()也包含作为脚本运行时的main()。其他模块可以通过from circle import circle_calculation导入函数。test_circle.py单独放测试代码不污染业务代码。这样的结构是很多实际 Python 库的最小形态一个模块文件一个测试文件。再往后项目变大可以加入包目录、配置文件、依赖文件但这个雏形已经足够支撑你理解模块化开发的核心思想。7. 常见报错与排查路径7.1 报错 TypeError: cant multiply sequence by non-int of type float现象TypeError: cant multiply sequence by non-int of type float原因把input()返回的字符串直接和math.pifloat相乘。例如r input(请输入半径: ) area math.pi * r ** 2这里r是字符串字符串和 float 不能直接做乘法。排查检查type(r)是不是str。在乘法之前是否调用了float()。确认r ** 2是否发生在类型转换之前。解决r float(input(请输入半径: ))预防写代码时先明确变量类型。如果从函数参数接收变量也要确认调用方传入的是数字。7.2 计算结果出现负数或巨大数字现象输入-3程序输出负数周长输入一个极大的数例如10**20程序输出科学计数法。原因没有对输入值做范围校验或者没有考虑业务上限。排查是否在计算前检查r 0。是否考虑了实际业务场景中半径的最大值。是否误把r ** 2写成r * 2。解决if r 0: print(半径不能为负数) return如果业务要求半径不能超过某个上限可以增加上限判断if r 10000: print(半径超出支持范围) return预防在设计入口函数时把输入校验单独抽成一个小函数便于统一处理。7.3 中文乱码与浮点精度问题现象Windows 控制台输出中文变成乱码。这可能发生在print(半径)时。原因控制台编码与 Python 输出编码不一致。Windows 的cmd默认使用 GBK 编码而 Python 脚本文件如果保存为 UTF-8可能产生差异。Python 3 中字符串是 Unicode但在 Windows 终端输出有时会因编码设置出现乱码。排查检查文件保存编码是否为 UTF-8。在cmd中执行chcp 65001切换到 UTF-8 代码页。更好的方案是使用现代终端如 Windows Terminal、VS Code 集成终端它们默认支持 UTF-8。浮点精度问题0.1 0.2的结果是0.30000000000000004这属于正常的二进制浮点表示误差。如果要求精确输出可以使用round()或格式化但要注意round()在 Python 中的行为round(2.675, 2) # 结果为 2.67不是 2.68这不是 bug而是浮点表示和舍入规则导致的。对于入门几何计算保留两位小数已经足够。如果未来遇到金额、税率等需要精确计算的场景再引入decimal。7.4 排错清单表问题现象常见原因检查方式解决方案输出字符串相乘结果如 55input()返回字符串未做类型转换type(r)查看类型使用float(r)转换TypeError: cant multiply sequence字符串与 float 相乘定位到相乘行检查变量类型调用float()后再计算半径为负数仍输出结果没有做输入校验查看代码是否有if r 0加入负数判断并提示打印出的浮点小数很长格式化未设置小数位查看print语句使用f{x:.2f}中文输出乱码控制台编码与文件编码不一致在终端执行chcp查看代码页使用 UTF-8 终端设置chcp 65001程序启动后没反应可能没有调用main()检查if __name__ __main__:正确调用入口函数批量输出数字没有对齐未指定格式化宽度观察输出列是否错开
返回列表