文章
合集Python 核心与面向对象第 1 / 5 篇

Python 核心语言机制

1. 对象模型

1.1 一切皆对象

Python 中,函数、类、模块、None、整数字面量都是对象,都有:

  • 身份(id()):对象在内存中的唯一标识
  • 类型(type()):决定对象支持哪些操作
  • 值:对象存储的数据
print(type(42))       # <class 'int'>
print(type(int))      # <class 'type'>
print(type(type))     # <class 'type'>(type 是所有类的元类,且 type(type) is type)

def greet(): pass
print(type(greet))    # <class 'function'>
print(isinstance(greet, object))   # True,函数也是对象

这意味着函数可以赋值给变量、作为参数传递、存入列表——这是高阶函数和装饰器的基础。

1.2 引用与绑定

Python 变量不"存储值",而是存储对象的引用。赋值是让变量名指向对象,不是复制数据。

a = [1, 2, 3]
b = a           # b 和 a 指向同一个列表对象
b.append(4)
print(a)        # [1, 2, 3, 4]
print(a is b)   # True,同一个对象

函数参数传递是"传对象引用"(Pass by Object Reference):

def modify(lst, num):
    lst.append(99)    # 修改列表内容,影响外部
    num += 1          # 重新绑定局部变量,不影响外部

my_list = [1, 2]
my_num = 10
modify(my_list, my_num)
print(my_list)   # [1, 2, 99]
print(my_num)    # 10

1.3 is 与 ==、对象驻留

  • ==:比较值(调用 __eq__)
  • is:比较身份(id() 是否相同)

对象驻留(interning):CPython 对小整数(-5 ~ 256)和编译期可确定的短字符串做缓存,is 比较可能意外为 True:

a = 256
b = 256
print(a is b)    # True(缓存范围内)

c = 257
d = 257
print(c is d)    # False(超出缓存范围,但在交互式环境可能为 True)

s1 = "hello"
s2 = "hello"
print(s1 is s2)  # True(字符串驻留)

# 规则:只用 is 判断 None、True、False 和单例
print(x is None)   # ✅
print(x == None)   # ❌ 不推荐

1.4 可变与不可变

不可变(Immutable)可变(Mutable)
int, float, strlist, dict, set
tuple, frozensetbytearray
bytes自定义类(默认)

不可变对象的"修改"实际是创建新对象:

s = "hello"
print(id(s))
s += " world"
print(id(s))    # id 变了,s 指向新对象

经典陷阱:可变默认参数

# ❌ 错误:默认参数在函数定义时创建,所有调用共享
def append_to(item, lst=[]):
    lst.append(item)
    return lst

print(append_to(1))   # [1]
print(append_to(2))   # [1, 2]  ← 不是 [2]!

# ✅ 正确:用 None 作哨兵
def append_to(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

1.5 浅拷贝与深拷贝

import copy

original = [[1, 2], [3, 4]]

shallow = copy.copy(original)       # 新容器,内部元素共享
deep    = copy.deepcopy(original)   # 完全独立的副本

shallow[0].append(99)
print(original[0])   # [1, 2, 99]  ← 浅拷贝,内层对象共享

deep[1].append(99)
print(original[1])   # [3, 4]      ← 深拷贝,完全独立

选择原则:

  • 只需独立容器,不需独立元素 → 浅拷贝(性能更好)
  • 需要完全独立的副本 → 深拷贝(递归复制所有嵌套对象)

2. 函数与作用域

2.1 LEGB 规则

Python 按 L → E → G → B 的顺序查找名称:

层级全称说明
LLocal当前函数内部
EEnclosing外层函数(闭包)
GGlobal模块顶层
BBuilt-inPython 内置命名空间
x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print(x)   # local
    inner()
    print(x)       # enclosing

outer()
print(x)           # global

global 和 nonlocal 关键字:

counter = 0

def increment():
    global counter      # 声明修改全局变量
    counter += 1

def make_counter():
    count = 0
    def inc():
        nonlocal count  # 声明修改外层函数的变量
        count += 1
        return count
    return inc

原则:少用 global,多用闭包或类封装状态。

2.2 高阶函数

接收函数作为参数,或返回函数的函数:

nums = [1, -3, 2, -1, 4]

# map / filter / sorted
squares = list(map(lambda x: x**2, nums))
positives = list(filter(lambda x: x > 0, nums))
by_abs = sorted(nums, key=abs)

# functools 工具
from functools import reduce, partial

total = reduce(lambda acc, x: acc + x, nums)   # 求和

double = partial(pow, exp=2)   # 固定部分参数
print(double(base=3))          # 9

2.3 闭包

函数记住了它被定义时的词法作用域,即使该作用域已执行完毕:

def make_multiplier(n):
    def multiplier(x):
        return x * n   # n 来自外层,被"捕获"
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))   # 10
print(triple(5))   # 15

