Python 函数参数缺失或无效的处理方法

PythonBeginner
立即练习

导言

Python 的函数参数是编程的一个基本方面,但是处理缺失或无效的参数可能具有挑战性。本教程将指导你了解 Python 中函数参数的处理,从理解基础知识到实现强大的验证和错误处理策略。最后,你将能够编写 Python 代码,从而优雅地管理函数参数,从而使应用程序更可靠、更易于维护。

理解基本函数参数和默认值

在 Python 中,函数是执行特定任务的可重用代码块。当你定义一个函数时,你可以指定函数期望接收的参数。让我们学习如何使用不同类型的参数设置函数,并探索如何提供默认值。

创建我们的第一个函数

让我们从创建一个简单的 Python 文件开始。在 WebIDE 中,导航到项目目录并创建一个名为 function_args.py 的新文件:

  1. 点击 WebIDE 中的 "File" 菜单
  2. 选择 "New File"
  3. 输入 function_args.py 作为文件名
  4. 点击 "OK"

现在,让我们向此文件添加一个基本函数:

def greet(name):
    """一个简单的函数,通过名字问候一个人。"""
    return f"你好,{name}!"

## 调用函数并打印结果
result = greet("Alice")
print(result)

保存文件(Ctrl+S 或 File > Save),然后在终端中运行它:

python3 function_args.py

你应该看到输出:

你好,Alice!

理解必需参数

在上面的例子中,name 是一个必需参数。如果你尝试在不提供此参数的情况下调用该函数,Python 将引发一个错误。

让我们修改我们的文件来演示这一点:

def greet(name):
    """一个简单的函数,通过名字问候一个人。"""
    return f"你好,{name}!"

## 这将起作用
result = greet("Alice")
print(result)

## 这将引发一个错误
try:
    result = greet()
    print(result)
except TypeError as e:
    print(f"错误:{e}")

保存文件并运行它:

python3 function_args.py

输出:

你好,Alice!
错误:greet() 缺少 1 个必需的位置参数:'name'

正如你所看到的,当我们没有提供必需参数时,Python 会引发一个 TypeError

添加默认值

为了使参数可选,我们可以提供默认值。让我们更新我们的函数:

def greet(name="Guest"):
    """一个通过名字问候一个人的函数,带有一个默认值。"""
    return f"你好,{name}!"

## 带参数
result1 = greet("Alice")
print(result1)

## 没有参数 - 使用默认值
result2 = greet()
print(result2)

保存并运行:

python3 function_args.py

输出:

你好,Alice!
你好,Guest!

现在,该函数既可以带参数也可以不带参数地工作。

具有默认值的多个参数

让我们扩展我们的函数来处理多个参数,其中一些带有默认值:

def greet(name="Guest", message="你好", punctuation="!"):
    """一个具有多个参数和默认值的函数。"""
    return f"{message},{name}{punctuation}"

## 使用所有默认值
print(greet())

## 仅提供名字
print(greet("Alice"))

## 提供名字和消息
print(greet("Bob", "嗨"))

## 提供所有参数
print(greet("Charlie", "欢迎", "!!!"))

保存并运行:

python3 function_args.py

输出:

你好,Guest!
你好,Alice!
嗨,Bob!
欢迎,Charlie!!!

使用关键字参数

你也可以通过名称指定参数,而不管它们的顺序如何:

def greet(name="Guest", message="你好", punctuation="!"):
    """一个具有多个参数和默认值的函数。"""
    return f"{message},{name}{punctuation}"

## 使用关键字参数
print(greet(message="嘿", name="David"))
print(greet(punctuation="...", message="欢迎回来", name="Emma"))

保存并运行:

python3 function_args.py

输出:

嘿,David!
欢迎回来,Emma...

当一个函数有许多参数,而你只想指定其中几个参数时,这特别有用。

现在你了解了如何创建带有默认参数的函数以及如何使用关键字参数。在下一步中,我们将探索更高级的方法来处理缺失或无效的函数参数。

使用 *args 和 **kwargs 处理缺失参数

在 Python 中,我们有时需要创建可以接受可变数量参数的灵活函数。为了处理这些情况,Python 提供了两个特殊的语法元素:*args**kwargs

