Part 2: 函数 & 闭包 & 装饰器


一、函数基础

1.1 函数参数类型(必须精通!)

"""
Python 函数参数有 5 种类型:
1. 位置参数 (Positional)
2. 默认参数 (Default)
3. 可变位置参数 (*args)
4. 可变关键字参数 (**kwargs)
5. 仅关键字参数 (Keyword-only)
6. 仅位置参数 (Positional-only, Python 3.8+)
"""

# ============ 完整示例 ============
def example(
    pos1, pos2,           # 位置参数(必填)
    /,                    # 之前的只能位置传参 (Python 3.8+)
    default1="a",         # 默认参数
    default2="b",
    *args,                # 可变位置参数
    kw_only1,             # 仅关键字参数(*args 之后的)
    kw_only2="default",
    **kwargs              # 可变关键字参数
):
    print(f"pos1={pos1}, pos2={pos2}")
    print(f"default1={default1}, default2={default2}")
    print(f"args={args}")
    print(f"kw_only1={kw_only1}, kw_only2={kw_only2}")
    print(f"kwargs={kwargs}")

# 调用
example(1, 2, "x", "y", 3, 4, 5, kw_only1="must", extra="value")
# 输出: 
# pos1=1, pos2=2
# default1=x, default2=y
# args=(3, 4, 5)
# kw_only1=must, kw_only2=default
# kwargs={'extra': 'value'}
参数顺序规则
"""
参数定义顺序(必须遵守):
位置参数 → / → 默认参数 → *args → 仅关键字参数 → **kwargs

或简化版(不使用 /):
位置参数 → 默认参数 → *args → 仅关键字参数 → **kwargs
"""

# ============ 常见错误 ============
# def wrong(name="default", age):  # SyntaxError:  默认参数后不能有位置参数
#     pass

# ============ 仅关键字参数的两种写法 ============
# 方式1:在 *args 之后
def func1(*args, keyword_only):
    pass

# 方式2:使用单独的 * (不接收可变参数时)
def func2(a, b, *, keyword_only):
    pass

func2(1, 2, keyword_only=3)  # ✅
# func2(1, 2, 3)  # ❌ TypeError
/ 和 * 的含义
"""
/  :  之前的参数只能通过位置传递
*  : 之后的参数只能通过关键字传递
"""

# Python 3.8+ 的仅位置参数
def greet(name, /, greeting="Hello"):
    return f"{greeting}, {name}!"

greet("Alice")              # ✅ "Hello, Alice!"
greet("Alice", "Hi")        # ✅ "Hi, Alice!"
greet("Alice", greeting="Hi")  # ✅
# greet(name="Alice")       # ❌ TypeError:  name 是仅位置参数

# 实际应用:len() 函数
# def len(obj, /):  # obj 不能用关键字传递
#     ... 

1.2 参数传递机制(高频考点!)

"""
Python 参数传递是 "传对象引用"(Pass by Object Reference)
- 不是传值(Pass by Value)
- 也不是传引用(Pass by Reference)

关键理解:
- 函数内部的参数名是原对象的新引用
- 对可变对象的 "原地修改" 会影响外部
- 对参数的 "重新赋值" 不会影响外部
"""

# ============ 示例1:不可变对象 ============
def modify_int(x):
    print(f"函数内修改前: id={id(x)}")
    x = x + 1  # 创建新对象,x 指向新对象
    print(f"函数内修改后: id={id(x)}")
    return x

a = 10
print(f"调用前: a={a}, id={id(a)}")
modify_int(a)
print(f"调用后: a={a}")  # a 仍然是 10

# ============ 示例2:可变对象 - 原地修改 ============
def modify_list_inplace(lst):
    lst.append(4)  # 原地修改,影响外部
    lst[0] = 100   # 原地修改,影响外部

my_list = [1, 2, 3]
modify_list_inplace(my_list)
print(my_list)  # [100, 2, 3, 4] ← 被修改了!

# ============ 示例3:可变对象 - 重新赋值 ============
def reassign_list(lst):
    lst = [7, 8, 9]  # 重新赋值,不影响外部
    print(f"函数内:  {lst}")

