当前位置: 首页 > news >正文

现代 Python 学习笔记:Statements Syntax

现代 Python 学习笔记:Statements & Syntax(Advanced)

前言

笔者最后一小段自由时间了,打算仔细的学习现代的Python编程。这个也算是希望给自己的自动化脚本编程搞一点摸鱼的小技术了

理解现代Python的脚本与模块化

​ 在 Python 中,每个 .py 文件本质上都是一个模块(Module)。将代码组织到不同的模块中,而不是将所有逻辑堆砌在一个巨大的脚本里,是专业软件开发的基石。这个思想非常常见,熟悉C/C++的朋友会把不同模块的功能分门别类组织起来,打包成为一个一个非常可具备移植性的模块,这样下一次工程使用的时候就可以进行复用。

​ 简单的说,模块化代码可以提高可维护性、可读性和重用性。为了详细说明,笔者将一个计算圆面积的程序分为了geometry.py,使用代码完成业务的部分放到了main.py上。

可维护性 (Maintainability)的提高

当我们需要优化圆面积的计算方法(比如使用更高精度的 math.pi),我们只需要修改geometry.py下的相关函数 。main.py 或其他任何使用此模块的文件都无需改动。 当出现计算错误时,您可以立刻确定问题出在 geometry.py 模块中,而不是在数千行的 main.py 中大海捞针。

可读性 (Readability)的提高

geometry.py 这个文件名本身就具有描述性。当另一位开发者(或未来的您)看到 from geometry import ... 时,能立刻明白代码的意图是处理几何相关的操作。而且,我们不需要关心 area_of_circle 是如何实现的(是用 math.pi 还是 3.14)。他/她只需要知道“我传入半径,它返回面积”。这使得 main.py 的逻辑更清晰、更高级。

重用性 (Reusability)的提高

geometry.py 模块可以被复制到任何其他需要计算面积的项目中。所以之后我们需要几何图形的面积计算的时候,随意引入,这个概念在现代软件架构设计中被认为是一个库。

Tips: DRY 原则 (Don’t Repeat Yourself): 如果您有三个不同的脚本都需要计算圆的面积,您不需要在三个脚本中都写一遍 math.pi * r ** 2。您只需要导入 geometry 模块并调用函数即可。

命名空间隔离 (Namespace Isolation)

​ 模块创建了独立的命名空间。这意味着您可以在 geometry.py 中定义一个名为 version 的变量,同时在 main.py 中也定义一个 version 变量,它们不会相互冲突。这对于大型项目至关重要,可以有效避免函数和变量重名导致的问题。

示例:geometry.py

"""Geometry moduleProvides functions to calculate areas of basic shapes.
"""import math
def area_of_circle(r):"""Compute the area of a circle.:param r: radius of the circle:return: area (float)"""return math.pi * r ** 2def area_of_square(a):"""Compute the area of a square.:param a: side length:return: area (float)"""return a * a

​ 这样我们就能很自然的写下代码

from geometry import area_of_circle, area_of_squareprint("Circle area (r=5):", area_of_circle(5))
print("Square area (a=4):", area_of_square(4))

导入(Import)的更多方式

from ... import ...是最常见的方式之一。了解不同的导入方式有助于您更好地控制命名空间。假设我们有 geometry.py

方式一:导入特定函数
from geometry import area_of_circle, area_of_square# 直接使用函数名
print(area_of_circle(5))
print(area_of_square(4))
  • 优点: 代码简洁。
  • 缺点: 如果导入的函数很多,或者函数名很通用(例如 read),可能会与您main.py中的函数名冲突。
方式二:导入整个模块
import geometry# 必须通过 "模块名." 来访问
print(geometry.area_of_circle(5))
print(geometry.area_of_square(4))
  • 优点: 命名空间完全隔离。geometry.area_of_circle 永远不会和您本地的 area_of_circle 冲突。可读性强,一眼就知道函数来源。
  • 缺点: 代码稍微冗长。
方式三:使用别名 (Alias)
import geometry as geo
from geometry import area_of_circle as circleprint(geo.area_of_square(4)) # 模块别名
print(circle(5))             # 函数别名
  • 优点: 在模块名很长(如 matplotlib.pyplot常被别名为 plt)或存在命名冲突时非常有用。