创建一个新的 Python 文件

让我们创建一个新文件来使用这些概念:

  1. 点击 WebIDE 中的 "File" 菜单
  2. 选择 "New File"
  3. 输入 flexible_args.py 作为文件名
  4. 点击 "OK"

理解 *args

*args 语法允许一个函数接受任意数量的位置参数,这些参数被收集到一个元组中。

将以下代码添加到 flexible_args.py

def sum_numbers(*args):
    """一个对任意数量的参数求和的函数。"""
    result = 0
    for num in args:
        result += num
    return result

## 使用不同数量的参数测试函数
print(f"1、2 的和:{sum_numbers(1, 2)}")
print(f"1、2、3、4、5 的和:{sum_numbers(1, 2, 3, 4, 5)}")
print(f"没有参数:{sum_numbers()}")

保存文件并运行它:

python3 flexible_args.py

输出:

1、2 的和:3
1、2、3、4、5 的和:15
没有参数:0

这演示了 *args 如何处理任意数量的参数,包括根本没有参数。在函数内部,args 是一个包含所有提供参数的元组。

理解 **kwargs

**kwargs 语法允许一个函数接受任意数量的关键字参数,这些参数被收集到一个字典中。

让我们向我们的文件添加另一个函数:

def build_profile(**kwargs):
    """一个从关键字参数构建用户配置文件的函数。"""
    profile = {}

    ## 添加带有默认值的必需字段
    profile["name"] = kwargs.get("name", "匿名")
    profile["age"] = kwargs.get("age", "未指定")

    ## 添加任何其他字段
    for key, value in kwargs.items():
        if key not in ["name", "age"]:
            profile[key] = value

    return profile

## 使用不同的关键字参数测试函数
print("基本配置文件:", build_profile())
print("完整配置文件:", build_profile(name="Alice", age=30, occupation="开发者", location="纽约"))
print("自定义字段:", build_profile(hobby="阅读", favorite_color="蓝色"))

保存并运行:

python3 flexible_args.py

输出:

基本配置文件:{'name': '匿名', 'age': '未指定'}
完整配置文件:{'name': 'Alice', 'age': 30, 'occupation': '开发者', 'location': '纽约'}
自定义字段:{'name': '匿名', 'age': '未指定', 'hobby': '阅读', 'favorite_color': '蓝色'}

注意 kwargs.get("key", default_value) 如何允许我们在不存在时使用默认值检索值。

组合必需参数、默认参数、*args 和 **kwargs

让我们创建一个更复杂的函数,它结合了所有这些类型的参数:

def format_message(recipient, message="你好", *args, **kwargs):
    """
    一个使用各种自定义选项格式化消息的函数。
    - recipient:必需 - 消息的接收者
    - message:默认问候语
    - *args:其他消息部分
    - **kwargs:格式化选项
    """
    ## 从基本消息开始
    full_message = f"{message},{recipient}!"

    ## 添加任何其他消息部分
    if args:
        full_message += " " + " ".join(args)

    ## 应用格式化选项
    if kwargs.get("upper", False):
        full_message = full_message.upper()

    if kwargs.get("wrap", False):
        full_message = f"[{full_message}]"

    return full_message

## 使用不同的组合进行测试
print(format_message("Alice"))
print(format_message("Bob", "嗨"))
print(format_message("Charlie", "欢迎", "希望", "你", "一切", "安好"))
print(format_message("David", "问候", upper=True))
print(format_message("Emma", wrap=True))
print(format_message("Frank", "嘿", "最近怎么样?", upper=True, wrap=True))

保存并运行:

python3 flexible_args.py

输出:

你好,Alice!
嗨,Bob!
欢迎,Charlie!希望你一切安好
问候,DAVID!
[你好,Emma!]
[嘿,FRANK!最近怎么样?]

此示例演示了如何将所有类型的函数参数一起使用:

  1. recipient 是一个必需的位置参数
  2. message 具有默认值,使其成为可选参数
  3. *args 捕获任何其他位置参数
  4. **kwargs 捕获任何关键字参数

