当前位置:首页>python>Python(九) 函数详解

Python(九) 函数详解

  • 2026-10-11 06:06:31
Python(九) 函数详解

Python 函数详解

从基础语法到高级应用的完整指南

Python 学习笔记 · 第九篇

作为测试工程师,你是否遇到过这些问题:自动化脚本中重复代码越来越多?用例执行日志到处都是难以维护?同一个功能在多处实现导致修改困难?函数就是解决这些问题的最佳方案!掌握函数,让你的测试代码更优雅、更高效、更易维护。

第一部分:函数基础
01什么是函数基础概念

函数就是一段可重用的代码块,用来实现特定功能。就像一个"黑盒子",你给它输入(参数),它给你输出(返回值)。在测试自动化中,函数可以帮我们封装登录、断言、数据准备等常用操作。

Python
# 函数的基本结构# def 函数名(参数):#     """文档字符串"""#     函数体#     return 返回值# 最简单的函数 - 打印测试日志def print_test_log():    """打印测试日志"""    print("=" * 50)    print(f"测试开始时间: 2024-01-15 10:00:00")    print(f"测试用例编号: TC001")    print("=" * 50)# 调用函数print_test_log()print_test_log()  # 可以多次调用

💡 小贴士

函数名应该见名知意,用动词开头(如 login、assert_equal、generate_test_data)。文档字符串(docstring)是好习惯,团队协作时别人一看就知道函数怎么用。

◆ ─────── ◆
02带参数的函数核心功能

参数是函数的"输入",让函数更加灵活。在测试中,我们经常需要给函数传入用户名、密码、期望结果等参数。

Python
# ===== 1. 单个参数的函数def login(username):    """模拟用户登录"""    print(f"用户 [{username}] 正在登录系统...")    # 这里可以写真正的登录逻辑    return Truelogin("admin")       # 用户 [admin] 正在登录系统...login("tester001")   # 用户 [tester001] 正在登录系统...# ===== 2. 多个参数的函数def assert_equal(actual, expected, case_name):    """断言相等,测试中最常用的函数"""    print(f"\n执行用例: {case_name}")    print(f"实际结果: {actual}")    print(f"期望结果: {expected}")    if actual == expected:        print("✅ 测试通过!")        return True    else:        print("❌ 测试失败!")        return False# 调用断言函数result1 = assert_equal(200, 200, "接口响应码校验")result2 = assert_equal("success", "success", "登录状态校验")result3 = assert_equal(3, 5, "用户数量校验")# ===== 3. 带返回值的函数def calculate_coverage(passed, total):    """计算测试覆盖率"""    if total == 0:        return 0.0    coverage = (passed / total) * 100    return round(coverage, 2)rate = calculate_coverage(85, 100)print(f"\n测试覆盖率: {rate}%")  # 测试覆盖率: 85.0%

⚠️ 踩坑经验

没有 return 语句的函数默认返回 None!不要把 print() 当成返回值。很多新手会写:def add(a,b): print(a+b),然后 result = add(1,2),结果 result 是 None!

◆ ─────── ◆
第二部分:四种参数类型
03位置参数 & 默认参数进阶语法

Python 支持多种参数类型,让函数调用更加灵活。位置参数是最基础的,默认参数可以减少重复传参。

Python
# ===== 1. 位置参数(必须按顺序传递)def api_request(url, method, headers):    """发送API请求"""    print(f"发送 {method} 请求到: {url}")    print(f"请求头: {headers}")# 必须按顺序传参api_request("https://api.example.com/users", "GET", {"Content-Type": "application/json"})# ===== 2. 默认参数(有默认值的参数,可以不传)def api_request_v2(url, method="GET", headers=None):    """带默认值的API请求函数"""    if headers is None:        headers = {"Content-Type": "application/json"}    print(f"发送 {method} 请求到: {url}")    print(f"请求头: {headers}")# 只传必填参数api_request_v2("https://api.example.com/users")  # 默认 GET# 覆盖默认值api_request_v2("https://api.example.com/users", "POST")# ===== 3. 测试场景:生成测试报告def generate_report(test_results, format="HTML", output_dir="./reports"):    """生成测试报告    Args:        test_results: 测试结果列表        format: 报告格式 (HTML/JSON/XML)        output_dir: 输出目录    """    passed = sum(1 for r in test_results if r == "PASS")    total = len(test_results)    print(f"\n生成 {format} 格式报告...")    print(f"测试结果: {passed}/{total} 通过")    print(f"输出目录: {output_dir}")# 使用默认参数generate_report(["PASS", "PASS", "FAIL", "PASS"])# 自定义参数generate_report(["PASS", "PASS"], format="JSON", output_dir="./test_reports")

⚠️ 踩坑经验