# 查看捕获的自由变量
print(double.__closure__[0].cell_contents)   # 2

经典陷阱:循环中的闭包后期绑定(Late Binding)

闭包捕获的是变量名,不是变量的值。循环结束后,所有闭包看到的 i 都是最终值:

# ❌ 错误:所有函数都返回 4
funcs = [lambda: i for i in range(5)]
print([f() for f in funcs])   # [4, 4, 4, 4, 4]

# ✅ 修复方案一:用默认参数捕获当前值
funcs = [lambda i=i: i for i in range(5)]
print([f() for f in funcs])   # [0, 1, 2, 3, 4]

# ✅ 修复方案二:用 functools.partial
from functools import partial
funcs = [partial(lambda i: i, i) for i in range(5)]

2.4 装饰器

装饰器是一个接收函数并返回新函数的高阶函数,本质是语法糖:

@decorator
def func(): ...
# 等价于
func = decorator(func)

基础装饰器(函数形式):

import functools
import time

def timer(func):
    @functools.wraps(func)   # 保留原函数的 __name__、__doc__ 等元信息
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} 耗时 {elapsed:.4f}s")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(0.1)

带参数的装饰器(三层嵌套):

def retry(max_times=3, exceptions=(Exception,)):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_times):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == max_times - 1:
                        raise
                    print(f"第 {attempt + 1} 次失败,重试... ({e})")
        return wrapper
    return decorator

@retry(max_times=3, exceptions=(ConnectionError,))
def fetch_data(url):
    ...

类装饰器(用 __call__ 实现,适合需要保存状态的场景):

class CallCounter:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} 被调用了 {self.count} 次")
        return self.func(*args, **kwargs)

@CallCounter
def greet(name):
    return f"Hello, {name}!"

greet("Alice")   # greet 被调用了 1 次
greet("Bob")     # greet 被调用了 2 次
print(greet.count)   # 2

装饰器叠加顺序:

@decorator_a
@decorator_b
@decorator_c
def func(): ...
# 等价于:func = decorator_a(decorator_b(decorator_c(func)))
# 执行时由外向内:a 的 wrapper → b 的 wrapper → c 的 wrapper → 原函数

functools.lru_cache 缓存装饰器:

from functools import lru_cache, cache

@lru_cache(maxsize=128)   # 缓存最近 128 次结果
def fib(n):
    if n < 2: return n
    return fib(n - 1) + fib(n - 2)

@cache   # Python 3.9+,等价于 lru_cache(maxsize=None)
def fib_unlimited(n):
    if n < 2: return n
    return fib_unlimited(n - 1) + fib_unlimited(n - 2)

print(fib(40))         # 瞬间完成(无缓存需递归 2^40 次)
print(fib.cache_info())  # CacheInfo(hits=38, misses=41, maxsize=128, currsize=41)

2.5 参数机制

Python 函数参数的五种形式:

def func(
    pos_only1, pos_only2,   # 位置参数
    /,                       # / 之前只能位置传参(Python 3.8+)
    normal,                  # 普通参数(位置或关键字均可)
    *args,                   # 可变位置参数(收集为 tuple)
    kw_only,                 # 关键字参数(* 之后必须用关键字传参)
    **kwargs                 # 可变关键字参数(收集为 dict)
):
    pass
def log(level, *args, sep=" ", **kwargs):
    print(f"[{level}]", *args, sep=sep)
    if kwargs:
        print("extra:", kwargs)

log("INFO", "server", "started")
log("DEBUG", "a", "b", sep="-", timestamp="12:00")

# 解包传参
args = (1, 2, 3)
kwargs = {"name": "Alice"}
some_func(*args, **kwargs)

3. 迭代与生成

3.1 可迭代对象与迭代器

可迭代对象(Iterable):实现了 __iter__() 的对象,可以被 for 循环遍历。
迭代器(Iterator):同时实现了 __iter__() 和 __next__() 的对象,持有迭代状态。

class MyRange:
    def __init__(self, n):
        self.n = n
        self.current = 0

    def __iter__(self):
        return self   # 迭代器返回自身

    def __next__(self):
        if self.current >= self.n:
            raise StopIteration
        val = self.current
        self.current += 1
        return val