⚠️ 避免使用的导入方式:from geometry import *这种方式!因为会导入模块中所有非下划线开头的变量和函数。这会严重“污染”当前文件的命名空间,导致命名冲突,并使代码极难阅读(您根本不知道某个函数是从哪里来的)。

处理长行代码

PEP 8 建议每行不超过 79 字符。使用括号隐式换行是 Python 推荐方式,举个例子,如果一行话非常非常长,考虑分开一下。

result = compute_total_price(base_price,tax_rate,discount_rate,shipping_fee,special_coupon
)

参考:PEP 8 — Maximum Line Length


多if elif优化

​ 在日常 Python 开发中,我们经常需要根据条件返回不同结果,例如根据分数返回等级(A–F)。传统的 if...elif 链写法虽然直观,但当条件变多时可读性下降。那么,我们要如何进行一定的优化呢?

当然,第一个方法自然是整齐我们的代码,看起来更好读一些。。。

def grade(score: int) -> str:"""Return letter grade for a single score using if...elif.:param score: Exam score (0-100):type score: int:return: Letter grade (A-F):rtype: str"""if score >= 90:return "A"elif score >= 80:return "B"elif score >= 70:return "C"elif score >= 60:return "D"else:return "F"

字典映射 / 阈值列表优化

我们可以用 字典或阈值列表映射分数到等级,遍历阈值判断即可,代码更简洁易扩展。

from typing import List, Union# 规则集中在 THRESHOLDS 中,修改非常方便。
THRESHOLDS: list[tuple[int, str]] = [(90, "A"),(80, "B"),(70, "C"),(60, "D"),(0,  "F")
]def grade_threshold(score: int) -> str:"""Return letter grade using threshold mapping.:param score: Exam score (0-100):type score: int:return: Letter grade (A-F):rtype: str:raises ValueError: If score is out of 0-100 range"""if not 0 <= score <= 100:raise ValueError("Score must be between 0 and 100")for threshold, grade in THRESHOLDS:if score >= threshold:return graderaise RuntimeError("Unexpected error in grade_threshold")

支持单个分数或分数列表

利用列表推导式,可以方便地批量处理分数:

def grade_general(scores: Union[int, List[int]]) -> List[str]:"""Return letter grades for single score or list of scores.:param scores: Single score or list of scores:type scores: int or List[int]:return: List of letter grades:rtype: List[str]"""if isinstance(scores, list):return [grade_threshold(s) for s in scores]return [grade_threshold(scores)]

示例使用:

print(grade_threshold(85))                 # 输出: 'B'
print(grade_general([95, 82, 67, 58]))     # 输出: ['A', 'B', 'D', 'F']
print(grade_general(73))                   # 输出: ['C']

海象运算符 :=

Python 3.8 引入海象运算符,可以在表达式中直接赋值。

sentence = "Writing Python code is surprisingly enjoyable"
words = sentence.split()
max_word = ""
max_len = 0for w in words:if (l := len(w)) > max_len:max_len = lmax_word = wprint(f"Longest word: {max_word} ({max_len})")

Tips:

  • 避免重复计算,提高代码可读性。
  • 常用于循环或条件表达式。
  • 参考资料:PEP 572 — Assignment Expressions

Python 的异常处理部分讨论

Python利用经典的try catch throw机制来处理异常,当然,关键字是try...except 结构,他不仅仅是用来“防止程序崩溃”的工具,它更是一种强大的控制流机制。正确地使用它,可以极大地提高代码的健壮性、可读性和可调试性。

基础:捕获特定的异常 (Safe Exception Catching)

异常处理的第一原则是**永远只捕获你“预期”会发生,并且“知道”如何处理的异常。**其他的异常不属于你管辖的范畴,甚至,是只捕捉你打算处理的异常!

try:# 尝试执行一个可能失败的操作result = 1 / 0
except ZeroDivisionError as e:# 明确处理 "除零错误"print(f"Error occurred: {e}")result = 0 # 提供一个回退值
Tip 1: “使用具体异常类型,而不是裸 except:
  • 为什么? 裸露的 except: (或 except Exception:) 会捕获所有类型的异常。这包括:
    • MemoryError (内存耗尽)
    • KeyboardInterrupt (用户按下了 Ctrl+C)
    • SystemExit (程序被要求退出,例如 sys.exit())
  • 危险性: 当您捕获了 KeyboardInterrupt,您的程序将变得无法通过 Ctrl+C 停止。当您捕获 MemoryError 时,您可能在隐藏一个严重的内存泄漏。裸露的 except: 会让调试变得异常困难,因为它隐藏了所有未预料到的错误。