永远不要用可变对象(列表、字典)作为默认参数!因为默认参数只在函数定义时创建一次,多次调用会共享同一个对象。正确写法是 def func(x=None): if x is None: x = []。

◆ ─────── ◆
04关键字参数 & 不定长参数高级技巧

关键字参数让调用更清晰,不定长参数让函数接受任意数量的输入,这在处理测试数据时特别有用。

Python
# ===== 1. 关键字参数(调用时指定参数名)def create_user(username, password, email, role="tester"):    """创建测试用户"""    print(f"\n创建用户: {username}")    print(f"邮箱: {email}")    print(f"角色: {role}")# 使用关键字参数,顺序可以任意create_user(    username="test_user",    email="test@example.com",    password="123456",    role="admin")# ===== 2. *args - 接收任意数量的位置参数(元组)def run_multiple_tests(*test_cases):    """批量运行多个测试用例"""    print(f"\n准备运行 {len(test_cases)} 个测试用例:")    for i, case in enumerate(test_cases, 1):        print(f"  {i}. 运行: {case}")# 可以传任意数量的参数run_multiple_tests("登录测试", "注册测试", "查询测试")run_multiple_tests("接口测试1", "接口测试2")# 也可以传入一个列表(需要拆包)cases = ["UI测试", "性能测试", "安全测试"]run_multiple_tests(*cases)  # 用 * 拆包# ===== 3. **kwargs - 接收任意数量的关键字参数(字典)def log_test_step(**kwargs):    """记录测试步骤,可传入任意字段"""    print("\n" + "-" * 40)    for key, value in kwargs.items():        print(f"{key}: {value}")    print("-" * 40)# 任意传参log_test_step(    step="用户登录",    username="admin",    timestamp="2024-01-15 10:30:00",    status="PASS",    duration="0.5s")log_test_step(    step="数据库查询",    sql="SELECT * FROM users",    rows_returned=100)# 也可以传入一个字典(需要拆包)step_data = {    "step": "API调用",    "url": "/api/users",    "method": "GET",    "response_code": 200}log_test_step(**step_data)  # 用 ** 拆包

💡 小贴士

记忆口诀:一个星号是"多值"(元组),两个星号是"多键值"(字典)。参数顺序必须是:位置参数 → *args → 默认参数 → **kwargs,否则会报错!

◆ ─────── ◆
第三部分:变量作用域
05局部变量 & 全局变量重要概念

作用域决定了变量在哪里可以被访问。理解作用域能避免"变量找不到"或"值被意外修改"等bug。

Python
# ===== 1. 局部变量:函数内部定义,外部无法访问def test_login():    # 这是局部变量,只在函数内有效    test_username = "test_user"    test_password = "123456"    print(f"登录测试: {test_username}")test_login()# print(test_username)  # ❌ 报错!NameError: name 'test_username' is not defined# ===== 2. 全局变量:函数外部定义,整个文件都可以访问TEST_ENV = "production"  # 全局变量,通常大写def print_env():    print(f"当前测试环境: {TEST_ENV}")  # 可以读取全局变量print_env()# ===== 3. 修改全局变量需要 global 关键字test_count = 0  # 全局计数器def run_test():    global test_count  # 声明要使用全局变量    test_count += 1    # 修改全局变量    print(f"已执行第 {test_count} 个测试")run_test()  # 已执行第 1 个测试run_test()  # 已执行第 2 个测试run_test()  # 已执行第 3 个测试print(f"总共执行了 {test_count} 个测试")# ===== 4. 测试场景:全局配置示例CONFIG = {    "base_url": "https://api.example.com",    "timeout": 30,    "retry_times": 3}def get_config(key):    """获取配置项"""    return CONFIG.get(key)def update_config(key, value):    """更新配置(字典是可变对象,不需要global)"""    CONFIG[key] = valueprint(f"超时时间: {get_config('timeout')}")  # 30update_config("timeout", 60)print(f"修改后: {get_config('timeout')}")  # 60

⚠️ 踩坑经验

如果函数内部定义了和全局同名的变量,Python会认为你要创建一个新的局部变量,而不是修改全局的!比如全局有 x = 10,函数里写 x = 20,这不会改变全局的 x,而是创建了一个新的局部变量 x!

◆ ─────── ◆
第四部分:递归函数
06递归函数基础进阶编程

递归就是"函数自己调用自己"。在测试中,递归特别适合处理嵌套的数据结构,比如多层嵌套的JSON、树形菜单等。

