Python / 命令文档

Python 基础核心(面试向)

$ python 11 个章节 Linux 命令详解

概述

本篇是「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 解决什么问题项目级依赖隔离,避免不同项目对同一包的版本冲突

同分类 · 其他命令文档