Python进阶系列 - 17讲 函数

37 阅读6分钟

在这篇文章中我们将详细讲讲函数参数相关知识。

我们将学习一下内容:

  • 实参和参数的区别
  • 位置和关键字参数
  • 默认参数
  • 可变长度参数(*args**kwargs
  • 容器解包成函数参数
  • 本地与全局参数
  • 参数传递(通过值还是通过引用?)

实参和参数

  • 参数是定义函数时在括号内定义或使用的变量
  • 实参是调用函数时为这些参数传递的值

代码:

def print_name(name): # name 是参数
    print(name)
print_name('Alex') # 'Alex' 是实参

结果:

Alex

位置和关键字参数

我们可以将参数作为位置参数或关键字参数传递。

关键字参数的一些好处可以是:

  • 我们可以通过它们的名字来调用参数,以使其更清楚它们代表什么
  • 我们可以重新排列参数位置,使它们最易读

代码:

def foo(a, b, c):
    print(a, b, c)

# 位置参数
foo(1, 2, 3)
# 关键字实参
foo(a=1, b=2, c=3)
foo(c=3, b=2, a=1) # 可以打乱顺序

# 混合使用
foo(1, b=2, c=3)
# 混合使用时,关键字实参必须出现在位置实参之前
# foo(1, b=2, 3) # ❌
# foo(1, b=2, a=3) # ❌ 冲突

结果:

1 2 3
1 2 3
1 2 3
1 2 3

默认参数

函数可以有带有预定义值的默认参数。 该参数可以省略,然后将默认值传递给函数,或者该参数可以与不同的值一起使用。 请注意,默认参数必须定义在非默认参数之后。

代码:

def foo(a, b, c, d=4):
    print(a, b, c, d)


foo(1, 2, 3, 4)
foo(1, b=2, c=3, d=100)

# ❌ 不允许默认参数在非默认参数之前
# def foo(a, b=2, c, d=4):
#     print(a, b, c, d)

结果:

1 2 3 4
1 2 3 100

可变参数(*args**kwargs

  • 如果用一个星号 (*) 标记参数,则可以将任意数量的位置参数传递给函数(通常称为 *args)。
  • 如果用两个星号 (**) 标记参数,则可以将任意数量的关键字参数传递给此函数(通常称为 **kwargs)。

代码:

def foo(a, b, *args, **kwargs):
    print(a, b)
    for arg in args:
        print(arg)
    for kwarg in kwargs:
        print(kwarg, kwargs[kwarg])


# 1,2:位置参数,3,4,5:args参数,6,7:kwargs参数
foo(1, 2, 3, 4, 5, six=6, seven=7)
print()
# *args和**kwargs是可变的,也可以省略。
foo(1, 2, three=3)

结果:

1 2
3
4
5
six 6
seven 7
1 2
three 3

强制关键字参数

有时您希望一些参数必须是关键字参数,不能是位置参数。

您可以通过两种方法实现:

  • 在函数参数列表中写入'*',则之后的所有参数都必须作为关键字参数传递。

  • 在可变参数后面定义参数一定是关键字参数。

代码:

# ✅方法一: 用*号分割
def foo(a, b, *, c, d):
    print(a, b, c, d)


foo(1, 2, c=3, d=4)
# foo(1, 2, 3, 4) # ❌:c,d是不可省略的关键字参数

# ✅方法二: 在*args或**kwargs之后定义
def foo(*args, last):
    for arg in args:
        print(arg)
    print(last)
foo(8, 9, 10, last=50)

结果:

1 2 3 4
8
9
10
50

参数解包

  • 如果列表的长度与函数参数的数量相匹配,则可以将列表或元组解压缩为带有一个星号 (*) 的参数。
  • 同理,字典可以解压成带有两个星号(**)的参数,长度和键与函数参数匹配。

代码:

def foo(a, b, c):
    print(a, b, c)


my_list = [4, 5, 6]  # 列表或者元组
foo(*my_list)  # 参数拆包

my_dict = {"a": 1, "b": 2, "c": 3}  # 字典
foo(**my_dict)

# my_dict = {'a': 1, 'b': 2, 'd': 3} # ❌ 关键字参数不匹配

结果:

4 5 6
1 2 3

局部变量和全局变量

Global variables can be accessed within a function body, but to modify them, we first must state global var_name in order to change the global variable.

代码:

def foo1():
    print("打印全局变量:", number)  # number函数内没有定义,生成一个局部变量,值和全局变量一样
    # number = 1 # ❌ 不允许修改未声明的全局变量

def foo2():
    global number  # 声明全局变量,修改才有效
    number = 3
    print("打印函数内全局变量:", number)

number = 0
print("打印全局变量:", number)
foo1()
print("执行完foo1函数后,打印全局变量:", number)

foo2()  # 修改全局变量
print("执行完foo2函数后,打印全局变量:", number)

结果:

打印全局变量: 0
打印全局变量: 0
执行完foo1函数后,打印全局变量: 0
打印函数内全局变量: 3
执行完foo2函数后,打印全局变量: 3

如果我们不写 global var_name 并为与全局变量同名的变量分配一个新值, 这将在函数内创建一个局部变量。 全局变量保持不变。

参数传递

Python 使用一种称为“Call-by-Object”或“Call-by-Object-Reference”的机制。 必须考虑以下规则:

  • 传入的参数其实是一个对象的引用(但是引用是按值传递的)
  • 可变和不可变数据类型之间的区别

这意味着:

  1. 可变对象(例如列表、字典)可以在方法内更改。
  2. 但是如果在方法中重新绑定引用,外部引用仍然会指向原来的对象。
  3. 不可变对象(例如 int、string)不能在方法内更改。
  4. 但是可变对象中包含的不可变对象可以在方法中重新分配。

1. 不可变对象(例如 int、string)不能在方法内更改。

代码:

def foo(x):
    x = 5  # x是不可变对象。
var = 10
print(f"函数执行前:var={var}")
foo(var)  # 执行foo函数,参数var会被更改为5
print(f"函数执行后:var={var}")

结果:

函数执行前:var=10
函数执行后:var=10

2. 可变对象(例如列表、字典)可以在方法内更改。

代码:

def foo(列表):
    列表.append(4)
A = [1, 2, 3]
print(f'函数执行前: A = {A}')
foo(A)
print(f'函数执行后: A = {A}')

结果:

函数执行前: A = [1, 2, 3]
函数执行后: A = [1, 2, 3, 4]

3. 可变对象中包含的不可变对象可以在方法中重新分配。

代码:

def foo(a_list):
    a_list[0] = -100
    a_list[2] = "Paul"

my_list = [1, 2, "Max"]
print(f'函数执行前: my_list = {my_list}')
foo(my_list)
print(f'函数执行后: my_list = {my_list}')

结果:

函数执行前: my_list = [1, 2, 'Max']
函数执行后: my_list = [-100, 2, 'Paul']

4. 参数传递的是对象的引用。

函数内,重新绑定引用,外部引用仍然会指向原来的对象。

代码:

def foo(a_list):
    a_list = [50, 60, 70] # 重新定义了a_list,为局部变量。
    a_list.append(50)
my_list = [1, 2, 3]
print(f'函数执行前: my_list = {my_list}')
foo(my_list)
print(f'函数执行后: my_list = {my_list}')

结果:

函数执行前: my_list = [1, 2, 3]
函数执行后: my_list = [1, 2, 3]

小心使用可变类型的 +== 操作。 +=不会重新定义变量,而=会重新定义变量。 也就是说,+=是对变量进行追加,而=是重新定义变量。

代码:

def foo(a_list):
    a_list += [4, 5] #⚠️ 改变了传入的参数,不是局部变量。
def bar(a_list):
    a_list = a_list + [4, 5] #⚠️ 重新定义了传入的参数,是局部变量。

my_list = [1, 2, 3]
print(f'foo函数执行前: my_list = {my_list}')
foo(my_list)
print(f'foo函数执行后: my_list = {my_list}')
my_list = [1, 2, 3]
print(f'bar函数执行前: my_list = {my_list}')
bar(my_list)
print(f'bar函数执行后: my_list = {my_list}')

结果:

foo函数执行前: my_list = [1, 2, 3]
foo函数执行后: my_list = [1, 2, 3, 4, 5]
bar函数执行前: my_list = [1, 2, 3]
bar函数执行后: my_list = [1, 2, 3]