# 运维常见题-Python运维开发



## 🤔 为什么说 Python “一切皆为对象”？ 
- **Python 中“一切皆为对象”：`int`/`str`/`list` 等基础类型、函数、类、模块、甚至代码本身都是对象——都有类型（`type`）、身份（`id`，内存地址）、可赋值/传参/存容器。核心：“`type(x)` 都有返回、`id(x)` 都有地址、`x` 都能传”——没有裸的“值”，只有对象”。**
    - **对象的三要素**
        - **身份（`id`）**：对象在内存的唯一标识（`id(x)` 返回该标识——CPython 中即内存地址，`is` 比较身份）
        - **类型（`type`）**：决定对象的行为与可操作（`type(x)`，`type(x) is type(y)`）
        - **值**：对象承载的数据（可变对象值可改，不可变对象不可改）

    - **“一切都是对象”的体现**
        - **基础类型也是对象**：`int`/`float`/`str`/`bool` 都是对象（有自己的方法，如 `1 .bit_length()`）
        - **函数是一等公民**：函数可赋值给变量、当参数传、当返回值（高阶函数/装饰器的基础）
        - **类本身也是对象**：类是 `type` 的实例（元类可动态建类）
        - **模块/异常/None**：也都是对象（`None` 是 `NoneType` 的实例）

    - **实际价值**
        - **统一操作**：所有对象都走“传对象引用/共享传参”（`pass-by-object-reference`——重新绑定参数名不影响外部，只有原地修改才影响）、都能放进容器（`[1, 'a', len, int]` 合法）
        - **动态性基础**：运行时可检查/修改（`type`/`dir`/`getattr`），支撑反射、装饰器、元编程

- **协助记忆**
    - 口诀：“万物皆对象：有类型、有地址、可传递——数字/字符串/函数/类都一样”。

- **进阶思考**
    - **函数为什么能当参数传（装饰器的原理）？**
        - 因为函数也是对象（有类型 `function`、有 `id`），可以像普通对象一样赋值/传参/返回。装饰器“接收函数返回新函数”正是把函数当一等公民用的典型（`@decorator` 语法糖）。
    - **`is` 和 `==` 有什么区别？**
        - `is` 比**身份**（是否同一个对象，`id` 相同），`==` 比**值**（调用 `__eq__`）。小整数/短字符串有缓存（`is` 可能恰好为真），但判断相等应该用 `==`；`is` 只用于 `x is None`、`x is True` 这类身份判断。

- **扩展信息**
    - **对象模型**：Python 一切对象基于 `PyObject`（引用计数 + 类型指针），CPython 实现层面也统一
    - **元类**：类是 `type` 的实例（`type(name, bases, dict)` 动态建类），可自定义元类（`metaclass`）控制类的创建

## 🤔 Python 里常见可变 / 不可变对象分别有哪些？ 
- **可变对象（值可原地改，`id` 不变）：`list`/`dict`/`set`/`bytearray`/自定义类实例；不可变对象（值不可改，改了就是新对象）：`int`/`float`/`bool`/`str`/`tuple`/`frozenset`/`bytes`/`None`。核心：“改可变对象原地生效（会影响引用它的所有变量），改不可变对象等于创建新对象”。**
    - **可变对象（Mutable）**
        - `list`（`append`/`pop`/`sort` 原地改）、`dict`（增删键值）、`set`（`add`/`remove`）
        - `bytearray`（可变字节串）、自定义类实例（属性可改）
        - **特点**：原地修改不换 `id`，所有引用同步看到变化

    - **不可变对象（Immutable）**
        - 数字：`int`/`float`/`complex`/`bool`
        - 序列：`str`（`s += 'x'` 是新建对象）、`tuple`（元素不可替换）、`bytes`
        - `frozenset`（不可变集合）、`None`
        - **特点**：任何“修改”操作都返回新对象（原对象不变）

    - **可变性的实际影响**
        - **函数传参**：传可变对象，函数内修改会影响外部（如默认参数别用 `[]`/`{}`，用 `None`）
        - **作为 `dict` key/`set` 元素**：必须**可哈希**（通常是不可变对象），`list`/`dict`/`set`/`bytearray` 不行，`str`/`int`/`frozenset` 可以，`tuple` 仅当元素都可哈希时才可（含 `list` 的 `tuple` 不可哈希）
        - **别名陷阱**：`a = b` 是引用同一对象，改可变对象两边都变（浅拷贝解决一层）

- **协助记忆**
    - 口诀：“可变 `list/dict/set`，不可变数字/`str`/`tuple`——可变改原地、不可变换新对象”。

- **进阶思考**
    - **`tuple` 里有 `list`，还算不可变吗？**
        - `tuple` 本身不可变（不能增删/替换元素），但元素是引用——若元素是可变对象（如 `list`），那个 `list` 的内容还能改。所以“不可变”指 tuple 的槽位不变，不深及元素内容（这也是它哈希可能失败的坑）。
    - **为什么默认参数别用 `[]`？**
        - 默认参数在**函数定义时求值一次**（存到 `__defaults__`），所有调用共享同一个 `list` 对象；第一次调用往里 `append`，之后所有调用都带着这批数据。正确写法：默认 `None`，函数内 `if x is None: x = []`。

- **扩展信息**
    - **小对象缓存**：CPython 对小整数（-5~256）/短字符串做缓存，`is` 比较可能为真（实现细节，勿依赖）
    - **哈希与不可变**：可哈希 = 实现了 `__hash__` 且生命周期内哈希值不变（不可变对象天然满足）

## 🤔 Python 浅拷贝 & 深拷贝 有什么区别？ 
- **`= 赋值`只是引用同一对象；`浅拷贝`（`copy.copy`）只复制最外层容器，内层元素仍共享引用；`深拷贝`（`copy.deepcopy`）递归复制所有层级，完全独立。核心：“浅拷贝复制外壳、深拷贝复制全部——嵌套结构要独立必须深拷贝”。**
    - **三种复制方式**
        - **`b = a`（赋值）**：不复制，`a`/`b` 是同一个对象（`a is b` 为真），改一个都变
        - **浅拷贝 `copy.copy(a)`/`a.copy()`/`list(a)`**：新建外层容器，内层元素是引用（第一层独立、嵌套共享）
        - **深拷贝 `copy.deepcopy(a)`**：递归复制每一层，完全独立（改任何一层都不互相影响）

    - **示例（嵌套列表）**
        - `a = [1, [2, 3]]`；浅拷贝 `b = copy.copy(a)`：`b[0]` 独立（改 `b[0]` 不影响 `a[0]`），但 `b[1]` 与 `a[1]` 是同一个 `list`（改 `b[1].append(4)` 两边都变）
        - 深拷贝 `c = copy.deepcopy(a)`：`c[1]` 也是新对象，随便改都不影响 `a`

    - **深拷贝的特点/代价**
        - **循环引用处理**：`deepcopy` 用 `memo` 字典记录已复制对象，能正确处理自引用/循环引用（不会死循环）
        - **代价**：递归复制开销大（大结构/深嵌套慢、耗内存）；对象可能需要自定义 `__deepcopy__`

    - **怎么选**
        - 只改第一层 → 浅拷贝；嵌套结构要完全独立 → 深拷贝；只是别名 → 赋值

- **协助记忆**
    - 比喻：“赋值 = 同一把钥匙，浅拷贝 = 复印了房子图纸但家具还是那些（内层共享），深拷贝 = 整栋楼连同家具全部重建”。
    - 口诀：“浅拷一层、深拷到底；嵌套独立必深拷”。

- **进阶思考**
    - **为什么浅拷贝后改 `b[0]` 不影响 `a[0]`，改 `b[1]` 却影响？**
        - 浅拷贝新建了外层 `list`，`b` 的槽位是独立数组——给 `b[0]` 赋新值只是改 `b` 自己的槽位；但 `b[1]` 引用的还是原来的内层 `list` 对象，通过它做**原地修改**（`append`）时，两边看到的是同一个对象。
    - **`dict.copy()` 和 `deepcopy` 在运维脚本里怎么选？**
        - 配置字典改一层字段 → `dict.copy()`（快）；要把模板配置深改后各自独立（嵌套字典/列表）→ `deepcopy`。运维场景常见坑：用 `dict.copy()` 复制模板后改嵌套 `list`，把模板也改了——嵌套必须 `deepcopy`。

- **扩展信息**
    - **相关 API**：`copy.copy`/`copy.deepcopy`、`list()`/`dict()`/`set()` 构造器（浅拷贝）、切片 `a[:]`（浅拷贝）、`__copy__`/`__deepcopy__` 自定义
    - **序列化替代**：`json.loads(json.dumps(x))` 可实现“伪深拷贝”（要求可 JSON 化，函数/对象不行）

## 🤔 List（列表）和 Tuple（元组）的核心区别？ 
- **核心区别是可变性：`list` 可变（增删改、动态数组）、`tuple` 不可变（定长、不可改）。衍生差异：`tuple` 可哈希（能做 `dict` key/`set` 元素）、内存更小且可被缓存优化、语义上 `tuple` 表示“固定结构”而 `list` 表示“同类集合”。核心：“变用 `list`、不变用 `tuple`，做 `key` 必须 `tuple`”。**
    - **可变性（根本区别）**
        - `list`：`append`/`insert`/`remove`/`pop`/`sort` 都可原地改（动态数组）
        - `tuple`：不可增删改元素（定长）；“修改”只能新建对象

    - **哈希与用途**
        - `tuple` 可哈希（元素都不可变时）→ 可作 `dict` key、`set` 元素（如坐标 `(x, y)` 缓存）
        - `list` 不可哈希 → 不能作 `key`（可变性会导致哈希不稳定）

    - **性能与内存**
        - `tuple` 创建更快、内存更小（无预留扩容空间）、常量可被解释器缓存/优化
        - `list` 有扩容机制（预留空间），动态增删灵活

    - **语义惯例**
        - `list`：同质元素的集合（一列数据，会增删）
        - `tuple`：异质固定结构（一条记录：`(name, age, city)`、函数多返回值）

- **协助记忆**
    - 口诀：“`list` 动态可变，`tuple` 定长不可变；做 `key` 用 `tuple`，装数据用 `list`”。

- **进阶思考**
    - **单元素元组怎么写（常见的坑）？**
        - `(1)` 是数字 1（括号被当运算符），`(1,)` 才是元组——必须带逗号。函数返回/解包时容易踩：`x = (1)` 得到 `int`。
    - **函数返回多个值其实是返回什么？**
        - 返回的是**一个 tuple**（`return a, b` 打包成元组），调用方 `x, y = f()` 是序列解包。理解这点就不会困惑“返回多个值”的机制。

- **扩展信息**
    - **`namedtuple`/`dataclass`**：给 `tuple` 加字段名（`Point = namedtuple('Point', 'x y')`），兼顾轻量与可读
    - **列表推导 vs 元组**：`[x for x in ...]` 是 list，`(x for x in ...)` 是生成器（不是 tuple）

## 🤔 Set（集合）有哪些特性？怎么实现去重的？ 
- **`set` 特性：无序、元素唯一（自动去重）、可变、元素必须可哈希；底层是哈希表，去重靠 `hash()` 定位 + `==` 判等（同哈希且相等才算重复）。支持集合运算（交/并/差/对称差）。核心：“哈希表存储，`hash` 找位、`eq` 判重，天然去重”。**
    - **核心特性**
        - **无序**：不保插入顺序（遍历顺序不保证与加入顺序一致、不可依赖；要保序用 `dict`（3.7+）或 `list`+去重）
        - **唯一**：重复元素自动去重（`{1, 1, 2}` → `{1, 2}`）
        - **可变**：`add`/`remove`/`discard`/`pop`；不可变版本 `frozenset`
        - **元素可哈希**：`int`/`str`/`tuple` 可以，`list`/`dict`/`set` 不行

    - **去重原理（哈希表）**
        - 底层是哈希表：加入元素先算 `hash(x)` 定位桶，桶为空直接放；不空则 `==` 比较现有元素——**相等视为重复不加入，不等则探测下一个位置**
        - 所以去重要求：元素可哈希，且“哈希相同 + 相等”的对象只保留一个
        - 复杂度：平均 `O(1)` 判重/查找（哈希冲突退化到 `O(n)`）

    - **集合运算**
        - 交 `a & b`/`a.intersection(b)`、并 `a | b`、差 `a - b`、对称差 `a ^ b`
        - 子集/超集：`a.issubset(b)`/`a.issuperset(b)`

    - **常见去重场景**
        - 列表去重：`list(set(lst))`（不保序）；保序去重：`list(dict.fromkeys(lst))`（`dict` 3.7+ 保序）

- **协助记忆**
    - 口诀：“`set` 无序唯一去重，哈希定位、相等判重；保序去重用 `dict.fromkeys`”。

- **进阶思考**
    - **为什么 `set` 元素必须可哈希？**
        - 哈希表靠 `hash(x)` 定位存储位置；不可变对象哈希稳定可定位，`list`/`dict` 可变（内容改了哈希就错位、找不回），所以禁止。自定义对象要进 `set`/当 `key`，需正确实现 `__hash__` 和 `__eq__`（相等对象哈希必须相同）。
    - **大日志去重怎么选？**
        - 量大且元素是 `IP`/`URL` 等短字符串 → `set` 内存可承受就用；百万级以上且只数唯一值 → 数据库 `DISTINCT`/外部排序/布隆过滤器（允许误判、省内存）。

- **扩展信息**
    - **相关**：`frozenset`（不可变集合，可哈希）、`collections.Counter`（计数+`most_common`）、`dict.fromkeys` 保序去重
    - **布隆过滤器**：概率型去重结构（可能误判存在、不会漏判不存在），超大数据量去重的空间优化方案

## 🤔 Dict（字典）的底层实现原理是什么？ 
- **`dict` 底层是哈希表：`key` 经 `hash()` 算哈希定位桶，冲突用开放寻址解决，扩容按负载因子触发（重建表再散列）；Python 3.7+ 用“紧凑 + 分离”结构（`entries` 数组 + `indices` 稀疏索引），并保证插入顺序。核心：“哈希定位 + 开放寻址 + 按需扩容，3.7+ 保序”。**
    - **哈希定位（存取流程）**
        - 写入/查询 `d[k]`：先 `hash(k)` → 按表大小取模得到槽位 → 比对槽位上的 `key`（先比哈希再比 `==`）→ 命中返回/写入
        - 平均 `O(1)`，最坏 `O(n)`（大量冲突，实际罕见）

    - **冲突解决：开放寻址**
        - 冲突（槽位被占且 `key` 不同）时按探测序列找下一个空槽（Python 用扰动哈希 `perturb` 打散，减少聚集）
        - 不同于链地址法（Java `HashMap` 挂链表），Python 不挂链、全靠探测

    - **扩容（rehash）**
        - 负载因子超阈值（约 2/3）触发扩容：分配更大的表（通常约 2 倍），把所有条目重新散列
        - 扩容是 `O(n)` 但均摊后单次操作仍近 `O(1)`

    - **紧凑结构 + 保序（3.7+）**
        - 旧版：一张大数组（稀疏、浪费）；3.6 起（3.7 语言保证）：`entries` 紧凑数组存键值 + `indices` 稀疏数组存下标——省内存且**按插入顺序遍历**

    - **对 `key` 的要求**
        - `key` 必须可哈希（不可变：`str`/`int`/`tuple`/`frozenset`）；自定义对象需实现 `__hash__`/`__eq__`（相等对象哈希必须相同）
        - `value` 任意（可变/不可变都行）

- **协助记忆**
    - 口诀：“`dict` = 哈希表：`hash` 定位、探测解冲突、满了扩容重建；3.7 起紧凑结构还保插入序”。

- **进阶思考**
    - **为什么 `dict` 查找这么快（和 `list` 遍历比）？**
        - `list` 查值是逐个比对（`O(n)`）；`dict` 用哈希直接算出存储位置（`O(1)`）——不扫描、直接跳到桶。所以“按 key 找值”永远优先用 `dict`（如 `IP` → 信息的映射）而不是并行 `list`。
    - **key 改了会怎样（可变 key 的坑）？**
        - `key` 存的是哈希值定位；若 `key` 是自定义可变对象且内容被改，哈希变了但槽位没变——再查就找不到（哈希错位）。所以 `key` 必须用不可变对象或保证 `__hash__` 与内容一致。

- **扩展信息**
    - **相关**：`collections.OrderedDict`（3.7 前的保序方案，现多用于 `move_to_end` 等 `LRU` 语义）、`defaultdict`（缺省值）、`Counter`（计数）
    - **`__slots__`**：给类实例省内存（禁用 `__dict__`），大量对象时的优化手段

## 🤔 *args 和 **kwargs 的作用是什么？ 
- **`*args` 把多余的位置参数收进一个 `tuple`，`**kwargs` 把多余的关键字参数收进一个 `dict`——用于定义“参数数量不定”的函数（转发/包装/装饰器必备）。调用时 `*`/`**` 反向解包：`*lst` 拆序列、`**dct` 拆字典。核心：“定义时收集（打包），调用时展开（解包）”。**
    - **定义侧（收集参数）**
        - `def f(*args)`：多余位置参数打包成 `tuple`（`f(1, 2, 3)` → `args == (1, 2, 3)`）
        - `def f(**kwargs)`：多余关键字参数打包成 `dict`（`f(a=1, b=2)` → `kwargs == {'a': 1, 'b': 2}`）
        - 顺序固定：`def f(普通, 默认=值, *args, **kwargs)`（`*args` 在 `**kwargs` 前）
        - 命名可换（`*values`/`**options`），`*args`/`**kwargs` 只是惯例名

    - **调用侧（解包传参）**
        - `f(*[1, 2])` 等价 `f(1, 2)`（序列拆成位置参数）
        - `f(**{'a': 1})` 等价 `f(a=1)`（字典拆成关键字参数）

    - **典型应用**
        - **装饰器透传**：`def wrapper(*args, **kwargs): return func(*args, **kwargs)`（不关心被包函数签名）
        - **子类转发**：`super().__init__(*args, **kwargs)` 透传构造参数
        - **通用封装**：执行命令/`API` 封装，参数灵活透传

    - **相关语法**
        - 仅限关键字参数：`def f(a, *, key=None)`（`*` 后只能关键字传）
        - 仅位置参数（3.8+）：`def f(a, b, /)`（`/` 前只能位置传）

- **协助记忆**
    - 口诀：“`*args` 收位置成 `tuple`，`**kwargs` 收关键字成 `dict`；调用加 `*` 是拆包——一收一放”。

- **进阶思考**
    - **为什么装饰器必须写 `(*args, **kwargs)`？**
        - 装饰器要包任意签名的函数，不能预知参数——用 `*args, **kwargs` 全量接收，再原样 `func(*args, **kwargs)` 透传，才能“包一切”。少写 `**kwargs` 会导致关键字参数全部报错。
    - **`*args` 后还能加普通参数吗？**
        - `def f(*args, key=None)` 合法——`key` 变成**仅限关键字参数**（只能 `f(1, key=2)`，不能 `f(1, 2)`）。这是设计 `API` 时强制调用方写清楚参数名的常用手段。

- **扩展信息**
    - **`functools.wraps`**：装饰器里保留原函数元信息（`__name__`/`__doc__`），配合 `*args/**kwargs` 使用
    - **`inspect.signature`**：运行时检查函数签名（写通用框架/CLI 工具时用）

## 🤔 什么匿名函数（Lambda）？ 
- **`Lambda` 是“没有名字的一次性小函数”：`lambda 参数: 表达式`——单表达式、隐式 `return`、不能写语句（无 `if` 块/`while`/赋值语句）。适用：需要函数的地方但不值得 `def`（排序 key、`map`/`filter` 回调）。核心：“一行小函数，当参数用完即弃”。**
    - **语法与等价**
        - `add = lambda x, y: x + y` 等价 `def add(x, y): return x + y`
        - 支持默认参数/可变参数：`lambda x, n=1: x*n`、`lambda *a: sum(a)`

    - **限制（重要）**
        - **只能一个表达式**：不能多行、不能写语句（`print` 是表达式可写，赋值/`import`/`return` 不行）
        - **复杂逻辑别用**：超过一行就该 `def`（可读性优先）
        - **别赋值给变量当 `def` 用**：`PEP8` 明确不建议 `f = lambda: ...`（应直接 `def f(): ...`）

    - **典型用法**
        - `sorted(data, key=lambda x: x[1])`（按第二列排序）
        - `map`/`filter` 回调、`functools.reduce`、`defaultdict` 的工厂、`Tkinter` 按钮回调

