page contents

Python面试官最爱问的6个坑,踩中3个以上基本凉凉

上周,一个学弟跟我吐槽:他面了三家公司的 Python 开发岗,每家面试官都问了那种"看起来很简单但一答就踩坑"的问题。

attachments-2026-06-M2EiaQ4O6a3b358ea66fc.png上周,一个学弟跟我吐槽:他面了三家公司的 Python 开发岗,每家面试官都问了那种"看起来很简单但一答就踩坑"的问题。

他说自己写代码的时候明明都能跑,但面试官一追问细节,他就懵了。比如"可变对象做默认参数会怎样?"他随口说了"没啥问题吧",面试官笑了笑,就没往下问了。

我听完就知道,他踩的是 Python 面试里最经典的那几个坑。这些坑不是什么高深的东西,但面试官偏偏爱问,因为它们能一眼看出你对 Python 的理解深度——是只会写能跑的代码,还是真的知道底层机制。

今天我把 Python 面试中最常被问的 6 个坑整理出来,每个都配代码和解释。花 15 分钟看完,下次面试你就不会在这些地方翻车了。

一、可变对象做默认参数——面试官最爱开场白

这个坑出现的频率高到离谱。面试官上来可能不问你算法,不问你框架,先甩一句:"Python 里用 list 当默认参数会有什么问题?"

如果你觉得没问题,那面试基本到此为止了。

看这段代码:

def add_item(item, result=[]):
    result.append(item)
    return result

print(add_item("a"))
print(add_item("b"))
print(add_item("c"))

你可能以为三次调用分别返回 ["a"]、["b"]、["c"],但实际输出是:

['a']
['a', 'b']
['a', 'b', 'c']

为啥?因为 Python 函数的默认参数是在函数定义时求值的,不是每次调用时重新创建。result=[] 这一行,只在 def 执行的时候创建了一个空列表,之后每次调用都共用同一个列表对象。

正确写法是把默认值设为 None,然后在函数内部创建新列表:

def add_item(item, result=None):
    if result is None:
        result = []
    result.append(item)
    return result

记住这条铁律:永远不要用 list、dict、set 等可变对象做函数默认参数。面试官听到你主动说出这句,基本就给你这题满分了。

二、is 和 == 的区别——答错了直接暴露基础薄弱

面试官问"is 和 == 有什么区别",看起来简单,但很多人答得模棱两可。面试官不是想听你背概念,是想看你能不能举出实际例子。

核心区别一句话:== 比较值,is 比身份(也就是内存地址)。

a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)  # True,值相等
print(a is b)  # False,不是同一个对象

c = a
print(a is c)  # True,指向同一个对象

面试官最爱追问的是小整数池的问题:

x = 1000
y = 1000
print(x is y)  # False

x = 100
y = 100
print(x is y)  # True

为什么 100 返回 True,1000 返回 False?因为 Python 为了节省内存,预先缓存了 -5 到 256 之间的整数对象。所以 x=100 和 y=100 指向的是同一个缓存对象,is 比较就返回 True。但 1000 超出了缓存范围,每次赋值都创建新对象。

面试加分点:主动提到比较 None 应该用 is 而不是 ==。因为 None 是单例对象,用 is 比较更快更规范。如果你说"判断空值用 if x is None",面试官会觉得你真的懂 Python 的风格。

三、列表拷贝的陷阱——浅拷贝和深拷贝你真搞懂了吗?

面试官给你看这段代码,问你输出是什么:

original = [[1, 2], [3, 4]]
copied = original[:]

copied[0][0] = 99
print(original)

很多人以为 original[:] 就是"完整复制一份,跟原列表没关系了",所以觉得 original 不会变。但实际输出:

[[99, 2], [3, 4]]

original 也变了!原因很简单:切片拷贝是浅拷贝,它只复制了外层列表的引用,内层的 [1, 2] 和 [3, 4] 还是跟原列表共享同一个对象。

打个比方:浅拷贝就像复印了一份文件目录,但文件本身还是那些原件。改了文件内容,两个目录看到的是同一份变更。

要真正独立复制,必须用深拷贝:

import copy

original = [[1, 2], [3, 4]]
copied = copy.deepcopy(original)

copied[0][0] = 99
print(original)  # [[1, 2], [3, 4]],不变了

面试加分点:主动总结三种拷贝方式的区别——赋值是共享引用,切片/list()是浅拷贝只复制一层,deepcopy 是深拷贝完全独立。这个总结一出,面试官就知道你不是背答案,是真理解了。