my_list = [1, 2, 3]
reassign_list(my_list)
print(f"函数外: {my_list}")  # [1, 2, 3] ← 未被修改

# ============ 示例4:常见陷阱 ============
def tricky(lst):
    lst = lst + [4]  # + 创建新列表,不影响外部
    # lst += [4]     # += 是原地修改(调用 __iadd__),会影响外部!

my_list = [1, 2, 3]
tricky(my_list)
print(my_list)  # [1, 2, 3]

def tricky2(lst):
    lst += [4]  # 原地修改

my_list = [1, 2, 3]
tricky2(my_list)
print(my_list)  # [1, 2, 3, 4] ← 被修改了!

面试标准答案:

Python 的参数传递既不是传值也不是传引用,而是 传对象引用
函数参数是实参的一个别名,指向同一个对象。
如果对参数进行原地修改(如 list.append()),会影响原对象;
如果对参数重新赋值,则只是让局部变量指向新对象,不影响原对象。


1.3 *args 和 **kwargs 解包

# ============ 定义时:收集参数 ============
def func(*args, **kwargs):
    print(f"args = {args}")      # tuple
    print(f"kwargs = {kwargs}")  # dict

func(1, 2, 3, a=4, b=5)
# args = (1, 2, 3)
# kwargs = {'a': 4, 'b': 5}

# ============ 调用时:解包参数 ============
def greet(name, age, city):
    print(f"{name}, {age} years old, from {city}")

# 列表/元组解包
args = ["Alice", 30, "Beijing"]
greet(*args)  # 等价于 greet("Alice", 30, "Beijing")

# 字典解包
kwargs = {"name": "Bob", "age": 25, "city": "Shanghai"}
greet(**kwargs)  # 等价于 greet(name="Bob", age=25, city="Shanghai")

# 组合使用
greet(*["Alice", 30], **{"city": "Beijing"})

# ============ 实际应用:装饰器转发参数 ============
def decorator(func):
    def wrapper(*args, **kwargs):
        print("Before")
        result = func(*args, **kwargs)  # 转发所有参数
        print("After")
        return result
    return wrapper

1.4 函数注解与类型提示

from typing import (
    List, Dict, Tuple, Set, Optional, Union, 
    Callable, Any, TypeVar, Generic
)

# ============ 基础类型注解 ============
def greet(name: str, age: int = 0) -> str:
    """
    参数类型和返回值类型注解
    注意:Python 运行时不强制检查类型!
    """
    return f"Hello, {name}! You are {age} years old."

# ============ 容器类型 ============
# Python 3.9+ 可以直接用 list, dict 等
def process_items(items: list[int]) -> dict[str, int]:
    return {str(i): i for i in items}

# Python 3.8 及之前需要从 typing 导入
def process_items_old(items: List[int]) -> Dict[str, int]: 
    return {str(i): i for i in items}

# ============ Optional 和 Union ============
# Optional[X] 等价于 Union[X, None]
def find_user(user_id: int) -> Optional[dict]:
    """可能返回 dict,也可能返回 None"""
    if user_id > 0:
        return {"id": user_id, "name": "Alice"}
    return None

# Union:多种类型
def process(value: Union[int, str]) -> str:
    return str(value)

# Python 3.10+ 可以用 | 替代 Union
def process_new(value: int | str) -> str:
    return str(value)

# ============ Callable(函数类型) ============
def apply_func(func: Callable[[int, int], int], a: int, b:  int) -> int:
    """
    Callable[[参数类型列表], 返回类型]
    """
    return func(a, b)

apply_func(lambda x, y: x + y, 1, 2)  # 3

# ============ TypeVar(泛型) ============
T = TypeVar('T')

def first(items: list[T]) -> T:
    """返回类型与列表元素类型一致"""
    return items[0]

first([1, 2, 3])     # 返回 int
first(["a", "b"])    # 返回 str

# ============ 访问注解信息 ============
print(greet.__annotations__)
# {'name': <class 'str'>, 'age':  <class 'int'>, 'return': <class 'str'>}

二、作用域与命名空间(LEGB 规则)

2.1 LEGB 规则详解