- **协助记忆**
    - 口诀：“`lambda` = 一行函数：单表达式、隐式返回、当参数用——复杂逻辑回 `def`”。

- **进阶思考**
    - **`lambda` 和 `def` 除了名字还有什么区别？**
        - 本质都是函数对象（类型都是 `function`），能力上 `lambda` 只是“单表达式”语法糖。区别是形式：`lambda` 便于内联定义（当参数）；`def` 有名字、可多行、可 `docstring`。运行期两者无性能差异。
    - **循环里用 `lambda` 捕获变量踩过坑吗（闭包晚绑定）？**
        - `fs = [lambda: i for i in range(3)]` 三个函数调用时都返回 2——闭包捕获的是**变量本身**不是当时值，循环结束 `i=2`。修复：默认参数绑定当前值 `lambda i=i: i`。

- **扩展信息**
    - **可调用对象**：实现 `__call__` 的类实例也能当“函数”传（比 `lambda` 更适合带状态），如 `operator.itemgetter('key')`
    - **运维场景**：`sorted(logs, key=lambda l: l.split()[0])` 按日志首字段排序、`filter` 筛选进程

## 🤔 匿名函数（Lambda）有哪些应用场景？ 
- **`Lambda` 的主战场是“把小函数当参数传”：①`sorted`/`list.sort` 的 `key`（最常见）②`map`/`filter`/`reduce` 回调 ③`dict`/`defaultdict` 工厂 ④`min`/`max` 的 `key` ⑤`GUI`/回调注册 ⑥运维脚本（日志排序/字段提取）。核心：“作为一次性 `key` 或回调，不值得 `def` 时用”。**
    - **排序 `key`（最常用）**
        - `sorted(users, key=lambda u: u['age'])` 按字典字段排序
        - 多级：`key=lambda x: (x[0], -x[1])`（第一列升、第二列降）
        - 运维例：`sorted(ips, key=lambda ip: int(ip.split('.')[-1]))` 按 `IP` 末段排序

    - **`map`/`filter`/`reduce` 回调**
        - `list(map(lambda x: x.strip(), lines))` 批量处理
        - `list(filter(lambda x: x > 90, scores))` 过滤
        - `functools.reduce(lambda a, b: a + b, nums)` 聚合

    - **`min`/`max`/`defaultdict` 工厂**
        - `max(servers, key=lambda s: s.load)` 取负载最高节点
        - `defaultdict(lambda: {'count': 0})` 复杂默认值工厂

    - **运维脚本常见**
        - 日志解析：`key=lambda line: line.split()[3]`（按时间列排序）
        - 按扩展名分组：`groupby(sorted(files, key=lambda f: f.rsplit('.', 1)[-1]), key=lambda f: f.rsplit('.', 1)[-1])`（`groupby` 只合并相邻相同键，必须先按分组键排序，否则同扩展名会散成多组）

- **协助记忆**
    - 场景口诀：“排序 `key`、`map/filter` 回调、`max/min` 取极值、工厂函数——一次性小逻辑就 `lambda`”。

- **进阶思考**
    - **什么时候不该用 `lambda`（用替代品更好）？**
        - 有现成可调用就别 `lambda`：取字典字段用 `operator.itemgetter('k')`、取属性 `attrgetter('a.b')`、按真值过滤用 `filter(None, lst)`、按字符串排序直接 `key=str.lower`——更快更可读。逻辑复杂（超过一行表达式）就 `def`。
    - **`lambda` 在类里当方法有什么坑？**
        - `class A: f = lambda self: ...` 语法可用但可读性差（`PEP8` 不建议）；闭包捕获 `self` 时注意晚绑定。定义实例行为用普通 `def`，`lambda` 留给参数场景。

- **扩展信息**
    - **`operator` 模块**：`itemgetter`/`attrgetter`/`methodcaller` 是排序 `key` 的更优解（性能好、语义清晰）
    - **`key` 函数思想**：Python 排序哲学“Schwartzian 变换”——先算 `key` 再比，`lambda` 是这个体系最灵活的入口

## 🤔 什么是高阶函数？ 
- **高阶函数（Higher-Order Function）是“接收函数当参数、或返回函数”的函数——因为 Python 里函数是对象（一等公民），可以像普通值一样传递。内置高阶函数：`map`/`filter`/`sorted(key)`/`max(key)`/`min(key)`；`reduce` 在 `functools`（Python 3 起非内置）；自建的：装饰器（返回新函数）。核心：“函数当参数/返回值用——抽象行为的手段”。**
    - **两个判定条件（满足其一）**
        - **接收函数作参数**：`sorted(data, key=func)`、`map(f, lst)`、`filter(f, lst)`
        - **返回函数**：装饰器（`def deco(f): def wrapper(): ... return wrapper`）、工厂函数（`make_multiplier(n)` 返回新函数）

    - **内置高阶函数**
        - `map(f, iterable)`：映射（惰性，返回迭代器）
        - `filter(f, iterable)`：过滤（`f` 返回真值保留）
        - `sorted(iterable, key=f)`：按 `key` 排序；`min`/`max` 同理
        - `functools.reduce(f, iterable, init)`：滚动聚合（两两合并）

    - **自建高阶函数（价值所在）**
        - **行为参数化**：`def run_tasks(tasks, handler): ...`（把“怎么处理”作为参数传入，框架思维）
        - **装饰器**：不改原函数逻辑，动态增强（日志/计时/缓存/重试）
        - **闭包工厂**：`def make_retry(times): def deco(f): ... return deco`（参数化装饰器）

    - **为什么有用**
        - **复用模式而非数据**：把“变化的部分”（规则/策略/回调）抽成函数传入，主体逻辑写一次
        - 是**装饰器/回调/策略模式/中间件**的统一底层机制

- **协助记忆**
    - 口诀：“收函数、吐函数，就是高阶——`map/filter/sorted(key)` 是内置代表，装饰器是自建代表”。

- **进阶思考**
    - **高阶函数和回调函数是什么关系？**
        - 回调是高阶函数的一种用法：把函数 A 传给 B，B 在合适时机“回过头来调用 A”。`sorted` 的 `key`、按钮的 `command`、异步框架的 `on_done` 都是回调——本质都是“函数作为参数”。
    - **`map`/`filter` 和列表推导式怎么选？**
        - 简单映射/过滤推导式更可读（`[f(x) for x in lst]`、`[x for x in lst if cond]`），且天然 `lambda`-free；`map`/`filter` 在已有现成函数（不用 `lambda`）或多序列并行时占优（`map(add, a, b)`）。风格上现代 Python 更偏推导式。

- **扩展信息**
    - **`functools` 家族**：`partial`（固定参数生成新函数）、`reduce`、`wraps`（装饰器元信息）、`lru_cache`（缓存装饰器）
    - **函数式工具**：`itertools`（`chain`/`groupby`/`islice`）+ 高阶函数组合，处理日志/文件流非常高效

## 🤔 装饰器解决了什么问题？ 
- **装饰器解决“不改原函数代码，统一给一批函数增强功能”的问题：把日志/计时/缓存/重试/鉴权等横切逻辑抽出来，用 `@deco` 语法一键套用——避免每个函数里复制粘贴同样的样板代码。本质是“接收函数、返回增强后新函数”的高阶函数。核心：“增强与业务解耦、一处定义多处复用”。**
    - **解决的问题（横切关注点）**
        - **重复样板**：每个函数都写一遍“记录开始/结束/异常”→ 装饰器写一次全函数复用
        - **侵入为零**：原函数代码不动（只在定义处加 `@`），方便随时增删
        - **统一收口**：鉴权/限流/监控等策略改一处即全局生效

    - **典型应用**
        - **日志/计时**：`@log_call`、`@timer`（性能排查常用）
        - **缓存**：`@functools.lru_cache(maxsize=128)`（纯函数结果缓存，递归/`API` 查询提速）
        - **重试**：`@retry(times=3, delay=1)`（网络请求/脚本容错，运维必备）
        - **注册/路由**：`@app.route('/api')`（Web 框架路由注册）、`@celery.task`（任务注册）
        - **权限/校验**：`@login_required`、参数校验

    - **标准写法（带透传 + 保留元信息）**
        - `def deco(f):` + `@functools.wraps(f)` + `def wrapper(*args, **kwargs): ... return f(*args, **kwargs)` + `return wrapper`
        - `*args/**kwargs` 保证任意签名可用；`wraps` 保留 `__name__`/`__doc__`（否则日志/调试里全叫 `wrapper`）

    - **带参装饰器**
        - 三层：`def deco(参数): def inner(f): def wrapper(...): ... return wrapper; return inner; return deco`——`@deco(参数)` 先求值得真装饰器

- **协助记忆**
    - 口诀：“装饰器 = 不改原函数、统一加料——`wraps` 保元信息、`*args/**kwargs` 透传签名”。
    - 一句话：“把每个函数都要写的横切代码（日志/缓存/重试）抽成一个 `@` 复用”。

- **进阶思考**
    - **装饰器执行时机（定义时还是调用时）？**
        - **定义时**：`@deco` 在函数定义处立即执行（包装一次）；**调用时**：执行的才是 `wrapper` 里增强逻辑。所以装饰器里的“装饰前打印”在 `import` 时就会输出——理解这点可解释很多“奇怪日志顺序”问题。
    - **多个装饰器的顺序？**
        - 自下而上包装：`@a` `@b` `def f` 等价 `f = a(b(f))`——`b` 先包、`a` 后包（最外层 `a`）。执行增强逻辑时从最外层进（`a` 的 `wrapper` 先跑）。排列时想清楚“谁在最外层生效”。

- **扩展信息**
    - **类装饰器**：实现 `__call__` 的类也能装饰（可维护状态，如计数器装饰器）
    - **运维实用**：`lru_cache` 缓存 `API` 结果、自写 `@retry` 包 `subprocess`/`requests`、`@timer` 定位慢函数

## 🤔 什么是闭包？ 
- **闭包（Closure）是“内层函数引用了外层函数的变量，并且外层函数返回后该变量依然存活”的现象：内层函数把外层作用域“打包带走”。价值：函数工厂（按参数生成定制函数）、带状态的函数（不暴露全局）、装饰器的实现基础。核心：“函数 + 它引用的外层环境 = 闭包”。**
    - **构成条件**
        - ①有嵌套函数（内层定义在外层里）②内层**引用**了外层的变量③在外层作用域结束后仍能使用（典型是外层**返回**内层函数——非必须“返回”，但要让闭包在外层调用后存活通常这么写）
        - 例：`def outer(n): def inner(x): return x + n; return inner`——`inner` 捕获了 `n`；`add5 = outer(5)` 之后 `n` 依然活着

    - **核心机制**
        - 被捕获的变量存在内层函数的 `__closure__`（cell 对象）里，不随外层栈帧销毁
        - **晚绑定**：捕获的是“变量”不是“当时的值”（循环里 `lambda: i` 全拿到最后值的原因）
        - **写回要用 `nonlocal`**：内层想修改外层变量（不是重新绑定自己的局部名）必须 `nonlocal` 声明

    - **典型用途**
        - **函数工厂**：`make_multiplier(3)` 生成“乘 3”的函数（配置模板生成）
        - **带状态函数**：计数器（`count` 变量 + `nonlocal`），比全局变量安全（状态封装在闭包里）
        - **装饰器**：`wrapper` 捕获 `func`——装饰器就是闭包最成功的应用
        - **回调配置**：`lambda: send(url)` 固定部分参数

- **协助记忆**
    - 口诀：“内层用外层变量 + 外层返回内层 = 闭包；改外层变量要 `nonlocal`；捕获的是变量不是值”。

- **进阶思考**
    - **闭包和全局变量有什么区别（为什么要用）？**
        - 闭包把状态**封装**在函数里：不污染全局命名空间、多次工厂调用互不干扰（`outer(1)`/`outer(2)` 各自独立的 `n`）、变量生命周期受控。全局变量人人可改，闭包只能通过返回的函数访问——安全性/可维护性好。
    - **循环 + 闭包的“晚绑定”坑怎么破？**
        - `fs = [lambda: i for i in range(3)]` 全返回 2（捕获变量 `i` 本身）。修复三选：①默认参数固化当前值 `lambda i=i: i` ②用 `functools.partial(f, i)` ③嵌套一层工厂函数把 `i` 变成参数。

- **扩展信息**
    - **查看闭包**：`f.__closure__`（cell 元组）、`cell.cell_contents` 取值；调试闭包状态有用
    - **类 vs 闭包**：带多状态/多方法用类更清晰；轻量单状态/一次性工厂用闭包（`lru_cache`、装饰器内部都是闭包）

## 🤔 Python 的 LEGB 作用域规则？ 
- **LEGB 是变量查找顺序：`L`ocal（当前函数内）→ `E`nclosing（外层嵌套函数）→ `G`lobal（模块级）→ `B`uilt-in（内建），逐层向外找、找到即停；赋值默认只在当前层创建变量（要跨层写用 `global`/`nonlocal`）。核心：“读从内向外找，写只写本层——跨层写必须声明”。**
    - **四层作用域**
        - **L（Local）**：当前函数内部定义的变量/参数
        - **E（Enclosing）**：外层嵌套函数的变量（闭包捕获层）
        - **G（Global）**：模块级变量（`global` 声明后可在函数内写）
        - **B（Built-in）**：内建名（`len`/`print`/`range`）

    - **查找规则（读）**
        - 从 L 往外逐层找，**首次命中即用**（所以函数内 `len = 5` 会遮蔽内建 `len`）
        - 全部未命中 → `NameError`

    - **赋值规则（写）——最常见的坑**
        - 函数内**任何赋值**（`x = 1`）默认把 `x` 声明为**局部变量**（编译期决定）——哪怕上面有同名全局变量
        - 若在赋值**之前**读它会 `UnboundLocalError`（不是拿到全局值！这是高频坑）
        - 想改全局 → `global x`；想改外层闭包变量 → `nonlocal x`

    - **特例**
        - **只引用不赋值**的变量会按 LEGB 正常向外查（函数里 `print(g_var)` 能拿到全局值）
        - `del x`/`import x`/`for x` 也算“赋值”（同样创建局部名）

- **协助记忆**
    - 口诀：“LEGB 由内向外找；**函数里一赋值，变量就变局部**——跨层写 `global`/`nonlocal`，先读后赋会 `UnboundLocalError`”。

- **进阶思考**
    - **`UnboundLocalError` 为什么会出现？怎么排查？**
        - 函数体内有 `x = ...` → 编译期把 `x` 标记为局部 → 执行到赋值前访问 `x`（如 `print(x)`）就报此错。排查：看函数里是否有对它的赋值；要么改名、要么 `global`/`nonlocal` 声明、要么先赋值再读。
    - **为什么 Python 不像 shell 那样默认“写也是全局”？**
        - 默认局部是**防意外污染**：函数自成命名空间，内部临时变量不会覆盖全局（否则大项目互相踩名字）。代价就是要显式声明跨层写入——这是刻意的安全设计。

- **扩展信息**
    - **类作用域不在 LEGB 链**：类体内定义的名字，方法里不能直接当外层访问（要 `self.`/类名）——常见混淆点
    - **`globals()`/`locals()`**：运行时查看作用域字典（调试/动态代码用）

## 🤔 生成器与迭代器有什么区别？ 
- **迭代器是协议（实现 `__iter__`/`__next__` 的对象），生成器是“写迭代器最简单的方式”（含 `yield` 的函数自动生成迭代器）。区别：①生成器用 `yield` 一行搞定，迭代器要写类 ②生成器天然惰性（现场算现场给、状态自动保存）③生成器是一次性的。核心：“生成器 = 语法糖版迭代器，惰性求值省内存”。**
    - **迭代器（Iterator，协议层）**
        - 实现**迭代器协议**：`__iter__()` 返回自身、`__next__()` 返回下一个值、耗尽抛 `StopIteration`
        - **可迭代（Iterable）≠ 迭代器**：`list`/`str` 可迭代（有 `__iter__`），但不是迭代器（没有 `__next__`）；`iter(lst)` 才得到迭代器
        - 迭代器**一次性**：遍历完就空了（再遍历需重新 `iter()`）

    - **生成器（Generator，语法层）**
        - **生成器函数**：函数体含 `yield` → 调用返回生成器（不执行函数体），每次 `next()` 执行到下一个 `yield` 暂停（局部变量/执行位置自动保存）
        - **生成器表达式**：`(x*2 for x in data)`（圆括号版推导式，惰性）
        - 是迭代器的**简写**：自动实现协议，不用手写类

    - **核心价值：惰性 + 省内存**
        - 数据不一次性载入内存——读 `10GB` 日志文件逐行处理（`for line in f` 就是文件对象这个迭代器）
        - 无限序列可行：`def counter(): n = 0; while True: yield n; n += 1`
        - 管道组合：`sum(int(x) for x in line.split())` 各环节惰性流式处理

    - **使用注意**
        - 生成器只能消费一次（读完即空；要复用得重新创建或转 `list`）
        - `len()` 不可用（不知道长度）；调试打印需显式消费

- **协助记忆**
    - 口诀：“迭代器是协议（`__iter__`/`__next__`），生成器是 `yield` 语法糖——惰性逐个给、省内存、只能读一遍”。

- **进阶思考**
    - **为什么处理大文件用生成器（不用 `readlines()`）？**
        - `f.readlines()` 一次把全文件装进内存（`10GB` 文件直接 `OOM`）；`for line in f`（文件迭代器）逐行给、处理完就释放——内存恒定。运维场景：日志分析、大 `CSV` 导入都用逐行惰性处理。
    - **`yield` 和 `return` 的区别？**
        - `return` 结束函数并交出全部结果；`yield` 暂停函数交出一个值，**下次 `next()` 从暂停处继续**（局部变量保留）。生成器函数调用时不执行任何函数体，返回生成器对象——首次 `next()` 才开始跑。

- **扩展信息**
    - **`yield from`**：委托子生成器（简化生成器嵌套），协程的前身
    - **相关工具**：`itertools`（`chain`/`islice`/`groupby` 惰性组合器）、`enumerate`/`zip`（本身就是迭代器）

## 🤔 如何高效处理 GB 级别大文件？ 
- **GB 级大文件的核心是“流式处理 + 不全量加载”：逐行迭代（`for line in f`，文件对象就是迭代器）替代 `readlines()`；必要时分块读二进制（`read(size)`/`iter(lambda: f.read(n), b'')`）；压缩文件用 `gzip`/`bz2` 流式解压；配合生成器管道逐层过滤。核心：“内存恒定、逐行/分块流过，绝不一次读入”。**
    - **逐行迭代（文本，最常用）**
        - `with open(path) as f: for line in f: process(line)`——文件对象是迭代器，逐行给、内存 `O(1)`
        - **忌 `f.readlines()`/`f.read()`**：全量进内存（`10GB` 文件直接 `OOM`）

    - **分块读（二进制/无行概念）**
        - `while chunk := f.read(8*1024*1024): process(chunk)`（`8MB` 一块，算 `MD5`/上传分片常用）
        - 迭代器写法：`iter(lambda: f.read(n), b'')` 直到空串

    - **压缩文件流式**
        - `gzip.open(path, 'rt')`/`bz2.open`/`lzma.open` 与 `open` 用法一致（逐行流式解压）
        - 日志按天轮转压缩（`.gz`）直接这样读，不用先解压落盘

    - **生成器管道（复杂处理）**
        - `read_lines() → parse() → filter() → aggregate()` 每级都是生成器，数据流水线惰性流过
        - 好处：解耦处理逻辑、内存恒定、可组合复用

    - **配合技巧**
        - `mmap`（内存映射，超大文件随机访问）、`pandas` 分块 `chunksize`、`linecache`（随机取行）
        - 统计类（求和/计数）适合流式；需要全局排序/去重则外部排序或落数据库