Tip 2: “可用 as e 捕获异常对象”
  • e 是什么? eZeroDivisionError 类的一个实例

  • 它有什么用?

    1. 打印信息: print(e) 会调用该对象的 __str__ 方法,通常会给出一个人类可读的错误信息(例如:“division by zero”)。

    2. 获取参数: e.args 是一个包含传递给异常构造函数参数的元组。

    3. 日志记录: 在生产环境中,您会使用 logging 模块记录完整的异常信息:

      import logging
      try:1 / 0
      except ZeroDivisionError as e:logging.error(f"A division error occurred: {e}", exc_info=True)
      

      exc_info=True 会将完整的堆栈跟踪(Traceback)记录到日志中。

异常匹配的继承与顺序

异常在 Python 中是类 (Class)。它们存在继承关系,except 语句块在匹配异常时,会从上到下依次检查。它会执行第一个匹配到的 except 块。“匹配”的定义是:isinstance(raised_exception, ExceptType)

class CustomError(Exception): pass
class SubCustomError(CustomError): pass # SubCustomError 是 CustomError 的子类
正确的捕获顺序(子类在前,父类在后)
try:# 我们抛出一个非常具体的 "子类" 异常raise SubCustomError("This is a sub-error")
except SubCustomError as e:# 1. Python 检查: isinstance(SubCustomError(), SubCustomError) -> True# 2. 匹配成功!执行此块。print(f"Caught SubCustomError specifically: {e}")
except CustomError as e:# 3. 此块被跳过print(f"Caught general CustomError: {e}")
  • 输出: Caught SubCustomError specifically: This is a sub-error
错误的捕获顺序(父类在前,子类在后)

让我们故意把顺序写错,看看会发生什么:

try:raise SubCustomError("This is a sub-error")
except CustomError as e:# 1. Python 检查: isinstance(SubCustomError(), CustomError) -> True#    (因为 SubCustomError 是 CustomError 的子类)# 2. 匹配成功!执行此块。print(f"Caught general CustomError: {e}")
except SubCustomError as e:# 3. 此块永远不会被执行!它成为了 "死代码" (Dead Code)。print(f"Caught SubCustomError specifically: {e}")
  • 输出: Caught general CustomError: This is a sub-error

这是因为异常匹配从上到下。由于子类也是父类的一个实例,所以必须在 except 语句中将**更具体(子类)的异常放在更通用(父类)**的异常之前。否则,父类的 except 块会“过早”地捕获子类异常,导致子类的特定处理逻辑永远无法执行。

高级:异常链与上下文 (Exception Chaining)

在复杂的系统中,一个函数调用另一个函数。当底层函数(例如数据库访问)失败时,上层函数(例如业务逻辑)可能需要捕获该异常,并抛出一个更具上下文含义的新异常。 如果您只是简单地 raise NewError,原始的错误(“根因”)就会丢失,这会给调试带来灾难。 这就要求我们使用 raise ... from ... 来构建异常链 (Exception Chaining)

def process_data():try:# 底层操作:可能发生各种具体错误result = 10 / 0except ZeroDivisionError as e:# 我们捕获了具体错误,但想抛出一个更高级别的、# 描述“业务逻辑”的错误。# Tip: 使用 "from e" 来链接异常raise RuntimeError("Calculation failed during data processing") from etry:process_data()
except RuntimeError as final_error:print(f"--- Top Level Error Caught ---")print(f"Error: {final_error}")# 异常对象 "final_error" 包含了完整的上下文

输出的堆栈跟踪 (Traceback) 会是这样的:

Traceback (most recent call last):File "...", line 3, in process_dataresult = 10 / 0
ZeroDivisionError: division by zeroDuring handling of the above exception (ZeroDivisionError), another exception occurred:Traceback (most recent call last):File "...", line 12, in <module>process_data()File "...", line 9, in process_dataraise RuntimeError("Calculation failed during data processing") from e
RuntimeError: Calculation failed during data processing
  1. Python 清楚地告诉您,原始错误是 ZeroDivisionError: division by zero
  2. 然后它说:“在处理上述异常时,发生了另一个异常”。
  3. 最后它显示了您抛出的新异常 RuntimeError: Calculation failed...