Python
# ===== 递归的两个关键要素:# 1. 基线条件(Base Case):什么时候停止递归# 2. 递归条件(Recursive Case):什么时候继续递归# ===== 经典例子:阶乘def factorial(n):    """计算 n! = n * (n-1) * ... * 1"""    # 基线条件:n = 1 时返回 1    if n == 1:        return 1    # 递归条件:n * (n-1)!    else:        return n * factorial(n - 1)print(f"5! = {factorial(5)}")  # 5! = 120# ===== 测试场景1:遍历嵌套JSON查找指定keyapi_response = {    "code": 200,    "data": {        "user": {            "id": 1001,            "name": "test_user",            "permissions": ["read", "write"]        },        "meta": {            "pagination": {                "page": 1,                "total": 100            }        }    }}def find_nested_value(data, target_key):    """递归查找嵌套字典中的值"""    # 如果是字典    if isinstance(data, dict):        # 先检查当前层有没有目标key        if target_key in data:            return data[target_key]        # 递归遍历每个值        for value in data.values():            result = find_nested_value(value, target_key)            if result is not None:                return result    # 如果是列表,递归遍历每个元素    elif isinstance(data, list):        for item in data:            result = find_nested_value(item, target_key)            if result is not None:                return result    # 都没找到    return None# 使用递归函数查找深层嵌套的值print(f"用户ID: {find_nested_value(api_response, 'id')}")       # 1001print(f"总记录数: {find_nested_value(api_response, 'total')}")  # 100print(f"用户名: {find_nested_value(api_response, 'name')}")     # test_user# ===== 测试场景2:遍历目录结构def count_files(path, depth=0):    """递归统计目录下的文件数量(模拟)"""    import os    if not os.path.exists(path):        return 0    count = 0    for item in os.listdir(path):        item_path = os.path.join(path, item)        indent = "  " * depth        if os.path.isdir(item_path):            print(f"{indent}📁 {item}/")            count += count_files(item_path, depth + 1)        else:            print(f"{indent}📄 {item}")            count += 1    return count# 统计当前目录的文件# total = count_files(".")# print(f"\n总文件数: {total}")

💡 小贴士

Python 默认递归深度限制是 1000,超过会报 RecursionError。写递归一定要确保基线条件正确,否则会无限递归直到栈溢出!

◆ ─────── ◆
第五部分:装饰器基础
07装饰器入门Python特色

装饰器是 Python 的特色语法,可以"在不修改原函数代码的情况下,给函数增加额外功能"。在测试中,装饰器特别适合做日志记录、性能计时、重试机制等。

Python
# 装饰器原理:函数可以作为参数传递,可以作为返回值# 装饰器就是一个"接收函数,并返回新函数"的函数import timeimport functools# ===== 1. 最简单的装饰器:日志记录def log_decorator(func):    """记录函数调用日志的装饰器"""    @functools.wraps(func)  # 保留原函数的元信息    def wrapper(*args, **kwargs):        # 调用前的操作        print(f"\n📝 开始执行: {func.__name__}")        print(f"   参数: args={args}, kwargs={kwargs}")        start_time = time.time()        # 调用原函数        result = func(*args, **kwargs)        # 调用后的操作        end_time = time.time()        print(f"   返回值: {result}")        print(f"   耗时: {(end_time - start_time):.4f}秒")        return result    return wrapper# 使用装饰器@log_decoratordef login(username, password):    """用户登录函数"""    print(f"   正在验证用户: {username}")    time.sleep(0.5)  # 模拟网络延迟    return {"status": "success", "user": username}@log_decoratordef search_user(keyword):    """搜索用户"""    print(f"   正在搜索: {keyword}")    time.sleep(0.3)    return ["user1", "user2"]# 调用被装饰的函数(自动加上了日志功能)login("admin", "123456")search_user("test")# ===== 2. 测试场景:重试装饰器def retry(max_times=3, delay=1):    """失败重试装饰器    Args:        max_times: 最大重试次数        delay: 重试间隔(秒)    """    def decorator(func):        @functools.wraps(func)        def wrapper(*args, **kwargs):            for attempt in range(1, max_times + 1):                try:                    return func(*args, **kwargs)                except Exception as e:                    print(f"⚠️ 第 {attempt} 次尝试失败: {str(e)}")                    if attempt < max_times:                        print(f"   {delay} 秒后重试...")                        time.sleep(delay)            raise Exception(f"❌ 经过 {max_times} 次尝试后仍然失败")        return wrapper    return decorator# 使用带参数的装饰器@retry(max_times=3, delay=2)def unstable_api_call():    """模拟不稳定的接口"""    import random    if random.random() < 0.7:  # 70% 概率失败        raise Exception("网络超时")    return {"code": 200, "data": "success"}# 测试重试机制try:    result = unstable_api_call()    print(f"✅ 最终成功: {result}")except Exception as e:    print(e)# ===== 3. 测试场景:性能计时装饰器def timer(func):    """性能计时装饰器"""    @functools.wraps(func)    def wrapper(*args, **kwargs):        start = time.perf_counter()        result = func(*args, **kwargs)        end = time.perf_counter()        print(f"⏱️  {func.__name__} 执行耗时: {(end - start):.4f}秒")        return result    return wrapper@timerdef generate_test_data(count):    """生成大量测试数据"""    data = [f"user_{i}" for i in range(count)]    return data# 测试性能data = generate_test_data(10000)print(f"生成了 {len(data)} 条测试数据")