- **协助记忆**
    - 口诀：“大文件三不：不 `read()`、不 `readlines()`、不全量进内存——逐行/分块/生成器管道流过去”。

- **进阶思考**
    - **`for line in f` 为什么内存恒定？**
        - 文件对象实现了迭代器协议，`__next__` 每次只读一行到缓冲、处理完即可回收——同一时刻内存里只有当前行（和固定缓冲）。`readlines()` 则是构造完整列表。
    - **怎么边读边写（原地修改大文件）？**
        - 文件不能“原地插行”——安全做法：读源文件逐行处理写到临时文件，`os.replace()` 原子替换；或先写新文件再改名。忌“边读边写同一文件”（会覆盖未读内容）。

- **扩展信息**
    - **相关 API**：`open` 的 buffering/encoding、`fileinput`（多文件逐行）、`mmap`、`gzip.open`、`shutil.copyfileobj`（流式复制）
    - **运维场景**：日志分析（`grep`/统计 `Top N`）、大 `CSV` 清洗、备份文件校验（分块算 `MD5`）、`nginx` 日志切割合并

## 🤔 什么是面向对象编程（OOP）？ 
- **OOP（面向对象编程）是以“对象”为中心组织代码：把数据（属性）和操作（方法）封装成类，通过封装/继承/多态三大特性建模现实问题。核心：“类是模板、对象是实例——封装藏细节、继承复用扩展、多态统一接口”。**
    - **基本概念**
        - **类（Class）**：对象的模板/图纸（定义属性和方法）
        - **对象/实例（Object/Instance）**：类的具体化（`Server('web1')` 生成实例）
        - **属性（Attribute）**：对象的数据（`self.hostname`）；**方法（Method）**：对象的行为（`self.restart()`）

    - **三大特性**
        - **封装（Encapsulation）**：数据+操作打包，隐藏内部细节（`_private`/`__name` 约定与改名），只暴露接口——降低耦合
        - **继承（Inheritance）**：子类复用/扩展父类（`class CMDB(Server): ...`），`super()` 调父类逻辑——代码复用 + 层次建模
        - **多态（Polymorphism）**：不同类的对象响应同一接口（都实现 `deploy()`，调用方不关心具体类）——`duck typing`：看行为不看类型

    - **Python 中的 OOP 特点**
        - 一切皆对象（类本身也是对象）、支持多继承（MRO 解决菱形问题）、`@property` 优雅封装属性
        - 魔法方法（`__init__`/`__str__`/`__len__`）让自定义对象接入语言协议

    - **什么时候用 OOP**
        - 有明确实体和状态（服务器/主机/任务）、多实例+统一操作（批量管理主机对象）、需要继承扩展的框架
        - 纯脚本/一次性任务用函数即可——不要为 OOP 而 OOP

- **协助记忆**
    - 口诀：“类模板对象实例，封装藏、继承扩、多态换——有状态多实例才用类”。

- **进阶思考**
    - **Python 的多态和 Java 有什么不同？**
        - Python 是 **duck typing**（鸭子类型）：不看继承体系，只要对象有该方法就能调（“走起来像鸭子就是鸭子”）。Java 要显式实现接口/继承父类。Python 更灵活，但错误推迟到运行时（可用 `abc` 抽象基类/`Protocol` 补约束）。
    - **组合优于继承是什么意思？**
        - 继承是“是一个”（强耦合父类实现）；组合是“有一个”（把功能对象作为属性复用）。继承层次深了易碎（父类改动牵连全部子类），优先组合 + 接口小继承——` composability` 比 `inheritance` 更稳。

- **扩展信息**
    - **相关机制**：`@property`/`@abstractmethod`、`__slots__`、MRO（`C3` 线性化）、`dataclass`（自动生成 `__init__` 等）
    - **运维应用**：批量主机管理（`Host` 类 + `deploy()`/`check()`）、`Ansible` 模块开发、`CMDB` 建模

## 🤔 __new__ 与 __init__ 的区别是什么？ 
- **`__new__` 负责“创建实例”（静态方法，先执行，返回实例对象）；`__init__` 负责“初始化实例”（给 `self` 填属性，后执行，不返回值）。`__new__` 没返回实例，`__init__` 根本不会被调用。核心：“`__new__` 生、`__init__` 养——单例/不可变类型定制才动 `__new__`”。**
    - **职责与时机**
        - **`__new__(cls, *args)`**：真正的构造（分配内存/返回实例）。隐式静态方法，第一个参数是**类 `cls`**
        - **`__init__(self, *args)`**：初始化（设置属性）。第一个参数是**实例 `self`**，只能返回 `None`
        - 调用顺序：`obj = A(x)` → 先 `A.__new__(A, x)` 拿到实例 → 再 `__init__(obj, x)`

    - **关键规则**
        - `__new__` 必须返回实例（通常 `return super().__new__(cls)`）；返回了别的类型对象 → `__init__` 不执行
        - 日常业务**不需要碰 `__new__`**（`__init__` 足够）

    - **什么时候用 `__new__`**
        - **单例模式**：类持 `_instance`，`__new__` 里判断已存在就返回同一实例（实现“全局唯一”）
        - **继承不可变类型**：`str`/`int`/`tuple` 子类定制（实例在 `__new__` 阶段已定型，`__init__` 改不了）
        - **元类/对象池**：控制实例创建过程

- **协助记忆**
    - 口诀：“`__new__` 先造人（返回实例），`__init__` 再穿衣（填属性）；`__new__` 不还人，`__init__` 不执行”。

- **进阶思考**
    - **用 `__new__` 写一个单例（运维配置中心场景）？**
        - `class Config: _inst = None;` + `def __new__(cls): if cls._inst is None: cls._inst = super().__new__(cls); return cls._inst`——多处 `Config()` 拿同一实例（配置只加载一次）。注意多线程下要加锁（或用模块级对象天然单例）。
    - **`__init__` 里为什么不能 `return` 值？**
        - 它的返回值被解释器强制忽略（约定只返回 `None`）；实例由 `__new__` 创建后交给 `__init__` 填充。若 `__init__` 返回非 `None` 直接 `TypeError`——返回新对象的活是 `__new__` 的。

- **扩展信息**
    - **相关**：`__del__`（析构，引用计数归零时调用，慎用）、`__call__`（实例可调用）、元类 `__call__` 控制类实例化
    - **单例替代**：模块级全局对象（`config.py` 里直接实例化）、`functools.lru_cache` 的工厂——多数场景比 `__new__` 单例更 Pythonic

## 🤔 @classmethod 类方法、@staticmethod 静态方法区别？ 
- **三者区别在“第一个参数绑定了什么”：实例方法绑 `self`（实例）；`@classmethod` 绑 `cls`（类本身，可访问类属性/造实例）；`@staticmethod` 什么都不绑（就是放进类命名空间的普通函数）。核心：“实例用 `self`、类用 `cls`、都不沾用 `staticmethod`——`classmethod` 最典型用途是替代构造器”。**
    - **三种方法对比**
        - **实例方法**：`def m(self)`——操作实例状态，最常用
        - **`@classmethod`**：`def m(cls)`——第一个参数是**类**；能访问/修改类属性、能 `cls(...)` 创建实例
        - **`@staticmethod`**：`def m()`——没有隐式第一参数，纯工具函数（放类里只为归组/避免散落模块）

    - **`@classmethod` 的杀手锏：替代构造器（factory）**
        - `@classmethod def from_string(cls, s): return cls(*parse(s))`——从字符串造实例
        - `cls(...)` 而非硬编码类名 → **子类调用返回子类实例**（继承友好，工厂可扩展）
        - 运维例：`Server.from_json(data)`、`HostConfig.from_yaml(path)`

    - **`@staticmethod` 的适用**
        - 逻辑上属于类但不依赖实例/类状态的工具：参数校验、格式转换、`is_valid_ip(ip)`
        - 与模块级函数的区别：归组在类下、命名空间清晰

    - **怎么选**
        - 用到实例数据 → 实例方法；需要类信息或造实例 → `classmethod`；都不需要 → `staticmethod`（或干脆模块函数）

- **协助记忆**
    - 口诀：“`self` 管实例、`cls` 管类（造实例当工厂）、`staticmethod` 谁也不管——工具函数归个类”。

- **进阶思考**
    - **为什么替代构造器要用 `classmethod` 而不是 `staticmethod`？**
        - `staticmethod` 里造实例只能写死类名 `Server(...)`——子类调用还是返回父类实例；`classmethod` 用 `cls(...)`，`Sub.from_string(s)` 返回 `Sub` 实例，工厂随继承自动适配。这就是标准库 `dict.fromkeys` 用 `classmethod` 的原因。
    - **`@property` 和这三者是什么关系？**
        - `@property` 把方法伪装成属性（`obj.status` 不加括号），本质是**描述符**——访问器语义（读起来像字段、算起来是方法）。和 `classmethod`/`staticmethod` 一样都是装饰器改变方法绑定方式，只是目标不同：`property` 管属性访问、`classmethod` 管类绑定。

- **扩展信息**
    - **相关**：`@property`/`@x.setter`、`functools.cached_property`（首次访问缓存）、抽象类 `@classmethod` 工厂
    - **运维场景**：配置类多来源构造（`from_yaml`/`from_env`）、`Inventory.from_file()`、工具校验函数收进类

## 🤔 hasattr() 和 getattr() 有什么作用？ 
- **两者都是反射（运行时按名字访问对象成员）：`hasattr(obj, 'name')` 判断有没有该属性/方法（存在返回 `True`）；`getattr(obj, 'name', default)` 按字符串名字取属性/方法（可带默认值，取到方法还能直接调用）。核心：“把属性名从写死变成字符串参数——按配置/输入动态调用”。**
    - **`hasattr(obj, name)`**
        - 判断对象是否有某属性/方法：`hasattr(host, 'restart')` → 决定是否调用
        - 实现：内部尝试 `getattr`，异常（`AttributeError`）返回 `False`
        - 注意：会**触发** `property` 的 getter（有副作用/抛错的 `property` 要小心）

    - **`getattr(obj, name, default)`**
        - 按字符串取属性：`getattr(server, 'ip')` 等价 `server.ip`，但名字可以是变量
        - **带默认值**：属性不存在返回 `default`（不抛异常）——`getattr(cfg, 'timeout', 30)`
        - **取方法并调用**：`action = getattr(host, op_name); action()`——命令分发器

    - **典型应用（运维向）**
        - **命令/动作分发**：CLI 输入 `restart` → `getattr(module, f'do_{action}')` 动态调用（替代一长串 `if/elif`）
        - **配置容错读取**：对象配置里可选字段用 `getattr(obj, key, default)`
        - **插件机制**：模块里有没有 `handle` 函数就注册（`hasattr` + `getattr`）
        - **批量探测**：`[m for m in ('cpu', 'mem', 'disk') if hasattr(agent, f'get_{m}')]`

    - **配套**
        - `setattr(obj, name, value)` 动态设置、`delattr(obj, name)` 删除
        - `dir(obj)` 列出全部属性名（配合过滤探索对象）

- **协助记忆**
    - 口诀：“`hasattr` 问有没有，`getattr` 按名取（带默认不抛错），取到方法直接调——名字字符串化，分发全靠它”。

- **进阶思考**
    - **用字符串分发方法和写 `if/elif` 链怎么权衡？**
        - 动作少且固定 → `if/elif` 直观；动作多/可扩展（插件）→ `getattr(module, f'do_{name}')` + 注册表。注意安全：名字来自用户输入时必须**白名单校验**（前缀限定 `do_` + `hasattr` 确认），否则可能调到不该调的方法（如 `__class__`）。
    - **`getattr` 会不会破坏代码提示/静态检查？**
        - 会——IDE/`mypy` 看不到动态名字的调用关系。核心接口写显式调用保可读，边缘/插件场景用动态；`getattr` 处加注释和默认值兜底，降低维护成本。

- **扩展信息**
    - **反射家族**：`getattr`/`hasattr`/`setattr`/`delattr`、`__getattr__`/`__getattribute__`（拦截访问）、`inspect` 模块（更丰富的 introspection）
    - **`__getattr__` 区别**：类上定义 `__getattr__` 是“找不到属性时的钩子”（惰性加载/代理对象常用），和内置 `getattr` 函数不是一回事

## 🤔 super() 工作原理是什么？ 
- **`super()` 返回一个“沿 MRO（方法解析顺序）查找下一个类”的代理对象：调用 `super().m()` 不是“父类”，而是 MRO 链上当前类的下一个类（可能是兄弟类）。这解决了多继承/菱形继承的重复调用问题，并让协作式继承（每层只写增量）成为可能。核心：“`super` 找的是 MRO 下一个，不是父类”。**
    - **基本行为**
        - `super().__init__(...)`：调用 MRO 中**当前类的下一个**类的 `__init__`
        - 无参 `super()`（Python 3）自动绑定当前类与第一个参数——等价 `super(当前类, self)`

    - **MRO（方法解析顺序）**
        - 多继承时确定“先找谁”的线性顺序（Python 3 用 **C3 线性化**）
        - 查看方式：`ClassName.__mro__` 或 `ClassName.mro()`
        - 保证：子类在父类前、父类间保持声明顺序、同一类只出现一次

    - **菱形继承与协作式初始化**
        - `D(B, C)`，`B/C` 都继承 `A`：若 `B`/`C` 都用 `super().__init__()`，`A.__init__` **只执行一次**（沿 MRO 传递）
        - 若手动写 `B.__init__(self)`/`C.__init__(self)` 反而会重复调用 `A`——多继承下必须用 `super()`
        - 每层只写自己的增量 → “协作式继承”，参数用 `*args/**kwargs` 往下传

    - **常见坑**
        - **单继承心智误区**：`super()` ≈ 父类没错，但多继承里“下一个类”可能出乎意料（要看 MRO）
        - **参数不匹配**：链上各层 `__init__` 签名不同时，`super().__init__()` 传参会断——需要设计统一签名或 `**kwargs` 透传
        - 混用 `super()` 和显式父类调用 → 链路断裂/重复执行

- **协助记忆**
    - 口诀：“`super` = MRO 下一个（不是父类）；菱形继承必用 `super`，各层只加增量、`**kwargs` 往下传”。

- **进阶思考**
    - **为什么说“多继承里不用 super() 会重复初始化”？**
        - 手动 `B.__init__(self)` + `C.__init__(self)`，`B`/`C` 内部又各自调 `A.__init__` → `A` 初始化两次（状态被重置/资源重复申请）。`super()` 按 MRO 串成链，`A` 只会轮到一次——这是 `super()` 存在的核心意义。
    - **`super()` 在运维代码里哪里用得多？**
        - 封装主机/任务类时扩展父类初始化（`super().__init__(**kwargs)` 透传再加自己的字段）；插件基类约定：子类重写 `run()` 前后统一 `super().run()` 保钩子执行。

- **扩展信息**
    - **查看 MRO**：`A.__mro__`、`inspect.getmro(cls)`；`mro()` 与 C3 算法
    - **设计建议**：继承链超过 2-3 层就考虑组合；框架类（`unittest.TestCase`/`Django View`）里按规范 `super()`，钩子才不会漏

## 🤔 with 语句的原理是什么？ 
- **`with` 是上下文管理器协议的语法糖：进入时调用对象的 `__enter__`（拿到资源），无论成功/异常退出时必调 `__exit__`（释放资源）——等价于 `try/finally` 的标准化封装，保证“打开必关闭”。核心：“`__enter__` 拿资源、`__exit__` 兜底清理，异常也不会漏释放”。**
    - **工作流程**
        - `with expr as x:` → ①调用 `expr.__enter__()`，返回值绑定给 `x` ②执行块内代码 ③无论正常/异常，调用 `expr.__exit__(exc_type, exc_val, tb)`
        - `__exit__` 返回 `True` → **吞掉异常**（不再向外抛）；返回 `False`/`None` → 异常继续传播（默认行为是释放资源后照常抛）

    - **解决的痛点**
        - **必释放**：文件/锁/连接忘关、异常路径漏 `close` → `with` 结构上保证清理
        - **等价手写**：`f = open(...)` + `try: ... finally: f.close()` 的简洁替代

    - **常用场景（运维向）**
        - **文件**：`with open(path) as f:`（读大文件逐行）
        - **锁**：`with lock:`（临界区自动加解锁，`threading.Lock` 支持协议）
        - **网络/数据库连接**：`requests.Session`/`cx_Oracle`/`redis` 客户端支持时用 `with`（自动归还/断开）
        - **临时目录**：`tempfile.TemporaryDirectory()`（退出自动删除）
        - **事务**：`with conn:` 自动提交/回滚

    - **自定义上下文管理器**
        - 实现 `__enter__`/`__exit__` 的类；或 `@contextlib.contextmanager` + `yield`（更简洁：`yield` 前是 `__enter__`、后是 `__exit__`，`finally` 里清理）

- **协助记忆**
    - 口诀：“`with` = 自动 `finally`：进走 `__enter__`、出走 `__exit__`，异常也逃不掉；想吞异常 `__exit__` 返回 `True`”。

- **进阶思考**
    - **`__exit__` 返回 `True` 有什么风险？**
        - 它会**静默吞掉**块内所有异常（包括 `KeyboardInterrupt` 之外的真实错误），问题被掩盖。除非明确做异常转换（如把库异常转为自定义异常），默认返回 `None` 让异常正常抛出。
    - **`@contextmanager` 和类实现怎么选？**
        - 简单的一次性清理（`yield` 前后逻辑）用 `@contextmanager` 少写一个类；需要复用状态、多方法、或非 `with` 场景也能用 → 类实现 `__enter__/__exit__`。两者最终都走同一协议。

- **扩展信息**
    - **`contextlib` 家族**：`contextmanager`、`closing`（自动 `close`）、`suppress(Exception)`（吞指定异常）、`ExitStack`（动态管理多个上下文）
    - **运维价值**：批量文件处理/连接池/分布式锁/临时配置切换——凡“用完必须还原”的操作都套 `with`

## 🤔 多进程、多线程、协程的区别是什么？ 
- **三者是不同层级的并发模型：多进程（多个 Python 解释器，真并行，绕开 `GIL`，吃 CPU）→ 多线程（进程内多条执行流，系统调度，受 `GIL` 限制但 `IO` 等待时可切换）→ 协程（用户态单线程内切换，最轻量，专为高并发 `IO`）。核心：“`CPU` 密集用多进程、线程池够用的 `IO` 用线程、海量高并发 `IO` 用协程”。**
    - **多进程（multiprocessing）**
        - 每个进程独立解释器+独立 `GIL` → **真并行**，充分利用多核
        - 适合 `CPU` 密集（批量计算/压缩/解析）；开销大（启动慢、内存多、`IPC` 通信/序列化成本）
        - `concurrent.futures.ProcessPoolExecutor` 最省心

    - **多线程（threading）**
        - 同进程内共享内存，由 OS 调度；受 **GIL** 限制（同一时刻仅一个线程执行 Python 字节码）
        - **`IO` 密集可用**：等网络/磁盘时释放 `GIL`，线程切出去干活（爬虫/批量请求/文件 `IO`）
        - 共享状态需加锁（`threading.Lock`）；线程数不宜过多（上下文切换开销）

    - **协程（asyncio/async-await）**
        - **单线程用户态切换**：`async/await` + 事件循环，遇到 `await` 主动让出——切换成本极低（函数级），可支撑**数万并发**
        - 适合高并发 `IO`（网关/采集上万设备/长连接服务）；**忌在协程里跑 CPU 密集/阻塞调用**（会卡死整个循环）
        - 生态：`aiohttp`/`asyncpg`/`aiomqtt`——`IO` 库必须用异步版

    - **对比与选择**
        - **`CPU` 密集** → 多进程（绕 `GIL` 真并行）
        - **少量并发 `IO`**（几十~几百）→ 线程池（简单、生态全）
        - **海量并发 `IO`**（千~万+）→ 协程（轻量高并发）
        - 混合场景：进程池 + 每进程内线程/协程（`ProcessPoolExecutor` + `asyncio`）

- **协助记忆**
    - 口诀：“`CPU` 密集开进程，`IO` 密集线程够，海量并发上协程——进程重、线程中、协程轻”。

