2024-04-09    2024-04-12     3473 字  7 分钟

参数类型

Python 函数的参数类型主要有以下几种:位置参数、默认参数、关键字参数和可变参数。

  1. 位置参数(Positional arguments): 位置参数是最常见的参数类型,它们是根据参数在函数定义中出现的顺序传递的。调用函数时,必须按照函数定义中的顺序提供相应数量的位置参数,传入的参数值按照函数定义中的顺序依次赋给参数。位置参数的一个主要特点是它们不需要在调用时指明参数名称。

    def describe_pet(animal_type, pet_name):
        print(f"I have a {animal_type} named {pet_name}.")
    
    
    describe_pet('hamster', 'Harry') # hamster 是 animal_type,Harry 是 pet_name,严格按照顺序,'hamster' 被赋给 animal_type,'Harry' 被赋给 pet_name
    
  2. 默认参数(Default arguments): 函数定义时可以为参数指定默认值。调用函数时,如果没有传入这些参数,就使用默认值。

     def describe_pet(pet_name, animal_type="dog"):
         print(f"I have a {animal_type} named {pet_name}.")
    
    
     describe_pet("Willie")
    

    这里 animal_type 有一个默认值 'dog',所以即使调用 describe_pet() 时只提供了一个参数,函数也不会报错。

  3. 关键字参数(Keyword arguments): 关键字参数允许我们在调用函数时指定参数的名称,在调用函数时,可以通过“键 = 值”的形式指定参数,这样参数的顺序就不重要了。使用关键字参数可以提高代码的可读性,由于关键字参数的顺序不重要,因此,我们可以不按照函数定义中的参数顺序来传递参数。关键字参数通常用于函数定义中已经有默认值的参数,或者当我们想明确每个参数的作用时。

    def describe_pet(animal_type, pet_name):
        print(f"I have a {animal_type} named {pet_name}.")
    
    
    describe_pet(pet_name='Harry', animal_type='hamster') # 传入参数的顺序与定义中的顺序不同,但是 Python 也能正确匹配值。
    

从上面的例子上看,我们似乎并不能在函数定义上明显观察出关键字参数与位置参数有何区别。的确如此,从函数定义上,他们形式是一致的。在定义函数时,我们不需要特别声明哪些是位置参数,哪些是关键字参数。参数的类型是根据函数被调用时参数的指定方式决定的。位置参数需要按照定义时的顺序传递,而关键字参数则不需要,因为我们已经明确指定了参数的名称。比较而言,关键字参数可以增加代码的可读性。当函数有多个参数,特别是有多个布尔值或其他不明显的参数时,使用关键字参数可以使函数调用更清晰。

此外,我们也注意到"关键字参数通常用于函数定义中已经有默认值的参数"。这就导致这两种参数也有一定的联系。请看以下例子:

def create_graph(vertices, edges, is_directed=False):
    print(f"Graph with {vertices} vertices and {edges} edges. Directed: {is_directed}")


create_graph(5, 10, is_directed=True)  # 使用关键字参数覆盖默认值
def create_graph(vertices, edges, is_directed=False, allow_cycle=True):
    print(f"Graph with {vertices} vertices and {edges} edges. Directed: {is_directed}, Allow cycle: {allow_cycle}")


create_graph(5, 10, is_directed=True, allow_cycle=False) # 使用关键字参数覆盖默认值

is_directedallow_cycle 到底是关键字参数还是默认参数?有些糊涂了。

由于在这两个例子中,关键字参数被用来指定默认值,关键字参数和默认参数非常相似,但仔细思考,我们还是发现它们在函数定义和调用中的作用与用法有着本质上的区别。

对于关键字参数:

  • 关键字参数不一定要有默认值。它们的关键点是在函数调用时,我们需要明确指定参数的名称。
  • 使用关键字参数可以不按照函数定义中的参数顺序来传递参数,增强了代码的可读性。
  • 在调用函数时,如果我们使用参数名,那么这些参数就是关键字参数。

对于默认参数:

  • 默认参数是在函数定义时为参数提供的默认值。如果调用函数时没有传递这个参数,将使用默认值。
  • 默认参数允许我们在调用函数时省略这个参数,简化了函数的调用,特别是当函数有多个参数时。
  • 如果提供了参数的值,则使用提供的值,否则使用默认值。