💡 小贴士

装饰器是 Python 的"语法糖",核心原理是闭包和函数作为一等公民。记住要加 @functools.wraps(func),否则原函数的名称、文档字符串等元信息会丢失!

◆ ─────── ◆
第六部分:测试实战与总结
08测试实战技巧工作必备

分享几个测试工作中高频使用的函数设计技巧,帮你写出更专业的测试代码。

Python
# ===== 技巧1:函数返回字典,便于断言def run_test_case(case_id):    """执行测试用例,返回结构化结果"""    # 模拟测试执行    import random    passed = random.choice([True, False])    return {        "case_id": case_id,        "status": "PASS" if passed else "FAIL",        "start_time": "2024-01-15 10:00:00",        "duration": 2.5,        "error_msg": "断言失败" if not passed else ""    }# 使用起来非常清晰result = run_test_case("TC001")print(f"用例 {result['case_id']}: {result['status']}")# ===== 技巧2:函数注解(类型提示)def add(a: int, b: int) -> int:    """两个数相加    Args:        a: 第一个数        b: 第二个数    Returns:        两个数的和    """    return a + b# 测试框架常用的类型提示def assert_response(    response: dict,    expected_code: int = 200,    expected_data: dict = None) -> bool:    """断言API响应    Args:        response: 实际响应        expected_code: 期望的状态码        expected_data: 期望的数据(可选)    Returns:        断言是否通过    """    actual_code = response.get("code", 0)    if actual_code != expected_code:        print(f"❌ 状态码不匹配: 期望 {expected_code}, 实际 {actual_code}")        return False    print("✅ 状态码校验通过")    return True# ===== 技巧3:函数作为参数传递(回调函数)def execute_tests(test_list, callback=None):    """批量执行测试,支持回调函数"""    results = []    for test in test_list:        print(f"\n执行: {test}")        result = {"name": test, "status": "PASS"}        results.append(result)        # 如果有回调函数,执行它        if callback:            callback(result)    return results# 定义回调函数:发送测试结果到钉钉def send_to_dingtalk(result):    """测试结果回调"""    print(f"📤 发送结果到钉钉: {result['name']} - {result['status']}")# 执行测试并使用回调tests = ["登录测试", "注册测试", "搜索测试"]execute_tests(tests, send_to_dingtalk)# ===== 技巧4:lambda 匿名函数(简单的一行函数)# 语法: lambda 参数: 表达式square = lambda x: x * xprint(f"5的平方: {square(5)}")  # 25# 常用于排序test_cases = [    {"id": "TC003", "priority": "high"},    {"id": "TC001", "priority": "low"},    {"id": "TC002", "priority": "medium"},]# 按优先级排序priority_order = {"low": 1, "medium": 2, "high": 3}test_cases.sort(key=lambda x: priority_order[x["priority"]], reverse=True)print("\n按优先级排序后的用例:")for case in test_cases:    print(f"  {case['id']} - {case['priority']}")
◆ ─────── ◆
09知识点总结核心回顾

参数类型对比

类型
语法
特点
位置参数
def func(a, b)
必须按顺序传递,数量匹配
默认参数
def func(a, b=10)
有默认值,可以不传,必须放在位置参数后面
关键字参数
func(a=1, b=2)
调用时指定参数名,顺序可以任意
*args
def func(*args)
接收任意数量位置参数,打包成元组
**kwargs
def func(**kwargs)
接收任意数量关键字参数,打包成字典

变量作用域对比

类型
定义位置
访问范围
局部变量
函数内部
仅限函数内部访问
全局变量
函数外部(文件级)
整个文件都可以访问,修改需要 global
nonlocal
嵌套函数中
修改外层(非全局)函数的变量

💡 给测试工程师的建议

1. DRY原则:Don't Repeat Yourself,重复代码超过3次就要封装成函数2. 单一职责:一个函数只做一件事,不要写"万能函数"3. 好命名:函数名用动词开头,见名知意,不用写注释也能懂4. 返回结构化数据:优先返回字典或对象,而不是零散的多个值5. 善用装饰器:日志、重试、计时这类通用逻辑用装饰器统一处理

◆ ─────── ◆

函数是编程中最重要的概念之一,也是测试自动化的基础。掌握了函数的各种用法,你的代码将从"能跑"升级到"优雅、高效、可维护"。记住:好的函数像乐高积木,可以自由组合,构建出强大的测试框架!

下一篇:Python 模块与包详解 →

Python 学习笔记系列 | 第九篇

最新文章

随机文章