"""
Python 查找变量的顺序(LEGB):
L - Local      :  函数内部局部作用域
E - Enclosing  : 外层嵌套函数的作用域(闭包)
G - Global     : 模块全局作用域
B - Built-in   : Python 内置作用域

查找顺序:L → E → G → B
"""

# ============ 示例 ============
x = "global"  # G

def outer():
    x = "enclosing"  # E
    
    def inner():
        x = "local"  # L
        print(x)  # 输出: local
    
    inner()

outer()

# ============ 内置作用域 ============
# print, len, range 等都在 Built-in 作用域
# 可以被覆盖(但不推荐)
# list = [1, 2, 3]  # ❌ 覆盖了内置的 list

2.2 global 和 nonlocal 关键字

# ============ global:修改全局变量 ============
counter = 0

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

increment()
print(counter)  # 1

# 不使用 global 会报错
def wrong_increment():
    # counter += 1  # UnboundLocalError:  赋值使其成为局部变量
    pass

# ============ nonlocal:修改外层函数变量 ============
def outer():
    count = 0
    
    def inner():
        nonlocal count  # 声明使用外层变量
        count += 1
        return count
    
    return inner

counter = outer()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

# ============ 常见错误 ============
x = 10

def foo():
    # print(x)  # ❌ UnboundLocalError
    x = 20  # 这行使 x 成为局部变量,但上面的 print 在赋值前使用

# 正确做法
def foo_correct():
    global x
    print(x)
    x = 20

三、闭包(Closure)

3.1 什么是闭包?

"""
闭包 = 函数 + 它引用的外层变量

形成闭包的条件:
1. 有嵌套函数
2. 内层函数引用了外层函数的变量
3. 外层函数返回内层函数
"""

# ============ 闭包示例 ============
def make_multiplier(n:  int) -> Callable[[int], int]:
    """工厂函数:创建乘法器"""
    def multiplier(x: int) -> int:
        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__)  # (<cell at 0x...:  int object at 0x...>,)
print(double.__closure__[0].cell_contents)  # 2

# ============ 闭包的本质 ============
# 闭包让函数"记住"了创建时的环境
# n 的生命周期被延长了,不会随 make_multiplier 返回而销毁

3.2 闭包的经典陷阱(必考!)

# ============ 陷阱:循环变量捕获 ============
def create_functions():
    functions = []
    for i in range(3):
        def f():
            return i  # 捕获的是变量 i,不是值
        functions.append(f)
    return functions

funcs = create_functions()
print(funcs[0]())  # 期望 0,实际 2
print(funcs[1]())  # 期望 1,实际 2
print(funcs[2]())  # 2

# 原因:闭包捕获的是变量的引用,而非值
# 循环结束后 i = 2,所有函数都引用同一个 i

# ============ 解决方案1:默认参数(推荐) ============
def create_functions_fixed():
    functions = []
    for i in range(3):
        def f(x=i):  # 默认参数在定义时求值
            return x
        functions.append(f)
    return functions

# ============ 解决方案2:再包一层 ============
def create_functions_fixed2():
    functions = []
    for i in range(3):
        def make_f(n):
            def f():
                return n
            return f
        functions.append(make_f(i))
    return functions

# ============ 解决方案3:使用 functools.partial ============
from functools import partial

def create_functions_fixed3():
    def f(x):
        return x
    return [partial(f, i) for i in range(3)]

# ============ 解决方案4:lambda + 默认参数 ============
functions = [lambda x=i: x for i in range(3)]

3.3 闭包的实际应用

# ============ 应用1:计数器 ============
def make_counter(start:  int = 0) -> Callable[[], int]:
    count = start
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

counter = make_counter()
print(counter(), counter(), counter())  # 1 2 3

# ============ 应用2:缓存/记忆化 ============
def make_cache() -> Callable: 
    cache = {}
    
    def cached_func(func:  Callable) -> Callable:
        def wrapper(*args):
            if args not in cache:
                cache[args] = func(*args)
            return cache[args]
        return wrapper
    
    return cached_func

# ============ 应用3:配置工厂 ============
def make_logger(prefix: str, level: str = "INFO"):
    def log(message: str):
        print(f"[{level}] {prefix}:  {message}")
    return log