- **进阶思考**
    - **为什么协程能扛万级并发而线程不行？**
        - 线程：每个占 `MB` 级栈内存 + 内核调度切换成本，千级线程系统就吃紧；协程：用户态切换（保存恢复函数栈，几百字节级），单线程内 `await` 让出，万级并发内存/切换都可控。代价是全链路异步（一个阻塞调用毁掉整个事件循环）。
    - **GIL 到底挡住了什么（多线程就没用吗）？**
        - `GIL` 挡的是“同一时刻多线程执行 Python **字节码**”——`CPU` 密集多线程毫无加速；但**阻塞 `IO`**（网络/磁盘/`sleep`）会释放 `GIL`，所以 `IO` 密集多线程依然有效。另外 `C` 扩展（`numpy`/哈希计算）可在内部释放 `GIL`，也能并行。

- **扩展信息**
    - **标准库**：`concurrent.futures`（`ThreadPool/ProcessPoolExecutor` 统一接口）、`multiprocessing`、`asyncio`、`queue`（跨线程/进程通信）
    - **运维场景**：批量主机采集（线程池）、万级设备监控（`asyncio` + `MQTT`）、日志并行压缩（进程池）

## 🤔 GIL（全局解释器锁）有什么作用？ 
- **`GIL` 是 CPython 的“一把大锁”：保证同一时刻只有一个线程执行 Python 字节码，作用是保护解释器内部状态（引用计数/内存管理）的线程安全，简化 CPython 实现。代价：多线程无法并行执行 CPU 密集的 Python 代码。核心：“`GIL` 护解释器不护你的并发——`CPU` 密集靠多进程，`IO` 密集线程照样有效”。**
    - **GIL 是什么**
        - CPython 解释器级的互斥锁：线程要执行字节码必须先拿到 `GIL`
        - 目的：让引用计数等内部数据结构免于复杂加锁（实现简单、单线程性能高）
        - **注意**：`GIL` 是 **CPython 实现细节**，不是 Python 语言规范（`Jython`/`IronPython` 无 `GIL`）

    - **影响（关键结论）**
        - **`CPU` 密集 + 多线程**：无加速（甚至因锁竞争变慢）→ 应对：`multiprocessing` 多进程/`C` 扩展
        - **`IO` 密集 + 多线程**：**有效**——网络/磁盘等待时线程会**释放 `GIL`**，别的线程继续跑
        - **`C` 扩展可绕开**：`numpy`/`hashlib`/压缩库在 `C` 层计算时会释放 `GIL`，照样并行

    - **工作机制（简化）**
        - 线程默认执行一段就主动让出 `GIL`：旧版用 `sys.setcheckinterval`（按字节码指令数），新版用 `sys.setswitchinterval`（默认时间片 5ms）；遇到 `IO`/系统调用立即释放
        - 换回需要争抢 → 多线程频繁切换带来额外开销

    - **应对策略（运维开发向）**
        - `CPU` 密集批量任务 → `ProcessPoolExecutor`（进程各自有 `GIL`，真并行）
        - 高并发 `IO` → 线程池或 `asyncio` 协程（单线程事件循环根本绕开线程竞争）
        - 计算下沉 `C`：`numpy` 向量化/`hashlib`/`Pillow`——扩展内部并行
        - 前瞻：`PEP 703`（`free-threading` 无 `GIL` 构建）已在演进，Python 3.13+ 提供实验性无 `GIL` 版本

- **协助记忆**
    - 口诀：“一把锁锁字节码：`CPU` 密集被锁死（上进程），`IO` 等待会放锁（线程可用），`C` 扩展能绕行”。

- **进阶思考**
    - **为什么 CPython 不干脆去掉 GIL？**
        - 去掉 `GIL` 需要给所有对象/引用计数加细粒度锁——单线程性能大幅下降、`C` 扩展兼容性破坏。官方路线是 `free-threading` 独立构建（按需选择），而非直接替换默认版——兼容性与性能的折中。
    - **批量给 100 台机器算文件 `MD5`，怎么用对并发模型？**
        - `MD5` 由 `hashlib`（`C` 实现）计算，读盘是 `IO`、计算时释放 `GIL`——**线程池**就有不错的加速；若算完还要做大量 Python 层统计（纯 `CPU`），再叠加进程池。先分清“瓶颈在哪一层”再选模型。

- **扩展信息**
    - **相关**：`sys.setswitchinterval`（切换间隔）、`multiprocessing`、`concurrent.futures`、`asyncio`
    - **PEP 703 / free-threading**：`Python 3.13` 起官方提供无 `GIL` 实验构建，生态正在跟进——长期方向但生产仍在过渡期

## 🤔 简述 Python 垃圾回收机制工作原理？ 
- **CPython 回收内存以引用计数为主（对象计数归零立即释放）+ 标记-清除兜底处理循环引用 + 分代回收提升效率（0/1/2 三代，越老越少扫）。核心：“引用计数即时回收，循环引用靠标记-清除，分代降低扫描成本”。**
    - **引用计数（主力，即时回收）**
        - 每个对象记录“被引用次数”；`a = obj` +1、`del a`/离开作用域 -1；**归零立即释放**（确定性回收）
        - 查看计数：`sys.getrefcount(obj)`（调用本身临时 +1）
        - 局限：**循环引用**（`a.b = b; b.a = a`）计数永远不为 0 → 漏回收

    - **标记-清除（解决循环引用）**
        - 从根对象（当前栈/全局/模块）出发**标记**可达对象，**清除**不可达的——循环引用双方若外界不可达，一起回收
        - 只对“容器对象”（`list`/`dict`/自定义类实例等可能形成环的）做，开销较大所以不是每次都跑

    - **分代回收（性能优化）**
        - 经验假设：“对象越老越不容易死”——按存活时间分 **0/1/2 三代**
        - 新对象进 0 代；0 代扫描后存活晋升 1 代，1 代晋升 2 代；0 代扫描频繁、2 代稀少 → 摊薄总开销
        - 触发条件：各代分配/释放数量差超过阈值（`gc.get_threshold()`，默认 `(700, 10, 10)`）

    - **运维相关（实战要点）**
        - **内存不降不一定泄漏**：`gc` 回收有滞后；`del` 后计数是否归零、有无全局容器攥着引用才是关键
        - **`gc.disable()` 慎用**：纯短生命周期服务可关（省暂停），但长跑进程会积累循环垃圾
        - 大量临时对象/长列表 → 考虑生成器、`__slots__` 减压

- **协助记忆**
    - 口诀：“引用计数即时清，环引用标记-清除兜底，分三代少扫描——泄漏先查引用攥在哪”。

- **进阶思考**
    - **`del x` 之后内存为什么没立刻还给系统？**
        - `del` 只是减引用计数；计数归零对象被销毁，但内存归还的是 **pymalloc 内存池**（供后续对象复用），不一定立刻返还 OS——所以 `RSS` 不降不代表泄漏。判断泄漏看趋势：持续增长且不回落才是。
    - **`__del__` 方法有什么坑？**
        - 调用时机不确定（依赖 `gc`）、循环引用中可能不被调用、解释器退出时全局已部分销毁易报错——资源清理应该用 `with`/`try-finally`，`__del__` 只做兜底且要极简。

- **扩展信息**
    - **相关 API**：`gc.collect()`（手动触发）、`gc.get_count()`/`get_threshold()`/`set_threshold()`、`gc.freeze()`（`fork` 前冻结）
    - **内存诊断**：`tracemalloc`（分配追踪）、`objgraph`（引用关系/环可视化）、`pympler`——排查泄漏三件套

## 🤔 Python 如何递归遍历目录并统计总文件数量？ 
- **首选 `pathlib.Path.rglob('*')` 或 `os.walk()` 递归遍历，文件计数：`os.walk` 按目录产出 `(dirpath, dirs, files)`，累加 `len(files)`；`rglob` 直接筛 `is_file()`。核心：“`os.walk` 效率高（生成器逐目录给）、`pathlib` 更简洁，按类型过滤用 `Path` 的方法”。**
    - **方式一：`os.walk()`（经典，性能好）**
        - `total = sum(len(files) for _, _, files in os.walk(top_dir))`
        - `os.walk` 是生成器：逐目录产出 `(当前路径, 子目录列表, 文件列表)`，不一次性加载树
        - 带过滤：遍历时改 `dirs[:]`（原地筛掉要跳过的目录，如 `.git`/`node_modules`）——注意必须改 `dirs[:]` 本身

    - **方式二：`pathlib.Path.rglob()`（现代，简洁）**
        - `total = sum(1 for f in Path(dir).rglob('*') if f.is_file())`
        - `rglob('*')` 递归匹配全部；`Path.iterdir()` 只看一层；配合 `glob('*.log')` 按模式
        - `f.stat()` 可顺便算总大小/按扩展名统计

    - **按类型/大小统计（运维常用）**
        - 分组计数：`Counter(f.suffix for f in Path(d).rglob('*') if f.is_file())`
        - 大文件排查：`sorted((f.stat().st_size, f) for f in ... )` 找最大文件

    - **性能与边界**
        - 软链接：`os.walk(followlinks=False)` 默认不跟（防死循环）；`Path.rglob` 不跟目录软链
        - 权限不足目录会抛 `PermissionError`——包 `try/except` 跳过
        - 海量文件（百万级）用 `os.scandir`/`walk` 更快（`scandir` 附带文件类型，少一次 `stat`）

- **协助记忆**
    - 口诀：“统计用 `os.walk` 累加 `len(files)`，优雅用 `Path.rglob`；跳目录改 `dirs[:]`，权限异常要兜住”。

- **进阶思考**
    - **`os.walk` 怎么跳过指定目录？**
        - 遍历中 `dirs[:] = [d for d in dirs if d not in ('.git', 'logs')]`——**必须原地切片赋值**（`dirs = ...` 只换局部名，不影响后续遍历）。这是 `os.walk` 最经典的坑。
    - **百万级文件统计怎么提速？**
        - ①`os.scandir`（`DirEntry` 自带 `is_file` 结果，省 `stat` 系统调用）②多进程分目录并行（`ProcessPoolExecutor` 每个子树一个任务）③只统计必要信息（避免全量 `stat`）。

- **扩展信息**
    - **相关 API**：`os.walk`/`os.scandir`/`os.stat`、`pathlib.Path`（`rglob`/`glob`/`stat`/`suffix`）、`glob.glob(recursive=True)`
    - **运维场景**：磁盘小文件普查、日志目录按天统计大小、`inode` 排查（海量小文件）、清理前预览（`find` 的 Python 版）

## 🤔 Python 如何执行 Shell 命令？ 
- **用 `subprocess.run()`（标准推荐）：参数列表形式 `run(['ls', '-l'], capture_output=True, text=True, check=True)`——拿返回码/输出、可控超时；需要 shell 特性（管道/通配）才 `shell=True`。核心：“`subprocess.run` + 列表参数 + `check/capture`；`os.system` 不推荐用，`os.popen`/`commands` 已淘汰”。**
    - **标准姿势：`subprocess.run`**
        - `r = subprocess.run(['df', '-h'], capture_output=True, text=True, timeout=30)`
        - 结果：`r.returncode`（0 成功）、`r.stdout`/`r.stderr`（`text=True` 时是 `str`，否则 `bytes`）
        - **`check=True`**：返回码非 0 直接抛 `CalledProcessError`（脚本里快速失败）
        - **`timeout=30`**：超时抛 `TimeoutExpired`（防止命令挂死拖垮脚本）

    - **`shell=True` 什么时候用**
        - 需要管道/重定向/通配：`run('cat access.log | awk \'{print $1}\' | sort -u', shell=True)`
        - **安全警告**：`shell=True` + 拼接用户输入 = 命令注入风险——输入必须走 `shlex.quote()` 或尽量用列表参数

    - **进阶用法**
        - **管道串联**：`p1 = Popen([...], stdout=PIPE); p2 = Popen([...], stdin=p1.stdout, ...)`
        - **流式读输出**：逐行 `for line in p.stdout`（长任务实时处理，不憋内存）
        - **后台进程**：`Popen([...], start_new_session=True)` 脱离会话（守护化）
        - **环境变量**：`env={**os.environ, 'EXTRA': '1'}`

    - **历史方案（了解即可）**
        - `os.system`（只返回码、无法捕获输出，**未被正式废弃但不建议**）、`os.popen`（老式管道）、`commands`（Python 2 已删，**已被移除**）——**统一用 `subprocess`**

- **协助记忆**
    - 口诀：“执行命令就 `subprocess.run`：列表参数防注入、`capture` 拿输出、`check` 快失败、`timeout` 防挂死”。

- **进阶思考**
    - **列表参数和 `shell=True` 的本质区别？**
        - 列表参数：Python 直接 `execvp`，参数原样传递，**不经过 shell 解释**（`;`/`|`/`$VAR` 都是普通字符）——注入面小；`shell=True` 起一个 `/bin/sh -c`，字符串被 shell 展开/解析——功能强但危险。原则：能用列表就不用 `shell=True`。
    - **远程批量执行怎么组合？**
        - `subprocess` + `ssh`（`run(['ssh', host, cmd])`，配 `BatchMode=yes` 免交互）——多机并发用线程池/`asyncio` 包起来；更进一步用 `paramiko`（纯 Python SSH 库，可控 `SFTP`/隧道）或直接上 `Ansible`。

- **扩展信息**
    - **相关 API**：`subprocess.run`/`Popen`/`CompletedProcess`、`shlex.split`（把命令串安全拆成列表）、`shlex.quote`
    - **运维场景**：封装系统命令（磁盘/服务/网络检查）、`CI` 脚本调用、采集命令输出入库、自动化巡检

## 🤔 Python 如何实现在 100 台服务器上执行脚本？ 
- **核心是“SSH 通道 + 并发 + 结果聚合”：①简单场景 `subprocess` + `ssh` 配线程池（`ThreadPoolExecutor`）并发执行 ②纯 Python 用 `paramiko`（SSH 库，可控超时/密钥/SFTP）+ 并发 ③生产级直接用 `Ansible`（`ad-hoc`/`playbook`，幂等+失败重试）。核心：“SSH 到 100 台 → 线程/协程并发 → 超时与失败收集 → 结果汇总报告”。**
    - **方案一：`subprocess` + `ssh` + 线程池（最快落地）**
        - 前提：主机互信（`ssh-keygen` + `ssh-copy-id` 分发公钥）、`Host` 列表文件
        - `ThreadPoolExecutor(20)` + `run(['ssh', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=5', host, cmd])`
        - `BatchMode=yes` 禁止交互（密码提示直接失败）、`ConnectTimeout` 防挂死
        - 收集 `(host, returncode, stdout, stderr)` 汇总——失败的重试或单独报告

    - **方案二：`paramiko`（纯 Python SSH）**
        - `SSHClient` + `AutoAddPolicy` + 私钥/密码认证，`exec_command(cmd)` 拿 `stdin/stdout/stderr`
        - 优点：不依赖系统 `ssh`、可设超时、可 `SFTP` 传文件、异常可控
        - 并发：线程池（每主机一个连接）或 `asyncio` + `asyncssh`

    - **方案三：`Ansible`（生产推荐）**
        - `ansible all -i hosts.ini -m shell -a 'uptime' -f 20`（20 并发）
        - 优势：免写代码、幂等模块、失败重试、结果格式化、`playbook` 固化成任务
        - `Python` 里可调 `ansible-runner`/`ansible-python api` 集成

    - **工程要点（100 台规模的坑）**
        - **并发度**：20-50 合理（太高压垮自己网络/目标机 `sshd`）
        - **超时**：连接超时 + 命令超时都要设（防个别机器卡死拖整批）
        - **失败收集**：哪台成功/失败/超时分类输出，失败重试队列
        - **凭据安全**：密钥而非密码、`known_hosts` 管控、日志脱敏

- **协助记忆**
    - 口诀：“互信打底，线程池 + `ssh` 并发跑，`BatchMode`/超时防挂死；生产上 `Ansible`，`paramiko` 补定制”。

- **进阶思考**
    - **为什么用线程池而不是进程池跑 SSH？**
        - SSH 执行是 `IO` 密集（等网络回包），线程足够且轻量（`GIL` 在等待时释放）；进程池启动开销大、内存翻倍，只有命令本身要吃 `CPU` 时才考虑。百台规模线程池（20-50 并发）几秒到几十秒能完成。
    - **执行结果怎么保证可信（有机器假成功）？**
        - ①校验返回码 + 关键输出（命令 `echo` 特定标记）②抽查/全量比对预期结果（如版本号一致性）③失败/超时机器单独列出人工复核。批量操作后必须给“主机-结果”清单，而不是一句“执行完成”。

- **扩展信息**
    - **相关库**：`paramiko`/`asyncssh`/`fabric`（`paramiko` 封装，部署脚本利器）、`plumbum`、`ansible`/`ansible-runner`
    - **运维进阶**：`Fabric` 写批量部署脚本很顺手；长期固化任务上 `Ansible playbook` + `CI`；万级规模考虑 `SaltStack`/自研 Agent

## 🤔 Socket 是什么？有哪些应用场景？ 
- **`Socket`（套接字）是网络通信的端点抽象：`IP + 端口 + 协议` 组成的“门牌”，应用通过它读写网络数据——它是 `HTTP`/`SSH`/`MySQL` 等一切网络应用的底层。类型：`TCP`（可靠流，`SOCK_STREAM`）、`UDP`（无连接报文，`SOCK_DGRAM`）、`Unix Socket`（本机进程间，性能更高）。核心：“一切网络通信都建立在 socket 之上”。**
    - **Socket 是什么**
        - 操作系统提供的网络编程接口（`API`）：应用 ↔ 内核网络栈的通道
        - 定位五元组：协议 + 源 `IP` + 源端口 + 目的 `IP` + 目的端口（一条连接的唯一标识）

    - **三种主要类型**
        - **TCP（`SOCK_STREAM`）**：面向连接、可靠有序、流量/拥塞控制——`HTTP`/`SSH`/数据库
        - **UDP（`SOCK_DGRAM`）**：无连接、低开销、不保证送达——`DNS`/监控打点/视频流
        - **Unix Domain Socket**：本机进程间通信（文件路径而非 `IP`），不走网络栈更快——`MySQL`/`Docker`/`PHP-FPM` 本机场景

    - **TCP 编程模型（Python）**
        - 服务端：`socket() → bind(地址) → listen() → accept()` 循环收连接 → `recv/send`
        - 客户端：`socket() → connect() → send/recv → close()`
        - Python 示例：`s = socket.socket(AF_INET, SOCK_STREAM); s.bind(('0.0.0.0', 9000)); s.listen(5)`

    - **应用场景（运维向）**
        - **服务探活**：`socket.create_connection((ip, port), timeout=2)` 判端口通（比 `ping` 准）
        - **自定义采集/Agent 通信**：`Agent` 与服务端长连接上报（自定义协议或 `MQTT`/`HTTP`）
        - **端口监听检查**：`ss/netstat` 之外用脚本验证服务真实可连
        - **本机加速**：应用连本机 `MySQL`/`Redis` 走 `Unix Socket`（省 `TCP` 开销）
        - **日志/打点**：`syslog`（UDP/Unix socket）、`statsd`（UDP 打点）

- **协助记忆**
    - 口诀：“socket = `IP`+端口+协议的网络门牌；`TCP` 可靠、`UDP` 轻快、本机走 Unix socket——探活/Agent/自定义协议都靠它”。

- **进阶思考**
    - **写 TCP 服务为什么一般用 `socketserver`/框架而不是裸 socket？**
        - 裸 `socket` 的 `accept/recv` 循环是单线程阻塞，一次只能服务一个客户端；生产要并发（每连接线程/`asyncio`）、处理半包/粘包、优雅关闭——`socketserver.ThreadingTCPServer`、`asyncio.start_server` 或现成框架（`gunicorn`/`uvicorn`）都封装好了。裸 socket 适合理解原理和小工具。
    - **`TIME_WAIT` 大量堆积和 socket 什么关系？**
        - 主动关闭方进入 `TIME_WAIT`（等 2MSL 确保对端收到 `FIN` 重传）——短连接高频（压测/爬虫）会堆积几万个端口。缓解：客户端复用连接（连接池/长连接）、`SO_REUSEADDR`（服务端重启快）、内核参数 `tw_reuse`。本质是“短连接太多”，治本是长连接化。