四、for 循环里的变量泄露——Python 独有的"遗留问题"

在大多数语言里,for 循环的迭代变量是循环内部的局部变量,循环结束就消失。但在 Python 2 里,for 循环结束后变量会"泄露"到外层作用域。Python 3 做了一个改动:列表推导式的变量不再泄露了,但 for 循环本身的迭代变量还是会泄露。

面试官可能给你看这个:

x = 0
for x in range(5):
    pass

print(x)  # 输出什么?

答案是 4。循环结束后 x 保留了最后一次迭代的值。这看起来没什么危害,但如果 x 在循环前有其他用途,就会被覆盖:

x = "重要数据"
for x in [1, 2, 3]:
    print(x)

print(x)  # 3,原始数据丢了

面试官还可能追问列表推导式和 for 循环的区别:

# Python 3 中,列表推导式变量不泄露
y = 10
result = [y for y in range(3)]
print(y)  # 还是 10,推导式里的 y 不影响外面的

# 但 for 循环会泄露
y = 10
for y in range(3):
    pass
print(y)  # 变成了 2

记住:Python 3 中列表推导式的变量是局部的,不会泄露;但 for 循环的迭代变量会泄露到外层作用域。养成好习惯:不要在 for 循环里用跟外层同名的变量。

五、字符串拼接的效率坑——+ 号和 join 的差距有多大?

面试官问你"Python 里拼接大量字符串用什么方法最快",如果你说"用 + 号呗",那这个坑你就踩了。

原因是:Python 里字符串是不可变对象。每次用 + 拼接,都会创建一个新字符串对象,把旧的内容复制过来再追加新内容。拼接 1000 次就要创建 1000 个中间对象,内存和时间的开销都很大。

看这个对比:

import time

# 方法1:用 + 号拼接
start = time.time()
s = ""
for i in range(100000):
    s += str(i)
print("+ 号耗时:", time.time() - start)

# 方法2:用 join 拼接
start = time.time()
parts = []
for i in range(100000):
    parts.append(str(i))
s = "".join(parts)
print("join 耗时:", time.time() - start)

实测数据:+ 号拼接 10 万次大约需要 15-20 秒,join 方法只需要 0.02 秒左右。差距是几百倍。

为什么 join 快这么多?因为 join 一次性计算好总长度,分配一块足够大的内存,然后一次写入完成。不用反复创建中间对象。

面试加分点:你可以主动提到f-string 是 3.6+ 版本的最佳选择,适合拼接少量字符串的场景,可读性比 + 号和 format() 都好。但如果要拼接大量字符串,join 依然是最优方案。

# 少量拼接,f-string 最可读
name = "小白"
msg = f"你好, {name}!"

# 大量拼接,join 最快
lines = ["第一行", "第二行", "第三行"]
text = "\n".join(lines)

六、try-except 里的 return——finally 总会执行,但顺序有讲究

这是最容易被忽略的一个坑。面试官问你:"try 里有 return,finally 里也有 return,最后执行哪个?"

很多人知道 finally 总会执行,但不知道finally 的 return 会覆盖 try 里的 return。

def test():
    try:
        return "try 的返回值"
    finally:
        return "finally 的返回值"

print(test())

输出是 "finally 的返回值"。因为 finally 块是无论如何都会执行的,它在 try 的 return 之后执行,而 finally 自己的 return 又把 try 的返回值给覆盖了。

更隐蔽的情况:finally 里没有 return,但有修改操作:

def test():
    result = "原始值"
    try:
        return result
    finally:
        result = "被修改了"

print(test())

输出是 "原始值"。这里 finally 修改了 result 变量,但 try 的 return 已经把 "原始值" 的引用返回出去了。finally 的赋值只是让 result 指向了新字符串,不影响已经返回的旧引用。

但如果返回的是可变对象呢?

def test():
    result = [1, 2, 3]
    try:
        return result
    finally:
        result[0] = 99

print(test())

输出是 [99, 2, 3]。因为返回的是列表引用,finally 修改了列表内容,外面的引用也能看到这个修改。

总结:finally 的 return 会覆盖 try/except 的 return;finally 里修改不可变对象不影响已返回的值,但修改可变对象会影响。实际开发中,千万不要在 finally 里写 return,这是 Python 官方文档明确建议避免的做法。

 更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

 

  • 发表于 2026-06-24 09:40
  • 阅读 ( 24 )
  • 分类:Python开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

2179 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 2228 文章
  3. Pack 2179 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章