所以,raise ... from e 会将 e (原始异常) 存储在新异常的 __cause__ 属性中。这为调试者提供了完整的上下文:不仅知道发生了什么RuntimeError),还知道为什么会发生ZeroDivisionError)。

上下文管理器与 with 语句:自动化资源管理

with 语句是 Python 中一种优雅且健壮的语法,用于管理资源。它保证无论代码块是正常执行完毕、发生异常还是提前 return,特定的“设置” (setup) 和“清理” (teardown) 操作都能被执行。最常见的例子就是文件处理:

传统方式 (容易出错):
f = open("myfile.txt", "w")
try:f.write("Hello")# 假设这里发生了一个错误,比如 1 / 0
finally:# 无论是否发生错误,finally 块都会执行f.close()

这种 try...finally 结构是必须的,因为您必须确保 f.close() 被调用,否则文件句柄可能会被泄漏。但这很冗长。

with 语句 (现代、安全的方式):
with open("myfile.txt", "w") as f:f.write("Hello")# 假设这里发生错误 1 / 0
# 当代码块退出时(无论何种原因),f.close() 会被 *自动调用*

with 语句极大地简化了代码,并消除了忘记 close() 的风险。让我们来拆解这个示例,它完美地展示了如何使用内置的上下文管理器。

"""
File processorReads a text file, prints the longest line length, and logs results.
"""file_path = input("Enter file path: ")
log_file = "process.log"try:# 1. 核心:with 语句 + 异常处理with open(file_path, encoding="utf-8") as f:max_len = 0longest_line = ""for line in f:# 2. 现代语法:海象运算符 (:=)if (l := len(line.strip())) > max_len:max_len = llongest_line = line.strip()print(f"Longest line ({max_len} chars): {longest_line}")# 3. 嵌套的 with 语句 (打开日志文件)with open(log_file, "a", encoding="utf-8") as log:log.write(f"{file_path}: {max_len}\n")except FileNotFoundError as e:# 4. 健壮性:处理特定异常print(f"File not found: {e}")

如何让我们自定义的类支持With语句

with 语句之所以能工作,是因为它遵循一个协议,该协议依赖于两个“魔术方法”:__enter____exit__

import timeclass timer:def __init__(self, name):self.name = namedef __enter__(self):"""(1) 进入 with 块时调用"""print(f"[Timer '{self.name}' starting...]")self.start = time.time()# (2) a. return 的值会赋给 'as' 后面的变量return self def __exit__(self, exc_type, exc_value, traceback):"""(3) 退出 with 块时调用"""end = time.time()print(f"[Timer '{self.name}' finished] Took: {end - self.start:.4f}s")# (4) 异常处理if exc_type:print(f"   L-> Exited with an exception: {exc_type.__name__}")# (2) b. 返回 False (或 None) 会重新抛出异常#     返回 True 则会“吞噬”异常return False 
__enter__(self):
  • 这是“设置”阶段。在您的示例中,它记录了开始时间。
  • return self 的意义: 当您编写 with timer("Query") as t: 时,变量 t 将被赋值为 __enter__ 方法的返回值。在这里返回 self 允许您在 with 块内部与 timer 实例交互(尽管您的示例没有这样做)。

__exit__(self, exc_type, exc_value, traceback):

  • 这是“清理”阶段。它总是会被调用
  • 这三个参数至关重要:
    • 如果 with正常完成exc_type, exc_value, traceback 全部为 None
    • 如果 with发生异常:这三个参数会接收到异常的类型、值和堆栈跟踪信息(与 sys.exc_info() 相同)。
  • 异常处理能力:
    • __exit__ 方法内部,您可以检查 exc_type 是否为 None 来判断是否发生了错误。
    • 返回 True: 如果 __exit__ 返回 True,它告诉 Python:“我已经处理了这个异常,请不要将它传播出去。” 异常被“吞噬”了。
    • 返回 False (或 None): 如果 __exit__ 返回 FalseNone(默认),它告诉 Python:“我没有处理这个异常(或者我只是记录了它),请在 __exit__ 执行完毕后,继续将它抛出。”
演示 __exit__ 的异常处理:
with timer("Test Exception"):print("   L-> Inside block, about to raise error...")x = 1 / 0 # 故意制造一个错误print("   L-> This line will not be printed.")print("\n--- Program continues (because __exit__ handled it) ---")

上述代码(使用我们修改后的 timer)的输出:

