18. 函数参数 — Python 进阶

在本文中,我们将详细讨论函数形参(parameters)和函数实参(arguments)。 我们将学习:

  • 形参和实参之间的区别
  • 位置和关键字参数
  • 默认参数
  • 变长参数( *args**kwargs
  • 容器拆包成函数参数
  • 局部与全局参数
  • 参数传递(按值还是按引用?)

形参和实参之间的区别

  • 形数是定义函数时在括号内定义或使用的变量
  • 实参是调用函数时为这些参数传递的值
def print_name(name): # name 是形参
    print(name)

print_name('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) # 'a' 有多个值
    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])

# 3, 4, 5 合并入 args
# six and seven 合并入 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)

# 变长参数后面的参数必须是关键字参数
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)

# list/tuple 拆包,长度必需匹配
my_list = [4, 5, 6] # or tuple
foo(*my_list)

# dict 拆包,键和长度必需匹配
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 var_name 才能更改全局变量。

def foo1():
    x = number # 全局变量只能在这里访问
    print('number in function:', x)

number = 0
foo1()

# 修改全局变量
def foo2():
    global number # 现在可以访问和修改全局变量
    number = 3

print('number before foo2(): ', number)
foo2() # 修改全局变量
print('number after foo2(): ', number)
    number in function: 0
    number before foo2():  0
    number after foo2():  3

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

number = 0

def foo3():
    number = 3 # 这是局部变量

print('number before foo3(): ', number)
foo3() # 不会修改全局变量
print('number after foo3(): ', number)
    number before foo3():  0
    number after foo3():  0

参数传递

Python使用一种称为“对象调用”或“对象引用调用”的机制。必须考虑以下规则:

  • 传入的参数实际上是对对象的引用(但引用是按值传递)
  • 可变和不可变数据类型之间的差异

这意味着:

  1. 可变对象(例如列表,字典)可以在方法中进行更改。但是,如果在方法中重新绑定引用,则外部引用仍将指向原始对象。
  2. 不能在方法中更改不可变的对象(例如int,string)。但是包含在可变对象中的不可变对象可以在方法中重新分配。
# 不可变对象 -> 不变
def foo(x):
    x = 5 # x += 5 也无效,因为x是不可变的,必须创建一个新变量

var = 10
print('var before foo():', var)
foo(var)
print('var after foo():', var)
    var before foo(): 10
    var after foo(): 10
# 可变对象 -> 可变
def foo(a_list):
    a_list.append(4)
    
my_list = [1, 2, 3]
print('my_list before foo():', my_list)
foo(my_list)
print('my_list after foo():', my_list)
    my_list before foo(): [1, 2, 3]
    my_list after foo(): [1, 2, 3, 4]
# 不可变对象包含在可变对象内 -> 可变
def foo(a_list):
    a_list[0] = -100
    a_list[2] = "Paul"
    
my_list = [1, 2, "Max"]
print('my_list before foo():', my_list)
foo(my_list)
print('my_list after foo():', my_list)
# 重新绑定可变引用 -> 不变
def foo(a_list):
    a_list = [50, 60, 70] # a_list 是函数内新的局部变量
    a_list.append(50)
    
my_list = [1, 2, 3]
print('my_list before foo():', my_list)
foo(my_list)
print('my_list after foo():', my_list)
    my_list before foo(): [1, 2, 3]
    my_list after foo(): [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('my_list before foo():', my_list)
foo(my_list)
print('my_list after foo():', my_list)

my_list = [1, 2, 3]
print('my_list before bar():', my_list)
bar(my_list)
print('my_list after bar():', my_list)
    my_list before foo(): [1, 2, 3]
    my_list after foo(): [1, 2, 3, 4, 5]
    my_list before bar(): [1, 2, 3]
    my_list after bar(): [1, 2, 3]

GitHub repo: qiwihui/blog

Follow me: @qiwihui

Site: QIWIHUI

View on GitHub