通过结合这些方法,你可以创建高度灵活的函数,这些函数可以优雅地处理缺失或可选参数。

验证函数参数

在 Python 中创建函数时,在继续函数的主要逻辑之前,验证传递给你的函数的参数是否有效非常重要。在这一步中,我们将学习几种验证函数参数的技术。

创建一个新的 Python 文件

让我们创建一个新文件来使用验证概念:

  1. 点击 WebIDE 中的 "File" 菜单
  2. 选择 "New File"
  3. 输入 validate_args.py 作为文件名
  4. 点击 "OK"

使用条件语句进行基本验证

验证参数的最简单方法是使用条件语句。让我们从一些基本验证开始:

def calculate_rectangle_area(length, width):
    """计算矩形的面积,验证输入。"""
    ## 验证输入是否为数字
    if not isinstance(length, (int, float)):
        raise TypeError("长度必须是数字")
    if not isinstance(width, (int, float)):
        raise TypeError("宽度必须是数字")

    ## 验证输入是否为正数
    if length <= 0:
        raise ValueError("长度必须为正数")
    if width <= 0:
        raise ValueError("宽度必须为正数")

    ## 计算面积
    return length * width

## 使用有效输入进行测试
try:
    area = calculate_rectangle_area(5, 3)
    print(f"矩形面积:{area}")
except (TypeError, ValueError) as e:
    print(f"错误:{e}")

## 使用无效类型进行测试
try:
    area = calculate_rectangle_area("5", 3)
    print(f"矩形面积:{area}")
except (TypeError, ValueError) as e:
    print(f"错误:{e}")

## 使用无效值进行测试
try:
    area = calculate_rectangle_area(5, -3)
    print(f"矩形面积:{area}")
except (TypeError, ValueError) as e:
    print(f"错误:{e}")

保存并运行:

python3 validate_args.py

输出:

矩形面积:15
错误:长度必须是数字
错误:宽度必须为正数

此函数在执行任何计算之前,验证其参数的类型和值。当检测到无效参数时,会引发相应的错误消息。

使用断言进行验证

验证参数的另一种方法是使用断言。断言是如果条件不满足则引发 AssertionError 的语句:

def calculate_discount(price, discount_percent):
    """计算折扣后的价格。"""
    ## 断言输入是否有效
    assert isinstance(price, (int, float)), "价格必须是数字"
    assert isinstance(discount_percent, (int, float)), "折扣必须是数字"
    assert price >= 0, "价格不能为负数"
    assert 0 <= discount_percent <= 100, "折扣必须在 0 到 100 之间"

    ## 计算折扣
    discount_amount = price * (discount_percent / 100)
    return price - discount_amount

## 使用有效输入进行测试
try:
    discounted_price = calculate_discount(100, 20)
    print(f"折扣后价格:${discounted_price}")
except AssertionError as e:
    print(f"错误:{e}")

## 使用无效折扣百分比进行测试
try:
    discounted_price = calculate_discount(100, 120)
    print(f"折扣后价格:${discounted_price}")
except AssertionError as e:
    print(f"错误:{e}")

保存并运行:

python3 validate_args.py

输出:

折扣后价格:$80.0
错误:折扣必须在 0 到 100 之间

断言对于开发和调试很有用,但它们可以在生产代码中被禁用,因此它们并不总是用于实际应用程序中验证的最佳选择。

使用类型提示进行文档记录

Python 3.5+ 支持类型提示,这可以帮助记录函数参数和返回值的预期类型。虽然类型提示本身不会执行运行时验证,但它们提供了有用的文档,并且可以由 mypy 等外部工具进行检查:

def calculate_average(numbers: list[float]) -> float:
    """计算数字列表的平均值。"""
    if not numbers:
        raise ValueError("无法计算空列表的平均值")

    if not all(isinstance(n, (int, float)) for n in numbers):
        raise TypeError("所有元素都必须是数字")

    return sum(numbers) / len(numbers)

## 使用有效输入进行测试
try:
    avg = calculate_average([1, 2, 3, 4, 5])
    print(f"平均值:{avg}")
except (TypeError, ValueError) as e:
    print(f"错误:{e}")

