2026校招又快开始了,校招算法岗面试,Python 是绕不开的第一关。网上"Python面试题"一搜一大把,但要么只有答案没解释,要么东拼西凑不成体系。这个系列从面试官视角出发,每题先给答案、再拆原理、最后附面试追问,帮你从"能回答"升级到"能讲透"。
第一篇从语言基础和数据结构开始,共 16 个高频问答。
一、Python语言基础
1. Python是解释语言还是编译语言?
Python通常被归类为解释型语言,但从底层实现来看,它实际上是"解释 + 编译"的混合模式。
Python的执行过程分为两步:
- 编译:Python源代码(
.py)首先被编译为字节码(.pyc),这一步由Python编译器完成。 - 解释:字节码由Python虚拟机(PVM)逐条解释执行。
因此,Python本质上是一种"编译到字节码的执行方式",这与纯解释型语言(如Shell脚本,逐行解析执行)不同。
解释型语言的优点是可移植性好(一次编写,到处运行),缺点是运行速度通常比编译型语言慢。
编译型语言(如C/C++)直接将源代码编译为机器码,运行速度快,但可移植性较差,需要针对不同平台重新编译。
为什么它依然被定义为"解释型语言"?
虽然有编译的过程,但计算机科学界普遍仍将 Python 划分为解释型语言,原因在于:
- 没有生成独立的机器码文件:Python 编译出的
.pyc 字节码不是直接给 CPU 运行的二进制程序,依然需要依赖 Python 解释器(PVM)才能运行。而 C/C++ 编译出的 .exe/.out 文件可以直接在操作系统上独立执行。 - 具备动态语言特征:Python 的类型检查、内存管理和变量绑定都是在程序运行期间(Runtime)由解释器动态完成的,这与 C/C++ 等在编译期就固定好一切的纯编译型语言有本质区别。
补充:通过JIT(Just-In-Time)编译器(如PyPy、Numba),Python可以进一步将热点字节码编译为机器码,大幅提升运行效率。
2. Python中的可变对象和不可变对象?
可变对象与不可变对象的区别在于对象本身的值是否可以被修改。
| |
|---|
| 可变对象 | list(列表)、dict(字典)、set(集合)、bytearray(字节数组) |
| 不可变对象 | int(整型)、float(浮点型)、bool(布尔型)、str(字符串)、tuple(元组)、frozenset(冻结集合)、bytes(字节) |
关键理解:
- 对不可变对象的"修改"操作实际上会创建一个新对象。
# 不可变对象:看似修改,实为创建新对象
a = "hello"
print(id(a)) # 例如:140234567890
a = a + " world"
print(id(a)) # 不同了!是新对象
# 可变对象:原地修改
lst = [1, 2, 3]
print(id(lst)) # 例如:140234567891
lst.append(4)
print(id(lst)) # 同一个id!
3. Python中None代表什么含义?
None 是Python中的一个特殊常量,属于 NoneType 类型,表示"没有值"或"空值"。
核心特性:
None 是全局唯一的单例对象,所有 None 都指向同一个对象。None- 判断一个变量是否为
None 应使用 is 而非 ==。
常见用法:
# 1. 函数无返回值时默认返回None
def no_return():
pass
print(no_return()) # None
# 2. 作为默认参数
def process(data=None):
if data isNone:
data = []
return data
# 3. 表示查找失败
def find(items, target):
for item in items:
if item == target:
return item
returnNone
# 4. 正确判断方式
a = None
if a isNone: # ✅ 推荐
print("a isNone")
if a == None: # ⚠️ 可行但不推荐(可能被自定义__eq__覆盖)
print("a equals None")
None vs 空值:None 表示"没有值",而 ""(空字符串)和 [](空列表)表示"值是空的",二者在语义上不同。
4. Python中is和==的区别?
| | |
|---|
is | 对象身份 | |
== | 对象值 | 判断两个对象的值是否相等(调用 __eq__ 方法) |
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True (值相等)
print(a is b) # False (不同对象)
print(a is c) # True (同一对象)
关于整数和字符串的"缓存"陷阱:
Python会缓存小整数(-5到256)和部分短字符串,使得它们的 is 比较碰巧为 True,但这不是语言规范保证的行为,不应依赖:
a = 256
b = 256
print(a is b) # True(在缓存范围内)
a = 257
b = 257
print(a is b) # 可能为False!(超出缓存范围,行为取决于实现)
总结:除非明确要判断是否为同一对象(如 is None),否则应使用 == 进行值比较。
5. Python中type()和isinstance()的区别?
class Animal:
pass
class Dog(Animal):
pass
dog = Dog()
print(type(dog) == Dog) # True
print(type(dog) == Animal) # False(不认父类)
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True(识别继承关系)
# isinstance 支持多类型检查
print(isinstance(42, (int, float, str))) # True
使用建议:在需要检查类型时,优先使用 isinstance(),它在面对继承和多态时更加健壮。
6. Python中全局变量与局部变量之间的区别?
在函数内修改全局变量必须使用 global 关键字,否则Python会创建一个同名的局部变量:
x = 10 # 全局变量
def update():
global x # 声明要修改的是全局变量
x = 20
def bad_update():
x = 30 # 这创建了一个新的局部变量,不会影响全局x!
update()
print(x) # 20
bad_update()
print(x) # 仍然是20
对于嵌套函数中修改外层(非全局)变量,使用 nonlocal 关键字:
def outer():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
7. Python中`if __name__ == '__main__'`的作用?
这个语句用于判断当前脚本是被直接运行还是被其他脚本导入。
- 当脚本被直接运行时,
__name__ 的值为 '__main__',条件成立。 - 当脚本被其他模块
import 时,__name__ 的值为该模块的名字,条件不成立。
# example.py
def my_function():
print("This function is defined in the module.")
if __name__ == '__main__':
# 只有直接运行 example.py 时才会执行
my_function()
典型用途:
- 在模块中放置测试代码,直接运行时测试,被导入时不执行。
8. Python中assert的作用?
assert 用于调试阶段的断言检查。当条件为 False 时,抛出 AssertionError 异常。
# 基本语法
assert condition, "可选的错误消息"
# 示例:验证函数输入
def print_inverse(number):
assert number > 0, "The number must be positive"
print(1 / number)
print_inverse(5) # 正常
print_inverse(-1) # AssertionError: The number must be positive
重要注意事项:
assert 可以被全局优化选项 -O 禁用。在 python -O 模式下运行时,所有 assert 语句都会被跳过。- 不要用
assert 做生产环境的数据验证,因为它可能被禁用。生产环境应使用显式的 if/raise 异常处理。 assert
二、Python数据结构
9. Python中有哪些内建数据类型?
Python的内建数据类型可分为以下几大类:
| | | |
|---|
| None类型 | NoneType | | None |
| 数值类型 | int | | 42 |
| 序列类型 | str | str/tuple/range不可变,list可变 | "hello" |
| 集合类型 | set | | {1,2,3} |
| 映射类型 | dict | | {'key': 'value'} |
| 布尔类型 | bool | | True |
| 二进制类型 | bytes | | b'hello' |
选型建议:
- 需要不可修改的有序集合(如作为dict的key)→
tuple - 需要高效查找 →
set 或 dict(O(1)平均复杂度)
10. Python中列表和元组的区别?
| | |
|---|
| 可变性 | | |
| 语法 | [1, 2, 3] | (1, 2, 3) |
| 内存 | | |
| 性能 | | |
| 可作为dict的key | | |
| 适用场景 | | |
# 列表可变
lst = [1, 2, 3]
lst[0] = 10 # ✅ OK
# 元组不可变
tup = (1, 2, 3)
# tup[0] = 10 # ❌ TypeError
# 元组作为字典键
locations = {(0, 0): "origin", (1, 0): "right"}
11. Python中dict(字典)的底层结构?
Python字典使用哈希表作为底层实现,平均查找、插入、删除时间复杂度均为 O(1)。
CPython的实现演进:
Python 3.6之前:使用单一稀疏哈希表,每个槽位存储 (hash, key, value) 三元组。通过开放寻址法的伪随机探查(pseudo-random probing)解决哈希冲突。稀疏表内存利用率不高。
Python 3.6+:Raymond Hettinger 提出紧凑字典(compact dict),核心创新是将索引与条目分离存储:
dk_indices:稀疏的索引数组(存储条目在 entries 中的位置)dk_entries:密集的条目数组(顺序存储键值对,自然保持插入顺序)
Python 3.7+:字典正式保证插入顺序(3.6是CPython实现细节,3.7成为语言规范)。
d = {'a': 1, 'b': 2, 'c': 3}
# Python 3.7+ 保证遍历顺序为 a → b → c
for k in d:
print(k) # a, b, c
注意事项:
- 字典的key必须是可哈希(hashable)的,即不可变类型(str、int、tuple等);list和dict不能作为key。
- 负载因子达到 2/3 阈值时会触发 rehash 扩容(USABLE_FRACTION 宏控制,例如 8 槽表在第 6 个元素插入时触发扩容)。
12. Python中的推导式?
推导式(Comprehensions)是Python中一种简洁、高效地构建数据结构的语法。共有四种:
列表推导式(List Comprehension)
# 语法:[expression for item in iterable if condition]
squares = [x**2 for x inrange(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 带条件过滤
even_squares = [x**2 for x inrange(10) if x % 2 == 0]
print(even_squares) # [0, 4, 16, 36, 64]
字典推导式(Dict Comprehension)
# 语法:{key_expr: value_expr for item in iterable if condition}
squares_dict = {x: x**2 for x inrange(5)}
print(squares_dict) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
集合推导式(Set Comprehension)
# 语法:{expression for item in iterable if condition}
# 注意:结果自动去重
square_set = {x**2 for x inrange(-5, 5)}
print(square_set) # {0, 1, 4, 9, 16, 25}
生成器推导式(Generator Expression)
# 语法:(expression for item in iterable if condition)
# 注意:圆括号,惰性求值,节约内存
square_gen = (x**2 for x inrange(10))
print(square_gen) # <generator object <genexpr> at 0x...>
print(list(square_gen)) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
性能提示:列表推导式通常比等价的 for 循环 + append 更快,因为它在C层面执行循环。
13. Python中常见的切片操作
切片语法:sequence[start:stop:step]
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 基本操作
example[:6] # [1, 2, 3, 4, 5, 6] 前6个
example[6:] # [7, 8, 9, 10] 从第7个到末尾
example[1:] # [2, 3, 4, 5, 6, 7, 8, 9, 10] 从第2个到末尾
example[:-1] # [1, 2, 3, 4, 5, 6, 7, 8, 9] 除最后一个
example[-1] # 10 最后一个元素
example[::-1] # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] 反转列表
example[2:7:2] # [3, 5, 7] 步长为2
example[4::-1] # [5, 4, 3, 2, 1] 从索引4开始反向
14. Python中互换变量有不用创建临时变量的方法吗?
有,利用Python的元组打包与解包:
原理:
- 右侧
y, x 先求值,创建一个临时元组 (y原值, x原值) - 通过解包(unpacking),将元组元素分别赋给左侧的
x 和 y
这种写法简洁且Pythonic,也适用于多变量交换:
15. Python中index使用注意事项
list.index(element, start, end) 返回元素在列表中首次出现的索引。
lst = [1, 2, 3, 2, 4]
print(lst.index(2)) # 1(首次出现的索引)
print(lst.index(2, 2)) # 3(从索引2开始找)
注意事项:
- 在无法确定列表中元素是否唯一的情况下,建议使用
enumerate 遍历:
# 查找所有匹配位置
indices = [i for i, val inenumerate(lst) if val == 2]
print(indices) # [1, 3]
16. Python中remove、del以及pop之间的区别?
| | | |
|---|
remove(value) | | | 删除第一个匹配的值,值不存在则抛 ValueError |
del | | | |
pop(index) | | | index |
a = [0, 1, 2, 1, 3]
# remove:按值删除第一个匹配项
a.remove(1) # a → [0, 2, 1, 3]
# del:按索引删除(支持切片)
a = [0, 1, 2, 1, 3]
del a[1] # a → [0, 2, 1, 3]
del a[1:3] # 切片删除多个元素
# pop:按索引删除并返回
a = [0, 1, 2, 1, 3]
val = a.pop(1) # val = 1, a → [0, 2, 1, 3]
val = a.pop() # val = 3, a → [0, 2, 1](默认删最后一个)
算法岗校招面试系列目前规划10+ 章 50+ 篇内容,涵盖 Python、C/C++、计网、数据结构、数字图像、机器学习、深度学习、大模型、Agent、算法题等所有算法岗面试核心模块。关注本号,第一时间收到更新。