总结下就是,关键字参数的核心是“通过参数名指定参数值”,而默认参数的核心是“为参数提供一个默认值”。关键字参数不必有默认值,而默认参数的定义就是给参数一个默认值。在某些情况下,我们可以同时使用这两种参数。例如,可以定义一个函数,它既有默认参数,也接受通过参数名(即以关键字方式)传递的参数值(如 create_graph)。可以说 is_directedallow_cycle 结合了关键字参数和默认参数的特性。

  1. 可变参数:

    • 可变位置参数(Arbitrary Positional Arguments): 使用 *args 可以接收任意数量的位置参数,这些参数被存储在一个元组中。

      def make_pizza(*toppings):
          print("Making a pizza with the following toppings:")
          for topping in toppings:
              print(f"- {topping}")
      
      
      make_pizza('pepperoni')
      make_pizza('mushrooms', 'green peppers', 'extra cheese')
      

      make_pizza 函数可以接受任意数量的 toppings*toppings 收集所有传入的位置参数到一个名为 toppings 的元组中。

    • 可变关键字参数(Arbitrary Keyword Arguments): 使用 **kwargs 可以接收任意数量的关键字参数,这些参数被存储在一个字典中。当我们需要处理带有名称的参数,而这些参数事先不确定时,可变关键字参数非常有用,例如 matplotlib 中很多方法均支持可变关键字参数。此外,在创建或修改记录、配置项等需要键值对的数据结构时也特别有用。

      def build_profile(first, last, **user_info):
          user_info['first_name'] = first
          user_info['last_name'] = last
          return user_info
      
      
      user_profile = build_profile('albert', 'einstein', location='princeton', field='physics')
      print(user_profile)
      

    这里,build_profile 可以接受两个固定的位置参数(firstlast)和任意数量的关键字参数(user_info)。**user_info 收集所有额外的关键字参数到一个名为 user_info 的字典中。

参数顺序问题