- **扩展信息**
    - **相关库**：`socket`/`socketserver`/`asyncio`（`open_connection`/`start_server`）、`select`/`selectors`（`IO` 多路复用）、`paramiko`（`SSH` 协议跑在 TCP 上）
    - **排查工具**：`ss -antp`（连接状态）、`tcpdump`（抓包看协议交互）、`nc`（手工 socket 测试）

## 🤔 你都用 Python 调用过哪些 API 及应用场景？ 
- **运维开发中 Python 调 API 主线是“云厂商 + 监控 + 协作工具”：阿里云 `SDK`（`ECS`/`OSS`/`SLB`/`RAM` 管理）、`K8s` `client`（资源操作）、`Prometheus`/`Zabbix`（指标拉取/告警）、企业微信/钉钉/飞书机器人（告警通知）、`GitHub`/`GitLab`（`CI` 集成）。核心：“`requests` 通用打底，官方 `SDK` 优先，认证凭据走配置/环境变量”。**
    - **云厂商 API（资源自动化）**
        - **阿里云**：`aliyun-python-sdk-*`/`alibabacloud-*`——`ECS` 启停/创建、`OSS` 传取、`SLB` 挂载、`RAM` 临时凭证
        - **场景**：定时开关测试环境 `ECS`（省成本）、`OSS` 日志归档、自动扩缩容脚本
        - 认证：`AccessKey` 走环境变量/`RAM` 角色实例（**忌硬编码**）

    - **K8s API（容器平台）**
        - `kubernetes` 官方 client：读 `Pod`/`Node` 状态、扩缩容 `Deployment`、批量清理 `Evicted`
        - 场景：集群巡检脚本、`Pod` 异常自动处理、自定义 `CRD` 操作

    - **监控 API（数据/告警）**
        - **Prometheus**：`/api/v1/query`（`PromQL` 查询当前/历史指标）、`/api/v1/alerts`
        - **Zabbix**：`zabbix-api`（主机/触发器/事件管理）
        - 场景：巡检报告拉数据、容量预测、告警闭环自动化

    - **协作/通知 API（告警闭环）**
        - **企业微信/钉钉/飞书机器人 webhook**：`POST` 一个 `JSON` 就能把告警/日报推进群
        - **工单系统**（`Jira`/`ServiceNow`）：告警自动建单/关联

    - **开发协作 API**
        - **GitHub/GitLab**：拉 `MR` 状态、自动打标签、`CI` 触发
        - **CMDB/内部平台**：资产查询、变更单流转

    - **调用通用规范**
        - `requests.Session` 复用连接、**超时必设**（`timeout=(3, 10)`）、重试用 `urllib3.Retry`/装饰器、限速控制（防打爆对端）、异常捕获 + 日志

- **协助记忆**
    - 口诀：“云管资源（阿里云/`K8s`）、监控取数（`Prometheus`/`Zabbix`）、通知闭环（企微/钉钉 webhook）——`requests` 打底 + 官方 `SDK`，凭据绝不硬编码”。

- **进阶思考**
    - **调用第三方 API 最容易踩什么坑？**
        - ①没设超时（对方挂了你的脚本也挂）②没处理限流（`429`，要退避重试）③分页没拉全 ④凭据硬编码进代码/日志。规范：`timeout` + 重试（指数退避）+ 分页封装 + 凭据走环境变量/`KMS` + 日志脱敏。
    - **官方 SDK 和直接 requests 调 REST 怎么选？**
        - 有官方 `SDK` 优先（签名/重试/类型模型都封装好，如阿里云 `SDK` 的 `V3` 签名手写极易错）；`SDK` 覆盖不到的接口、或轻量一两个接口的场景，`requests` + 官方签名文档直接调。关键看签名复杂度和维护成本。

- **扩展信息**
    - **相关库**：`requests`/`httpx`（同步/异步）、各云官方 `SDK`、`kubernetes`、`prometheus-api-client`、`PyGithub`/`python-gitlab`
    - **架构化**：告警通知统一封装（级别→路由到不同群/值班）、`API` 调用统一中间层（重试/限速/审计一处管）

## 🤔 如何排查 Python 程序的内存泄漏问题？ 
- **排查思路“先确认 → 找增量 → 定对象 → 查引用”：①监控确认 `RSS` 持续涨（排除正常缓存）②`tracemalloc` 对比快照找增长最多的分配点 ③`objgraph` 看对象数量增长和引用链 ④常见根因：全局容器只进不出、闭包/缓存无限累积、循环引用带 `__del__`、`C` 扩展泄漏。核心：“快照对比 + 对象增长定位 + 引用链追凶”。**
    - **第一步：确认是泄漏（不是正常增长）**
        - 监控 `RSS`/进程内存随时间趋势（`Prometheus`+`node_exporter`/容器 `memory`）
        - 排除合理缓存（`lru_cache` 有上限的没事）、连接池预热——看“持续单调上涨且 `gc.collect()` 后不回落”

    - **第二步：`tracemalloc`（定位分配点，首选）**
        - `tracemalloc.start()` → 运行一段时间拍快照 `snapshot1` → 再拍 `snapshot2`
        - `snapshot2.compare_to(snapshot1, 'lineno')` → 按**代码行**列出增长最多的分配——直击“哪行代码在涨”
        - 生产可采样降低开销（`tracemalloc.start(5)` 保留 5 帧栈）

    - **第三步：`objgraph`（定位对象与引用链）**
        - `objgraph.growth()`：对比两次对象类型计数增长（哪类对象暴涨）
        - `objgraph.show_most_common_types()`：当前对象 `Top`（`dict` 多？`Request` 多？）
        - `objgraph.show_backrefs([obj])`：画引用链——看“谁攥着这个对象不放”（全局列表/缓存/闭包）
        - `gc.get_referrers(obj)`：标准库版查引用者

    - **常见根因清单**
        - **全局容器只进不出**：日志缓存/请求记录 `append` 无上限、`dict` 当缓存无淘汰
        - **闭包/回调持有**：注册的回调/事件监听从不注销（长连接场景常见）
        - **缓存无界**：自写缓存不用 `lru_cache(maxsize)`、`functools` 默认无限
        - **循环引用 + `__del__`**：带析构的循环对象旧版处理异常
        - **线程本地/会话未清理**：`threading.local` 长期线程池累积
        - **`C` 扩展泄漏**：`tracemalloc` 看不到——换 `memray`（能看到 `C` 层）

- **协助记忆**
    - 口诀：“先看趋势确泄漏，`tracemalloc` 找行，`objgraph` 找引用；全局容器/无界缓存/注销回调是最惯犯”。

- **进阶思考**
    - **`gc.collect()` 前后内存差异说明什么？**
        - `collect` 后明显回落 → 存在**循环引用垃圾**在等分代回收（正常机制，量大才要优化对象结构）；`collect` 后不回落 → 对象仍被活引用攥着（真泄漏，去查引用链）——这个试验是快速分流的第一刀。
    - **生产环境不能重启怎么在线排查？**
        - `py-spy dump`（无侵入看栈）、`memray attach`（附加追踪）、`gdb` + `python-gdb` 扩展、或程序预留诊断端点（触发 `tracemalloc` 快照/`objgraph` 报告）。重度问题可灰度开采样后重启一次拿完整数据。

- **扩展信息**
    - **工具链**：`tracemalloc`（标准库）、`objgraph`、`memray`（`C` 层+火焰图，最强）、`pympler`（对象大小）、`py-spy`（无侵入栈）
    - **预防**：缓存一律带上限（`lru_cache`）、全局容器定界清理（定时裁剪）、长连接回调注册/注销配对、`CI` 内存回归测试（压测看曲线）

## 🤔 SQLAlchemy 是什么？ 
- **`SQLAlchemy` 是 Python 最主流的数据库工具包与 `ORM`：①Core 层：SQL 表达式语言/连接池/事务（贴近 SQL 的工程化封装）②ORM 层：把表映射成类、行映射成对象（2.0 风格 `session.execute(select(User).where(User.name=='x'))`，旧 `session.query(...)` 在 2.0 已弃用）。核心：“连接池 + SQL 抽象 + ORM 映射，Python 操作数据库的事实标准”。**
    - **两套能力（分层）**
        - **Core（表达式语言）**：用 Python 表达 SQL（`select(users).where(...)`），拿 `Result` 行——贴近 SQL、性能可控
        - **ORM（对象映射）**：定义模型类 ↔ 表，操作对象即操作行（增删改查对象化），带身份映射/关系加载（`relationship`）
        - 典型用法：ORM 日常 + 复杂查询下沉 Core/原生 SQL

    - **核心组件**
        - **Engine/连接池**：`create_engine(url, pool_size=10, pool_pre_ping=True)`——连接复用、断线重连
        - **Session/事务**：工作单元（`with Session() as s: s.add(obj); s.commit()`），自动 flush/回滚
        - **模型（Declarative）**：`class User(Base): __tablename__='users'; id = Column(...)`
        - **relationship**：表关系对象化（一对多/多对多，`user.servers` 直接拿列表）

    - **运维场景**
        - **CMDB/资产平台**：主机/服务/变更记录建模 CRUD
        - **巡检/采集入库**：采集结果批量写入（`bulk_insert_mappings` 高效批量）
        - **工单/发布系统**：Django 之外的轻量选择（配合 `Flask`/`FastAPI`）

    - **常用生态**
        - **Alembic**：数据库迁移（表结构版本化，像 git 管理 schema）
        - **SQLModel**：`FastAPI` 作者把 `SQLAlchemy`+`Pydantic` 合一（现代轻量）

- **协助记忆**
    - 口诀：“`SQLAlchemy` = 连接池（Engine）+ 会话事务（Session）+ 表类映射（ORM）；复杂 SQL 下沉 Core，结构变更交给 `Alembic`”。

- **进阶思考**
    - **ORM 和手写 SQL 怎么权衡？**
        - 常规 `CRUD`/业务逻辑用 `ORM`（类型安全、防注入、可维护）；**复杂报表/大查询/`DBA` 调优场景**用 Core 或原生 `text()` SQL（`ORM` 生成的 `SQL` 不一定最优）。注意 `N+1` 查询（循环里查关系）——用 `selectinload/joinedload` 预加载。
    - **运维脚本里用 SQLAlchemy 要注意什么？**
        - ①`engine` 全局建一次（连接池别反复建）②批量写入用 `bulk`/`executemany` 而非循环单条 ③`pool_pre_ping=True` 防长空闲断连 ④脚本结束 `engine.dispose()` 释放连接。

- **扩展信息**
    - **版本**：2.0 风格在 1.4 作为过渡引入（`select()` 语法/`Session.execute`），到 SQLAlchemy 2.0 才正式定型（迁移旧 `Query` 风格）——新项目直接 2.0 风格
    - **相关**：`Alembic`（迁移）、`SQLModel`、`asyncmy/asyncpg` + `SQLAlchemy async`（异步引擎）、`Django ORM`（对照）

## 🤔 Python 运维自动化脚本你都用过哪些模块？ 
- **运维自动化模块按“系统→文件→网络→并发→监控→第三方”分：系统交互 `subprocess`/`os`/`sys`/`shutil`；文件 `pathlib`/`glob`/`fileinput`；网络 `requests`/`paramiko`/`socket`/`dnspython`；并发 `concurrent.futures`/`threading`/`asyncio`；解析 `re`/`json`/`yaml`/`csv`；监控 `psutil`；打包 `argparse`/`logging`。核心：“`subprocess`+`psutil`+`paramiko`+`requests` 是运维四大件”。**
    - **系统交互**
        - `subprocess`（执行命令，`run/Popen`）、`os`/`os.path`/`sys`（环境/参数/路径）
        - `shutil`（复制/移动/删树/磁盘用量 `disk_usage`）、`signal`（信号处理，优雅退出）
        - `psutil`：`CPU`/内存/磁盘/网络/进程全家桶（监控采集必备）

    - **文件与文本**
        - `pathlib`（路径对象化）、`glob`/`fnmatch`（模式匹配）、`fileinput`（多文件逐行）
        - `re`（正则解析日志）、`collections`（`Counter` 统计）、`csv`/`json`/`yaml`/`configparser`（数据交换与配置）

    - **网络与远程**
        - `requests`/`httpx`（HTTP `API` 调用）、`paramiko`/`fabric`（`SSH` 批量）、`socket`（端口探活）
        - `dnspython`（`DNS` 查询）、`netifaces`（网卡信息）、`netmiko`（网络设备 `CLI`）

    - **并发与任务**
        - `concurrent.futures`（线程/进程池）、`threading`/`multiprocessing`、`asyncio`（高并发 `IO`）
        - `queue`（生产消费）、`sched`/`APScheduler`（定时任务）、`celery`（分布式任务）

    - **工程化（脚本质量）**
        - `argparse`/`click`/`fire`（命令行参数）、`logging`（日志分级落盘）、`tenacity`（重试）
        - `pymysql`/`psycopg2`/`redis-py`/`pymongo`（数据库客户端）、`SQLAlchemy`（`ORM`）

    - **打包分发**
        - `venv`/`pip`/`pyproject.toml`（环境）、`pyinstaller`（打成单文件可执行，方便分发到无 Python 主机）

- **协助记忆**
    - 口诀：“系统 `subprocess`+`psutil`，远程 `paramiko`，`API` 用 `requests`，并发 `futures`，配置 `yaml`，参数 `argparse`，日志 `logging`——四大件加工程化”。

- **进阶思考**
    - **写一个可长期维护的运维脚本，模块组合套路是什么？**
        - `argparse` 收参数 → `logging` 起日志（分级+落盘）→ `yaml`/环境变量读配置 → `tenacity` 包重试 → `subprocess`/`paramiko` 干活 → 结果结构化（`json/csv`）输出 + 异常分类退出码。骨架统一，脚本才可复用可交接。
    - **什么时候该从脚本升级到框架（Ansible 等）？**
        - 重复 `SSH` 批量逻辑超过几处、需要幂等与变更留痕、多人协作维护 → 上 `Ansible`/`SaltStack`；脚本留在“轻量采集/胶水/一次性修复”定位。别用 Python 重造配置管理轮子。

- **扩展信息**
    - **一大波轮子**：`glances`/`psutil`（监控）、`supervisor`（进程管理，`Python` 写的）、`ansible`（自动化，`Python` 写的）
    - **质量工具**：`pytest`（脚本测试）、`ruff`/`black`（规范）、`pre-commit`——脚本也要过基本质量关

## 🤔 Python 如何统计 Nginx 日志 Top 10 访问 IP？ 
- **标准三步：“提取 IP → `Counter` 计数 → `most_common(10)` 输出”：逐行读取日志（大文件流式）、正则或 `split` 取第一列 `IP`、`collections.Counter` 累加、`most_common(10)` 拿 `Top`。核心：“生成器逐行 + `Counter` 计数 + `most_common`，内存 `O(唯一IP数)`”。**
    - **基础实现（十几行搞定）**
        - 用生成器逐行读（`GB` 级也不怕）：`for line in open(log)`
        - 取 `IP`：`line.split()[0]`（`nginx` 默认 `combined` 格式第一列就是 `IP`）或正则 `re.match(r'(\d+\.\d+\.\d+\.\d+)', line)`
        - `Counter` 累加：`Counter` 对象 `update` 或 `c[ip] += 1`
        - 输出：`c.most_common(10)` 直接得到 `[(ip, count), ...]`

    - **代码骨架**
        - `from collections import Counter` + `c = Counter()` + 循环里 `c[line.split()[0]] += 1` + `for ip, n in c.most_common(10): print(ip, n)`
        - 加 `with open(...)` 管理文件句柄；异常行 `try/except` 跳过（脏数据容错）

    - **进阶变体**
        - **按状态码过滤**：`if ' 500 ' in line` 再计 `IP`（找错误请求来源）
        - **多文件/压缩日志**：`fileinput.input(files)`、`gzip.open` 流式读 `.gz`
        - **多维度 Top**：按 `URL`/`UA`/时段统计——正则捕获组 + 多个 `Counter`
        - **时间窗口**：记录 `(时间, IP)` 按分钟分桶（`Counter` 键用 `IP+分钟`）找突发 `CC`

    - **性能要点**
        - 逐行流式（内存 `O(唯一IP数)`，百万 `IP` 也就几十 `MB`）；正则比 `split` 慢数倍，能用 `split` 不用正则
        - 超大数据量（多 `GB` 多文件）→ 多进程分文件统计再合并 `Counter`，或交给 `goaccess`/`ELK`

- **协助记忆**
    - 口诀：“逐行读 → `split[0]` 取 `IP` → `Counter` 计数 → `most_common(10)`——三行核心 + 流式读文件”。

- **进阶思考**
    - **为什么用 `Counter` 而不是普通 dict？**
        - `Counter` 就是“计数 `dict`”的封装：`c[k] += 1` 不用判 key 存在（缺失默认 0）、直接给 `most_common`/`update` 合并/算术运算（`c1 - c2`）——统计场景少一半样板代码。`defaultdict(int)` 也能干，但 `most_common` 得自己 `sorted`。
    - **日志里有代理/`CDN`，第一列不是真实 IP 怎么办？**
        - `nginx` 配了 `X-Forwarded-For`/`X-Real-IP` 时真实 `IP` 在 `http_x_forwarded_for` 字段——解析时取 `XFF` 链的第一段（或按可信代理剥离）。注意 `XFF` 可伪造，安全统计要结合可信代理列表。

- **扩展信息**
    - **相关工具**：`goaccess`（实时日志面板）、`awk '{print $1}' access.log | sort | uniq -c | sort -rn | head`（一行流）、`ELK/Loki`（平台化）
    - **延伸场景**：`CC` 攻击识别（短窗 `Top IP` + 阈值告警）、慢请求分析（结合 `request_time` 字段）、`PV/UV` 日报自动化

## 🤔 Python 如何获取 CPU、内存、硬盘、网卡指标？ 
- **用 `psutil`（跨平台系统监控库）一把抓：`cpu_percent()`/`virtual_memory()`/`disk_usage()`/`net_io_counters()` 分别拿 `CPU`/内存/磁盘/网卡；进程级 `psutil.Process()` 全覆盖。核心：“`psutil` 采集 + 换算百分比 + 定时采样，替代手拼 `top`/`free` 输出”。**
    - **CPU**
        - `psutil.cpu_percent(interval=1)`：整体使用率（阻塞 1 秒采样）；`percpu=True` 每核
        - `cpu_count()` 核数、`cpu_times()` 用户/系统/空闲细分、`load_avg()` 负载（`Unix`）

    - **内存**
        - `psutil.virtual_memory()`：`total/available/percent`（**用 `available` 算使用率**，比 `100-used` 准——含可回收缓存）
        - `swap_memory()`：交换分区

    - **磁盘**
        - `psutil.disk_usage('/')`：单分区 `total/used/percent`
        - `disk_partitions()`：枚举所有挂载点（遍历逐个 `usage`）
        - `disk_io_counters()`：读写 `IO`（次数/字节/耗时）

    - **网卡**
        - `psutil.net_io_counters(pernic=True)`：每网卡收发字节/包/错包——两次采样差值÷时间 = 速率
        - `net_if_addrs()`：`IP`/`MAC`；`net_if_stats()`： up/down、速率、`MTU`

    - **进程级**
        - `psutil.Process(pid)`：`.cpu_percent()`/`.memory_percent()`/`.open_files()`/`.connections()`（查端口占用）
        - `process_iter()` 遍历所有进程（找 `CPU` 最高的进程）

    - **监控落地**
        - 定时采样脚本（`while True + sleep`）→ 推 `Prometheus`（`prometheus_client`）/`Zabbix`（`sender`）/写 `InfluxDB`
        - 阈值告警：`mem.percent > 90` 触发通知；配合历史趋势看异常

- **协助记忆**
    - 口诀：“`psutil` 一把抓：`cpu_percent`、`virtual_memory`（用 available）、`disk_usage`、`net_io_counters`（采样差值算速率）”。