[Timer 'Test Exception' starting...]L-> Inside block, about to raise error...
[Timer 'Test Exception' finished] Took: 0.0001sL-> Exited with an exception: ZeroDivisionError
Traceback (most recent call last):File "...", line X, in <module>x = 1 / 0
ZeroDivisionError: division by zero

注意:计时器仍然正确打印了它的结束信息和错误信息,然后异常被重新抛出(因为 __exit__ 返回了 False)。

更简单的方式:@contextmanager

对于像 timer 这样简单的上下文管理器,Python 在 contextlib 模块中提供了一个更简单的创建方式:使用 @contextmanager 装饰器。这允许您将一个生成器 (generator) 转换为上下文管理器:

from contextlib import contextmanager
import time@contextmanager
def timer_generator(name):# --- 这部分是 __enter__ ---print(f"[GenTimer '{name}' starting...]")start = time.time()try:# yield 将控制权交回给 with 块yieldfinally:# --- 这部分是 __exit__ (在 finally 中保证执行) ---end = time.time()print(f"[GenTimer '{name}' finished] Took: {end - start:.4f}s")# 使用方法完全相同!
with timer_generator("Sleeping"):time.sleep(1)
  • yield 之前的代码是 __enter__
  • yield 之后的代码(放在 try...finally 中)是 __exit__
  • 这种方式更简洁,并且自动处理了异常的传播。

总结表:现代 Python 核心技巧

技巧说明参考资料
docstring & RST提供函数/模块文档PEP 257
长行折叠使用括号换行,符合 PEP 8PEP 8 — Maximum Line Length
if…elif 优化字典映射或列表推导官方 Tutorial — More Control Flow Tools
海象运算符 :=在表达式中赋值PEP 572
for-elseelse 在循环自然结束时执行官方 Tutorial
异常顺序子类异常先于父类官方 Tutorial — Errors and Exceptions
安全捕获避免 bare except官方 Tutorial
异常链raise ... from ... 保留原异常官方 Tutorial
with 语句自动管理资源官方 Reference

Reference

参考资料

  • Python 官方教程:The Python Tutorial
  • PEP 8 — Style Guide for Python Code
  • PEP 257 — Docstring Conventions
  • Python 3.8: Assignment Expressions (walrus operator)
  • Python 官方异常处理文档
  • Python with statement & Context Managers
http://www.dtcms.com/a/540912.html

相关文章:

  • Debian、Ubuntu、CentOS:Linux 三大发行版的核心区别
  • 石家庄桥西网站制作公司阮一峰wordpress
  • WordPress网站接入公众号设计参考图网站
  • Spring Boot 中 controller层注解
  • 润滑油东莞网站建设技术支持网页美工培训中心
  • Jmeter:接口测试流程(附图)
  • 大模型面试题:简述GPT和BERT的区别?
  • myalsa仓库体验
  • 全域互联,统一管控:EasyCVR构建多区域视频监控“一网统管”新范式
  • 使用 Fast GraphRAG 和 LM Studio 搭建本地技术文档分析系统
  • 【技术变迁脉络解析】Axure RP 介绍、版本历史及推荐
  • 【C端】底部导航栏实现
  • 智能科技的附加特性:提升用户体验的多样选择
  • Python爬虫定时任务:自动化抓取豆瓣每日最新短评
  • 6.1.1.2 大数据方法论与实践指南-实时任务(spark/flink)任务的 cicd 解决方案
  • 基于神经元的多重分形分析在大模型神经元交互动力学中的应用
  • 客户案例:SLIP ROBOTICS+OAK—物流自动化边缘 AI 视觉应用
  • Flink DataStream API 从基础原语到一线落地
  • RAPID常用数据类型以及API中文
  • 网站建设公司要多少钱智慧团建平台
  • ECharts 3D立体柱状图组件开发全解析:Bar3D_2.vue 深度剖析
  • ARM《6》_给sd卡中拷入uboot程序
  • iOS 26 开发者工具推荐,构建高效调试与性能优化工作流
  • 综述:deepSeek-OCR,paddle-OCR,VLM
  • 邢台市地图全图高清版小红书seo软件
  • 网安面试题收集(5)
  • 台州新农村建设网站沈阳工程信息交易网
  • 全国酒店网站建设金融网站欣赏
  • WebForms TextBox:深入解析与最佳实践
  • 北京商城网站开发如何进行域名注册