尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

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

Python核心概念精讲:变量内存、函数参数、OOP与异常处理实战

Python核心概念精讲:变量内存、函数参数、OOP与异常处理实战 1. 项目概述为什么需要一份“自己的”Python基础整理最近在带几个刚入行的新人发现一个挺普遍的现象大家学Python要么是跟着网上的视频课敲一遍要么是抱着某本经典教材从头啃。学的时候感觉都懂了过俩月要写个小工具或者看同事的脚本立马又得回头去查“列表推导式怎么写”、“with语句到底关了个啥”。这问题我也经历过本质上是因为知识是零散接收的没有形成自己的体系。网上的资料浩如烟海但往往要么太浅只讲语法要么太深直接上框架源码缺少一个从“知道”到“能用”再到“用好”的连贯路径。这份“Python基础知识整理”就是针对这个痛点来的。它不是一个简单的语法罗列而是以一个一线开发者的视角重新梳理那些真正影响编码效率、决定代码质量的基础概念。我们会跳过那些“print(‘Hello World’)”式的入门直接切入变量与内存、函数作用域、面向对象核心、异常处理逻辑等容易混淆但至关重要的领域。目标读者是已经看过一些教程、能写简单脚本但渴望写出更健壮、更高效、更“Pythonic”代码的初学者以及那些想巩固基础、查漏补缺的中级开发者。通过这份整理你不仅能回顾语法更能理解其背后的设计哲学和实用技巧最终构建起属于你自己的、可随时调用的Python知识网络。2. 核心基石深入理解变量、对象与内存管理很多人学Python第一个接触的就是a 10但可能直到踩了坑才去思考a到底是什么。这一章我们抛开抽象概念直接看Python在内存里是怎么玩的。2.1 变量不是盒子而是便利贴一个最经典的误解是把变量当作存储数据的“盒子”。在Python中更准确的比喻是变量是贴在对象上的“便利贴”或“标签”。对象比如整数10、列表[1,2,3]存在于计算机的内存中变量名如a,my_list只是一个指向该内存位置的引用。a [1, 2, 3] # 创建一个列表对象贴上一个叫a的标签 b a # 不是复制列表而是给同一个列表对象贴上了第二个叫b的标签 b.append(4) print(a) # 输出[1, 2, 3, 4]上例中a和b指向同一个列表对象。通过b修改对象通过a访问时变化立即可见。这引出了可变对象与不可变对象的根本区别。2.2 可变对象 vs. 不可变对象一切行为的根源这是Python中最重要的概念之一它直接决定了赋值、参数传递、拷贝等操作的行为。不可变对象对象一旦创建其内容值就不能被改变。对不可变对象的操作如拼接、修改实际上会创建一个新的对象。常见类型int,float,str,tuple,bytes,frozenset。示例与影响x 10 y x # x和y都指向内存中的10这个整数对象 x x 1 # 整数10不可变x1计算得到11创建了一个新的整数对象11并将x的标签贴到11上。 print(y) # 输出10。y仍然贴着10不受影响。字符串的“修改”也是同理任何看似修改的操作如replace,upper都返回新字符串。可变对象对象创建后其内容可以被修改而对象在内存中的身份id保持不变。常见类型list,dict,set, 用户自定义的类实例通常。示例与影响list_a [1, 2] list_b list_a # 两个标签指向同一个列表对象 list_b.append(3) # 直接修改了共享的列表对象 print(list_a) # 输出[1, 2, 3]。list_a看到的变化这就是为什么在函数中传递一个列表并修改它会影响到函数外原始列表的原因。注意理解可变/不可变是避免出现“莫名其妙的数据被更改”这类Bug的关键。当你需要传递一个可变对象又不希望原对象被修改时必须使用拷贝。2.3 赋值、浅拷贝与深拷贝如何复制你的数据根据需求选择正确的“复制”方式是写出可靠代码的基本功。赋值 ()如前所述只是增加一个标签引用没有创建新对象。浅拷贝创建一个新的容器对象但只拷贝容器内元素的引用。对于不可变元素这没问题但对于可变元素新容器和旧容器共享这些可变子对象。实现方式列表的切片操作list_copy original_list[:]list.copy()方法copy模块的copy.copy()函数。示例与坑点import copy original [1, 2, [3, 4]] shallow original.copy() # 或 original[:] shallow[2].append(5) # 修改了共享的子列表对象 print(original) # 输出[1, 2, [3, 4, 5]]原数据被意外修改。深拷贝创建一个新的容器对象并递归地拷贝容器内所有元素包括嵌套的可变对象。最终得到的是一个完全独立的对象。实现方式copy模块的copy.deepcopy()函数。示例import copy original [1, 2, [3, 4]] deep copy.deepcopy(original) deep[2].append(5) print(original) # 输出[1, 2, [3, 4]]。原数据安全。 print(deep) # 输出[1, 2, [3, 4, 5]]选择策略如果数据结构简单没有嵌套的可变对象浅拷贝通常足够且更快。如果数据结构复杂包含列表的列表、字典的字典等并且你需要一个完全独立的副本务必使用深拷贝。不确定时用深拷贝更安全但需注意性能开销对于非常大的对象。3. 函数进阶作用域、参数与装饰器核心函数是组织代码逻辑的单元理解其运行机制才能写出灵活且强大的代码。3.1 作用域与LEGB规则变量从哪里来当你在函数内部使用一个变量名时Python会按照LEGB规则依次查找L (Local)局部作用域即当前函数内部。E (Enclosing)嵌套函数的外层函数作用域闭包相关。G (Global)全局作用域即当前模块文件级别。B (Built-in)内建作用域包含len,print等Python内置函数和异常。x global # G def outer(): x enclosing # E def inner(): x local # L print(x) # 输出local优先找到L inner() print(x) # 输出enclosing当前作用域是E outer() print(x) # 输出global当前作用域是G关键行为读取Python可以向上查找从L到B。修改在函数内部如果对一个变量进行赋值操作Python会默认在**当前局部作用域(L)**创建一个新的局部变量而不是修改外部变量。count 0 def increment(): count count 1 # 错误右侧count试图读取全局count但左侧赋值又在本地创建新count导致读取时本地count未定义。 increment()global与nonlocal声明用于修改外部作用域的变量。global var声明var来自全局作用域(G)后续操作将修改全局变量。nonlocal var声明var来自外层嵌套函数作用域(E)用于闭包场景。3.2 参数传递的真相既不是传值也不是传引用Python的参数传递是“按对象引用传递”。简单说就是把实参外部变量所指向的对象引用赋值给了形参函数内部变量。因此如果传递的是不可变对象如整数、字符串函数内部对形参的重新赋值不会影响外部实参因为创建了新对象。如果传递的是可变对象如列表、字典函数内部通过形参对对象内容进行修改如append,update会直接影响外部实参所指向的同一个对象。def modify_data(num, alist): num 100 # 重新赋值创建新int对象不影响外部 alist.append(99) # 修改可变对象影响外部 x 10 my_list [1, 2] modify_data(x, my_list) print(x) # 输出10 (未变) print(my_list) # 输出[1, 2, 99] (已变)3.3 参数类型详解让函数接口清晰灵活Python函数参数非常灵活理解每种参数的用法和顺序是关键。位置参数最常见的参数按顺序传递。def greet(name, greeting): print(f{greeting}, {name}!) greet(Alice, Hello) # nameAlice, greetingHello默认参数为参数提供默认值调用时可省略。重要陷阱默认参数必须指向不可变对象# 正确做法 def add_to_list(value, target_listNone): if target_list is None: target_list [] target_list.append(value) return target_list # 错误做法默认参数为可变对象 def buggy_func(arg[]): # 危险所有调用共享同一个默认列表 arg.append(1) return arg print(buggy_func()) # [1] print(buggy_func()) # [1, 1] 不是预期的[1]可变位置参数 (*args)接收任意数量的位置参数在函数内部打包成一个元组args。def sum_all(*numbers): return sum(numbers) print(sum_all(1, 2, 3, 4)) # 输出10可变关键字参数 (**kwargs)接收任意数量的关键字参数在函数内部打包成一个字典kwargs。def print_info(**info): for key, value in info.items(): print(f{key}: {value}) print_info(nameBob, age25, cityNew York)仅限关键字参数在*args参数之后或单独使用一个*符号之后的参数必须通过关键字指定。def connect(host, port, *, timeout, retries): # host, port 是位置或关键字参数 # timeout, retries 必须是关键字参数 pass connect(localhost, 8080, timeout10, retries3) # 正确 # connect(localhost, 8080, 10, 3) # 错误TypeError参数顺序规则必须遵守(位置参数, 默认参数, *args, 仅限关键字参数, **kwargs)例如def func(a, b10, *args, c, d20, **kwargs)3.4 装饰器初窥不修改函数而增强功能装饰器是Python中非常强大的特性它允许你在不修改原函数代码的情况下为函数添加额外的功能如日志、计时、权限检查。核心思想装饰器本身是一个接受函数作为参数并返回一个新函数的高阶函数。import time def timer_decorator(func): 一个记录函数运行时间的装饰器 def wrapper(*args, **kwargs): # 包装函数接收任意参数 start_time time.time() result func(*args, **kwargs) # 执行原函数 end_time time.time() print(f函数 {func.__name__} 运行耗时{end_time - start_time:.4f}秒) return result # 返回原函数的结果 return wrapper # 使用装饰器 timer_decorator def slow_function(): time.sleep(1) print(任务完成) slow_function() # 输出 # 任务完成 # 函数 slow_function 运行耗时1.0012秒timer_decorator只是语法糖等价于slow_function timer_decorator(slow_function)。装饰器返回的wrapper函数替换了原来的slow_function在调用slow_function()时实际上执行的是wrapper()。实操心得初学装饰器最难理解的是多层函数的嵌套和参数传递。记住一个套路装饰器函数定义decorator(func)内部定义wrapper(*args, **kwargs)在wrapper里调用func(*args, **kwargs)并返回结果最后decorator返回wrapper。用这个模板可以解决大部分基础装饰器需求。4. 面向对象编程核心从“是什么”到“怎么用”面向对象OOP不是语法记忆而是一种组织代码的思维方式。Python的OOP有其独特之处。4.1__init__不是构造函数self是什么常见的误解是把__init__称为构造函数。实际上真正创建对象实例的是__new__方法通常不用自己定义。__init__更准确的叫法是初始化方法它负责在对象被创建之后初始化其属性。self代表类的实例对象本身。在实例方法中Python会自动将调用该方法的实例对象作为第一个参数传入这个参数约定俗成命名为self。通过self你可以访问和修改该实例的属性调用该实例的其他方法。class Dog: def __init__(self, name, age): # self 指代即将被初始化的那个Dog对象 self.name name # 创建实例属性 self.age age def bark(self): # 调用时dog.bark() 会自动把dog传给self print(f{self.name} says: Woof!) my_dog Dog(Buddy, 3) # __init__ 被调用self 就是 my_dog my_dog.bark() # 输出Buddy says: Woof! # 等价于 Dog.bark(my_dog)4.2 类属性与实例属性分清共享与独有类属性定义在类内部但在任何方法之外。它属于类本身所有实例共享。通常用于定义该类所有实例共有的常量或默认值。class Circle: pi 3.14159 # 类属性 def __init__(self, radius): self.radius radius # 实例属性 def area(self): return self.pi * self.radius ** 2 # 通过self或Circle.pi访问 c1 Circle(1) c2 Circle(2) print(c1.pi, c2.pi, Circle.pi) # 输出都是 3.14159 Circle.pi 3.14 # 修改类属性所有实例访问到的值都改变 print(c1.area()) # 使用新的pi值计算实例属性通常在__init__方法中通过self.xxx yyy定义。它属于单个实例每个实例都有自己的副本。c1.color red # 可以动态给实例添加属性但这通常不是好做法 print(c2.color) # AttributeErrorc2没有color属性关键区别与访问顺序当通过实例访问一个属性如obj.attr时Python会先查找实例的命名空间如果找不到再查找类的命名空间。这解释了为什么实例可以访问类属性以及为什么修改obj.class_attr有时会创建一个新的实例属性如果直接赋值而不是修改类属性。4.3 继承与MRO方法如何被找到继承允许子类获取父类的属性和方法。Python支持多重继承。class Animal: def __init__(self, name): self.name name def speak(self): raise NotImplementedError(子类必须实现此方法) class Dog(Animal): def speak(self): return f{self.name} says Woof! class Cat(Animal): def speak(self): return f{self.name} says Meow! class Robot: def speak(self): return Beep boop class RoboDog(Dog, Robot): # 多重继承 pass rd RoboDog(Sparky) print(rd.speak()) # 输出Sparky says Woof!当调用rd.speak()时Python需要确定使用哪个speak方法。这个查找顺序由方法解析顺序MRO决定。你可以通过ClassName.__mro__或ClassName.mro()查看。MRO采用C3线性化算法基本原则是子类优先于父类多个父类时按定义顺序从左到右同时保证每个类只出现一次。对于RoboDog(Dog, Robot)MRO大致是[RoboDog, Dog, Animal, Robot, object]。所以rd.speak()找到了Dog中的方法。注意事项多重继承功能强大但容易导致设计复杂化“菱形继承”问题。在实际项目中应谨慎使用优先考虑组合“has-a”关系而非继承“is-a”关系。如果必须用务必理清MRO。4.4 特殊方法魔术方法让对象更“Pythonic”以双下划线开头和结尾的方法如__init__,__str__,__len__称为特殊方法或魔术方法。它们被Python解释器在特定场景下自动调用用于实现或定制对象的某些内置行为。常用特殊方法触发场景说明__str__(self)print(obj),str(obj)返回对象的“非正式”字符串表示给人看的。__repr__(self)repr(obj), 交互式环境直接输入obj返回对象的“正式”字符串表示应能用于eval()重新创建对象给开发者看的。__len__(self)len(obj)定义对象的“长度”。__getitem__(self, key)obj[key]定义通过索引或键访问元素的行为。__setitem__(self, key, value)obj[key] value定义通过索引或键设置元素的行为。__iter__(self)for x in obj:返回一个迭代器对象。__next__(self)next(iterator)迭代器对象自身需要的方法。__enter__(self),__exit__(...)with obj as x:实现上下文管理协议用于资源自动管理。示例让一个类支持len()和for循环class Playlist: def __init__(self, songs): self.songs list(songs) def __len__(self): return len(self.songs) def __getitem__(self, index): return self.songs[index] def __str__(self): return fPlaylist with {len(self)} songs. my_list Playlist([Song A, Song B, Song C]) print(len(my_list)) # 输出3 调用了 __len__ print(my_list[1]) # 输出Song B 调用了 __getitem__ for song in my_list: # 可以迭代因为实现了 __getitem__ print(song) print(my_list) # 输出Playlist with 3 songs. 调用了 __str__实现这些特殊方法能让你的自定义对象像内置类型一样自然、好用这是写出“Pythonic”代码的重要一环。5. 异常处理从被动防御到主动掌控程序运行时难免出错。异常处理不是为了消灭所有错误而是为了优雅地管理错误防止程序意外崩溃并提供清晰的错误信息。5.1try...except...else...finally完整结构一个完整的异常处理块包含四个部分每个部分都有其明确职责try: # 可能抛出异常的代码区 result 10 / divisor print(f结果是{result}) except ZeroDivisionError as e: # 捕获并处理特定的异常ZeroDivisionError print(f捕获到除零错误{e}) except (ValueError, TypeError) as e: # 捕获并处理一组异常 print(f输入类型错误{e}) except Exception as e: # 捕获所有其他未被前面处理的异常慎用 print(f发生了未知错误{e}) # 通常这里应该记录日志或重新抛出 else: # 当try块中的代码没有抛出任何异常时执行 print(计算成功完成) finally: # 无论是否发生异常最终都会执行的代码 # 常用于清理资源如关闭文件、网络连接 print(清理现场...)各部分执行逻辑try监控这里的代码。except如果try中发生异常且异常类型匹配则跳转到对应的except块。异常一旦被某个except捕获就不会再向外传递除非在except块内用raise再次抛出。elsetry块无异常时才执行。它很好地将正常逻辑try和错误处理except分离。finally无论有无异常最后必定执行。即使try或except中有return语句finally也会在return之前执行。5.2 常见的异常类型与使用建议了解常见异常类型有助于编写更有针对性的处理代码。异常类型常见触发原因处理建议ZeroDivisionError除数为零检查除数或提前判断。TypeError操作或函数应用于不适当类型的对象检查变量类型使用类型注解或isinstance判断。ValueError函数参数类型正确但值不合法如int(abc)验证输入数据的有效性。IndexError序列索引超出范围访问列表/元组/字符串前检查长度。KeyError字典中不存在指定的键使用dict.get(key, default)或key in dict判断。FileNotFoundError尝试打开不存在的文件使用os.path.exists()检查或直接捕获异常。AttributeError对象没有指定属性或方法检查对象类型或使用hasattr()判断。ImportError/ModuleNotFoundError导入模块失败检查模块名拼写、安装情况或路径。使用建议精确捕获尽量捕获具体的异常类型避免使用裸露的except:或过于宽泛的except Exception:这会隐藏你未预料到的错误。最小化try块只将可能出错的代码放入try块范围过大不利于定位问题。记录异常信息在except块中至少打印或记录异常信息str(e)或repr(e)可以使用logging模块。异常链在Python 3中使用raise NewError(...) from original_error可以保留原始异常的上下文便于调试。5.3 主动抛出异常与自定义异常有时你需要主动中断程序流程或者创建更符合业务逻辑的错误类型。使用raise抛出异常def set_age(age): if not isinstance(age, int): raise TypeError(年龄必须是整数) if age 0 or age 150: raise ValueError(年龄必须在0到150之间) # ... 正常逻辑定义自定义异常继承自Exception类或其子类。class MyBusinessError(Exception): 自定义的业务逻辑异常 def __init__(self, message, error_code): super().__init__(message) self.error_code error_code def process_data(data): if not data: raise MyBusinessError(输入数据为空, 1001) # ... 处理数据 try: process_data(None) except MyBusinessError as e: print(f业务错误 {e.error_code}: {e})自定义异常可以让你的错误分类更清晰调用者可以通过捕获特定的异常类型来处理不同的错误情况。6. 模块与包管理组织你的代码宇宙当代码超过一个文件时合理的组织至关重要。模块和包是Python代码复用的基石。6.1 导入机制详解import背后发生了什么import module_name这条语句执行了以下操作在sys.path一个目录列表指定的路径中搜索名为module_name的模块.py文件或包包含__init__.py的目录。找到后执行该模块的全部顶层代码函数和类定义也是可执行代码它们定义了对象。在当前命名空间创建一个名为module_name的变量指向已加载的模块对象。几种导入方式import module导入整个模块通过module.function访问。from module import func1, func2从模块导入特定对象到当前命名空间可直接使用func1。from module import *导入模块中所有公开对象不以下划线开头。不推荐容易引起命名冲突。import module as alias导入并设置别名常用于长模块名如import numpy as np。if __name__ __main__:的作用模块既可以被导入也可以直接运行。__name__是一个内置变量当模块被直接运行时__name__的值为__main__。当模块被导入时__name__的值为模块的名字如my_module。因此if __name__ __main__:块下的代码仅在模块直接运行时执行而在被导入时不执行。这常用于为模块编写测试代码或演示用法。# my_module.py def useful_function(): print(Im useful!) if __name__ __main__: # 这部分代码只在 python my_module.py 时运行 print(模块被直接运行) useful_function()6.2 包的结构与相对导入包是一种包含多个模块的特殊目录它必须包含一个__init__.py文件可以是空文件Python 3.3中非必须但显式存在是良好实践。my_package/ ├── __init__.py ├── module_a.py ├── subpackage/ │ ├── __init__.py │ └── module_b.py └── utils.py绝对导入从包的根目录或已安装的模块开始。# 在 module_a.py 中导入 utils import my_package.utils # 或 from my_package import utils # 在 module_b.py 中导入 module_a from my_package import module_a相对导入使用点号.表示当前目录..表示父目录。只能在包内部使用且模块必须作为包的一部分被导入不能作为顶层脚本直接运行。# 在 module_b.py 中 from . import module_b_in_same_dir # 导入同目录模块 from .. import module_a # 导入父目录模块 from ..utils import helper_func # 导入父目录下utils模块的函数__init__.py的妙用它可以用来初始化包或定义__all__列表来控制from package import *的行为。# my_package/__init__.py __all__ [module_a, utils] # 指定 from my_package import * 时导入哪些模块 from .module_a import main_function # 将子模块的函数提升到包级别方便使用 # 之后可以from my_package import main_function6.3 虚拟环境与依赖管理项目的隔离与复现这是现代Python开发中至关重要的一环。虚拟环境为每个项目创建独立的Python运行环境包括独立的解释器、库路径和安装的包解决了项目间依赖冲突的问题。使用venv创建虚拟环境Python 3.3内置# 在项目目录下 python -m venv venv # 创建名为 venv 的虚拟环境目录 # 激活虚拟环境 # Windows (cmd): venv\Scripts\activate.bat # Windows (PowerShell): venv\Scripts\Activate.ps1 # macOS/Linux: source venv/bin/activate # 激活后命令行提示符通常会变化pip安装的包将只在此环境中 (venv) $ pip install requests numpy # 退出虚拟环境 deactivate依赖管理requirements.txt将项目依赖的包及其版本固定下来确保在任何地方都能复现相同的环境。# 生成当前环境的所有包列表 pip freeze requirements.txt # 内容示例 # requests2.28.1 # numpy1.23.5 # 在新环境中安装所有依赖 pip install -r requirements.txt对于更复杂的项目可以考虑使用pipenv或poetry等工具它们集成了虚拟环境管理和依赖解析功能更强大。避坑指南永远不要在系统全局的Python环境中直接pip install项目依赖。务必使用虚拟环境。requirements.txt应纳入版本控制如Git。在团队协作中确保所有成员使用相同的虚拟环境工具和依赖安装流程。如果遇到“模块找不到”的错误首先检查1是否在正确的虚拟环境中2sys.path是否包含模块所在目录3包结构是否正确有无__init__.py7. 迭代器、生成器与上下文管理器高效处理数据与资源这三个概念是Python中实现惰性计算和资源自动管理的核心工具能极大提升代码效率和优雅度。7.1 迭代器协议for循环的本质迭代器是一个可以记住遍历位置的对象。它实现了两个方法__iter__(self)返回迭代器对象自身self。__next__(self)返回容器的下一个值。如果没有更多元素则抛出StopIteration异常。for item in iterable:这个语句背后Python做了以下事情调用iter(iterable)获取一个迭代器对象。这实际上调用了可迭代对象的__iter__方法。反复调用next(iterator)将返回值赋给item直到捕获StopIteration异常。任何实现了__iter__方法的对象都是可迭代对象。列表、元组、字典、字符串、文件对象等都是可迭代对象。手动模拟for循环my_list [1, 2, 3] iterator iter(my_list) # 等同于 my_list.__iter__() try: while True: item next(iterator) # 等同于 iterator.__next__() print(item) except StopIteration: pass7.2 生成器惰性计算的利器生成器是一种特殊的迭代器它在迭代时动态生成值而不是一次性将所有值存储在内存中。这对于处理大量数据或无限序列非常高效。创建生成器有两种方式生成器函数使用yield关键字。def count_up_to(max): count 1 while count max: yield count # 每次next()执行到这里暂停返回count下次从下一行继续 count 1 counter count_up_to(3) # 调用函数返回一个生成器对象此时函数内代码并未执行 print(next(counter)) # 输出1 print(next(counter)) # 输出2 for num in counter: # 继续迭代剩下的 print(num) # 输出3 # 之后再次调用next(counter)会抛出StopIteration生成器表达式类似列表推导式但用圆括号。# 列表推导式立即计算占用内存 squares_list [x**2 for x in range(1000000)] # 生成器表达式惰性计算几乎不占内存 squares_gen (x**2 for x in range(1000000)) print(next(squares_gen)) # 输出0 print(next(squares_gen)) # 输出1 # 可以用于for循环 for sq in squares_gen: if sq 100: break print(sq)生成器的优势节省内存一次只产生一个值。表示无限序列如生成所有斐波那契数。管道化处理可以将多个生成器串联形成数据处理管道。7.3 上下文管理器与with语句优雅的资源管理上下文管理器用于定义在代码块执行前后需要自动执行的设置和清理操作最常见的用途是管理文件、网络连接、锁等资源。with语句会获取上下文管理器对象并在代码块执行前调用其__enter__方法在执行后无论是否发生异常调用其__exit__方法。基于类的实现class ManagedFile: def __init__(self, filename, mode): self.filename filename self.mode mode self.file None def __enter__(self): print(f打开文件 {self.filename}) self.file open(self.filename, self.mode) return self.file # as 子句接收到的对象 def __exit__(self, exc_type, exc_val, exc_tb): print(f关闭文件 {self.filename}) if self.file: self.file.close() # 如果返回True则with块中的异常会被抑制否则异常会继续向上传播 return False # 使用 with ManagedFile(test.txt, w) as f: f.write(Hello, context manager!) # 输出 # 打开文件 test.txt # 关闭文件 test.txt # 即使write时发生异常文件也一定会被关闭使用contextlib简化对于简单的场景可以使用contextlib.contextmanager装饰器将生成器函数变为上下文管理器。from contextlib import contextmanager contextmanager def managed_file(filename, mode): print(f打开文件 {filename}) f open(filename, mode) try: yield f # yield之前是__enter__之后是__exit__ finally: print(f关闭文件 {filename}) f.close() with managed_file(test.txt, w) as f: f.write(Hello, simplified context manager!)with语句确保了资源被正确释放避免了因忘记关闭或异常发生而导致的资源泄漏这是编写健壮代码的必备习惯。
返回列表