资讯动态

Python面试27题精讲:单例、lambda、property与内存管理

发布时间:2026/10/21 23:46:15 来源:尧图企业网站定制
简介Python经典面试题集锦是一份面向Python初学者、求职者及备考人员的练习题与考试重点整理文档聚焦Web开发、数据分析、人工智能等方向的基础技能考察。资源仅含1个PDF文件大小约390KB共18页内容紧凑实用。目前已有292人学习下载。文档覆盖面向对象、函数、类型转换、内存管理等核心维度重点讲解单例模式的类方法与装饰器两种实现辨析lambda函数、property特性、staticmethod与classmethod的用法并给出反序迭代、tuple与list互转、去掉list重复元素的简洁写法。除基础考点外还扩展了文件操作、随机数生成、发送邮件、对象拷贝、查找bug与静态代码分析、设置全局变量、正则贪婪与懒惰匹配区别等实战问题既有概念讲解也有代码示意能帮助读者快速回顾核心语法提升面试与考试中的应答效率。1. 这份Python面试题PDF从单例模式到range()的27个高频题准备Python面试或者期末复习的时候我拿到过不少资料大部分是厚厚一摞概念堆砌。这份18页的Python经典面试题集锦反而更实用——27道题从单例模式、lambda、property、staticmethod、classmethod一路覆盖到内存管理、列表去重、正则匹配、异常捕获几乎全是笔试和一面环节的高频点。它适合两类人一是准备Python岗位面试、想快速过一遍基础盘的求职者二是学完基础语法但不知道怎么验证掌握程度的人。本文会照着原题的顺序拆开讲指出哪些写法现在还在用、哪些已经过时把容易翻车的地方一并标出来。2. 单例模式与面向对象三件套元类、classmethod、property怎么配合用2.1 单例模式元类、装饰器与__new__三种写法单例模式在面试里出现的频率非常高答案是「确保一个类只有一个实例」但面试官真正想听的是你能写出几种实现。原题给了类方法和装饰器两种我建议至少掌握三种元类、装饰器、__new__。先说元类实现这是Python 3里最正统的写法# 用元类实现单例控制类的创建过程 class SingletonMeta(type): def __init__(cls, name, bases, attrs): super().__init__(name, bases, attrs) cls._instance None # 每个类单独维护实例 def __call__(cls, *args, **kwargs): if cls._instance is None: cls._instance super().__call__(*args, **kwargs) return cls._instance class AppConfig(metaclassSingletonMeta): pass a AppConfig() b AppConfig() print(a is b) # True逻辑说明类被定义时元类的__init__先执行给cls挂上一个_instance属性调用AppConfig()时真正走的是元类的__call__第一次创建实例并缓存后续直接返回同一个对象。参数说明metaclassSingletonMeta是Python 3的写法如果你在代码里看到__metaclass__ Singleton那是在Python 2时代生效的老语法3.x里这个属性不生效这点后面避坑章还会细说。装饰器写法理解起来更直观# 装饰器实现用闭包字典缓存实例 def singleton(cls): _instances {} def get_instance(*args, **kwargs): if cls not in _instances: _instances[cls] cls(*args, **kwargs) # 第一次才真正创建 return _instances[cls] return get_instance singleton class Logger: def __init__(self): self.level info逻辑说明_instances是闭包里的字典键是类对象值是实例。singleton装饰后Logger这个名字实际指向的是get_instance函数所以每次Logger()调用都走缓存判断。这里有一个值得注意的细节它把实例缓存在类的字典里但类本身没有变化多个装饰器叠加时行为可能不一致实际项目中我更常用元类方案。第三种用__new__拦截class Singleton: _instance None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance super().__new__(cls) return cls._instance参数说明__new__是真正分配内存创建实例的入口它先于__init__执行。这种写法简洁但坑在于_instance挂在基类上子类继承时共享同一个实例如果你需要每个子类独立单例就得按cls存字典跟装饰器方案类似。面试时能把三种写法的差异讲清楚这一题基本就过了。2.2 property、staticmethod、classmethod的分工边界原题用Circle类讲property核心是property把一个方法变成只读属性调用时触发函数执行外部却感知不到区别import math class Circle: def __init__(self, radius): self.radius radius property def area(self): return math.pi * self.radius ** 2 # 动态计算面积 property def perimeter(self): return 2 * math.pi * self.radius # 动态计算周长 c Circle(10) print(c.radius) # 10 print(c.area) # 314.1592653589793 print(c.perimeter) # 62.83185307179586逻辑说明area和perimeter看起来像普通属性但每次访问都会重新计算。这样做的好处有两个第一调用方不需要知道数据是存好的还是算出来的统一访问原则第二后续radius的存储方式变了只要area接口不变调用方代码不用动。如果后面需要允许外部修改半径但不允许直接改面积可以加setter去校验property正是为了这种受控访问设计的。staticmethod和classmethod的区别原题用Date类的now()和tomorrow()解释得很清楚。staticmethod是放在类命名空间里的普通函数不自动传实例或类classmethod第一个参数接收的是类本身子类调用时会自动传入子类。我用一个精简的对比class Date: def __init__(self, year, month, day): self.year year self.month month self.day day staticmethod def now(): import time t time.localtime() return Date(t.tm_year, t.tm_mon, t.tm_mday) # 注意这里硬编码了 Date子类调用会得到 Date 而不是子类 classmethod def tomorrow(cls): import time t time.localtime(time.time() 86400) return cls(t.tm_year, t.tm_mon, t.tm_mday) # 用 cls哪个类调用就返回哪个类的实例逻辑说明对比两处returnstaticmethod里写死Date而classmethod用cls。如果定义了一个EuroDate(Date)子类调用EuroDate.tomorrow()会正确返回EuroDate实例调用EuroDate.now()却拿到一个Date这就是两者的核心边界。实际项目中如果需要「子类感知」的工厂方法一律用classmethod。3. 函数与类型系统lambda、类型转换、内存管理的边界在哪3.1 lambda的适用边界单表达式函数lambda在面试题里出现得很多但实际代码里经常被滥用。先看原题的示例g lambda x, y: x * y print(g(3, 4)) # 12 g2 lambda x, y0, z0: x y z print(g2(1)) # 1 print(g2(3, 4, 7)) # 14 # 不赋值直接调用 print((lambda x, y0, z0: x y z)(3, 5, 6)) # 14参数说明lambda后跟参数列表冒号后是表达式表达式的结果就是返回值。它支持默认参数但函数体只能有一个表达式不能包含赋值语句、循环或if/else块。常见的错误认知是「lambda比def快」——不是lambda只是省了函数名和return关键字执行效率跟def函数没有实质差异。什么时候该用lambda常见场景是给sorted、filter、map传一个简单的排序键或映射规则。反过来说只要逻辑超过一个表达式就应该用def定义普通函数否则可读性会急剧下降。3.2 类型转换int()、float()、str()的三个坑原题列出了int、float、str的基本用法我补充几个实际编码里更容易踩的边界。int()能把符合数字格式的字符串转成整数但遇到1234ab会直接抛ValueError这个报错不是运行时bug而是类型转换的正常行为——正确的做法是在转换前先校验或捕获异常。int把浮点数转成整数时小数部分被直接截断而不是四舍五入。int(34.1234)得到34int(-2.46)得到-2。如果需要四舍五入应该用round()而不是int()。float转字符串、字符串转float的规则这里不展开关键是记住一点整数1和浮点数1.0在Python里是不同的对象1 1.0为True但type(1) ! type(1.0)类型不同存储结构也不同涉及精确计算时尤其要注意。3.3 内存管理与id()/type()空口说不如跑一段「Python是如何进行内存管理的」这题原题给的答案是「由解释器负责」。面试时这么答只能拿一半分需要补充引用计数和垃圾回收机制每个对象都维护一个引用计数计数归零时立即回收内存循环引用由垃圾回收器定期扫描处理。实际工作中你不需要手动释放内存但要注意可能长期占用资源的对象比如大列表、打开的连接用完后手动解引用或使用上下文管理器。查看变量内存地址和类型用id()和type()。写一段验证代码a [1, 2, 3] b a # b 引用同一个对象 print(id(a)) # 例如 140718605465864 print(id(b)) # 与 id(a) 完全相同 print(type(a)) # class list c [1, 2, 3] print(id(c)) # 与 id(a) 不同虽然是相等内容的两个对象逻辑说明id()返回对象的内存地址十进制表示两个引用指向同一对象时地址相同。type()返回类型对象。这个知识点配合拷贝那一题很好用——浅拷贝和深拷贝的区别本质就是「新对象还是新引用」的问题。4. 序列操作实战反序迭代、去重、tuple/list转换别再凭记忆写4.1 反序迭代list.reverse()、切片与range倒序原题给了两个反序方案。如果序列是list最快的办法是list.reverse()原地反转迭代完再反转回去恢复原序items [1, 2, 3, 4, 5] # 方式1原地反转后迭代再用 try/finally 保证恢复 try: items.reverse() for x in items: print(x, end ) finally: items.reverse() print() # 输出 5 4 3 2 1原列表已恢复逻辑说明reverse()是原地操作不生成新列表速度快。但因为它修改了原列表迭代前反转、迭完还要反转回来容易漏掉finally导致原列表顺序被打乱。如果不是list比如元组或range对象原题的做法是range(len(sequence)-1, -1, -1)倒序取索引。不过在Python 3里最通用的反而是reversed()内置函数它不修改原序列对list、tuple、range都适用还支持迭代器for x in reversed(range(10)): # 倒序输出 9 到 0 print(x, end ) # 对字符串也有效 for ch in reversed(abc): print(ch, end ) # c b a补充一个常见误区list也可以用items[::-1]切片生成反转副本代码简洁但会复制整个列表大列表时内存开销高。面试时建议先说reversed()的通用性再提reverse()的原地特性最后提切片写法及代价。4.2 删除list重复元素几种写法的取舍原题的方案是排序后从尾部扫描删除这是适合「不引入额外数据结构」的解法时间复杂度取决于排序。但实际工作里最常见的写法是set()——需要保持原顺序就用dict.fromkeys()data [1, 2, 1, 2, 1, 13, 3, 3, 4, 4, 2, 6, 7, 8, 8, 9] # 写法1不保持顺序最快 unique_set list(set(data)) print(unique_set) # [1, 2, 3, 4, 6, 7, 8, 9, 13] 顺序不固定 # 写法2保持原顺序 unique_ordered list(dict.fromkeys(data)) print(unique_ordered) # [1, 2, 13, 3, 4, 6, 7, 8, 9]逻辑说明set()去重后顺序不可预期如果输出顺序有要求就别用。dict.fromkeys(data)利用字典键唯一的特性键第一次出现的位置就是它在字典里的顺序所以能保持原顺序。Python 3.7以后dict保证插入顺序这段代码在3.6之前的行为不保证但现在的环境基本是3.8可以放心用。如果列表里的元素是字典或对象set和fromkeys会要求元素可哈希这种情况就得换思路比如用列表推导加一次遍历def dedupe(items): seen set() result [] for item in items: if item not in seen: seen.add(item) result.append(item) return result这个是通用的去重模板面试时能写出这个说明你理解了set的查找特性。4.3 tuple和list互转、range用法与文件操作的零碎知识点tuple和list转换这题很简单但经常有人记混。tuple([1,2,3])返回(1,2,3)list((1,2,3))返回[1,2,3]。注意如果参数已经是tupletuple()不会创建拷贝而是直接返回原对象参数是list时list()会做一份拷贝。range函数也是高频考点。range(5)产生0到4range(5,10)产生5到9range(0,10,3)产生0、3、6、9步长也可以是负数range(-10,-100,-30)产生-10、-40、-70。配合len()迭代索引序列是老式写法a [a, b, c] for i in range(len(a)): print(i, a[i])现代Python里直接for i, item in enumerate(a)更清晰但面试时能说出range的完整参数含义也算基本功。文件操作那两题简单记一下删除文件用os.remove(path)或os.unlink(path)两者等价拷贝文件用shutil.copyfile(src, dst)它会打开源文件并写入目标文件路径不存在时会抛FileNotFoundError。随机数用random.random()返回[0,1)浮点数random.randint(a,b)返回[a,b]整数。这些题没有什么玄学就是记API但要注意shutil.copy和shutil.copyfile的区别copy会同时拷贝权限位copyfile只拷文件内容。5. 踩坑排查正则匹配、异常捕获与老版Python语法5.1 正则的两个高频坑贪心匹配与match()的起始位置坑1现象用.*匹配HTML标签re.match(.*, s).group()返回的是htmlheadtitleTitle/title而不是预想中的html。原因*是贪心量词正则表达式会尽可能多地匹配字符直到最后一个才停下。因为html后面还有很多所以一口气吞掉了整个字符串。解决改用非贪心量词.*?它匹配到第一个就停止返回html。import re s htmlheadtitleTitle/title print(re.match(.*, s).group()) # htmlheadtitleTitle/title print(re.match(.*?, s).group()) # html坑2现象用re.match(super, insuperable)得到None但明明目标字符串里包含super。原因match()只从字符串起始位置匹配insuperable以in开头不满足super从0位置匹配的要求。解决需要扫描整个字符串时用re.search()它会返回第一个成功的匹配位置re.match()只有在该模式恰好从开头匹配时才有返回。面试时这道题常被拿来考正则API的边界记住一句话就够了match查开头search查全文。5.2 异常捕获裸except让你找不到出错位置现象try: foo opne(file)里opne拼错了但因为后面跟着裸except: sys.exit(could not open file!)程序直接退出输出只有一句「could not open file!」你完全不知道是文件不存在、权限不足还是拼写错误。原因裸except捕获所有异常类型包括NameError、KeyboardInterrupt、SystemExit。它会把真正的问题吞掉同时把「伪信息」抛给用户。解决捕获具体异常类型并保留错误信息。import sys try: foo open(file) # 故意写错成 opne 会触发 NameError except IOError as e: print(FBI WARNING: %s % e) except Exception as e: print(unexpected error: %s % e)参数说明except IOError as e只捕获文件相关错误e是异常对象打印它就能看到是No such file还是其他系统错误。原题的写法在Python 3里还有个额外问题——sys.exit(could not open file!)会在打印信息后抛SystemExit可能被外层更大的异常处理器二次捕获把退出码搞乱。建议用raise或print后再sys.exit(1)。5.3 老版Python语法混用metaclass、print语句和raw_input现象照着原题敲代码class MyClass(object): __metaclass__ Singleton没有任何反应print (MyClass())看起来能跑但风格怪异某些代码里还有raw_input(From: )直接在Python 3报NameError。原因这份题目整理自Python 2时代__metaclass__属性在Python 3里不生效必须用class MyClass(metaclassSingleton)print在Python 2是语句、Python 3是函数raw_input在Python 3被重命名为input。解决把__metaclass__改成metaclass参数。原题元类实现里还用了super(Singleton, cls).__init__(...)这种老式写法Python 3里用super().__init__(...)更简洁。raw_input换成input()print统一加括号。如果手上有一批Python 2老代码要迁移可以用2to3工具做初步转换但__metaclass__这种类定义级的差异要手动改。5.4 单例装饰器的一个隐蔽坑现象用原题的装饰器方案时singleton修饰完的类其实已经变成了函数isinstance(obj, MyClass)返回False装饰后的类名也被替换成了getinstance。原因装饰器返回的是getinstance函数原来的MyClass被覆盖了类的身份信息丢失。解决用functools.wraps保留元数据或者干脆换用元类方案。这段补充很重要因为面试官很可能追问「你写的单例还能不能用isinstance」。from functools import wraps def singleton(cls): _instances {} wraps(cls) def get_instance(*args, **kwargs): if cls not in _instances: _instances[cls] cls(*args, **kwargs) return _instances[cls] return get_instancewraps(cls)能把__name__、__doc__等属性从原类复制到包装函数上虽然救不了isinstance至少类名和文档串保持正确。6. 把27题变成自测脚本两小时过一遍基础盘的玩法这套题最好的用法不是读完就扔而是当成自测清单去跑。我习惯的做法是建一个quiz.py把每个知识点压缩成一个断言能跑通说明这个点过关跑不通就先翻原题再回头看报错。这样比一遍遍背题有效得多。import math import re def test_lambda(): g lambda x, y0, z0: x y z assert g(1) 1 assert g(3, 4, 7) 14 def test_singleton(): class SingletonMeta(type): def __init__(cls, name, bases, attrs): super().__init__(name, bases, attrs) cls._instance None def __call__(cls, *args, **kwargs): if cls._instance is None: cls._instance super().__call__(*args, **kwargs) return cls._instance class AppConfig(metaclassSingletonMeta): pass assert AppConfig() is AppConfig() def test_property(): class Circle: def __init__(self, radius): self.radius radius property def area(self): return math.pi * self.radius ** 2 assert abs(Circle(10).area - 314.159) 0.01 def test_reverse_and_dedupe(): data [1, 2, 1, 3, 3, 4] assert list(dict.fromkeys(data)) [1, 2, 3, 4] assert list(reversed([1, 2, 3])) [3, 2, 1] def test_regex(): s htmlheadtitleTitle/title assert re.match(.*?, s).group() html assert re.search(super, insuperable).span() (2, 7) if __name__ __main__: for name, fn in sorted(globals().items()): if name.startswith(test_): fn() print(f{name} passed)跑这个脚本五个函数全打印passed说明单例、property、lambda、反序、去重、正则这几块的基本功是稳的。每道题都对应原题里的讲解出错就翻回对应章节对比比单纯刷题效率高得多。我自己用这套方法突击过几轮基础面试最大的教训是别背题——题目稍微变形就露馅。从那以后我每次复习都强制自己把答案写成可执行的断言能跑才算真会。这份PDF里27道题覆盖的范围刚刚好下载下来存一份面试前过一遍比临时翻书踏实。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