## 使用空列表进行测试
try:
    avg = calculate_average([])
    print(f"平均值:{avg}")
except (TypeError, ValueError) as e:
    print(f"错误:{e}")

## 使用非数字元素进行测试
try:
    avg = calculate_average([1, 2, "3", 4, 5])
    print(f"平均值:{avg}")
except (TypeError, ValueError) as e:
    print(f"错误:{e}")

保存并运行:

python3 validate_args.py

输出:

平均值:3.0
错误:无法计算空列表的平均值
错误:所有元素都必须是数字

请注意,类型提示(list[float]-> float)本身不会执行任何验证 - 我们仍然需要编写自己的验证代码。它们充当文档,并且可以由外部工具进行检查。

创建一个具有验证功能的强大函数

现在,让我们应用所有这些技术来构建一个强大的函数,该函数计算带有折扣的商品总成本:

def calculate_total_cost(items=None, tax_rate=0, discount=0):
    """
    计算带有税款和折扣的商品总成本。

    参数:
        items:(item_name, price) 元组的列表
        tax_rate:税率百分比 (0-100)
        discount:折扣百分比 (0-100)

    返回:
        一个包含总额、小计、税额和折扣额的字典
    """
    ## 验证商品
    if items is None:
        items = []

    if not isinstance(items, list):
        raise TypeError("商品必须是列表")

    ## 验证列表中的每个商品
    for i, item in enumerate(items):
        if not isinstance(item, tuple) or len(item) != 2:
            raise ValueError(f"商品 {i} 必须是 (name, price) 的元组")

        name, price = item
        if not isinstance(name, str):
            raise TypeError(f"商品 {i} 的名称必须是字符串")
        if not isinstance(price, (int, float)):
            raise TypeError(f"商品 {i} 的价格必须是数字")
        if price < 0:
            raise ValueError(f"商品 {i} 的价格不能为负数")

    ## 验证 tax_rate 和 discount
    if not isinstance(tax_rate, (int, float)):
        raise TypeError("税率必须是数字")
    if not isinstance(discount, (int, float)):
        raise TypeError("折扣必须是数字")

    if not (0 <= tax_rate <= 100):
        raise ValueError("税率必须在 0 到 100 之间")
    if not (0 <= discount <= 100):
        raise ValueError("折扣必须在 0 到 100 之间")

    ## 计算总额
    subtotal = sum(price for _, price in items)
    discount_amount = subtotal * (discount / 100)
    tax_amount = (subtotal - discount_amount) * (tax_rate / 100)
    total = subtotal - discount_amount + tax_amount

    return {
        "subtotal": subtotal,
        "discount_amount": discount_amount,
        "tax_amount": tax_amount,
        "total": total
    }

## 使用有效输入进行测试
shopping_cart = [
    ("笔记本电脑", 1000),
    ("鼠标", 25),
    ("键盘", 45)
]

try:
    result = calculate_total_cost(shopping_cart, tax_rate=8.5, discount=10)
    print("购物车总额:")
    for key, value in result.items():
        print(f"  {key.replace('_', ' ').title()}:${value:.2f}")
except (TypeError, ValueError) as e:
    print(f"错误:{e}")

## 使用无效商品进行测试
try:
    invalid_cart = [
        ("笔记本电脑", 1000),
        ("鼠标", "二十五"),  ## 无效价格
        ("键盘", 45)
    ]
    result = calculate_total_cost(invalid_cart)
    print(result)
except (TypeError, ValueError) as e:
    print(f"无效商品错误:{e}")

保存并运行:

python3 validate_args.py

输出:

购物车总额:
  小计:$1070.00
  折扣额:$107.00
  税额:$81.86
  总额:$1044.86
无效商品错误:商品 1 的价格必须是数字

此函数通过以下方式演示了强大的验证:

  1. 检查所有输入的类型
  2. 验证数值的范围
  3. 提供详细的错误消息
  4. 为可选参数设置合理的默认值
  5. 使用文档字符串记录预期的输入和返回值

通过在你的函数中实现彻底的验证,你可以防止错误,为用户提供更好的反馈,并使你的代码更健壮和可维护。

构建一个完整的应用程序

