1. Python函数参数:*args与**kwargs的实战解析
面试中最常被问到的*args和**kwargs,本质上就是Python处理可变参数的两种方式。很多同学背下了"*args是位置参数,**kwargs是关键字参数"的答案,但真正用起来还是会踩坑。让我用实际开发中的例子带你理解它们的本质区别。
*args在函数内部会被处理成元组,而**kwargs会被处理成字典。这个特性在日志记录函数中特别有用。比如我们写一个debug_log函数:
def debug_log(prefix, *args, **kwargs): print(f"[{prefix}]", end=" ") for arg in args: print(arg, end=" ") if kwargs: print("\n附加信息:", kwargs) else: print() # 使用示例 debug_log("WARNING", "磁盘空间不足", "剩余:", "10GB") debug_log("INFO", "用户登录", username="admin", ip="192.168.1.1")在Web开发中,我经常用**kwargs来处理API的可选参数。比如用Flask开发RESTful API时:
@app.route('/search') def search(): keyword = request.args.get('q') page = request.args.get('page', 1) per_page = request.args.get('per_page', 10) # 等价于 params = request.args.to_dict() # 获取所有查询参数为字典实际踩坑经验:当同时使用*args和**kwargs时,注意参数顺序必须是(普通参数, *args, **kwargs)。有次我在中间插了个默认参数,导致运行时参数错位,排查了半天才发现。
2. 装饰器:从语法糖到高级应用
装饰器是Python最优雅的特性之一,但很多面试者只停留在@语法糖的层面。理解装饰器的关键在于:它本质上是个高阶函数,接收函数作为参数并返回新函数。
一个实用的性能分析装饰器示例:
import time from functools import wraps def timer(func): @wraps(func) # 保留原函数元信息 def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) elapsed = time.perf_counter() - start print(f"{func.__name__} 耗时: {elapsed:.6f}秒") return result return wrapper @timer def complex_calculation(n): return sum(i*i for i in range(n))在Web开发中,装饰器常用于权限验证。比如Django的@login_required,Flask的@jwt_required。我们自己实现一个简易版:
def admin_required(func): def wrapper(request, *args, **kwargs): if not request.user.is_admin: raise PermissionError("需要管理员权限") return func(request, *args, **kwargs) return wrapper进阶技巧:带参数的装饰器。比如实现一个重试机制:
def retry(max_attempts=3, delay=1): def decorator(func): def wrapper(*args, **kwargs): attempts = 0 while attempts < max_attempts: try: return func(*args, **kwargs) except Exception as e: attempts += 1 if attempts == max_attempts: raise time.sleep(delay) return wrapper return decorator3. Python内存管理与垃圾回收机制
Python的垃圾回收(GC)主要依靠引用计数为主,标记-清除和分代回收为辅的混合机制。理解这个机制对写出高性能代码至关重要。
引用计数的简单示例:
import sys a = [] # 引用计数=1 b = a # 引用计数=2 print(sys.getrefcount(a)) # 注意:传参时引用会临时+1 del b # 引用计数-1循环引用是引用计数的天敌,这时候标记-清除就派上用场了:
class Node: def __init__(self): self.parent = None self.children = [] # 创建循环引用 node1 = Node() node2 = Node() node1.children.append(node2) node2.parent = node1 # 即使删除引用,对象也不会被立即回收 del node1 del node2实际项目中的经验:处理大文件时,及时释放不再需要的引用:
def process_large_file(filename): with open(filename) as f: for line in f: process(line) # 文件会在with块结束后自动关闭 # 但如果有大数据处理,应该主动释放内存 import gc gc.collect() # 手动触发垃圾回收4. Python多线程与GIL的真相
GIL(全局解释器锁)是Python多线程编程中最容易被误解的概念。简单说,GIL确保同一时刻只有一个线程执行Python字节码。
I/O密集型任务使用多线程仍然有效:
import threading import requests def fetch_url(url): response = requests.get(url) print(f"{url} 响应长度: {len(response.text)}") urls = ["https://www.example.com", "https://www.python.org"] threads = [] for url in urls: t = threading.Thread(target=fetch_url, args=(url,)) t.start() threads.append(t) for t in threads: t.join()CPU密集型任务应该用多进程:
from multiprocessing import Pool def cpu_bound_task(n): return sum(i*i for i in range(n)) if __name__ == '__main__': with Pool() as pool: results = pool.map(cpu_bound_task, [10_000_000, 20_000_000])实际项目中的选择:我处理图像时,会用多进程池:
from concurrent.futures import ProcessPoolExecutor def process_image(img_path): # 图像处理逻辑 pass with ProcessPoolExecutor(max_workers=4) as executor: results = list(executor.map(process_image, image_paths))5. os与sys模块的实用技巧
这两个模块经常被混淆,其实分工明确:sys处理Python解释器相关,os处理操作系统相关。
sys的典型应用场景:
import sys # 处理命令行参数 if len(sys.argv) > 1: filename = sys.argv[1] # 修改Python路径 sys.path.append('/custom/module/path') # 退出程序并返回状态码 if error_occurred: sys.exit(1)os模块的文件操作:
import os # 安全的文件路径拼接 config_path = os.path.join(os.getcwd(), 'config', 'app.ini') # 递归遍历目录 for root, dirs, files in os.walk('.'): for file in files: if file.endswith('.py'): print(os.path.join(root, file))实际项目经验:用os.environ处理环境变量:
import os db_host = os.environ.get('DB_HOST', 'localhost') db_port = int(os.environ.get('DB_PORT', '5432'))6. lambda表达式的正确使用姿势
lambda是创建匿名函数的快捷方式,但滥用会让代码难以维护。适合用在需要小函数作为参数的场景。
合理使用案例:
# 排序时指定key users.sort(key=lambda u: u.last_name) # 配合map/filter numbers = [1, 2, 3, 4] squared = list(map(lambda x: x**2, numbers))反面教材(应该用普通函数):
# 过于复杂的lambda process = lambda x: (x**2 if x > 0 else (0 if x == 0 else -x**2))我在数据处理中常用的方式:
import pandas as pd df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}) df['C'] = df.apply(lambda row: row['A'] + row['B'], axis=1)7. 对象拷贝的深浅陷阱
Python的赋值操作不会创建新对象,只是增加引用。需要真正拷贝时要注意区别:
import copy original = [[1, 2], [3, 4]] shallow = copy.copy(original) deep = copy.deepcopy(original) original[0][0] = 99 print(shallow) # [[99, 2], [3, 4]] # 受影响 print(deep) # [[1, 2], [3, 4]] # 不受影响实际项目中的经验:配置字典的深拷贝:
default_config = {'debug': False, 'db': {'host': 'localhost'}} user_config = copy.deepcopy(default_config) user_config['db']['host'] = '192.168.1.100' # 不影响default_config8. __new__与__init__的协作关系
__new__负责创建对象,__init__负责初始化。理解这点对实现单例模式等场景很重要。
class Singleton: _instance = None def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super().__new__(cls) return cls._instance def __init__(self, value): self.value = value实际应用:我在实现数据库连接池时这样用:
class ConnectionPool: _pool = None def __new__(cls, *args, **kwargs): if not cls._pool: cls._pool = super().__new__(cls) cls._pool._initialize() return cls._pool def _initialize(self): self.connections = [] # 初始化连接池9. Python模块的命名艺术
__name__这个特殊属性在不同场景下有不同用途:
# module.py def useful_function(): pass if __name__ == '__main__': # 当直接运行该脚本时执行 print("模块测试代码")我在项目中的实践:
# 包结构 my_package/ __init__.py utils.py core.py # __init__.py中 from .utils import helper_function from .core import main_function __all__ = ['helper_function', 'main_function'] # 控制import *10. 上下文管理器与with语句
with语句背后的协议是__enter__和__exit__方法,理解这点可以创建自己的资源管理类。
class DatabaseConnection: def __enter__(self): self.conn = connect_to_database() return self.conn def __exit__(self, exc_type, exc_val, exc_tb): self.conn.close() if exc_type is not None: print(f"错误发生: {exc_val}") return True # 抑制异常 # 使用 with DatabaseConnection() as conn: conn.execute("SELECT * FROM users")实际项目中的异步版本:
class AsyncDatabaseConnection: async def __aenter__(self): self.conn = await async_connect() return self.conn async def __aexit__(self, exc_type, exc_val, exc_tb): await self.conn.close()