for i in MyRange(3):
    print(i)   # 0 1 2

可迭代对象 vs 迭代器的区别:

lst = [1, 2, 3]       # 可迭代对象,但不是迭代器
it = iter(lst)         # 从可迭代对象获取迭代器

print(next(it))   # 1
print(next(it))   # 2

# 可迭代对象可以多次遍历,迭代器是一次性的
for x in lst: print(x)   # 正常
for x in it:  print(x)   # 只有 3(前两个已被消耗)

迭代器按需计算下一个值,适合处理大数据流或无限序列。

3.2 生成器

生成器是编写迭代器最简洁的方式,使用 yield 关键字:

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

gen = fibonacci()
print([next(gen) for _ in range(8)])   # [0, 1, 1, 2, 3, 5, 8, 13]

工作原理:

  1. 调用生成器函数 → 返回生成器对象(不执行函数体)
  2. 调用 next() → 从上次 yield 处继续执行
  3. 遇到 yield → 暂停并返回值
  4. 函数执行完毕 → 抛出 StopIteration

生成器表达式(比列表推导式节省内存):

# 列表推导:立即计算,占用全部内存
squares_list = [x**2 for x in range(1_000_000)]

# 生成器表达式:惰性计算,只占极少内存
squares_gen = (x**2 for x in range(1_000_000))

# 可以直接传入接受可迭代的函数
total = sum(x**2 for x in range(1_000_000))   # 括号可省略

双向通信:send() 和 throw():

def accumulator():
    total = 0
    while True:
        value = yield total   # yield 既返回值又接收 send 的值
        if value is None:
            break
        total += value

gen = accumulator()
next(gen)          # 启动生成器(必须先 next 或 send(None))
print(gen.send(10))   # 10
print(gen.send(20))   # 30
print(gen.send(5))    # 35

3.3 yield from

yield from 将迭代委托给另一个可迭代对象,等价于逐一 yield,但更高效,且会透传 send() 和 throw():

# 没有 yield from:需要手动循环
def chain_manual(*iterables):
    for it in iterables:
        for item in it:
            yield item

# 使用 yield from:简洁高效
def chain(*iterables):
    for it in iterables:
        yield from it

list(chain([1, 2], [3, 4], [5]))   # [1, 2, 3, 4, 5]

递归展平嵌套结构:

def flatten(nested):
    for item in nested:
        if isinstance(item, list):
            yield from flatten(item)   # 递归委托
        else:
            yield item

list(flatten([1, [2, [3, 4]], 5]))   # [1, 2, 3, 4, 5]

3.4 itertools — 迭代器工具箱

itertools 提供了一组高效的迭代器工具,全部惰性求值:

import itertools

# --- 无限迭代器 ---
counter = itertools.count(start=1, step=2)     # 1, 3, 5, 7, ...
cycler = itertools.cycle([1, 2, 3])             # 1, 2, 3, 1, 2, 3, ...
repeater = itertools.repeat("x", times=3)       # 'x', 'x', 'x'

# --- 有限迭代器 ---
# chain:连接多个可迭代对象
list(itertools.chain([1, 2], [3, 4], [5]))      # [1, 2, 3, 4, 5]

# islice:截取片段
list(itertools.islice(range(100), 5, 10))       # [5, 6, 7, 8, 9]

# takewhile / dropwhile:按条件截取
list(itertools.takewhile(lambda x: x < 5, [1, 3, 6, 2]))   # [1, 3]
list(itertools.dropwhile(lambda x: x < 5, [1, 3, 6, 2]))   # [6, 2]

# groupby:分组(需先排序)
data = [("a", 1), ("a", 2), ("b", 3)]
for key, group in itertools.groupby(data, key=lambda x: x[0]):
    print(key, list(group))

# --- 组合迭代器 ---
list(itertools.product([1, 2], ["a", "b"]))
# [(1,'a'), (1,'b'), (2,'a'), (2,'b')]

list(itertools.combinations([1, 2, 3], 2))
# [(1,2), (1,3), (2,3)]

list(itertools.permutations([1, 2, 3], 2))
# [(1,2), (1,3), (2,1), (2,3), (3,1), (3,2)]

4. 上下文与协议

4.1 上下文管理器

协议:实现 __enter__ 和 __exit__ 方法,配合 with 语句使用。

class DatabaseConnection:
    def __enter__(self):
        self.conn = connect_to_db()
        return self.conn

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.conn.close()
        return False   # False = 不压制异常;True = 压制(吞掉异常)

