(接上篇)本篇继续 Python 面试精讲,聚焦函数方法和高级特性两大模块,包括实例方法vs类方法、*args/**kwargs、装饰器原理、迭代器与生成器,共 14 个高频问答。
三、函数与方法
17. Python中的实例方法、静态方法和类方法三者区别?
| | | | | |
|---|
| 实例方法 | | self | | | |
| 类方法 | @classmethod | cls | | | |
| 静态方法 | @staticmethod | | | | |
class MyClass:
class_var = "类变量"
def instance_method(self):
"""实例方法:可访问实例和类的属性"""
return f"实例方法: {self}, 类变量={MyClass.class_var}"
@classmethod
def class_method(cls):
"""类方法:只能访问类属性,常用于工厂方法"""
return f"类方法: {cls}, 类变量={cls.class_var}"
@staticmethod
def static_method(x, y):
"""静态方法:与普通函数无异,只是放在类的命名空间内"""
return x + y
obj = MyClass()
print(obj.instance_method()) # 通过实例调用
print(MyClass.class_method()) # 通过类调用
print(MyClass.static_method(1, 2)) # 无需实例
使用场景选择:
- 实例方法
- 类方法:操作类级别的属性、工厂方法(如创建实例的替代构造函数)→
@classmethod - 静态方法:功能与类相关但不需要访问类/实例状态 →
@staticmethod
AI实战示例——类方法避免重复加载模型:
class Infer(object):
def __init__(self, cfg: dict) -> None:
self.cfg = cfg
self.load_model(self.cfg)
@classmethod
def load_model(cls, cfg: dict):
"""使用类方法确保模型只加载一次"""
cls.cfg = cfg
ifnothasattr(cls, "model"):
cls.model = torch.load("xxx.pt") # 类级别的模型缓存
18. Python中的函数参数有哪些类型与规则?
五种参数类型
| | |
|---|
| 位置参数 | def func(a, b) | |
| 关键字参数 | func(a=1, b=2) | |
| 默认参数 | def func(a, b=10) | |
| 可变位置参数 | def func(*args) | |
| 可变关键字参数 | def func(**kwargs) | |
参数定义顺序(必须严格遵守)
位置参数 → 默认参数 → *args → 关键字限定参数 → **kwargs
def example(a, b=2, *args, **kwargs):
print(f"a={a}, b={b}, args={args}, kwargs={kwargs}")
example(1) # a=1, b=2, args=(), kwargs={}
example(1, 3, 4, 5, x=10, y=20) # a=1, b=3, args=(4,5), kwargs={'x':10,'y':20}
默认参数的"陷阱"
默认参数在函数定义时只计算一次,如果默认值是可变对象,可能导致非预期行为:
# ❌ 错误:所有调用共享同一个列表
def add_item(item, lst=[]):
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] 不是预期的 [2]!
# ✅ 正确做法
def add_item(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
19. Python中*args和**kwargs的使用?
*argskwargs:将不定数量的关键字参数**打包为字典。
# *args 示例
def test_var_args(f_arg, *argv):
print(f"固定参数: {f_arg}")
for arg in argv:
print(f"可变参数: {arg}")
test_var_args('hello', 'python', 'test')
# 固定参数: hello
# 可变参数: python
# 可变参数: test
# **kwargs 示例
def greet_me(**kwargs):
for key, value in kwargs.items():
print(f"{key} == {value}")
greet_me(name="yasoob", age=25)
# name == yasoob
# age == 25
参数解包(反向操作):
def func(a, b, c):
return a + b + c
# 用*解包列表/元组
args = [1, 2, 3]
print(func(*args)) # 6
# 用**解包字典
kwargs = {'a': 1, 'b': 2, 'c': 3}
print(func(**kwargs)) # 6
20. Python中的lambda表达式?
Lambda表达式(匿名函数)是一种创建小型、一次性函数的简洁语法。
# 语法:lambda 参数: 表达式
f = lambda x: x * 2
print(f(3)) # 6
add = lambda x, y: x + y
print(add(2, 3)) # 5
常见使用场景——与高阶函数配合:
# sorted 的 key 参数
students = [('Alice', 85), ('Bob', 92), ('Charlie', 78)]
sorted(students, key=lambda s: s[1]) # 按分数排序
# map 和 filter
nums = [1, 2, 3, 4, 5]
list(map(lambda x: x**2, nums)) # [1, 4, 9, 16, 25]
list(filter(lambda x: x % 2 == 0, nums)) # [2, 4]
优点:简洁、内联、适合函数式编程风格。
局限:只能包含单个表达式,复杂逻辑应使用 def 定义命名函数。
21. Python中函数传参时会改变参数本身吗?
这取决于参数是可变对象还是不可变对象。Python的传参机制是"传对象引用"(pass-by-object-reference)。
# 不可变对象:不受影响
def modify(x):
x = 10 # 只是让局部变量x指向了新对象
a = 5
modify(a)
print(a) # 5,不受影响
# 可变对象:受影响
def modify_list(lst):
lst.append(3) # 原地修改
my_list = [1, 2]
modify_list(my_list)
print(my_list) # [1, 2, 3],已被修改
# 如何避免?传入拷贝
import copy
original = [1, 2]
modify_list(copy.deepcopy(original))
print(original) # [1, 2] 保持不变
22. Python中海象运算符(:=)介绍
海象运算符 :=(Python 3.8+引入)允许在表达式内部进行赋值,减少重复计算。
# 传统写法:需要先赋值再判断
n = len(data)
if n > 10:
print(f"列表太长,有{n}个元素")
# 海象运算符:在表达式中直接赋值
if (n := len(data)) > 10:
print(f"列表太长,有{n}个元素")
# 循环中读取文件
while (line := file.readline()) != '':
process(line)
# 列表推导中避免重复计算
results = [y for x in data if (y := expensive_func(x)) > 0]
注意事项:海象运算符不能替代普通的 = 赋值,只在表达式上下文中使用。
四、Python高级特性
23. Python中迭代器的概念?
可迭代对象(Iterable):实现了 __iter__ 方法的对象,可以被 for 循环遍历(如 list、tuple、dict、set、str)。
迭代器(Iterator):同时实现了 __iter__ 和 __next__ 方法的对象。__iter__ 返回自身,__next__ 返回下一个元素,耗尽时抛出 StopIteration。
from collections.abc import Iterable, Iterator
# 判断是否可迭代
print(isinstance([1, 2, 3], Iterable)) # True
print(isinstance(123, Iterable)) # False
# 将可迭代对象转为迭代器
lst = [1, 2, 3]
it = iter(lst)
print(type(it)) # <class 'list_iterator'>
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# print(next(it)) # StopIteration!
for循环的本质:
# for循环底层等价于:
it = iter(iterable)
whileTrue:
try:
item = next(it)
except StopIteration:
break
# 处理 item
迭代器的优势:惰性求值(lazy evaluation),只在调用 next() 时才计算下一个值,可以处理超大数据集而不一次性加载到内存。
24. Python中生成器的相关知识
生成器(Generator)是一种特殊的迭代器,用更简洁的方式创建。
两种创建方式
方式一:生成器表达式
# 将列表推导的 [] 换成 ()
gen = (x * x for x inrange(10))
print(gen) # <generator object <genexpr> at 0x...>
print(next(gen)) # 0
print(next(gen)) # 1
方式二:yield 函数
def spam():
yield "first"
yield "second"
yield "third"
for x in spam():
print(x) # first → second → third
yield 的特性:
- 函数执行完毕时自动抛出
StopIteration
生成器的高级用法
# send():向生成器发送值
def accumulator():
total = 0
whileTrue:
value = yield total
if value is None:
break
total += value
acc = accumulator()
print(next(acc)) # 0(启动生成器)
print(acc.send(10)) # 10
print(acc.send(20)) # 30
# close():关闭生成器
acc.close()
生成器是实现协程(coroutine)的基础,也是Python异步编程的核心机制之一。
25. Python中装饰器的相关知识
装饰器(Decorator)是一种在不修改原函数代码的前提下为函数添加额外功能的设计模式,本质是一个接受函数作为参数并返回新函数的高阶函数。
基本装饰器
import logging
def use_log(func):
"""装饰器:在函数执行前记录日志"""
def wrapper(*args, **kwargs):
logging.warning(f'{func.__name__} is running')
return func(*args, **kwargs)
return wrapper
@use_log # 等价于 bar = use_log(bar)
def bar():
print('I am bar')
bar()
# WARNING:root:bar is running
# I am bar
带参数的装饰器(三层嵌套)
def repeat(n):
"""让函数重复执行n次"""
def decorator(func):
def wrapper(*args, **kwargs):
for _ inrange(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hello():
print("Hello!")
say_hello() # 打印3次 Hello!
保留函数元信息
使用 functools.wraps 保留原函数的 __name__、__doc__ 等元信息:
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
26. 什么是Python中的魔术方法?
魔术方法(Magic Methods)是以双下划线 __ 开头和结尾的特殊方法,让自定义类可以与Python内置操作无缝集成。
常用魔术方法速览
| | |
|---|
| 构造/表示 | __init__ | obj = MyClass() |
| __str__ | str(obj) |
| __repr__ | repr(obj) |
| 运算符 | __add__ | obj1 + obj2 |
| __sub__ | obj1 - obj2 |
| __mul__ | obj1 * obj2 |
| __truediv__ | obj1 / obj2 |
| 比较 | __eq__ | obj1 == obj2 |
| __lt__ | obj1 < obj2 |
| __gt__ | obj1 > obj2 |
| 容器 | __len__ | len(obj) |
| __getitem__ | obj[key] |
| __setitem__ | obj[key] = value |
| 迭代 | __iter__ | iter(obj) |
| __next__ | next(obj) |
| 可调用 | __call__ | obj() |
运算符重载示例:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
27. Python中match-case语句(Python 3.10+)
Python 3.10引入的 match-case 是结构化模式匹配(Structural Pattern Matching),比传统的 switch-case 更强大。
基本模式
# 1. 常量匹配
def describe(x):
match x:
case 0:
return "零"
case 1:
return "一"
case _: # _ 是通配符,匹配任何值
return "其他"
# 2. 变量绑定
match x:
case 0:
print("零")
case n: # n 捕获值(因为是在 case 中首次出现)
print(f"值是 {n}")
# 3. 序列匹配
def process_point(point):
match point:
case (0, 0):
print("原点")
case (x, 0):
print(f"在x轴上,x={x}")
case (0, y):
print(f"在y轴上,y={y}")
case (x, y):
print(f"坐标({x}, {y})")
process_point((3, 0)) # 在x轴上,x=3
类模式匹配
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
match p:
case Point(x=0, y=0):
print("原点")
case Point(x=x, y=0):
print(f"在x轴上,x={x}")
case Point(x=0, y=y):
print(f"在y轴上,y={y}")
case Point(x=x, y=y):
print(f"坐标({x}, {y})")
守卫条件(Guard)
match value:
case n if n < 0:
print("负数")
case n if n == 0:
print("零")
case n if n > 0:
print("正数")
注意:match-case 不会"穿透"(fall-through),匹配成功即退出。Python不支持 case 0..10: 这样的区间语法,应使用守卫条件 case n if 0 <= n <= 10: 实现区间匹配。
28. Python中eval函数的作用?
eval(source, /, globals=None, locals=None) 将字符串作为Python表达式执行并返回结果。
x = 7
print(eval('3 * x')) # 21
print(eval('2 + 2')) # 4
# 字符串转数据类型
num = eval("42")
print(type(num)) # <class 'int'>
# 可以指定命名空间
print(eval('x + 1', {'x': 10})) # 11(在指定的globals中执行)
⚠️ 安全警告:
eval()会执行任意Python代码,如果传入的字符串来自不可信来源(如用户输入),可能导致代码注入攻击。- 永远不要对用户输入使用
eval()
- 如果只是需要安全地解析简单数据(如JSON数字),使用
int()、float()、ast.literal_eval() 等更安全的替代方案。
29. Python中的字符串格式化技术
Python有三种字符串格式化方式:
1. f-string(Python 3.6+,推荐)
name = "Alice"
age = 30
print(f"Name: {name}, Age: {age}") # Name: Alice, Age: 30
print(f"Pi: {3.14159:.2f}") # Pi: 3.14
print(f"{age=}") # age=30(调试模式,Python 3.8+)
2. str.format() 方法
print("Name: {}, Age: {}".format(name, age)) # 按位置
print("Name: {0}, Age: {1}".format(name, age)) # 按索引
print("Name: {n}, Age: {a}".format(n=name, a=age)) # 按名称
print("Pi: {:.2f}".format(3.14159)) # Pi: 3.14
3. 百分号(%)格式化(旧式)
print("Name: %s, Age: %d" % (name, age)) # Name: Alice, Age: 30
print("Pi: %.2f" % 3.14159) # Pi: 3.14
推荐优先级:f-string > str.format() > %格式化。f-string 最简洁、可读性最好且性能最高。
30. Python中文件有哪些打开模式?
# 基本语法
with open('file.txt', mode='r', encoding='utf-8') as f:
content = f.read()
| |
|---|
'r' | |
'w' | |
'a' | |
'x' | 独占创建,文件已存在则抛出 FileExistsError |
't' | |
'b' | |
'+' | 读写模式(与以上组合):'r+'、'w+'、'a+' |
组合示例:
# 读取文本文件
with open('data.txt', 'r', encoding='utf-8') as f:
text = f.read()
# 写入二进制文件(如图像处理中间结果)
with open('output.bin', 'wb') as f:
f.write(b'\x00\x01\x02')
# 逐行读取
with open('large_file.txt', 'r') as f:
for line in f: # 迭代器,不会一次性加载全部
process(line)
最佳实践:始终使用 with 语句确保文件自动关闭。
算法岗校招面试系列目前规划10+ 章 50+ 篇内容,涵盖 Python、C/C++、计网、数据结构、数字图像、机器学习、深度学习、大模型、Agent、算法题等所有算法岗面试核心模块。关注本号,第一时间收到更新。