现在我们已经学习了处理和验证函数参数的各种技术,让我们应用这些技能来构建一个简单但完整的应用程序。我们将创建一个基本的费用跟踪系统,该系统演示了函数参数处理的良好实践。

创建应用程序文件

让我们为我们的费用跟踪器创建一个新的 Python 文件:

  1. 点击 WebIDE 中的 "File" 菜单
  2. 选择 "New File"
  3. 输入 expense_tracker.py 作为文件名
  4. 点击 "OK"

设计费用跟踪器函数

我们的费用跟踪器将有几个函数来处理费用的不同方面:

def create_expense(description, amount, category=None, date=None):
    """
    创建一个新的费用条目。

    参数:
        description (str):费用的描述
        amount (float):花费的金额
        category (str, optional):费用的类别
        date (str, optional):日期,格式为 YYYY-MM-DD

    返回:
        dict:一个费用条目
    """
    ## 验证描述
    if not isinstance(description, str):
        raise TypeError("描述必须是字符串")
    if not description:
        raise ValueError("描述不能为空")

    ## 验证金额
    if not isinstance(amount, (int, float)):
        raise TypeError("金额必须是数字")
    if amount <= 0:
        raise ValueError("金额必须为正数")

    ## 创建费用字典
    expense = {
        "description": description,
        "amount": float(amount),
        "category": category or "未分类",
        "date": date or "未指定"
    }

    return expense


def add_expense_to_list(expenses, **kwargs):
    """
    将新的费用添加到费用列表中。

    参数:
        expenses (list):费用列表
        **kwargs:要传递给 create_expense 的费用详细信息

    返回:
        list:更新后的费用列表
    """
    ## 验证费用列表
    if not isinstance(expenses, list):
        raise TypeError("费用必须是列表")

    ## 提取必需的参数
    if "description" not in kwargs:
        raise ValueError("需要费用描述")
    if "amount" not in kwargs:
        raise ValueError("需要费用金额")

    ## 创建费用并将其添加到列表中
    expense = create_expense(
        kwargs["description"],
        kwargs["amount"],
        kwargs.get("category"),
        kwargs.get("date")
    )

    expenses.append(expense)
    return expenses


def get_total_expenses(expenses, category=None):
    """
    计算费用的总额,可以选择按类别过滤。

    参数:
        expenses (list):费用列表
        category (str, optional):如果提供,则按此类别过滤

    返回:
        float:总额
    """
    ## 验证费用列表
    if not isinstance(expenses, list):
        raise TypeError("费用必须是列表")

    ## 计算总额
    if category:
        return sum(e["amount"] for e in expenses if e["category"] == category)
    else:
        return sum(e["amount"] for e in expenses)


def get_expense_summary(expenses):
    """
    按类别获取费用摘要。

    参数:
        expenses (list):费用列表

    返回:
        dict:一个字典,其中类别作为键,总额作为值
    """
    ## 验证费用列表
    if not isinstance(expenses, list):
        raise TypeError("费用必须是列表")

    ## 创建摘要
    summary = {}
    for expense in expenses:
        category = expense["category"]
        if category in summary:
            summary[category] += expense["amount"]
        else:
            summary[category] = expense["amount"]

    return summary

使用我们的费用跟踪器

现在让我们使用我们的函数来跟踪一些费用:

def print_expense_summary(summary):
    """打印按类别格式化的费用摘要。"""
    print("\n按类别划分的费用摘要:")
    print("-" * 30)
    for category, amount in summary.items():
        print(f"{category}:${amount:.2f}")
    print("-" * 30)
    print(f"总计:${sum(summary.values()):.2f}")

## 初始化一个空的费用列表
expenses = []