api_logger = make_logger("API", "DEBUG")
db_logger = make_logger("Database", "ERROR")

api_logger("Request received")  # [DEBUG] API: Request received
db_logger("Connection failed")  # [ERROR] Database: Connection failed

四、装饰器(Decorator)

装饰器是面试的 重中之重,几乎每场面试都会问!

4.1 装饰器的本质

"""
装饰器本质上是:
1. 一个函数(或类)
2. 接收一个函数作为参数
3. 返回一个新函数(通常是包装后的函数)

语法糖:@decorator 等价于 func = decorator(func)
"""

# ============ 最简单的装饰器 ============
def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before function call")
        result = func(*args, **kwargs)
        print("After function call")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"Hello, {name}!")
    return "Done"

# 等价于:say_hello = my_decorator(say_hello)

say_hello("Alice")
# 输出: 
# Before function call
# Hello, Alice!
# After function call

4.2 保留原函数元信息(functools.wraps)

from functools import wraps

# ============ 问题:装饰器会丢失原函数信息 ============
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def my_func():
    """这是 my_func 的文档"""
    pass

print(my_func.__name__)  # wrapper ← 应该是 my_func
print(my_func.__doc__)   # None ← 应该是文档字符串

# ============ 解决:使用 @wraps ============
def good_decorator(func):
    @wraps(func)  # 保留原函数的元信息
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@good_decorator
def my_func():
    """这是 my_func 的文档"""
    pass

print(my_func.__name__)  # my_func ✅
print(my_func.__doc__)   # 这是 my_func 的文档 ✅
print(my_func.__wrapped__)  # 可以访问原函数

4.3 带参数的装饰器

from functools import wraps
from typing import Callable, Any
import time
import logging

logger = logging.getLogger(__name__)