- **进阶思考**
    - **内存使用率为什么用 `available` 而不是 `100 - used`？**
        - `Linux` 把空闲内存拿去做页缓存（`buffers/cached`），`used` 看着很高但缓存可回收；`available` 是“不给新进程添乱的前提下还能分配多少”——才是真实可用量。这也是 `free` 命令新口径的含义。
    - **网卡速率为什么要两次采样？**
        - `net_io_counters` 给的是**累计字节数**（开机以来），不是速率——取 `t1/t2` 两次差值除以间隔才是 `B/s`。`cpu_percent(interval=1)` 内部就是帮你做了这个两次采样，而网卡要自己算。

- **扩展信息**
    - **相关库**：`psutil`（跨平台 `Windows/Linux`）、`prometheus_client`（暴露指标端点）、`netifaces`、`py3nvml`（`GPU`）
    - **对照命令**：`top`/`free -h`/`df -h`/`ifconfig`——`psutil` 即它们的编程化（且跨平台统一接口）

## 🤔 Python 有哪些 Web 框架及各自特点？ 
- **Python Web 框架按“全能 vs 轻量 vs 高性能”分：`Django`（全家桶，大而全）、`Flask`（微框架，灵活组装）、`FastAPI`（现代异步，类型驱动+自动文档）、`Tornado`/`Sanic`（高性能异步）、`aiohttp`（异步客户端+服务端）。核心：“企业级全能用 `Django`、轻量脚本用 `Flask`、`API` 高并发用 `FastAPI`”。**
    - **`Django`（全能型）**
        - 自带 `ORM`/`Admin` 后台/认证/中间件/模板/迁移（`migrations`）—— batteries included
        - 适合：完整业务系统（`CMS`/管理平台/`CMDB`），开发效率高、生态成熟
        - 代价：重、学习曲线陡、灵活性低（约定大于配置）

    - **`Flask`（微框架）**
        - 核心极小（路由+请求对象），其他靠扩展拼装（`Flask-SQLAlchemy`/`Flask-Login`）
        - 适合：中小服务、内部工具、运维脚本 `Web` 化——自由度高
        - 同步为主（`gevent` 补异步），大并发需额外架构

    - **`FastAPI`（现代 API 框架）**
        - 基于 `Starlette`（`ASGI` 框架）构建、运行于 `ASGI` 服务器（如 `uvicorn`）原生异步；`Pydantic` 类型校验；**自动生成交互式文档**（`Swagger`/`ReDoc`）
        - 适合：高性能 `API` 服务、微服务、`OpenAPI` 驱动的平台接口
        - 当前新项目 `API` 服务的热门首选

    - **其他**
        - `Tornado`：老牌异步（长连接/`WebSocket` 先驱）；`Sanic`：`async` 高性能；`aiohttp`：异步 `client+server` 一体
        - `Starlette`：`FastAPI` 的底层（轻量 `ASGI` 工具箱）

    - **选型**
        - 完整平台带后台 → `Django`；内部小工具/脚本服务 → `Flask`；纯 `API` 高并发/要文档 → `FastAPI`

- **协助记忆**
    - 口诀：“`Django` 全家桶、`Flask` 拼积木、`FastAPI` 异步快——按项目体量和接口形态选”。

- **进阶思考**
    - **运维开发大多用哪个？**
        - 内部平台（`CMDB`/工单/发布系统）常用 `Django`（`Admin` 白捡管理后台）；对接 `API`/采集上报用 `FastAPI`（异步高并发+文档）；一次性小服务 `Flask` 最快。三者共存的团队很常见，按场景不是二选一。
    - **`WSGI` 和 `ASGI` 是什么关系？**
        - `WSGI`：同步网关协议（`Flask`/`Django` 传统部署，`gunicorn`+`nginx`）；`ASGI`：异步扩展（`FastAPI`/`Starlette`，支持 `WebSocket`/长连接，`uvicorn`/`daphne`）。`Django` 自 3.0 起支持 `ASGI`（现主流 5.x；3.0-3.2 已 EOL）——协议决定并发模型。

- **扩展信息**
    - **部署标配**：`gunicorn`/`uvicorn`（应用服务器）+ `nginx`（反代/静态）——框架只管应用层
    - **相关**：`Pydantic`（数据校验）、`OpenAPI`（接口规范）、`celery`（异步任务，各框架通用）

## 🤔 简述 Django MVT 架构？ 
- **`Django` 用 `MTV` 模式（官方称谓）；常被写作 `MVT`：`M`odel（模型，管数据与业务规则，映射数据库）、`T`emplate（模板，管展示 `HTML`）、`V`iew（视图，管请求处理逻辑，连接 `M` 和 `T`）+ `URLconf` 路由分发。与传统 `MVC` 对应：`Django` 的 `View` ≈ `MVC` 的 `Controller`、`Template` ≈ `View`。核心：“`M` 管数据、`V` 管逻辑、`T` 管展示，`URL` 把请求引到 `V`”。**
    - **三个角色**
        - **Model（模型）**：`class Host(models.Model): hostname = models.CharField(...)`——定义表结构、校验、业务方法；`ORM` 负责 SQL
        - **View（视图）**：`def host_list(request): ...`——接收请求、查 Model、组装上下文、返回 `HttpResponse`/渲染模板
        - **Template（模板）**：`{{ host.hostname }} {% for %}`——Django 模板语言渲染 `HTML`（展示与逻辑分离）

    - **请求完整流程（面试常问）**
        - 浏览器请求 → `WSGI`/`ASGI` 服务器（`gunicorn`/`uvicorn`）→ Django 中间件链 → `URLconf`（`urls.py`）匹配路由 → 对应 `View` 执行 → `View` 查 `Model`（数据库）→ 数据传给 `Template` 渲染 → `HttpResponse` 原路返回

    - **与 MVC 的对应（易混淆点）**
        - `Django MVT`：`Model`≈`M`、`View`≈`C`（控制器角色）、`Template`≈`V`（视图角色）
        - 叫 `MTV` 更准确（官方文档用词）——考察的是“`View` 在 `Django` 里不是展示层”

    - **各文件归属（工程结构）**
        - `models.py`（M）、`views.py`（V）、`templates/`（T）、`urls.py`（路由）、`forms.py`（表单校验）、`admin.py`（后台）

- **协助记忆**
    - 口诀：“`MVT`：`Model` 管库、`View` 管逻辑、`Template` 管页面，`urls` 做引路人——`Django` 的 `View` 是控制器不是视图”。

- **进阶思考**
    - **前后端分离后 MVT 还有意义吗？**
        - 有变化：`Template` 层被前端框架（`Vue`/`React`）替代，`Django` 退为“`M`+`V`”提供 `API`（`DRF` 出 `JSON`）——`Model`/`View`/路由/中间件/`ORM` 全部照用。`MVT` 的分层思想（数据/逻辑/展示解耦）不变，只是展示层外移。
    - **`View` 里的业务逻辑要不要塞进 Model？**
        - 数据相关的业务规则（校验、状态流转）放 `Model` 方法/`Manager`（可复用、事务安全）；纯请求编排（解析参数、选模板、多模型组合）放 `View`。别把业务都堆 `View`——`fat models, thin views` 是 Django 社区主流建议。

- **扩展信息**
    - **配套机制**：`forms`/`serializers`（输入校验）、`middleware`（横切处理）、`signals`（解耦事件）、`admin`（自动后台）
    - **运维应用**：`CMDB`/工单/发布平台用 `Django` 开发，`Admin` 快速生成管理界面

## 🤔 Django ORM 是什么？ 
- **`Django ORM` 是 `Django` 的对象关系映射层：把数据库表映射成 Python 类（Model）、行映射成对象、字段映射成属性——用 `Host.objects.filter(rack='A1')` 这样的 Python 代码代替手写 `SQL`。核心：“表↔类、行↔对象、查询↔方法链，附带防注入/迁移/多数据库支持”。**
    - **核心概念**
        - **Model**：`class Host(models.Model): hostname = CharField(max_length=64, unique=True)` → 自动建表 `app_host`
        - **QuerySet**：查询结果集（惰性、可链式）——`Host.objects.filter().exclude().order_by()` 生成 `SQL` 后才执行
        - **Manager**：`Host.objects`——查询入口（可自定义 `Manager` 加常用查询方法）

    - **常用能力**
        - `CRUD`：`save()`/`delete()`、`objects.get()`/`create()`/`update()`
        - 过滤：`filter(rack='A1', status='on')`（AND）、`Q(rack='A1') | Q(rack='A2')`（OR）、`__` 跨关系/字段查找（`hostname__contains`、`created__gte`）
        - 聚合：`annotate`/`aggregate` + `Count`/`Sum`（统计报表）
        - 关联：`ForeignKey`/`ManyToMany` + `related_name` 反向查询

    - **优点与代价**
        - **优点**：免写 `SQL` 防注入、数据库可切换（`MySQL`/`PG`）、`migrations` 管理表结构版本、模型即文档
        - **代价**：复杂查询生成 `SQL` 可能低效（要看 `query`/日志）、魔法多黑盒、`N+1` 查询坑

    - **配套**
        - **`migrations`**：`makemigrations` + `migrate`——表结构变更版本化（像 git 管理 schema）
        - `Django` 外可用 `SQLAlchemy`（更通用）；`ORM` 理念相通

- **协助记忆**
    - 口诀：“`ORM` = 表变类、行变对象、`filter` 代替 `WHERE`；惰性 `QuerySet`、`__` 穿关系、迁移管结构”。

- **进阶思考**
    - **什么是 N+1 查询问题，怎么解决？**
        - 遍历 100 个 `Host` 再逐个取 `host.owner.name` → 1 次查列表 + 100 次查外键 = `N+1`。解决：`Host.objects.select_related('owner')`（外键 `JOIN`）或 `prefetch_related('tags')`（多对多二次查询）——一次批量取，列表页性能天壤之别。
    - **QuerySet 是惰性的意味着什么？**
        - `qs = Host.objects.filter(...)` 只构造不执行；迭代/`list()`/`len()`/切片带 `step` 才触发 `SQL`。利用惰性可自由拼条件（不同分支追加 `filter`）；QuerySet **一旦求值会缓存结果**——重复迭代**同一个对象**不重查；只有新 `filter`（产生新 QuerySet）才重查——注意底层数据变更**不会**自动刷新已缓存结果（stale 旧值），要拿新数据得重建查询。

- **扩展信息**
    - **性能手段**：`select_related`/`prefetch_related`、`only`/`defer`（字段裁剪）、`bulk_create`/`bulk_update`（批量）、`iterator()`（大结果集流式）
    - **运维场景**：`CMDB` 资产模型（机房/机架/主机关系）、工单流转、发布记录审计——`Admin` 后台白捡管理界面

## 🤔 Django ORM 有哪些常用查询方法？ 
- **常用查询分五类：取数据（`all`/`get`/`filter`/`exclude`/`first`/`last`）、字段条件（`__` 系列：`__contains`/`__in`/`__gte`/`__isnull`）、排序截断（`order_by`/`[切片]`/`distinct`）、聚合统计（`count`/`exists`/`aggregate`/`annotate`）、关系查询（`select_related`/`prefetch_related`/`__` 跨表）。核心：“`filter` 链式组合 + `__` 双下划线穿字段/关系 + 聚合函数收尾”。**
    - **基础取数**
        - `Host.objects.all()` 全部（惰性）；`.filter(rack='A1')` 条件筛选（返回 QuerySet）
        - `.get(pk=1)` 唯一一条（多/无都抛异常）；`.first()`/`.last()` 取一（无则 None）
        - `.exclude(status='off')` 反向排除；`.exists()` 是否存在（比 `count()>0` 快）

    - **字段条件（`__` 查找后缀）**
        - `hostname__contains='web'`（含）/`__icontains`（忽略大小写）/`__startswith`/`__endswith`
        - `id__in=[1,2,3]`、`created__gte=start`/`__lte=end`（范围）、`owner__isnull=True`
        - 跨关系：`host__rack__name='A1'`（双下划线一路穿到底）

    - **排序与截断**
        - `.order_by('-created', 'hostname')`（`-` 倒序）；`.distinct()` 去重
        - 切片：`qs[:10]`（LIMIT 10）、`qs[10:20]`（分页）；注意负索引不支持

    - **聚合与统计**
        - `.count()` 总数；`.aggregate(total=Sum('size'), avg=Avg('cpu'))` 整体聚合（返回 dict）
        - `.values('rack').annotate(n=Count('servers'))` 按**分组**（如机架）附加聚合值——`annotate` 单独用时按模型主键逐行附列，配合 `values()` 才做分组聚合
        - `values('rack')`/`values_list('hostname', flat=True)`——要字段不要对象（轻量）

    - **关联优化（性能关键）**
        - `.select_related('owner')`：外键 `JOIN` 一次取（一对一/多对一）
        - `.prefetch_related('tags')`：多对多/反向外键分两步批量取
        - 批量：`bulk_create([Host(...), ...])`、`update(rack='B2')`（批量改不逐个 save）

    - **逻辑组合**
        - `Q` 对象：`filter(Q(rack='A1') | Q(rack='A2'), status='on')`（`|` 或、`&` 与、`~` 非）
        - `F` 表达式：`update(count=F('count') + 1)` 数据库端自增（防并发丢更新）

- **协助记忆**
    - 口诀：“取数 `all/filter/get`，条件靠 `__`（contains/in/gte），排序 `order_by` 截断用切片，统计 `count/aggregate/annotate`，关联 `select_related`/`prefetch_related` 防 `N+1`”。

- **进阶思考**
    - **`get()` 和 `filter().first()` 怎么选？**
        - `get()` 语义是“必须恰好一条”——多条抛 `MultipleObjectsReturned`、没有抛 `DoesNotExist`（显式失败，适合主键/唯一键）；`first()` 宽容返回 `None`（适合“可能没有”的业务查询）。乱用 `get()` 会让脚本被脏数据炸掉。
    - **`annotate` 和 `aggregate` 的区别？**
        - `aggregate` 整表聚合一行结果（`{'total': 100}`）；`annotate` 给**每个分组行**附加计算列（每个机房多少台主机：`values('rack').annotate(n=Count('id'))`）。一个是汇总值，一个是分组标签。

- **扩展信息**
    - **进阶**：`Q`/`F`/`Subquery`/`Case-When`（条件表达式）、自定义 `Manager`、`raw()` 原生 `SQL` 逃生口
    - **调试**：`print(qs.query)` 看生成 `SQL`、`django-debug-toolbar`/日志 `sql` ——优化前先看它到底发了什么 `SQL`

## 🤔 Django REST Framework（DRF）是什么？ 
- **`DRF` 是基于 `Django` 的 REST API 开发框架：把模型/业务快速暴露成规范的 `JSON API`——`Serializer`（序列化/校验）、`ViewSet`+`Router`（一套代码自动生成 `CRUD` 路由）、`认证/权限/限流/分页/版本` 内置可插拔、自带 `Browsable API` 调试页。核心：“`Django` 管 `Web`，`DRF` 管 `API`——序列化 + 通用视图 + 可插拔组件”。**
    - **核心组件**
        - **Serializer（序列化器）**：对象 ↔ `JSON` 转换 + 输入校验（类似 `Form` 的 `API` 版）——`ModelSerializer` 从模型自动生成字段
        - **View/ViewSet**：`APIView`（最灵活）→ `GenericAPIView`（泛型基类）→ `ListCreateAPIView` 等（`GenericAPIView`+`Mixin` 组合）→ `ModelViewSet`（一套搞定增删改查）
        - **Router**：`DefaultRouter` 注册 `ViewSet` 自动生成 `RESTful` 路由（`/hosts/`、`/hosts/1/`）

    - **内置可插拔机制（面试重点）**
        - **认证（Authentication）**：`Session`/`Token`/`JWT`（`djangorestframework-simplejwt`）——我是谁
        - **权限（Permission）**：`IsAuthenticated`/`IsAdminUser`/自定义——能干什么
        - **限流（Throttling）**：`AnonRateThrottle`/`UserRateThrottle`——防刷
        - **分页（Pagination）**：`PageNumberPagination`/`LimitOffset`；**过滤/搜索**：`django-filter` + `SearchFilter`；**排序**：DRF 内置 `OrderingFilter`
        - **版本化**：`URLPathVersioning` 等；**渲染器**：`JSON` + `Browsable API`（浏览器直接调试）

    - **典型用法（三件套起步）**
        - 定义 `ModelSerializer` → 写 `ModelViewSet`（`queryset` + `serializer_class`）→ `router.register('hosts', HostViewSet)`——5 行代码一套完整 `CRUD API`

    - **运维场景**
        - `CMDB`/工单/发布平台对外 `API`、自动化平台与脚本的接口层、对接监控/告警回调

- **协助记忆**
    - 口诀：“`DRF` = 序列化（`Serializer`）+ 通用视图（`ViewSet`+`Router`）+ 插件（认证/权限/限流/分页）——`Django` 数据能力转 `REST` 接口”。

- **进阶思考**
    - **DRF 的 ViewSet 和普通 View 怎么选？**
        - 标准 `CRUD` 资源 → `ModelViewSet` + `Router`（最少代码、规范路由）；非标准动作 → `@action(detail=True)` 加自定义端点；完全自定义逻辑 → `APIView` 或 `Django` 原生 `View`。先 `ViewSet`，特殊再下沉。
    - **JWT 认证在 DRF 里怎么落地（运维平台场景）？**
        - `simplejwt` 提供获取/刷新 `token` 端点；脚本端先拿 `token`、后续 `Authorization: Bearer` 头携带；配合权限类控制只读/读写。相比 `Session`，`JWT` 更适合脚本/多服务调用（无状态、跨域友好）；注意 `token` 过期与刷新策略。

- **扩展信息**
    - **配套库**：`django-filter`（过滤）、`simplejwt`（`JWT`）、`drf-spectacular`（自动生成 `OpenAPI` 文档）、`drf-extensions`
    - **对比**：`FastAPI`（原生 `Python` 类型驱动，异步）vs `DRF`（`Django` 生态，`Admin`/`ORM` 复用）——已有 `Django` 项目加 `API` 首选 `DRF`

## 🤔 DRF ModelViewSet 的父类有哪些？ 
- **`ModelViewSet` = `GenericViewSet`（`ViewSetMixin` + `GenericAPIView` 骨架，提供查询集/序列化器/分页过滤）+ `Mixin` 五件套（`ListModelMixin`/`CreateModelMixin`/`RetrieveModelMixin`/`UpdateModelMixin`/`DestroyModelMixin`，分别提供 `list/create/retrieve/update/destroy` 动作）。核心：“`GenericAPIView` 定骨架 + `Mixin` 按需混入动作——`ReadOnlyModelViewSet` 只读、`ModelViewSet` 全有”。**
    - **继承结构（自底向上）**
        - `APIView`：`DRF` 根视图（`request`/`response`/认证权限限流/异常处理）
        - `GenericAPIView`：+ `queryset`/`serializer_class`/`get_object()`/分页过滤钩子（通用骨架，无具体动作）
        - **五个 `Mixin`**：`ListModelMixin.list()`、`CreateModelMixin.create()`、`RetrieveModelMixin.retrieve()`、`UpdateModelMixin.update()/partial_update()`、`DestroyModelMixin.destroy()`——各只实现一个动作
        - **`ModelViewSet` = `GenericViewSet`（内含 `GenericAPIView` + `ViewSetMixin`）+ 全部五个 `Mixin`** → 完整 `CRUD`

    - **组合产物（常用）**
        - **`ReadOnlyModelViewSet`**：只 `list`+`retrieve`（只读接口）
        - **`ModelViewSet`**：全 `CRUD`
        - 泛型具体视图：`ListAPIView`/`CreateAPIView`/`ListCreateAPIView`/`RetrieveUpdateDestroyAPIView`（单动作版，不用 `Router` 时直接绑 `URL`）

    - **定制方式**
        - 重写 `get_queryset()`（按用户/参数过滤数据——数据隔离的关键）
        - 重写 `perform_create(ser)`/`perform_update(ser)`（保存前后加逻辑：记录操作人/发事件）
        - `@action(detail=True/False)` 添加自定义端点（如 `/hosts/{id}/restart/`）

    - **路由**
        - `router = DefaultRouter(); router.register(r'hosts', HostViewSet)` → 自动映射 `GET/POST /hosts/`、`GET/PUT/PATCH/DELETE /hosts/{pk}/`