在定义函数时,这几类参数有一个特定的顺序要求。按照 Python 的语法规则,参数应该按照以下顺序排列:

  1. 位置参数:首先列出,它们是基础,需要按照定义时的顺序传递。
  2. 默认参数:跟在位置参数之后。如果位置参数和默认参数混用,所有的位置参数都应该放在默认参数前面。
  3. 可变位置参数(*args:如果函数接受任意数量的位置参数,*args 应该放在位置参数和默认参数之后。
  4. 关键字参数:如果函数接受不确定的关键字参数,它们通过 **kwargs 表示,并且应该放在所有其他参数之后。

这个顺序确保了函数在被调用时能够正确解析和匹配提供的参数。不遵守这个顺序会导致语法错误。下面是一个包含所有这些参数类型的函数定义示例,展示了它们的正确排序:

def example_function(pos1, pos2, def1=None, def2='default', *args, kw1, kw2='kw', **kwargs):
    print(f"pos1: {pos1}")
    print(f"pos2: {pos2}")
    print(f"def1: {def1}")
    print(f"def2: {def2}")
    print(f"args: {args}")
    print(f"kw1: {kw1}")
    print(f"kw2: {kw2}")
    print(f"kwargs: {kwargs}")

在这个函数中:

  • pos1pos2 是位置参数。
  • def1def2 是带有默认值的参数(默认参数)。
  • *args 是可变位置参数,可以接收任何额外的位置参数。
  • kw1kw2 是关键字参数,其中 kw2 有一个默认值。
  • **kwargs 是用于接收任意数量的额外关键字参数的可变关键字参数。

调用这个函数时,我们必须提供至少两个位置参数(pos1pos2),kw1 是必须的关键字参数(除非你定义了默认值),其他的参数都是可选的。以下是如何调用这个函数的示例:

example_function(1, 2, kw1='a', extra='hello', more='world')

这里,12 是位置参数的值,'a' 是关键字参数 kw1 的值,而 'hello''world' 是通过 **kwargs 捕获的额外关键字参数。因为 *args 没有在调用中使用,它将是一个空元组。如果提供了更多的非关键字参数,它们将被包含在 args 中。结果如下:

pos1: 1
pos2: 2
def1: None
def2: default
args: ()
kw1: a
kw2: kw
kwargs: {'extra': 'hello', 'more': 'world'}

使用场景示例

位置参数

使用场景一:数学运算函数:当编写执行数学运算的函数时,如一个简单的加法函数,通常使用位置参数来接收输入的数字。

def add(a, b):
    return a + b


result = add(5, 3)

使用场景二:字符串处理: 在处理字符串时,例如一个函数用于连接两个字符串,通常使用位置参数来接收这些字符串。

def concatenate(str1, str2):
    return str1 + str2


full_string = concatenate("Hello, ", "world!")

使用场景三:实现一个简单的数组搜索算法: 使用位置参数来实现一个线性搜索算法,该算法接受一个数组和一个目标值,返回目标值在数组中的索引。

def linear_search(array, target):
    for i, value in enumerate(array):
        if value == target:
            return i
    return -1
# 调用
index = linear_search([1, 4, 5, 2], 5)

在这个例子中,arraytarget 是位置参数,它们必须按照函数定义时的顺序传递

默认参数

使用场景一:日志记录: 在编写日志记录功能时,可以设置一个默认的日志级别,如果用户没有指定级别,则使用默认值。

def log(message, level='INFO'):
    print(f"{level}: {message}")


log("System started.")
log("An error occurred.", "ERROR")

使用场景二:用户配置: 在创建用户配置时,某些配置项可以有默认值,这样用户只需要修改与默认设置不同的部分。

def create_user(username, is_admin=False):
    print(f"Creating user {username}, Admin: {is_admin}")


create_user("john_doe") 
create_user("admin_user", is_admin=True) # 此时使用关键字参数覆盖默认值

使用场景三:创建一个具有默认容量的堆栈实现:使用默认参数来实现一个堆栈,其中堆栈的初始容量有一个默认值。

def create_stack(capacity=10):
    return {'elements': [], 'capacity': capacity}
# 调用
stack = create_stack()
stack_with_capacity = create_stack(20)

在这里,capacity 是一个默认参数,如果在创建堆栈时未指定,将默认为 10。

关键字参数

使用场景一:数据库查询:在进行数据库查询时,可以使用关键字参数来指定不同的查询条件,增强代码的可读性。

def query(database, table, **conditions):
    print(f"Querying table {table} in {database} with conditions {conditions}")


query("sales_db", "transactions", date="2021-01-01", salesperson_id=1234)

使用场景二:创建对象:在创建复杂对象时,使用关键字参数可以明确每个参数的意义,避免混淆。

def create_point(x=0, y=0, z=0):
    print(f"Point created at ({x}, {y}, {z})")


create_point(x=1, y=2, z=3)
# 使用关键字参数来创建一个图,可以指定图是有向的还是无向的,以及是否允许图中存在环。
def create_graph(vertices, edges, is_directed=False, allow_cycle=True):
    print(f"Graph with {vertices} vertices and {edges} edges. Directed: {is_directed}, Allow cycle: {allow_cycle}")


create_graph(5, 10, is_directed=True, allow_cycle=False)

这里,is_directedallow_cycle 是关键字参数,它们增强了函数调用的可读性。

可变参数

使用场景一:数值总和:创建一个函数来计算任意数量的数字的总和。

def sum_numbers(*numbers):
    return sum(numbers)


total = sum_numbers(1, 2, 3, 4, 5)

使用场景二:配置合并:合并多个配置字典,其中每个字典可能有不同的设置项。

def merge_configs(**configs):
    merged = {}
    for config in configs.values():
        merged.update(config)
    return merged


default_config = {"setting1": "value1", "setting2": "value2"}
custom_config = {"setting2": "custom_value2", "setting3": "custom_value3"}
merged_config = merge_configs(default=default_config, custom=custom_config)
# 合并多个数据集
# 合并列表
def merge_lists(*lists):
    merged_list = []
    for lst in lists:
        merged_list.extend(lst)
    return merged_list


merged_list = merge_lists([1, 2], [3, 4], [5])


# 合并集合
def merge_sets(*sets):
    merged_set = set()
    for st in sets:
        merged_set = merged_set.union(st)
    return merged_set



merged_set = merge_sets({1, 2}, {2, 3}, {3, 4})