Python 知识1

发布时间:2026/7/7 1:30:38
Python 知识1 在 Python 的学习旅程中有些坑几乎每个人都会踩有些特性看似简单却暗藏玄机。这篇笔记整理了函数默认参数、装饰器、字典哈希、并发模型等核心知识点带你从现象到本质彻底搞懂这些容易混淆的概念。一、函数默认参数的可变对象陷阱1.1 问题现象写过 Python 的人大概率都遇到过这个问题defappend_item(x,cache[]):cache.append(x)returncache append_item(1)# 返回 [1]没问题append_item(2)# 返回 [1, 2]而不是预期的 [2]第二次调用时列表竟然记住了上一次的结果。这不是魔法而是 Python 的设计特性。1.2 根本原因函数的默认参数只在函数定义时初始化一次而不是每次调用时重新创建。cache[]这个列表对象在函数定义的那一刻就被创建了之后所有不传参的调用都共享同一个列表实例。这就像你在班级里放了一个公共邮箱每个人都往里面塞东西自然会越积越多。1.3 正确的解决方案最标准的修复方式是将默认参数设为None在函数内部判断并初始化defappend_item(x,cacheNone):ifcacheisNone:cache[]cache.append(x)returncache这样每次不传参调用都会获得一个干净的新列表问题迎刃而解。1.4 常见错误选项分析选项 B使用copy.deepcopy这是治标不治本的做法。深拷贝只是复制了内容但函数每次还是从同一个默认参数开始而且性能差、逻辑混乱。选项 C改用元组()虽然元组不可变list(cache)每次都会新建列表但语义上很奇怪——默认参数传入的是空元组而函数期望的是可累积缓存逻辑不够清晰。选项 D在函数末尾调用cache.clear()这会清空缓存导致函数返回空列表完全破坏功能。如果函数外持有该缓存对象的引用清空还会引发副作用。经验之谈Python 中切忌使用可变对象如列表、字典作为函数默认参数。这是一个非常经典的面试题理解它你的代码就更健壮了。二、装饰器给函数无痛加功能2.1 装饰器的本质装饰器是 Python 中最优雅的语法糖之一它的核心作用是在不修改原函数代码的前提下给函数动态添加功能。importtimedeftimer(func):definner(*args,**kwargs):starttime.time()resultfunc(*args,**kwargs)print(f耗时:{time.time()-start:.6f}s)returnresultreturninnertimerdefhello():print(Hi!)hello()# 输出Hi! 耗时: 0.000123s2.2timer到底是什么timer是语法糖等价于hellotimer(hello)也就是说timer把hello这个函数对象传给timer再用返回的新函数覆盖原来的hello。以后调用hello()实际上是在调用inner()。2.3 为什么要用*args, **kwargs*args和**kwargs是装饰器的通用接口它们能接收任意数量的位置参数和关键字参数让同一个装饰器可以装饰任何函数。当原函数无参时args ()kwargs {}等价于func()当原函数有参时它们会自动展开传递。2.4return result为什么必不可少装饰器的责任是透明包装——不能改变原函数的行为包括返回值。如果省略return result原函数的返回值会丢失。timerdefadd(a,b):returnab xadd(3,5)# 如果装饰器不returnx会变成None个人见解装饰器就像给手机贴膜——膜不影响屏幕显示内容只是增加保护。好的装饰器应该是无感增强不该让调用者察觉变化。2.5functools.wraps的作用一个容易被忽略的细节装饰器会覆盖原函数的__name__等元信息。timerdefhello():passhello.__name__# 返回 inner而不是 hello为了解决这个问题需要使用functools.wrapsfromfunctoolsimportwrapsdeftimer(func):wraps(func)definner(*args,**kwargs):# ...returninner2.6 装饰器的应用场景装饰器的用途非常广泛常见的有日志记录记录函数调用信息性能监控统计函数执行时间权限校验检查用户是否有权限访问缓存缓存函数返回值避免重复计算重试机制网络请求失败时自动重试三、字典与哈希表3.1 字典的核心规则Python 字典中键key必须是不可变的值value可以是任意类型。d{name:Alice,# str ✅1:one,# int ✅(1,2):tuple_key,# tuple ✅# [1, 2]: list_key # list ❌ TypeError}3.2 为什么 key 必须不可变字典底层是哈希表hash table。Python 需要根据key计算出一个 hash 值来决定键值对存储的位置。如果key在放入字典后内容变了它的 hash 值也会变Python 就再也找不到它了。这就像图书馆的书如果书的编号变了你就无法通过索引找到它。3.3 一个常被误解的点key 不可变 ≠ key 不能被替换。你可以换 key但不能改 key 的内容。d{a:1}d[b]d.pop(a)# ✅ 这是换 key# d[a][0] c # ❌ 这是不行的字符串不可变3.4 tuple 作为 key 的注意事项虽然 tuple 是不可变的但如果 tuple 里包含可变元素也不能当 keyd{(1,2,[3]):oops# ❌ TypeError}因为 tuple 本身不可变但它的元素是可变的导致 hash 值不稳定。四、魔术方法让对象支持自定义行为4.1__getitem__让对象支持索引通过实现__getitem__方法可以让自定义类的实例像列表一样使用索引操作。classFoo:def__getitem__(self,pos):returnrange(0,30,10)[pos]fooFoo()foo[0]# 返回 0foo[1]# 返回 10foo[2]# 返回 20当你写foo[1]时Python 会自动调用Foo.__getitem__(foo, 1)pos参数就是方括号里的数字。4.2__init__vs__new__对象创建的两个阶段这是面试中非常经典的考点很多人分不清这两个方法的区别。方法职责参数返回值__new__创建并返回实例cls类新创建的实例__init__初始化已创建的实例self实例None关键点__new__是类方法隐式负责分配内存、创建对象__init__是实例方法负责对已创建的对象进行初始化如果__new__没有返回当前类的实例__init__不会被调用在不可变类型如 int、str、tuple的子类中必须在__new__中设置值。classMyInt(int):def__new__(cls,value):returnsuper().__new__(cls,value*2)xMyInt(5)# 输出 104.3pass语句的作用pass是 Python 的占位符语句表示什么都不做。Python 不允许函数体或类体为空所以必须放一个语句进去。def__init__(self):pass# 初始化方法什么都不做五、并发模型与 GIL5.1 GIL 的本质GILGlobal Interpreter Lock全局解释器锁是 CPython 解释器的一个互斥锁它确保同一时刻只有一个线程在执行 Python 字节码。这意味着即使你启用了多个线程在 CPython 中它们也不能真正并行执行 CPU 计算任务——必须轮流获取 GIL 才能运行。5.2 GIL 对不同任务的影响CPU 密集型任务如数值计算、循环、图像处理GIL 会成为瓶颈多线程无法利用多核优势性能提升不明显甚至更慢。I/O 密集型任务如文件读写、网络请求I/O 操作在等待时会主动释放 GIL其他线程可以抢占执行因此 GIL 的影响很小。5.3 正确的并发策略选择场景推荐方案CPU 密集型multiprocessing多进程绕开 GILI/O 密集型threading或asyncio极致性能Cython、Numba、或改用无 GIL 语言multiprocessing创建的是独立的子进程每个进程都有自己独立的 Python 解释器实例和 GIL因此可以在不同 CPU 核心上真正并行执行。面试口诀“算得快用多进程等得久用协程或线程。”六、日志系统从 print 到 logging6.1 为什么要用 loggingprint()是玩具logging是工业级工具。特性printlogging级别区分❌✅ DEBUG / INFO / WARNING / ERROR / CRITICAL输出目标仅屏幕控制台、文件、网络上线处理需要删除可统一关闭或降级附加信息无时间、模块名、行号6.2 logging 的核心概念Logger日志记录器入口Handler输出目的地控制台 / 文件Formatter日志格式Filter过滤日志Level日志级别DEBUG INFO WARNING ERROR CRITICAL6.3 常用配置方式最简单用法importlogging logging.basicConfig(levellogging.INFO,format%(asctime)s - %(levelname)s - %(message)s)logging.info(程序启动)logging.error(发生错误)同时输出到屏幕和文件loggerlogging.getLogger(my_app)logger.setLevel(logging.DEBUG)chlogging.StreamHandler()# 控制台fhlogging.FileHandler(app.log)# 文件formatterlogging.Formatter(%(asctime)s - %(name)s - %(levelname)s - %(message)s)ch.setFormatter(formatter)fh.setFormatter(formatter)logger.addHandler(ch)logger.addHandler(fh)6.4 官方推荐写法importlogging loggerlogging.getLogger(__name__)使用__name__作为 logger 名称可以自动带上模块名方便大型项目分层管理。6.5 记录异常try:1/0exceptException:logger.exception(发生异常)# 自动记录完整 traceback七、生成器内存的救星7.1 生成器的核心价值生成器是一种特殊的迭代器它通过yield关键字实现暂停并返回值下次调用时从暂停处继续执行。最大优势节省内存。处理百万级、亿级数据时生成器让你边读边处理而不是先全读进来再处理。7.2 生成器的两种创建方式方式一生成器函数使用yielddefcountdown(n):whilen0:yieldn n-1foriincountdown(5):print(i)# 输出 5, 4, 3, 2, 1方式二生成器表达式使用圆括号squares(x**2forxinrange(1000000))# 几乎不占内存7.3 常见误区澄清误区一生成器对象不占用内存错误。生成器对象本身会占用少量内存来保存当前执行状态、局部变量、代码指针等但远小于列表。误区二生成器只能返回数值类型错误。生成器可以yield任何类型的数据字符串、列表、字典、对象、None甚至另一个生成器。误区三生成器中不能使用循环错误。生成器中完全可以使用for循环、while循环很多生成器都依赖循环来产生一系列值。7.4 内存对比方法是否一次性加载内存占用适用场景列表推导✅高小数据量生成器表达式❌极低大数据量、流式处理tuple()✅中高需要不可变序列八、模式匹配Python 3.10 的新特性8.1 match-case 是什么match是 Python 3.10 引入的结构化模式匹配语法不是函数而是关键字类似if/for/while。match(100,200):case(x,y)ifxy:print(first)case(x,y)ifxy:print(second)case(x,y):print(third)8.2 执行流程match后面的值会依次和每个case的模式进行匹配匹配成功后如果有守卫条件if需要额外检查条件第一个匹配成功的case会被执行后续的case不再检查。8.3 核心能力解包匹配matchpoint:case(x,y):print(f坐标:{x},{y})守卫条件case(x,y)ifxy:# 模式匹配成功后额外检查条件通配符_case_:# 匹配任何值类似 else8.4 与 if-elif-else 的对比match-case比if-elif-else强在能匹配结构不只是值自动解包可读性更好尤其处理复杂数据结构。九、垃圾回收机制9.1 双轨制回收策略Python 采用引用计数 分代循环垃圾收集器的双轨制策略。引用计数对象被引用一次计数 1引用失效计数 -1。计数为 0 时立即回收。优点是实时性强、速度快但无法处理循环引用。分代 GC专门处理循环引用的对象。把对象分为 0 代新生代、1 代中年代、2 代老年代定期扫描使用标记-清除算法找出不可达的循环引用对象并回收。9.2 常见误区误区一Python 仅使用引用计数错误。Python 主要依赖引用计数但还有 GC 处理循环引用。误区二循环引用的对象无法被回收错误。GC 就是专门处理这种情况的。误区三del语句会立即回收对象错误。del只是删除变量名引用让对象的引用计数减 1。只有当引用计数降为 0 时对象才会立即被回收。9.3 手动干预 GCimportgcprint(gc.get_count())# 查看当前各代对象数量gc.collect()# 手动触发一次全量垃圾回收print(gc.get_threshold())# 查看 GC 阈值十、模块导入与查找顺序10.1 导入查找顺序Python 在使用import导入模块时查找顺序如下内建模块Python 解释器首先查找内建模块如sys、math当前路径即执行脚本所在的目录环境变量 PYTHONPATH在PYTHONPATH指定的目录中查找Python 安装路径最后查找 Python 安装目录下的标准库和第三方库。结语Python 是一门看似简单但实则博大精深的语言。这些知识点都是从实践中提炼出来的精华每一个都对应着实际开发中可能遇到的问题。理解这些概念不仅能让你写出更健壮的代码也是面试中展示技术深度的重要素材。学习 Python 的过程就像剥洋葱——一层一层深入每一层都有惊喜。保持好奇心多写代码多踩坑你会越来越接近这门语言的本质。