- **协助记忆**
    - 口诀：“`APIView` 打底、`GenericAPIView` 加骨架、五个 `Mixin` 各管一个动作——`ModelViewSet` 全家桶、`ReadOnly` 只读；定制就重写 `get_queryset` 和 `perform_*`”。

- **进阶思考**
    - **为什么用 Mixin 组合而不是一个大基类？**
        - 按需组装：只读接口继承 `ReadOnlyModelViewSet`（不会意外暴露写操作）、只要列表+创建就混对应两个 `Mixin`——**动作粒度可控**、权限更容易收窄。大基类要么全给要么裁剪，容易“多出不该有的接口”。
    - **ViewSet 里的数据权限怎么实现（用户只看自己的主机）？**
        - 重写 `get_queryset()`：`return Host.objects.filter(owner=self.request.user)`——列表/详情/修改删除全部自动收窄；再配权限类兜底。这是 `DRF` 数据隔离的标准姿势（比在 `serializer` 里过滤干净得多）。

- **扩展信息**
    - **相关**：`@action`（自定义路由）、`DefaultRouter`/`SimpleRouter`、`get_serializer_class()`（不同动作不同序列化器）
    - **调试**：`Browsable API` 页面直接点接口、`drf-spectacular` 生成 `OpenAPI` 给前端/脚本对接

## 🤔 Django 中间件的作用是什么？ 
- **中间件（Middleware）是 Django 请求/响应的全局钩子链：所有请求进入 View 前和响应返回后都会依次穿过它们——用于处理“横切所有请求”的事：认证会话、`CSRF`、`GZip` 压缩、日志审计、限流、异常兜底。核心：“请求进、响应出各过一遍中间件——全局横切逻辑的唯一标准位置”。**
    - **执行模型（洋葱模型）**
        - 请求 → `M1.process_request` → `M2.process_request` → `View` → `M2.process_response` → `M1.process_response` → 返回（先进后出）
        - `MIDDLEWARE` 列表顺序即层级（越靠前越外层）——顺序错了认证/`CSRF` 会失效

    - **五个钩子方法**
        - `process_request(request)`：进 View 前（记录开始时间/黑白名单）
        - `process_view(request, view, args, kwargs)`：路由确定后、View 执行前
        - `process_exception(request, exception)`：View 抛异常时（统一异常处理/告警）
        - `process_template_response()`：模板响应时
        - `process_response(request, response)`：响应返回时（加响应头/审计落库）

    - **Django 内置中间件（必知）**
        - `SecurityMiddleware`（安全头/`SSL` 重定向）、`SessionMiddleware`（会话）、`CommonMiddleware`（通用头/`APPEND_SLASH`）
        - `CsrfViewMiddleware`（`CSRF` 防护）、`AuthenticationMiddleware`（`request.user`）、`GZipMiddleware`（压缩，`Django 5.0` 已弃用、计划 `6.0` 移除——压缩多由 `Web` 服务器承担）

    - **运维场景（自定义中间件典型用途）**
        - **审计日志**：记录谁在什么时间动了什么接口（`process_request` 起 `process_response` 落库）
        - **接口耗时监控**：记录慢请求、上报指标
        - **维护模式/黑白名单**：`IP` 拦截、`token` 校验统一收口

- **协助记忆**
    - 口诀：“中间件 = 请求/响应的洋葱圈——先进后出，五钩子（`request`/`view`/`exception`/`template_response`/`response`）；顺序即安全（`CSRF` 在认证前，会话在认证前）”。

- **进阶思考**
    - **中间件顺序错会出什么问题？**
        - 经典例：`AuthenticationMiddleware` 必须在 `SessionMiddleware` 之后（它依赖 `request.session`）——放前面启动直接报错。`SecurityMiddleware` 要在最前（尽早拦）。自加的日志中间件放最外层才能记录到完整往返。
    - **中间件里能做耗时操作（查库/调接口）吗？**
        - 慎重——它在每个请求上执行，一次 200ms 的外部调用会拖垮全站 P99。审计落库用异步（`celery`/缓冲队列）、监控用轻量内存计数；确需同步的要加超时和降级。

- **扩展信息**
    - **相关**：`process_request` 里返回非 None 会短路（直接当响应返回，不再进 View）——限流/封禁的实现点
    - **对照**：`DRF` 也有自己的 `throttle`/`permission` 层（视图级）；全局横切还是中间件，视图内的策略放 `DRF` 组件——两层配合

## 🤔 简述 Django 中间件自定义流程？ 
- **自定义中间件三步：写类（按需实现 `process_request`/`process_response`/`process_exception` 等钩子，`__init__` 接 `get_response`）→ 注册到 `settings.MIDDLEWARE` 列表（注意位置）→ 验证请求/响应路径生效。轻量场景可用 `MiddlewareMixin`。核心：“类 + 钩子 + 注册，顺序决定层次”。**
    - **第一步：编写中间件类（新版风格）**
        - `class AuditMiddleware:` + `def __init__(self, get_response): self.get_response = get_response`
        - `def __call__(self, request):` 里前置逻辑 → `response = self.get_response(request)` → 后置逻辑 → `return response`（新式单一入口，覆盖 request/response 两段）
        - 兼容写法（`MiddlewareMixin`）：继承后只写 `process_request`/`process_response` 等独立钩子

    - **第二步：注册（settings.py）**
        - `MIDDLEWARE = [..., 'common.middleware.AuditMiddleware']`——列表位置决定外内层
        - 要全局最先/最后处理就放到列表头/尾

    - **第三步：验证**
        - 前置逻辑打日志/设 `request.start_time`；响应段算耗时加响应头（`X-Process-Time`）
        - 单元测试：`django.test.Client` 发请求断言副作用；中间件只作用于请求-响应周期（`manage.py` 管理命令不走请求、不经中间件；`DEBUG` 下静态文件由 `StaticFilesHandler` 拦截也绕过）

    - **典型自定义示例（运维向）**
        - **耗时监控**：`__call__` 前记 `t0`，响应段 `time.time()-t0` 超 1s 告警日志
        - **IP 黑名单**：`process_request` 里命中直接 `HttpResponseForbidden`（短路，不进 View）
        - **审计**：响应段把“用户+路径+状态码+耗时”异步落库（`celery` 派发）

    - **注意点**
        - 钩子里异常要兜住（中间件抛异常会 500 全站）
        - 只做横切通用逻辑——业务判断放 `View`/`DRF` 权限

- **协助记忆**
    - 口诀：“自定义中间件 = 类 + `__call__`（前处理 → `get_response` → 后处理）→ 注册 `MIDDLEWARE` → 调位置验证；抛错要兜底，短路能拦截”。

- **进阶思考**
    - **`process_request` 返回响应会发生什么？**
        - 短路：Django 不再调用后续 View（后续中间件的 `process_request` 也跳过），该响应沿外层中间件的 `process_response` 链返回——这就是 IP 封禁/维护页/限流的标准实现点。
    - **新旧风格（`MiddlewareMixin` vs `__call__`）怎么选？**
        - 新项目直接 `__call__` 单入口（逻辑集中、层次清晰）；需要兼容老钩子风格或只插某一环（只管异常）用 `Mixin` 钩子。两者可共存于一个类，但别重复处理同一段逻辑。

- **扩展信息**
    - **测试钩子**：`override_settings(MIDDLEWARE=[...])` 隔离测试；`request.META` 取真实 IP（注意 `X-Forwarded-For` 代理链）
    - **运维建议**：审计/监控中间件与业务解耦（配置开关 + 异常静默降级），避免运维组件反过来影响业务可用性

## 🤔 DRF 限流是怎么实现的？ 
- **`DRF` 限流（Throttling）基于“速率类 + 访问标识 + 计数存储”：内置 `AnonRateThrottle`（匿名按 IP）/`UserRateThrottle`（按用户 ID，匿名回退 IP）/`ScopedRateThrottle`（按视图分组），规则用 `'3/min'` 这类频率字符串，计数存缓存（`Redis`/`Memcached`），超限返回 429。核心：“节流类判定身份 → 按频率窗口计数 → 超了 429”。**
    - **内置节流类（选身份维度）**
        - `AnonRateThrottle`：未认证用户按 IP 限（防爬/防扫）
        - `UserRateThrottle`：认证用户按用户 ID（`request.user.pk`）限，匿名回退按 IP（防单用户滥用）
        - `ScopedRateThrottle`：按视图 `throttle_scope` 分组限（不同接口不同配额，如登录接口单独收紧）
        - 自定义：继承 `SimpleRateThrottle` 重写 `get_cache_key()`（如按 API Key/租户限）

    - **配置（settings）**
        - `DEFAULT_THROTTLE_CLASSES`：全局默认类；`DEFAULT_THROTTLE_RATES`：`{'anon': '100/hour', 'user': '1000/hour', 'login': '5/min'}`
        - 视图级覆盖：`throttle_classes = [...]`、`throttle_scope = 'login'`
        - 频率语法：`N/second`、`N/min`、`N/hour`、`N/day`

    - **实现机制**
        - `allow_request(request, view)`：算身份键（IP/用户）→ 从缓存读窗口内计数 → 判断是否超 rate → 超限返回 False → `DRF` 抛 429 Too Many Requests（响应头带 `Retry-After`）
        - 计数存储走 Django 缓存框架——生产必须配 `Redis`/`Memcached`（默认 `LocMemCache` 多进程不共享，限流形同虚设）

    - **运维场景**
        - 登录/验证码接口 `5/min`（防爆破）、开放 API 每用户每小时配额、`CMDB` 查询接口防爬
        - 配合监控：429 激增告警（有攻击或客户端 bug）

- **协助记忆**
    - 口诀：“限流三要素：节流类定身份（`anon`/`user`/`scope`）、频率字符串定配额、缓存存计数——超限 429，生产计数必须上 Redis”。

- **进阶思考**
    - **为什么默认本地缓存会让限流失效？**
        - `gunicorn` 多 worker/多机部署时，每个进程各有一份 `LocMemCache`——限流阈值被进程数放大 N 倍且互不可见。必须用共享存储（Redis）：`CACHES` 配 `django-redis`，计数才能全局一致。
    - **`DRF` 限流和 `nginx` 层限流怎么分工？**
        - `nginx`（`limit_req`）做粗粒度兜底（按 IP 挡洪水/扫描，保护后端）；`DRF` 做业务粒度（按用户/接口/租户的配额与计费逻辑）。两层都要：边缘挡量、应用管额度；只靠 `DRF` 限流，恶意流量已经打进了应用。

- **扩展信息**
    - **算法对照**：`DRF` 内置是“时间窗+历史时间戳”（近似滑动窗）；更严格可用自定义 token bucket；`nginx`/网关层是漏桶
    - **响应细节**：429 带 `Retry-After`/`X-RateLimit-*` 头——客户端按头退避，比裸报错友好

## 🤔 DRF 认证和权限怎么实现的？ 
- **`DRF` 把“你是谁”和“你能干什么”分成两层：认证（Authentication）在 `request.user` 上落实身份（`Session`/`Token`/`JWT`，多个认证类按序尝试，全失败则匿名）；权限（Permission）对认证结果做策略判断（`AllowAny`/`IsAuthenticated`/`IsAdminUser`/自定义，返回 403/401）。核心：“认证定身份、权限定策略，视图级 + 全局级可插拔配置”。**
    - **认证（Authentication → 谁在访问）**
        - 流程：请求进来逐个尝试 `authentication_classes` 里的类 → 每个类 `authenticate(request)` 返回 `(user, auth)` 则该认证生效；**返回 `None` 继续试下一个**；抛 `AuthenticationFailed` 立即中断整条认证链
        - 内置：`SessionAuthentication`（浏览器会话）、`TokenAuthentication`（请求头 `Authorization: Token <token>`）、`BasicAuthentication`
        - `JWT`：`djangorestframework-simplejwt`（`Authorization: Bearer <token>`，支持刷新）——脚本/前后端分离首选
        - 全失败 → `request.user` 为匿名（`AnonymousUser`）；凭据错误可抛 `AuthenticationFailed`（401）

    - **权限（Permission → 能否访问）**
        - `permission_classes` 逐个 `has_permission()`，全部通过才放行；对象级 `has_object_permission()` 控单条资源
        - 内置：`AllowAny`（全放）/`IsAuthenticated`（登录才行）/`IsAdminUser`（staff）/`IsAuthenticatedOrReadOnly`
        - 自定义：继承 `BasePermission` 实现 `has_permission`（如“运维组才可写”）/`has_object_permission`（“只能改自己的主机”）

    - **配置层级（就近覆盖）**
        - 全局：`settings.DEFAULT_AUTHENTICATION_CLASSES`/`DEFAULT_PERMISSION_CLASSES`
        - 视图级：`authentication_classes`/`permission_classes` 属性覆盖（如登录接口 `AllowAny`）
        - 语义：401=未认证、403=权限不足；实际返回码取决于认证类是否设 `WWW-Authenticate` 头（设置了通常 401，否则未认证的权限拒绝可能降级为 403）——语义区分而非绝对对应

    - **运维平台典型组合**
        - 脚本/平台间调用：JWT 认证 + `IsAuthenticated` + 自定义角色权限（只读组/操作组）
        - 数据隔离：`get_queryset()` 按用户过滤（权限管“能不能进”，`queryset` 管“能看见哪些数据”）

- **协助记忆**
    - 口诀：“认证答‘你是谁’（`Session`/`Token`/`JWT` 逐个试），权限答‘能不能’（`IsAuthenticated`/自定义 `has_permission`）——401 没身份、403 没资格，数据范围交给 `get_queryset`”。

- **进阶思考**
    - **认证和权限为什么必须分开成两层？**
        - 身份与授权解耦：同一套 JWT 认证可搭配不同权限策略（只读/读写/管理员）；同一权限策略可换认证方式（`Session` 换 JWT 不动权限代码）。混在一起写，接口变更时两边都要动，也难做统一审计。
    - **`has_object_permission` 什么时候会被调用（常见坑）？**
        - 只在视图显式调用 `self.check_object_permissions()` 时生效（`DRF` 通用视图对 `retrieve`/`update`/`destroy` 会调，list 不会）——列表数据泄露要在 `get_queryset()` 里过滤。两个机制配合才完整：`queryset` 管列表范围、对象权限管单条操作。

- **扩展信息**
    - **相关**：`simplejwt`（JWT + 黑名单/刷新）、`django-guardian`（对象级权限落地存储）、`throttling`（与权限同层）
    - **安全建议**：token 短有效期 + 刷新机制；敏感操作二次校验（操作密码/`MFA`）；权限变更留审计日志

## 🤔 Celery 是什么？什么情况下用？ 
- **`Celery` 是 Python 的分布式异步任务队列：业务代码把任务丢进消息队列（`Redis`/`RabbitMQ`），`worker` 进程集群异步取出执行——实现异步化（不阻塞请求）、解耦、定时调度（`celery beat`）与重试。核心：“生产者投递 → 队列缓冲 → `worker` 消费，API 秒回、重活后台跑”。**
    - **架构三件套**
        - **Producer（业务应用）**：`task.delay(args)` 把任务发到队列（秒级返回 task_id）
        - **Broker（消息中间件）**：`Redis`/`RabbitMQ`——存队列、缓冲削峰
        - **Worker（执行者）**：`celery -A app worker` 起多进程消费执行；Result Backend：存结果（`Redis`/数据库）供查询

    - **什么时候用（判断标准）**
        - **耗时**：任务超过几百毫秒就该异步（报表生成/批量扫描/视频转码）
        - **不需同步结果**：发通知/邮件/`webhook`——用户不必等它完成
        - **定时调度**：`celery beat` 周期任务（替代 `crontab`，统一管控+重试+监控）
        - **削峰/解耦**：高并发下把重操作排队慢慢消化（发布流水线、巡检集群）
        - **不该用**：必须同步返回结果的小查询（直接算更快，队列入栈出栈反成开销）

    - **运维价值（高频场景）**
        - 批量主机巡检/采集（`worker` 并发跑）、发布流水线各阶段任务化、告警通知异步发、定时备份校验、`CMDB` 自动发现

    - **工程要点**
        - **幂等 + 重试**：`task(bind=True, max_retries=3, autoretry_for=(ConnectionError, TimeoutError), retry_backoff=True, retry_backoff_max=60)`（用具体异常而非裸 `Exception`——后者会把编程 bug 也当可重试；`retry_backoff_max` 封顶指数退避）
        - **超时**：`soft_time_limit`/`time_limit` 防 worker 被卡死任务占满
        - **监控**：`flower` 看队列/worker/任务状态；队列积压告警（worker 扩容）
        - **序列化**：参数可 JSON 化（传 ID 不传大对象）；结果别存大块数据（存引用）

- **协助记忆**
    - 口诀：“`Celery` = `delay` 投递 + Broker 排队 + Worker 消费；耗时/免等/定时/削峰才用——配幂等、超时、监控三件套”。

- **进阶思考**
    - **用 Celery 和直接起个后台线程有什么区别？**
        - 线程在应用进程内：应用重启任务丢失、无持久化、无法横向扩；`Celery` 任务在队列里持久——取决于 `broker` 配置（`Redis` 默认不落盘需开 RDB/AOF，要稳选 `RabbitMQ` `durable` 队列+持久化消息）、`worker` 独立部署可扩容、自带重试/路由/定时。一次性轻量异步线程够用，业务关键任务必须队列化。
    - **任务积压了怎么处理？**
        - 查 `worker` 数/并发度（`flower`），扩 `worker`；看是否有慢任务堵队列（加 `time_limit`、拆分任务）；按优先级/队列拆分（`routes` 把轻重任务分到不同 worker）；积压清空需谨慎。预防靠容量评估 + 积压告警。

- **扩展信息**
    - **相关**：`kombu`（broker 抽象）、`flower`（监控面板）、`django-celery-beat`（定时任务入库管理）
    - **替代品对照**：`RQ`（轻量 Redis 队列）、`Dramatiq`、`arq`（`asyncio`）——复杂度不高时 `RQ` 足够；生态/功能完整性 `Celery` 最强

## 🤔 简述 Django CSRF 工作原理？ 
- **`CSRF`（跨站请求伪造）防护原理是“请求携带只有本站知道的随机令牌”：`Django` 给每个会话生成 `csrf_token`，表单里嵌隐藏字段（或 `AJAX` 带 `X-CSRFToken` 头），`CsrfViewMiddleware` 收到 POST 等不安全方法时做双重提交校验（cookie 里 + 提交内容里的令牌必须一致），第三方站点拿不到令牌所以伪造请求被拒。核心：“发令牌 + 回验令牌——验证请求真出自本站页面”。**
    - **攻击原理（为什么需要防护）**
        - 用户已登录 A 站（浏览器存 cookie）→ 访问恶意 B 站 → B 站页面偷偷向 A 发起请求 → 浏览器自动带上 A 站 cookie → A 以为是用户本人操作（转账/改密码）
        - 要害：cookie 自动携带 + 请求可被第三方页面发起

    - **Django 防护机制（双重提交）**
        - 发：`{% csrf_token %}` 在表单里渲染隐藏字段；`AJAX` 从名为 `csrftoken` 的 cookie（`CSRF_COOKIE_NAME` 配置）取值放进 `X-CSRFToken` 头
        - 验：POST/PUT/PATCH/DELETE（除 GET/HEAD/OPTIONS/TRACE 外的不安全方法）时，中间件比对“提交的令牌”与“cookie/session 中的令牌”是否匹配——不匹配返回 403
        - 双重提交 + 掩码：默认令牌存 `csrftoken` cookie（非会话绑定），提交时对 cookie 与表单/头里的令牌做掩码去敏后比对；攻击者跨站拿不到 cookie 内容，伪造请求缺令牌必被拒（`CSRF_USE_SESSIONS=True` 才移入 session）

    - **相关配置**
        - `MIDDLEWARE` 里的 `CsrfViewMiddleware`（删掉即全局关——勿动）
        - `CSRF_TRUSTED_ORIGINS`：跨域可信来源（前后端分离域名不同时必配，含协议）
        - `@csrf_exempt`：单视图豁免（如接收外部 `webhook`——改用其他鉴权），`@csrf_protect` 强制开启

    - **AJAX/前后端分离注意**
        - 前端从名为 `csrftoken` 的 cookie（`CSRF_COOKIE_NAME` 配置）读 token 放请求头（`axios` 拦截器统一加）；跨域时 `CSRF_TRUSTED_ORIGINS` + cookie 的 `SameSite`/`CORS` 凭据配置要一起对齐