# ============ 三层嵌套结构 ============
def repeat(times: int) -> Callable: 
    """装饰器工厂:返回真正的装饰器"""
    def decorator(func:  Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def say_hello(name: str):
    print(f"Hello, {name}!")

# 等价于:say_hello = repeat(times=3)(say_hello)

say_hello("Alice")
# 输出:
# Hello, Alice!
# Hello, Alice!
# Hello, Alice! 

# ============ 实用示例:计时装饰器 ============
def timer(precision: int = 3) -> Callable: 
    """测量函数执行时间"""
    def decorator(func:  Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            start = time.perf_counter()
            result = func(*args, **kwargs)
            elapsed = time.perf_counter() - start
            logger.info(f"{func.__name__} took {elapsed:.{precision}f}s")
            return result
        return wrapper
    return decorator

@timer(precision=4)
def slow_function():
    time.sleep(0.1)
    return "done"

# ============ 实用示例:重试装饰器 ============
def retry(
    max_attempts:  int = 3,
    exceptions: tuple = (Exception,),
    delay: float = 1.0
) -> Callable: 
    """失败时自动重试"""
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            last_exception = None
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions as e: 
                    last_exception = e
                    logger.warning(
                        f"{func.__name__} attempt {attempt}/{max_attempts} failed: {e}"
                    )
                    if attempt < max_attempts: 
                        time.sleep(delay)
            raise last_exception
        return wrapper
    return decorator

@retry(max_attempts=3, exceptions=(ConnectionError, TimeoutError))
def fetch_data(url: str) -> dict:
    # 可能失败的网络请求
    pass

4.4 类装饰器

from functools import wraps
from typing import Callable, Any
import time

# ============ 方式1:类作为装饰器 ============
class Timer:
    """使用 __call__ 使类实例可调用"""
    
    def __init__(self, func: Callable):
        wraps(func)(self)  # 保留原函数信息
        self.func = func
        self.call_count = 0
    
    def __call__(self, *args, **kwargs) -> Any:
        self.call_count += 1
        start = time.perf_counter()
        result = self.func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{self.func.__name__} (call #{self.call_count}) took {elapsed:.4f}s")
        return result

@Timer
def slow_func():
    time.sleep(0.1)

slow_func()  # slow_func (call #1) took 0.1001s
slow_func()  # slow_func (call #2) took 0.1002s
print(slow_func.call_count)  # 2

# ============ 方式2:带参数的类装饰器 ============
class Retry:
    def __init__(self, max_attempts: int = 3, delay: float = 1.0):
        self.max_attempts = max_attempts
        self.delay = delay
    
    def __call__(self, func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            for attempt in range(self.max_attempts):
                try: 
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == self.max_attempts - 1:
                        raise
                    time.sleep(self.delay)
        return wrapper

@Retry(max_attempts=5, delay=2.0)
def unstable_operation():
    pass

# ============ 方式3:用装饰器装饰类 ============
def singleton(cls):
    """单例装饰器"""
    instances = {}
    
    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances: 
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    
    return get_instance

@singleton
class Database:
    def __init__(self):
        print("Connecting to database...")

db1 = Database()  # Connecting to database... 
db2 = Database()  # 不会再打印
print(db1 is db2)  # True

4.5 装饰器叠加顺序

def decorator_a(func):
    print("Applying A")
    def wrapper(*args, **kwargs):
        print("A before")
        result = func(*args, **kwargs)
        print("A after")
        return result
    return wrapper

def decorator_b(func):
    print("Applying B")
    def wrapper(*args, **kwargs):
        print("B before")
        result = func(*args, **kwargs)
        print("B after")
        return result
    return wrapper

@decorator_a
@decorator_b
def my_func():
    print("Function")

# 等价于:my_func = decorator_a(decorator_b(my_func))

# 装饰时(从下往上): 
# Applying B
# Applying A

# 调用时(从上往下): 
my_func()
# A before
# B before
# Function
# B after
# A after

记忆口诀:装饰从下往上,执行从上往下(洋葱模型)

4.6 常用内置装饰器

# ============ @property(属性装饰器) ============
class Circle:
    def __init__(self, radius: float):
        self._radius = radius
    
    @property
    def radius(self) -> float:
        """getter"""
        return self._radius
    
    @radius.setter
    def radius(self, value: float):
        """setter"""
        if value < 0:
            raise ValueError("Radius must be positive")
        self._radius = value
    
    @property
    def area(self) -> float:
        """只读属性(没有 setter)"""
        return 3.14159 * self._radius ** 2

c = Circle(5)
print(c.radius)   # 5(调用 getter)
c.radius = 10     # 调用 setter
print(c.area)     # 314.159
# c.area = 100    # AttributeError: can't set attribute

# ============ @staticmethod 和 @classmethod ============
class MyClass:
    class_var = "I'm a class variable"
    
    def __init__(self, value):
        self.value = value
    
    def instance_method(self):
        """实例方法:第一个参数是 self"""
        return f"instance: {self.value}"
    
    @classmethod
    def class_method(cls):
        """类方法:第一个参数是 cls(类本身)"""
        return f"class: {cls.class_var}"
    
    @staticmethod
    def static_method(x, y):
        """静态方法:没有隐式参数,跟普通函数一样"""
        return x + y

obj = MyClass("hello")
print(obj.instance_method())     # instance: hello
print(MyClass.class_method())    # class: I'm a class variable
print(obj.class_method())        # 也可以通过实例调用
print(MyClass.static_method(1, 2))  # 3

# ============ @functools.lru_cache(缓存装饰器) ============
from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
    """带缓存的递归斐波那契"""
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(100))  # 瞬间返回,不会重复计算
print(fibonacci.cache_info())  # 查看缓存统计
fibonacci.cache_clear()  # 清除缓存

# Python 3.9+ 可以使用 @cache(无大小限制)
from functools import cache

@cache
def factorial(n: int) -> int:
    return n * factorial(n - 1) if n else 1

4.7 装饰器实战模板

from functools import wraps
from typing import Callable, Any, Optional, TypeVar, ParamSpec
import time
import logging

logger = logging.getLogger(__name__)

P = ParamSpec('P')
T = TypeVar('T')

# ============ 通用装饰器模板 ============
def decorator_template(func: Callable[P, T]) -> Callable[P, T]:
    """
    标准装饰器模板
    """
    @wraps(func)
    def wrapper(*args:  P.args, **kwargs: P.kwargs) -> T:
        # 前置逻辑
        try:
            result = func(*args, **kwargs)
            # 后置逻辑
            return result
        except Exception as e:
            # 异常处理
            raise
        finally:
            # 清理逻辑
            pass
    return wrapper


# ============ 带参数的装饰器模板 ============
def parametrized_decorator(
    param1: str = "default",
    param2: int = 0
) -> Callable[[Callable[P, T]], Callable[P, T]]: 
    """
    带参数的装饰器模板
    """
    def decorator(func:  Callable[P, T]) -> Callable[P, T]: 
        @wraps(func)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
            logger.info(f"Params: {param1}, {param2}")
            return func(*args, **kwargs)
        return wrapper
    return decorator


# ============ 可选参数的装饰器 ============
def flexible_decorator(
    func: Optional[Callable[P, T]] = None,
    *,
    param:  str = "default"
) -> Callable: 
    """
    同时支持 @decorator 和 @decorator() 两种写法
    """
    def decorator(f: Callable[P, T]) -> Callable[P, T]:
        @wraps(f)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
            print(f"param: {param}")
            return f(*args, **kwargs)
        return wrapper
    
    if func is not None:
        # @decorator 形式(无括号)
        return decorator(func)
    # @decorator() 或 @decorator(param="xxx") 形式
    return decorator

# 两种写法都可以:
@flexible_decorator
def func1():
    pass

@flexible_decorator()
def func2():
    pass

@flexible_decorator(param="custom")
def func3():
    pass

五、生成器与迭代器

5.1 迭代器协议

"""
可迭代对象 (Iterable):实现了 __iter__() 方法
迭代器 (Iterator):实现了 __iter__() 和 __next__() 方法

关系:
- 可迭代对象的 __iter__() 返回迭代器
- 迭代器的 __iter__() 返回自身
- 迭代器的 __next__() 返回下一个元素,没有元素时抛出 StopIteration
"""

# ============ 手动实现迭代器 ============
class CountDown:
    def __init__(self, start: int):
        self.current = start
    
    def __iter__(self):
        return self  # 返回自身(迭代器)
    
    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1

# 使用
for num in CountDown(5):
    print(num)  # 5 4 3 2 1

# ============ iter() 和 next() 内置函数 ============
lst = [1, 2, 3]
iterator = iter(lst)  # 获取迭代器
print(next(iterator))  # 1
print(next(iterator))  # 2
print(next(iterator))  # 3
# print(next(iterator))  # StopIteration

# next() 可以指定默认值
print(next(iterator, "finished"))  # finished

5.2 生成器函数(yield)

"""
生成器是一种特殊的迭代器,使用 yield 关键字定义
优点:
1. 惰性求值,节省内存
2. 代码更简洁
3. 可以表示无限序列
"""

# ============ 基础生成器 ============
def count_up_to(n: int):
    """生成 1 到 n 的数字"""
    i = 1
    while i <= n: 
        yield i  # 暂停并返回值
        i += 1

gen = count_up_to(3)
print(type(gen))  # <class 'generator'>
print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3

# 用于循环
for num in count_up_to(5):
    print(num)

# ============ 生成器 vs 列表 的内存对比 ============
import sys

# 列表:立即创建所有元素
list_comp = [x ** 2 for x in range(1000000)]
print(sys.getsizeof(list_comp))  # ~8 MB

# 生成器:惰性求值
gen_exp = (x ** 2 for x in range(1000000))
print(sys.getsizeof(gen_exp))  # ~200 bytes ✅

# ============ yield from(委托生成器) ============
def chain(*iterables):
    """连接多个可迭代对象"""
    for it in iterables: 
        yield from it  # 等价于:  for item in it: yield item

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

# ============ 生成器表达式 ============
# 列表推导式
squares_list = [x ** 2 for x in range(10)]  # 返回 list

# 生成器表达式
squares_gen = (x ** 2 for x in range(10))   # 返回 generator

# ============ 实用示例:读取大文件 ============
def read_large_file(file_path: str, chunk_size: int = 1024):
    """逐块读取大文件,避免内存溢出"""
    with open(file_path, 'r') as f:
        while True: 
            chunk = f.read(chunk_size)
            if not chunk:
                break
            yield chunk

for chunk in read_large_file("huge_file.txt"):
    process(chunk)

5.3 生成器高级用法(send、throw、close)

# ============ send():向生成器发送值 ============
def accumulator():
    """累加器:接收外部发送的值"""
    total = 0
    while True:
        value = yield total  # yield 返回当前值,并接收 send 的值
        if value is not None:
            total += value

acc = accumulator()
print(next(acc))       # 0 (启动生成器)
print(acc.send(10))    # 10
print(acc.send(20))    # 30
print(acc.send(5))     # 35

# ============ throw():向生成器抛出异常 ============
def careful_generator():
    try:
        yield 1
        yield 2
        yield 3
    except ValueError: 
        yield "caught ValueError"

gen = careful_generator()
print(next(gen))  # 1
print(gen.throw(ValueError))  # caught ValueError

# ============ close():关闭生成器 ============
def generator_with_cleanup():
    try:
        yield 1
        yield 2
    finally:
        print("Cleanup executed")

gen = generator_with_cleanup()
print(next(gen))  # 1
gen.close()       # Cleanup executed

六、Lambda 表达式

# ============ 基础语法 ============
# lambda 参数:  表达式

add = lambda x, y: x + y
print(add(1, 2))  # 3

# 等价于: 
def add(x, y):
    return x + y

# ============ 常见用途 ============
# 1. 排序 key
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
students.sort(key=lambda x: x[1], reverse=True)
# [('Bob', 92), ('Alice', 85), ('Charlie', 78)]

# 2. filter
numbers = [1, 2, 3, 4, 5, 6]
evens = list(filter(lambda x: x % 2 == 0, numbers))  # [2, 4, 6]

# 3. map
squares = list(map(lambda x: x ** 2, numbers))  # [1, 4, 9, 16, 25, 36]

# 4. reduce
from functools import reduce
total = reduce(lambda x, y: x + y, numbers)  # 21

# ============ lambda 的限制 ============
# 1. 只能是单个表达式,不能有语句(如 if-else 语句、赋值等)
# 2. 不能有 return、yield
# 3. 可读性差,复杂逻辑应使用普通函数

# 条件表达式可以用
max_val = lambda x, y: x if x > y else y

# ============ 不推荐的用法 ============
# 给 lambda 命名(应该用 def)
# f = lambda x: x ** 2  # ❌
def f(x): return x ** 2  # ✅

# 过于复杂的 lambda
# ❌ 难以阅读
complex_lambda = lambda x:  (x ** 2 if x > 0 else -x ** 2) + (1 if x % 2 == 0 else 0)

📝 Part 2 总结

面试高频考点

知识点 面试频率 难度 必须掌握程度
参数类型(*args, **kwargs) ⭐⭐⭐⭐⭐ ⭐⭐ 手写代码
参数传递机制 ⭐⭐⭐⭐⭐ ⭐⭐⭐ 能解释清楚
LEGB 作用域规则 ⭐⭐⭐⭐ ⭐⭐ 能解释清楚
闭包原理 ⭐⭐⭐⭐⭐ ⭐⭐⭐ 手写代码
闭包陷阱(循环变量) ⭐⭐⭐⭐⭐ ⭐⭐⭐ 必须掌握
装饰器原理 ⭐⭐⭐⭐⭐ ⭐⭐⭐ 手写代码
带参数的装饰器 ⭐⭐⭐⭐ ⭐⭐⭐⭐ 手写代码
functools.wraps ⭐⭐⭐⭐ ⭐⭐ 知道为什么用
生成器 yield ⭐⭐⭐⭐⭐ ⭐⭐⭐ 手写代码
迭代器协议 ⭐⭐⭐ ⭐⭐⭐ 能解释清楚

面试常见问题

  1. 什么是闭包?请手写一个闭包示例
  2. 装饰器的原理是什么?@语法糖等价于什么?
  3. 请手写一个带参数的装饰器
  4. Python 的参数传递是传值还是传引用?
  5. yield 和 return 的区别是什么?
  6. 什么情况下用生成器而不是列表?
  7. *args 和 **kwargs 是什么意思?
  8. global 和 nonlocal 的区别?
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