with DatabaseConnection() as conn:
    conn.query("SELECT 1")
# 离开 with 块时自动调用 __exit__,即使发生异常

__exit__ 参数说明:若 with 块内发生异常,exc_type/exc_val/exc_tb 分别为异常类型、值和 traceback;正常退出时三者均为 None。

contextlib.contextmanager:用生成器写上下文管理器:

from contextlib import contextmanager

@contextmanager
def managed_resource():
    print("进入")
    resource = acquire_resource()
    try:
        yield resource    # yield 的值赋给 as 变量
    finally:
        release_resource(resource)   # 无论是否异常都执行
        print("退出")

with managed_resource() as r:
    use(r)

contextlib 实用工具:

from contextlib import suppress, ExitStack, nullcontext

# suppress:忽略指定异常
with suppress(FileNotFoundError):
    os.remove("maybe_exists.txt")   # 文件不存在时静默跳过

# ExitStack:动态管理多个上下文
with ExitStack() as stack:
    files = [stack.enter_context(open(f)) for f in file_list]
    # 离开时按 LIFO 顺序关闭所有文件

# nullcontext:占位用,无操作的上下文管理器(Python 3.7+)
def process(conn=None):
    ctx = nullcontext(conn) if conn else DatabaseConnection()
    with ctx as c:
        c.query("SELECT 1")

4.2 描述符协议

见 元编程 → 「描述符」章节。

4.3 属性查找机制

描述符协议的完整属性查找链见 元编程 → 「描述符」。

Python 访问 obj.attr 时,按如下优先级查找(从高到低):

1. 数据描述符(类或父类中定义了 __set__ 的描述符)
2. 实例 __dict__
3. 非数据描述符(只有 __get__)及普通类属性
4. __getattr__(属性不存在时的最后兜底)
class Descriptor:
    def __get__(self, obj, objtype=None):
        print("__get__ 被调用")
        return 42
    def __set__(self, obj, value):
        print("__set__ 被调用")

class MyClass:
    attr = Descriptor()   # 数据描述符(有 __set__)

obj = MyClass()
obj.__dict__["attr"] = 999   # 直接写入实例字典
print(obj.attr)   # __get__ 被调用 → 42(数据描述符优先于实例 __dict__)

自定义属性访问钩子:

class FlexibleConfig:
    def __init__(self, **kwargs):
        self._data = kwargs

    def __getattr__(self, name):
        # 只在正常查找失败后才调用
        if name in self._data:
            return self._data[name]
        raise AttributeError(f"没有属性 {name!r}")

    def __setattr__(self, name, value):
        if name.startswith("_"):
            super().__setattr__(name, value)   # 正常存入实例 __dict__
        else:
            self._data[name] = value

cfg = FlexibleConfig(host="localhost", port=5432)
print(cfg.host)    # localhost
cfg.timeout = 30
print(cfg.timeout) # 30

注意:__getattribute__ 是每次属性访问都触发的底层钩子(包括成功的查找),覆盖它要极其小心,通常 __getattr__ 就足够了。

4.4 类创建流程

当 Python 解释器遇到 class 语句时,依次执行:

1. 确定元类(metaclass)
   → 检查 metaclass= 参数 → 检查父类的元类 → 默认 type

2. 准备类命名空间(namespace)
   → 调用 metaclass.__prepare__(name, bases)

3. 执行类体
   → 在命名空间中执行类体代码,收集属性和方法

4. 创建类对象
   → 调用 metaclass(name, bases, namespace)

5. 应用类装饰器(如果有)

用 type 动态创建类:

# class Animal: 等价于 type("Animal", (object,), {...})

def speak(self):
    return f"{self.name} says woof"

Dog = type("Dog", (object,), {
    "sound": "woof",
    "speak": speak,
})

rex = Dog()
rex.name = "Rex"
print(rex.speak())   # Rex says woof

元类的使用场景(元类的详细使用见 元编程 → 「元类」,此处只补充一个自动注册的例子):

class PluginMeta(type):
    registry = {}

    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if bases:   # 跳过基类自身
            mcs.registry[name] = cls
        return cls

class Plugin(metaclass=PluginMeta):
    pass

class UpperPlugin(Plugin):
    def process(self, text): return text.upper()

class ReversePlugin(Plugin):
    def process(self, text): return text[::-1]

print(PluginMeta.registry)
# {'UpperPlugin': <class 'UpperPlugin'>, 'ReversePlugin': <class 'ReversePlugin'>}