- **协助记忆**
    - 口诀：“`CSRF` 防的是‘借刀杀人’：登录态被第三方借用——`Django` 发随机令牌（表单/头），提交时双提交校验，外人拿不到令牌就伪造不了”。

- **进阶思考**
    - **`CSRF Token` 和 `JWT` 有什么区别（都能防 `CSRF` 吗）？**
        - `CSRF token` 专防“借 cookie 的伪造请求”；JWT 放在 `Authorization` 头（浏览器不自动携带）时天然免疫 `CSRF`——所以纯 JWT 的前后端分离可少管 `CSRF`。但 JWT 存 cookie 就又回到 `CSRF` 风险。规律：凭据自动携带（cookie）就需要 `CSRF` 防护，手动携带（header）就不需要。
    - **`SameSite` cookie 和 `CSRF` 是什么关系？**
        - `SameSite=Lax/Strict` 让浏览器跨站请求不带 cookie，从源头大幅缓解 `CSRF`（现代浏览器默认 Lax）。但仍有兼容性/绕过面（顶级 GET 导航、老浏览器），`Django` 的 token 校验仍应保留——纵深防御，不把宝押在单一机制上。

- **扩展信息**
    - **相关设置**：`CSRF_COOKIE_SECURE`（仅 HTTPS）、`CSRF_COOKIE_HTTPONLY`（JS 可读性与安全权衡）、`CSRF_FAILURE_VIEW`（自定义 403 页）
    - **运维提醒**：`webhook`/对外回调接口用 `csrf_exempt` 时必须自备签名校验（如钉钉/`GitHub` 的 sign 头验证），否则等于裸奔

## 🤔 Django 如何编写测试用例？ 
- **用 `django.test.TestCase`（自带测试数据库与事务回滚）继承编写：类继承 `TestCase`、方法以 `test_` 开头，用 `self.client` 模拟请求、`assertEqual`/`assertTrue` 断言；`pytest-django` 是现代增强（`fixture`/参数化）。运行 `python manage.py test`。核心：“`TestCase` 建事务沙箱 + `self.client` 发请求 + `assert` 断言”。**
    - **基础结构（Django 原生）**
        - `from django.test import TestCase` → `class HostViewTest(TestCase):` → `def setUp(self)` 准备公共数据 → `def test_list_hosts(self):` 具体用例
        - 运行：`python manage.py test app.tests -v 2`（自动建测试库、每个用例包在事务里执行完回滚）

    - **测试视图/API（`self.client`）**
        - `resp = self.client.get('/api/hosts/')`；断言 `self.assertEqual(resp.status_code, 200)`、`self.assertJSONEqual(resp.content, ...)`
        - POST：`self.client.post(url, data, content_type='application/json')`
        - 模拟登录：`self.client.force_login(user)`（跳过认证流程直接种会话）
        - `DRF`：`from rest_framework.test import APIClient`（用法类似，多 `format='json'`）

    - **数据与隔离**
        - `setUp` 造数据 / 工厂库（`factory_boy`）生成测试对象；每个测试方法独立（事务回滚隔离）；多方法共用数据用 `setUpTestData`（类级只建一次，更省时）
        - 外部依赖隔离：`unittest.mock.patch` 打桩（`requests`/`celery` 任务不真发）——`@patch('app.views.run_cmd')`
        - `pytest-django` 增强：`db` fixture、`client` fixture、参数化更灵活（现代项目主流）

    - **测什么（运维平台建议）**
        - API 状态码与响应结构（回归保护）、权限（未登录 401/越权 403）、模型方法与校验、URL 路由覆盖
        - 断言行为而非实现细节（换实现不换行为时测试不该挂）

- **协助记忆**
    - 口诀：“继承 `TestCase`，`test_` 开头写用例；`self.client` 发请求、`assert*` 验结果；外部依赖 `mock` 掉，每例事务自动滚”。

- **进阶思考**
    - **`TestCase` 和 `SimpleTestCase`/`TransactionTestCase` 区别？**
        - `TestCase`：每个用例包事务回滚（快，但不能测事务本身）；`TransactionTestCase`：真提交（可测事务/多连接，慢，需清理）；`SimpleTestCase`：不碰数据库（默认查询报错，可 `databases` 显式放行）。按需选择，默认 `TestCase`。
    - **CI 里跑 Django 测试要注意什么？**
        - 用独立测试数据库（容器化临时库，`--parallel` 需数据库用户有建库权限、为各进程建独立库）、覆盖率 `coverage run manage.py test` 出报告；外部服务（Redis/MQ）用容器起真实例或 mock，保证 CI 稳定可重复。

- **扩展信息**
    - **工具链**：`pytest` + `pytest-django`（现代主流）、`factory_boy`（造数据）、`mock`/`freezegun`（时间冻结）、`coverage`/`pytest-cov`（覆盖率）
    - **运维价值**：`CMDB`/发布系统改动用测试兜底回归；脚本类逻辑抽成函数后也能直接 `pytest`（不必是 Django 项目才能测）

## 🤔 Websocket 与 HTTP 有什么区别？ 
- **HTTP 是“请求-响应”的单向短交互协议（客户端问一次、答一次，连接可复用但服务端不能主动推）；`WebSocket` 是升级后的全双工长连接（一次握手建立后，双方随时互发，服务端可主动推送），适合实时场景（聊天/行情/终端/告警推送）。核心：“HTTP 一问一答，`WebSocket` 建一条常开管道双向随时说”。**
    - **核心区别**
        - **通信模型**：HTTP 请求→响应（客户端发起）；`WebSocket` 全双工（服务端可主动推）
        - **连接生命周期**：HTTP 短交互（`keep-alive` 复用但仍一问一答）；`WebSocket` 握手一次后长连接持续
        - **协议与开销**：`WebSocket` 帧头极小（2-14 字节），HTTP 每次带完整头；实时高频场景开销差数量级
        - **协议标识**：`ws://`/`wss://`（`TLS`）

    - **握手过程（WebSocket 从 HTTP 升级而来）**
        - 客户端发 `HTTP GET` + `Upgrade: websocket` + `Connection: Upgrade` + `Sec-WebSocket-Key`
        - 服务端回 `101 Switching Protocols` → 之后同一 TCP 连接切换为 `WebSocket` 帧协议

    - **适用场景**
        - **WebSocket**：实时推送（告警/行情/监控大盘）、聊天/协同、在线终端（`WebShell`/`K8s` exec）、多人编辑
        - **HTTP**：常规 CRUD/页面/API（请求-响应语义天然匹配）
        - 轮询/SSE 对比：HTTP 轮询浪费请求；SSE 单向推送（服务端→客户端）够用且简单；真双向才上 `WebSocket`

    - **运维要点**
        - 反代支持：`nginx` 需 `proxy_set_header` 透传 `Upgrade`/`Connection` 升级头；超时参数（`proxy_read_timeout`）要拉长
        - 连接保持：心跳（`ping`/`pong` 帧）防中间设备断链；扩容用粘性会话或 `Redis` pub/sub 跨实例广播

- **协助记忆**
    - 口诀：“HTTP 一问一答短平快，`WebSocket` 握手一次双向长聊——实时推送选 `WS`，普通接口选 HTTP；SSE 是单向的折中”。

- **进阶思考**
    - **轮询、长轮询、SSE、WebSocket 怎么演进与选择？**
        - 轮询（定时全量问，浪费）→ 长轮询（有更新才答，挂住连接）→ SSE（服务端单向流，HTTP 原生）→ `WebSocket`（全双工）。选择：只要服务端推且不需要客户端高频上行 → SSE 足够（简单可靠）；双向交互（终端/聊天）才上 `WebSocket`。
    - **运维平台里 `WebSocket` 的典型坑？**
        - `nginx` 忘透传 `Upgrade` 头（握手 200 而非 101）；长连接被负载均衡空闲超时掐掉（要心跳）；多实例部署消息只到一台（广播要走 `Redis` pub/sub/MQ）；连接数即资源（`fd` 上限、内存），要监控与上限控制。

- **扩展信息**
    - **实现库**：`websockets`/`aiohttp`（异步）、`channels`（Django `WebSocket`，走 `ASGI`）、`socket.io`（带降级回退的封装）
    - **相关协议**：SSE（`EventSource`，单向）、`MQTT over WebSocket`（IoT web 端接入常用）

## 🤔 Python 怎么使用正则？ 
- **用标准库 `re` 模块，标准三步：`re.compile(模式)` 编译 → `match`/`search`/`findall` 匹配 → `group()`/`groups()` 取结果。常用函数：`match`（从头）、`search`（任意位置）、`findall`（所有匹配）、`sub`（替换）、`split`（切分）；`r''` 原始字符串防转义坑。核心：“`compile` 预编译 + 按‘找一个/找全部/替换’选函数 + 命名分组取值”。**
    - **常用函数（按需求选）**
        - `re.match(p, s)`：从开头匹配（开头不合就 None）
        - `re.search(p, s)`：全文找第一处（日志解析最常用）
        - `re.findall(p, s)`：所有匹配列表（无分组返回整匹配，有分组返回分组元组）
        - `re.finditer(p, s)`：迭代器版（大文本流式处理）
        - `re.sub(p, repl, s)`：替换（`repl` 可用 `\1` 反向引用或函数）；`re.split(p, s)`：按模式切分
        - `re.fullmatch`：整串必须全匹配（校验格式用，如 IP/域名）

    - **标准用法模板**
        - `p = re.compile(r'(?P<ip>\d+\.\d+\.\d+\.\d+) .* (?P<status>\d{3})')` 编译一次
        - `m = p.search(line)` → `m.group('ip')`/`m.group('status')`（命名分组比数字可读）
        - 多行日志：`for m in p.finditer(text): ...`

    - **工程要点（运维高频坑）**
        - `r''` 原始字符串：`r'\d+'` 而非 `'\\d+'`（否则反斜杠双重转义）
        - `.` 不匹配换行：跨行用 `re.S`（DOTALL）；`^$` 逐行语义用 `re.M`（MULTILINE）
        - 预编译：`re` 内部有编译缓存（相同模式串命中缓存，非每次都编译），但缓存有上限易被剔除——循环里显式 `compile` 仍是更可控/更易读的实践
        - 贪婪 vs 非贪婪：`.*` 贪到头，`.*?` 最短匹配——取两标签之间内容用 `.*?`
        - 复杂日志解析先试 `split`/字符串方法（更快更可读），正则留给真模式匹配

- **协助记忆**
    - 口诀：“`compile` 编译一次，`search` 找一个、`findall` 找一堆、`sub` 替换；`r''` 写模式、命名分组取值、跨行加 `S` 多行加 `M`、贪婪改 `?`”。

- **进阶思考**
    - **`match`、`search`、`fullmatch` 的区别场景？**
        - `match` 锚定开头（前缀判断）；`search` 任意位置（日志里抠字段）；`fullmatch` 整串校验（格式合规：IP/手机号——更接近 `\A...\Z`，比 `^...$` 严格——后者 `$` 在串尾换行前也匹配、`re.M` 下按行）。用错锚定语义是误匹配的常见原因。
    - **解析 Nginx 日志用正则还是 `split`？**
        - 固定格式且字段无空格 → `split()` 索引取（快数倍）；字段可能含空格（`UA`/`referer` 带引号）或要容错 → 正则+命名分组（`(?P<ip>\S+).*(?P<method>\w+) (?P<url>\S+)`）。生产里常见“split 提列 + 正则兜底”混合。

- **扩展信息**
    - **进阶特性**：`(?P<name>...)` 命名分组、`(?:...)` 非捕获组、`(?=...)`/`(?<=...)` 环视、`re.VERBOSE` 写可读正则（加注释）
    - **调试工具**：`regex101.com`（在线解释正则）、`re.DEBUG` 打印解析树——复杂表达式先可视化再上线

## 🤔 Python 正则中有哪些常用的字符及作用？ 
- **常用分四组：元字符（`.` 任意字符 / `^` `$` 行首尾 / `*` `+` `?` 数量 / `|` 或 / `()` 分组 / `[]` 字符集）、预定义类（`\d` 数字 / `\w` 字母数字下划线 / `\s` 空白，大写取反）、数量词（`{m,n}` 区间 / `*?` `+?` 非贪婪）、分组扩展（`(?P<name>)` 命名 / `(?:)` 非捕获）。核心：“`.\d\w\s` + `*+?{}` + 分组，覆盖日志解析九成场景”。**
    - **元字符（结构）**
        - `.`：任意单字符（不含换行，`re.S` 才含）
        - `^` / `$`：串/行首、尾（`re.M` 下按行）
        - `|`：或（`cat|dog`）
        - `()`：分组捕获（`group(1)`）；`(?:...)` 只分组不捕获
        - `[abc]`：字符集任一；`[a-z0-9]` 范围；`[^abc]` 取反
        - `\`：转义（`.` 本身要写 `\.`——匹配 IP 点号必须转义）

    - **预定义字符类**
        - `\d` 数字 / `\D` 非数字（默认 Unicode 模式还会匹配全角等非 ASCII 数字，要纯 ASCII 加 `re.A`/`(?a)`，或显式用 `[0-9]`）
        - `\w` 字母数字下划线 / `\W` 反之
        - `\s` 空白（空格/制表/换行）/ `\S` 非空白
        - `\b` 单词边界（`\berror\b` 精确匹配单词）

    - **数量词（重复次数）**
        - `*` ≥0 次、`+` ≥1 次、`?` 0 或 1 次
        - `{m}` 恰好 m、`{m,}` 至少 m、`{m,n}` 区间
        - **非贪婪**：`*?` `+?` `??` `{m,n}?`——最短匹配（取两个引号间内容用 `(.*?)`，若确定非空才用 `(.+?)`——`+` 至少一字符，空串匹配不上）

    - **分组扩展（`(?...)`）**
        - `(?P<name>...)` 命名分组（`group('name')`）；`(?P=name)` 反向引用
        - `(?=...)`/`(?!...)` 正/负向前瞻、`(?<=...)`/`(?<!...)` 后顾——位置判断不消费字符
        - `(?i)` 忽略大小写（或 `re.I`）

    - **运维高频组合示例**
        - IP：`\d{1,3}(?:\.\d{1,3}){3}`（点必须转义）
        - 时间戳：`\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}`
        - 日志状态码：`\s(\d{3})\s`、请求行：`(?:GET|POST) (\S+)`

- **协助记忆**
    - 口诀：“点任意、星加问量、尖括号定首尾、方括号挑字符；`d` 数字 `w` 词 `s` 空白、大写全反；数量加问变懒惰、点要转义别忘”。

- **进阶思考**
    - **为什么匹配 IP 的 `.` 必须转义？**
        - 未转义的 `.` 是“任意字符”——`192.168.1.1` 写成未转义正则会把点当通配，`192x168y1z1` 也匹配（误匹配/安全漏洞）。字符类内 `[.]` 或转义 `\.` 才是字面点。
    - **`.*` 和 `.*?` 在日志解析里怎么选？**
        - `.*` 贪婪会“吃满一行再回溯”（嵌套引号/多字段时错位）；`.*?` 最短匹配在第一个结束符停。解析带引号字段（`UA`、`referer`）必须 `.*?`；注意两者都可能回溯暴增——能精确字符类（排除引号的 `[^"xxx]` 思路）就别用 `.*`。

- **扩展信息**
    - **`re.VERBOSE`**：允许正则里写空白与注释（复杂表达式必备可读性）
    - **性能与安全**：嵌套量词（`(a+)+`）可致回溯爆炸（ReDoS，正则 DoS）——对外输入的正则要避免嵌套不确定长度组

## 🤔 都做过哪些运维开发项目？ 
- **运维开发项目按“平台→自动化→工具→集成”组织回答：平台类（`CMDB` 资产管理、发布部署系统、工单审批流）、自动化类（批量巡检、日志采集分析、监控告警闭环）、工具类（`CLI` 运维工具、日志 `Top` 分析、备份校验脚本）、集成类（云 `API` 管控、`K8s` 平台、钉钉/企微机器人）。回答框架：背景痛点 → 方案与角色 → 技术栈 → 量化效果。核心：“讲清痛点-方案-技术栈-收益，突出自己在项目中的角色”。**
    - **平台类项目（系统建设）**
        - **`CMDB` 资产管理**：主机/机架/应用关系建模（Django+`DRF`），自动发现（agent/SSH 采集）+ 变更审计——解决“资产靠表格、变更没记录”
        - **发布部署系统**：Web 发单 → 审核 → 多机灰度发布（`Ansible`/`paramiko`）→ 回滚；解决手工发布事故多
        - **工单/审批流**：权限申请/变更审批线上化（状态机 + 通知集成）

    - **自动化项目（提质提效）**
        - **批量巡检平台**：Python + 线程池/`Celery` 并发采集 CPU/内存/磁盘/证书到期，结果入库 + 日报推送
        - **日志分析**：Nginx 日志 `Top IP`/错误率统计（`Counter`/生成器流式），CC 攻击自动识别
        - **告警闭环**：`Zabbix`/`Prometheus` 告警 `webhook` → 自动诊断脚本（看日志/重启策略）→ 钉钉通知 + 处理记录

    - **工具类项目（脚本利器）**
        - 批量执行工具（`subprocess`+`ssh`+并发）、证书到期检查、`OSS`/备份校验、数据库慢查询采集
        - 特点：`argparse` 参数化、`logging`、可定时跑

    - **集成类项目（云原生）**
        - **云管脚本/平台**：`ECS` 定时开关（降成本）、`OSS` 生命周期治理、`RAM`（阿里云资源访问控制）权限审计（云 SDK）
        - **`K8s` 运维**：集群巡检（`kubernetes` client）、`Pod` 异常自动处置、发布平台对接 `K8s API`
        - **机器人集成**：告警/发布/审批通知到钉钉/企微群（`webhook`）

    - **回答模板（每项目 1 分钟版）**
        - **痛点**：之前怎么做、什么问题（人工/慢/易错）→ **方案**：我设计的架构与流程 → **技术栈**：语言/框架/组件 → **效果**：量化（工时节省/故障率下降/覆盖台数）→ **角色**：独立开发/主导设计/参与模块

- **协助记忆**
    - 口诀：“平台（`CMDB`/发布/工单）+ 自动化（巡检/日志/告警）+ 工具（批量/校验）+ 集成（云/`K8s`/机器人）——每项目讲痛点、方案、技术栈、量化收益”。

- **进阶思考**
    - **没有大项目经验怎么回答（包装原则）？**
        - 从“重复劳动”找素材：手动巡检 → 写成自动化脚本就是项目（讲采集维度/并发量/报告形式）；手工备份 → 备份校验工具（覆盖率/失败告警）。真实小项目 + 深度讲透远胜虚构大项目——追问三层就露馅，诚实 + 量化是底线。
    - **面试官追问“系统架构怎么设计的”怎么应对？**
        - 按层拆：入口（Web/CLI）→ 逻辑层（任务调度/权限/状态机）→ 执行层（并发框架/Agent）→ 存储（MySQL/Redis）→ 集成（MQ/通知）。同时主动讲踩过的坑（并发控制/失败重试/幂等）——坑讲得具体，可信度立刻上来。

- **扩展信息**
    - **加分项**：开源贡献/内部工具开源化、`IaC`（`Terraform`/`Ansible` 代码化）、监控体系（`Prometheus` 栈）建设经验、`CI`/`CD` 流水线治理
    - **能力映射**：平台项目考工程能力（Django/`DRF`/数据库），自动化考语言功底（并发/异常/日志），集成考云与 `K8s` 视野——三线并举是运维开发的核心画像

---

> 作者: [0x5c0f](https://blog.0x5c0f.cc)  
> URL: https://blog.0x5c0f.cc/posts/other/%E8%BF%90%E7%BB%B4%E5%B8%B8%E8%A7%81%E9%A2%98-python%E8%BF%90%E7%BB%B4%E5%BC%80%E5%8F%91/  