## 添加一些费用
try:
    ## 仅使用必需的参数添加
    expenses = add_expense_to_list(
        expenses,
        description="杂货",
        amount=45.75
    )

    ## 使用所有参数添加
    expenses = add_expense_to_list(
        expenses,
        description="电影票",
        amount=25.00,
        category="娱乐",
        date="2023-11-15"
    )

    ## 添加另一笔费用
    expenses = add_expense_to_list(
        expenses,
        description="晚餐",
        amount=65.40,
        category="食物",
        date="2023-11-14"
    )

    ## 使用默认类别添加
    expenses = add_expense_to_list(
        expenses,
        description="汽油",
        amount=35.80,
        date="2023-11-16"
    )

    ## 显示所有费用
    print("所有费用:")
    for i, expense in enumerate(expenses, 1):
        print(f"{i}. {expense['description']}:${expense['amount']:.2f} " +
              f"({expense['category']},{expense['date']})")

    ## 获取并显示总额
    total = get_total_expenses(expenses)
    print(f"\n总费用:${total:.2f}")

    ## 获取并显示特定类别的费用
    food_total = get_total_expenses(expenses, "食物")
    print(f"食物费用:${food_total:.2f}")

    ## 获取并显示摘要
    summary = get_expense_summary(expenses)
    print_expense_summary(summary)

except (TypeError, ValueError) as e:
    print(f"错误:{e}")

我们还添加一些代码来演示错误处理:

## 尝试一些无效的输入
print("\n测试错误处理:")

try:
    ## 无效的费用描述
    expenses = add_expense_to_list(expenses, description="", amount=10)
except ValueError as e:
    print(f"捕获错误:{e}")

try:
    ## 无效的费用金额
    expenses = add_expense_to_list(expenses, description="咖啡", amount=-5)
except ValueError as e:
    print(f"捕获错误:{e}")

try:
    ## 缺少必需的参数
    expenses = add_expense_to_list(expenses, description="咖啡")
except ValueError as e:
    print(f"捕获错误:{e}")

保存文件并运行:

python3 expense_tracker.py

预期输出:

所有费用:
1. 杂货:$45.75 (未分类,未指定)
2. 电影票:$25.00 (娱乐,2023-11-15)
3. 晚餐:$65.40 (食物,2023-11-14)
4. 汽油:$35.80 (未分类,2023-11-16)

总费用:$171.95
食物费用:$65.40

按类别划分的费用摘要:
------------------------------
未分类:$81.55
娱乐:$25.00
食物:$65.40
------------------------------
总计:$171.95

测试错误处理:
捕获错误:描述不能为空
捕获错误:金额必须为正数
捕获错误:需要费用金额

应用程序回顾

我们的费用跟踪器演示了几个重要的概念:

  1. 参数验证:每个函数都会验证其参数,以确保它们符合预期的类型和约束。

  2. 默认值:我们使用默认值使某些参数成为可选参数,例如 categorydate

  3. 必需参数:对于基本信息,例如 descriptionamount,我们确保提供了这些信息并且有效。

  4. 关键字参数add_expense_to_list 函数使用 **kwargs 以灵活的方式接受费用详细信息。

  5. 错误处理:我们使用适当的异常和有意义的错误消息来使调试更容易。

  6. 文档字符串:每个函数都包含一个文档字符串,用于解释其目的、参数和返回值。

通过应用这些技术,我们创建了一个可靠的应用程序,该应用程序以可靠且用户友好的方式处理函数参数。这种方法有助于防止错误,提高代码可维护性,并在出现问题时提供清晰的反馈。

总结

在本教程中,你学习了在 Python 中处理缺失或无效函数参数的基本技术:

  1. 理解基本函数参数和默认值:你探索了不同类型的函数参数,包括必需参数、带有默认值的可选参数和关键字参数。这个基础知识可以帮助你设计既灵活又用户友好的函数。

  2. 处理可变数量的参数:你学习了如何使用 *args**kwargs 来创建可以接受任意数量参数的函数,从而使你的函数更适应不同的用例。

  3. 验证函数参数:你实现了各种验证技术,以确保参数满足预期的类型和约束,这有助于防止错误并提供清晰的错误消息。

  4. 构建一个完整的应用程序:你应用了这些概念来构建一个实用的费用跟踪应用程序,演示了如何正确处理参数可以使代码更健壮且更易于维护。

这些技能是编写可靠 Python 代码的基础。通过正确处理函数参数,你可以创建更灵活、更易于使用且不易出错的函数。在你继续你的 Python 之旅时,这些技术将帮助你构建更复杂的应用程序,这些应用程序可以优雅地处理意外的输入和边缘情况。