Python / 命令文档
Python 基础核心(面试向)
概述
本篇是「Python 面试核心」系列的第一篇,覆盖面试中最常被追问的基础层:语法、数据类型、函数、类、异常、模块与包、虚拟环境与依赖管理。所有示例代码均在 CentOS 测试机(Python 3.10)上实际运行通过,可直接复制到自己的环境验证。
系列共四篇:Python 基础核心、Python 工程实践核心、Python Web 开发核心、Python 测试核心。建议按顺序阅读,本篇打地基,后三篇盖楼。
面试共识:基础知识点的考察方式早已从「背概念」升级为「看代码说结果 + 说清为什么」,所以每个小节都配了能跑的最小案例,重点理解输出背后的机制。
基础语法
Python 用缩进划分代码块(PEP 8 推荐 4 空格),动态类型 + 强类型,变量名本质是「对象的引用标签」。以下是一组高频语法点:
# 交换与多重赋值
a, b = 1, 2
a, b = b, a
print(a, b) # 2 1
# f-string 格式化(3.6+,面试常问 %、format、f-string 三代对比)
name, score = "youngsu", 92.456
print(f"{name}: {score:.1f}") # youngsu: 92.5
# 类型注解:运行时不强制,但 IDE、静态检查、团队协作都依赖它
def add(x: int, y: int) -> int:
return x + y
print(add(1, 2)) # 3
# 真值判断:PEP 8 推荐写法,不要 == None / == True
items = []
if not items:
print("empty") # empty面试要点:
- 缩进是语法的一部分,混用 Tab 与空格会直接报
TabError - Python 是动态类型(变量无需声明类型)+ 强类型(
"1" + 1会报TypeError,不会隐式转换) is判断身份(同一对象),==判断值相等,小整数池与驻留只是 CPython 实现细节,别在业务里依赖
数据类型
四大内建容器 list / tuple / dict / set 是必考题,核心考「可变性」与「引用共享」:
# list / tuple / dict / set
lst = [1, 2, 2, 3]
tup = (1, 2, 3) # 不可变:可哈希,能当 dict 的 key
dic = {"a": 1, "b": 2}
st = {1, 2, 2, 3} # 自动去重
print(lst, tup, dic, st) # [1, 2, 2, 3] (1, 2, 3) {'a': 1, 'b': 2} {1, 2, 3}
# 浅拷贝 vs 深拷贝(高频考题)
import copy
x = [1, [2]]
y = x[:] # 浅拷贝:只复制第一层
y[0] = 99
y[1].append(3)
print(x) # [1, [2, 3]] —— 内层列表仍被共享
z = copy.deepcopy(x) # 深拷贝:递归复制,完全独立
# is 与 ==:== 比值,is 比身份
c, d = [1], [1]
print(c == d, c is d) # True False
# 推导式:面试爱考「一行代码完成转换 + 过滤」
sq = {n: n * n for n in range(4) if n % 2 == 0}
print(sq) # {0: 0, 2: 4}面试要点:
- 可变对象(list/dict/set)做函数默认参数、放进 tuple 里都要警惕共享副作用
- 浅拷贝三种写法:切片
x[:]、list(x)、copy.copy(x);嵌套结构必须deepcopy - dict 是哈希表:key 必须可哈希(不可变),3.7+ 保证插入有序
- 生成器表达式
(x for x in ...)省内存,适合大数据量流式处理
函数
函数是 Python 一等公民,面试三大坑:默认参数求值时机、参数传递方式、闭包与装饰器。
# 经典陷阱:默认参数只在 def 时求值一次,所有调用共享同一个 list
def bad(item, box=[]):
box.append(item)
return box
print(bad(1), bad(2)) # [1, 2] [1, 2] —— 两次调用共用一个列表
def good(item, box=None):
box = [] if box is None else box
box.append(item)
return box
print(good(1), good(2)) # [1] [2] —— 标准修复写法
# *args / **kwargs:位置不定参 + 关键字不定参
def fn(*args, **kwargs):
return args, kwargs
print(fn(1, x=2)) # ((1,), {'x': 2})
# 闭包与装饰器(必考)
import time, functools
def timer(fn):
@functools.wraps(fn) # 保留被装饰函数的 __name__/__doc__
def wrap(*a, **kw):
t0 = time.time()
r = fn(*a, **kw)
print(f"{fn.__name__} 耗时 {time.time()-t0:.3f}s")
return r
return wrap
@timer
def work():
time.sleep(0.2)
work() # work 耗时 0.200s面试要点:
- 默认参数陷阱的标准答案:用
None占位,函数体内再初始化 - 参数传递是「传对象引用」:传可变对象进来会被修改,传不可变对象等效传值
- 装饰器本质是「接收函数返回函数」的高阶函数,
@只是work = timer(work)的语法糖 functools.wraps的作用:装饰后保留原函数元信息,否则日志/调试全变成wrap
类
面向对象部分重点考:属性查找顺序、继承与 MRO、@property、魔术方法。
class Animal:
kind = "animal" # 类属性:所有实例共享
def __init__(self, name):
self.name = name # 实例属性:每个对象独立
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 标准父类初始化写法
self.breed = breed
def speak(self): # 重写 —— 多态
return f"{self.name}: 汪汪"
d = Dog("旺财", "柴犬")
print(d.speak(), "|", Dog.kind) # 旺财: 汪汪 | animal
# MRO:多继承时的方法查找顺序(C3 线性化)
print([c.__name__ for c in Dog.__mro__])
# ['Dog', 'Animal', 'object']
# @property:把方法包装成只读属性,调用方无感知
class Circle:
def __init__(self, r):
self._r = r
@property
def area(self):
return 3.14159 * self._r ** 2
print(f"{Circle(2).area:.2f}") # 12.57面试要点:
- 属性查找顺序:实例属性 → 类属性 → 父类(按 MRO)
__init__不是构造对象的那一步,真正的「出生」在__new__(单例模式常重写它)@property常用于「读起来像属性、算起来是方法」的场景,配合 setter 做校验- 魔术方法高频:
__repr__(调试显示)、__eq__(==)、__len__、__getitem__ - Python 没有真正 private,
_name是约定、__name触发名称改写(name mangling)
异常
异常处理考察点:精确捕获、异常链、else/finally 执行时机、自定义异常。
class BizError(Exception):
"""业务异常:继承 Exception,命名以 Error 结尾"""
def parse_int(s):
try:
return int(s)
except (ValueError, TypeError) as e: # 精确捕获,忌裸 except
raise BizError(f"非法整数: {s!r}") from e # from e 保留异常链
else:
print("没有异常才走 else")
finally:
print("finally 一定执行,常用于释放资源")
try:
print(parse_int("42"))
parse_int("abc")
except BizError as e:
print("捕获:", e, "| 原始异常:", repr(e.__cause__))输出:
没有异常才走 else
finally 一定执行,常用于释放资源
42
finally 一定执行,常用于释放资源
捕获: 非法整数: 'abc' | 原始异常: ValueError("invalid literal for int() with base 10: 'abc'")面试要点:
- 裸
except:会连KeyboardInterrupt/SystemExit一起吞掉,至少写except Exception finally即使 try/except 里有return也会执行(面试爱考 return 与 finally 的执行顺序)raise X from e保留原因链(__cause__),排查问题不丢现场- Python 哲学是 EAFP(先干再捕获异常),比 LBYL(先判断再干)更常用且线程更安全
模块与包
import 机制、__name__ == "__main__"、__init__.py 的作用是工程化第一课:
import pathlib, sys
# 1) 在临时目录搭一个标准包:mypkg/__init__.py + utils.py
root = pathlib.Path("/tmp/pkg_demo"); pkg = root / "mypkg"
pkg.mkdir(parents=True, exist_ok=True)
(pkg / "__init__.py").write_text('from .utils import greet\n__all__ = ["greet"]\n')
(pkg / "utils.py").write_text('def greet(name):\n return f"hello {name}"\n')
sys.path.insert(0, str(root)) # 让解释器找到包(真实项目靠安装或环境)
# 2) 两种导入方式
from mypkg import greet # __init__.py 已 re-export,__all__ 约束 * 导出
import mypkg.utils as utils
print(greet("python"), utils.greet("kb")) # hello python hello kb
# 3) 脚本 vs 模块:python a.py 时 __name__ == "__main__",被 import 时是模块名
print(__name__) # __main__面试要点:
__init__.py标记包边界,可在其中做导出整理(__all__);3.3+ 支持无它的命名空间包,但规范项目仍保留if __name__ == "__main__":保证「既能当脚本跑、又能被安全 import」- 循环导入(A import B、B import A)的标准解法:把导入下沉到函数内或抽公共模块
sys.path决定去哪找模块:脚本所在目录优先,其次是环境变量PYTHONPATH
虚拟环境
每个项目独立一套解释器视图 + 依赖,互不污染,是协作与部署的基本功:
# 创建并激活(标准库自带 venv;团队工具链可选 uv / poetry) python3 -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate python -V # 此时用的是 .venv 里的解释器 pip install requests # 包装进 .venv,不污染系统环境 deactivate # 退出虚拟环境
面试要点:
- 虚拟环境只隔离「第三方包与解释器版本」,不隔离系统库与网络
.venv目录不该进 Git(加入 .gitignore),可复刻的是依赖清单而不是目录- 常见踩坑:装了包却 ImportError,多半是「pip 和 python 不是同一个环境」,用
python -m pip最稳
requirements.txt
最通用的依赖清单格式,配合 pip 使用:
requests==2.31.0 pyyaml>=6.0,<7 pytest~=8.0
# 从当前环境导出(务必锁版本,否则团队成员/服务器装出不同环境) pip freeze > requirements.txt # 按清单安装 pip install -r requirements.txt
面试要点:
==精确锁版本,>=下限,~=兼容版本(~=8.0即>=8.0, <9)pip freeze会把依赖的依赖全部列出,适合锁「完整环境」;自己维护清单时只写直接依赖 + 范围约束更干净- 应用部署用 freeze 全锁,开源库的 requirements 只写直接依赖,两者目的不同
pyproject.toml
PEP 518/621 定下的现代 Python 项目标准配置文件,正在全面取代 setup.py/setup.cfg:
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.8"
dependencies = [
"requests>=2.31",
"pyyaml>=6.0",
]
[project.optional-dependencies]
dev = ["pytest>=8.0", "ruff>=0.4"]
[tool.pytest.ini_options] # 工具配置也统一住进来
testpaths = ["tests"]面试要点:
- 一个文件管齐「构建后端、依赖、元数据、工具配置」,
pip install -e .[dev]即可开发安装 setup.py时代的问题:安装前就要执行任意 Python 代码;pyproject 声明式描述,安装器先建隔离构建环境- poetry / uv / hatch 等工具都以 pyproject.toml 为中心,字段格式遵循 PEP 621,可迁移
高频面试题速答
| 问题 | 一句话答案 |
|---|---|
| 可变与不可变类型区别 | 可变对象原地改值共享可见,不可变对象改值即创建新对象 |
| is 和 == 的区别 | is 比对象身份(内存),== 比值(走 __eq__) |
| 深浅拷贝区别 | 浅拷贝只复制第一层引用,嵌套结构仍共享;deepcopy 递归复制 |
| 默认参数陷阱 | 默认值 def 时求值一次,可变默认值会被多次调用共享 |
| 装饰器是什么 | 接收函数并返回函数的高阶函数,@ 是语法糖,wraps 保留元信息 |
| GIL 是什么 | CPython 全局解释器锁:同一时刻单进程内仅一个线程执行字节码,IO 密集用线程、CPU 密集用多进程 |
| __init__ 与 __new__ | __new__ 创建并返回实例,__init__ 只负责初始化已创建的实例 |
| venv 解决什么问题 | 项目级依赖隔离,避免不同项目对同一包的版本冲突 |