函数的参数


参数是函数的精髓

一、形参与实参介绍

函数的参数两大分类分为形式参数和实际参数,简称形参和实参

形参即在函数定义阶段,括号内填入的变量名,用于接收外部传来的值

实参即在调用阶段,括号内传入的数据值。数据值可以是常量、变量、函数的返回值。

def my_func(a, b):
    pass
    # 此时,a,b就是函数my_func的形参


my_func(1,2)
# 此时,数据1,2就是函数my_func的实参

实参的不同表现形式:
核心都是数据值

# 实参的不同表现形式
def my_func(a, b):
    print(a, b)


# 1.实参是具体的数值
my_func(1, 2)  # 1 2

# 2.实参是一个变量
x = 10
y = 20
my_func(x, y)  # 本质上还是 my_func(10, 20)

# 3.实参可以是一个函数的返回值
# 3.1 
my_func(int('123'), int('345'))  # 123 345

# 3.2
def my_func1():
    return 111


def my_func2():
    return 222


my_func(my_func1(), my_func2())  # 111 222

形参和实参的关系是:传参是以变量名=数据值的形式进行传递

在函数调用阶段,实参(值)和形参(变量名)临时绑定,函数调用完毕,二者断开(了解)

二、形参与实参的具体使用

2.1 位置参数

位置即顺序,位置参数指的是按照顺序定义的参数,有两种情况:

  1. 在函数定义阶段,括号中从左到右填写的变量名就是位置形参,凡是按照这种形式定义的形参都必须被传值
def index(a, b, c):  # 定义位置形参:a, b, c,三者都必须被传值
    pass
  1. 在函数调用阶段,括号中从左到右传入的数据值就是位置形参,凡是按照这种形式定义的实参会按照从左到右的顺序与形参一一对应
index(1, 2, 3)  # 对应关系为:a=1, b=2, c=3

练习:

# 定义一个函数,传入两个数字参数,功能是:返回大的
a = 10
b = 20
if a > b:
    return a
else:
    return b
# 输出结果:
# 20

2.2 关键字实参

在调用函数时,实参可以是key = value的形式,成为关键字参数,凡是按照这种形式定义的实参,可以完全不按照从左到右的顺序定义,但仍能为指定的形参赋值

index(b=1, a=2, c=6)

练习:

def func(a, b):
    print(a, b)


# func(1, 2)  # 按照位置一一对应传值
# func(1)  # ×,少一个不行
# func(1, 2, 3)  # ×, 多一个不行

# func(a=1, b=2)  # 关键字实参(指名道姓的传)
# func(b=2, a=1)
# func(b=1)  # ×,少一个不行
# func(b=1, a=2, c=4)  # ×,多一个不行

# func(1, b=2)  # 混用
# func(b=2, 1)  # ×,关键字实参一定要跟在位置参数的后面
# func(1, a=2)  # ×, 1按照位置传参给了a,然后关键字传参又给a赋了值,多次赋值,错误
# func(1, a=2, b=3)  # ×, 同一个形参在调用阶段不能多次赋值


"""
1. 在函数调用阶段,位置实参与位置形参一一对应传递
2. 实参个数与形参个数要一样,多一个不行,少一个也不行
3.同一个形参在调用阶段不能多次赋值
4.(重要)格式越简单,越往前放
"""

2.3 默认参数

在函数定义阶段,就可以给变量赋值了。

  1. 在调用阶段如果你给了数据值,就使用你给的
  2. 在调用阶段如果你不传数据值,那就使用默认的数据值

练习1:

# 定义一个函数,功能是注册(name, age, gender)
def register(name, age, gender='male'):
    print("%s:%s:%s" % (name, age, gender))

register('kevin', 19)
register('kevin1', 19)
register('kevin2', 19, 'female')

"""
1. 函数在定义阶段,参数的传递已经固定死了
2. 根据变量名与变量值得绑定关系理解
"""

练习2:

# 参数的默认值如果给的是一个列表,那么,后续的数据都会指向同一个列表,(同一个内存空间)

def my_func(name, age, hobby=[]):
    hobby.append(age)
    print("%s:%s:%s" % (name, age, hobby))


my_func('kevin1', '20')
my_func('kevin2', '18')
my_func('kevin3', '39')

# 输出结果:
# kevin1:20:['20']
# kevin2:18:['20', '18']
# kevin3:39:['20', '18', '39']

"""注意事项:默认值是可变类型的时候,要注意多次调用函数的时候,数据值的变化"""
# 想要后面只有一个值
my_func('kevin1', '20', [])
my_func('kevin2', '18', [])
my_func('kevin3', '39', [])

# 输出结果:
# kevin1:20:['20']
# kevin2:18:['18']
# kevin3:39:['39']

练习3:

x = 100


def index(a, b, c=x):
    print(a, b, c)

x = 200

index(1, 2)
# 输出结果
# 1 2 100
为什么c的值是100而不是200呢?
解释:第一步.把100赋值给x,第二步.函数在定义阶段,把100赋值给了c,第三步.把200赋值给了x,那100到x的赋值就断掉了,第四步.调用函数时,c的值仍然是100

补充:

# 加个f,就相当于格式化输出format
name = 'kevin'
age = 18
s = f'my name is {name}, my age is {age}'
print(s)  # my name is kevin, my age is 18

2.4 可变长参数(*与**的用法)

2.4.1 可变长形参

  1. *的使用
# 1. 不管传入多少个位置参数,函数都可以正常运行
def func1(*a):
    print(a)
func1()  # ()
func1(1)  # (1,)
func1(1, 2)  # (1, 2)

def func2(b, *a):
    print(b, a)
# func2()  # ×, 函数至少需要一个参数给到b
func2(1)  # 1 ()
func2(1, 2)  # 1 (2,)
func2(1, 2, 3, 4)  # 1 (2, 3, 4)
func2(1, 2, 3, 4, 5, 6, 7)  # 1 (2, 3, 4, 5, 6, 7)

"""
    *在形参中的使用:接收多余的位置参数,并组装成元素的形式返回
"""

  1. **的使用
# 2. 不管传入多少个关键字参数,函数都可以正常运行
def func3(**k):
    print(k)
func3()  # {}
func3(a=1)  # {'a': 1}
func3(a=1, b=2, c=3)  # {'a': 1, 'b': 2, 'c': 3}

def func4(a, **k):
    print(a, k)
# func4()  # 函数至少需要一个参数给到a
func4(a=1)  # 1 {}
func4(a=1, b=2, c=3)  # 1 {'b': 2, 'c': 3}
func4(a=1, b=2, c=3, x='jason', y='kevin')  # 1 {'b': 2, 'c': 3, 'x': 'jason', 'y': 'kevin'}
"""
**在形参中得使用:接收多余的关键字参数,并把关键字参数组织成字典的形式返回

"""

3.混合使用

# 3. 不管传入多少个位置参数和关键字参数,函数都可以正常运行
def func5(n, *a, **k):
    print(n, a, k)
# func5()  # 函数至少需要一个参数给到n
func5(1, 2, 3)  # 1 (2, 3) {}
func5(111, a=1, b=2, c=3)  # 111 () {'a': 1, 'b': 2, 'c': 3}
func5(n=111, a=1, b=2, c=3)  # 111 () {'a': 1, 'b': 2, 'c': 3}
func5(a=1, b=2, c=3, n=111)  # 111 () {'a': 1, 'b': 2, 'c': 3}
func5(1, 2, 3, a=1, b=2, c=3)  # (2, 3) {'a': 1, 'b': 2, 'c': 3}

  1. 四种形式都存在
def index(a, b, *c, name, **d):
    print(a, b, c)
    print(name, d)
index(1, 2, 3, 4, 5, name='kevin', age=20, gender='male')
# 输出结果:
# 1 2 (3, 4, 5)
# kevin {'age': 20, 'gender': 'male'}

"""
	*和**后面的变量名可以随意的起名字,只是一个变量名而已
	python中推荐使用
		*args   arguments
		**kw args  keyword arguments
		
	def index(*args, **kwargs):
		pass
"""

2.4.1 可变长实参(非常重要)

  1. *的使用
def index(a, b, *args):
    print(a, b, args)

l = [11, 22, 33, 44, 55]
# index(l[0], l[1], l[2])  # 使用列表索引方式传值太麻烦

index(*l)  # <==>index(11, 22, 33, 44, 55)
# 输出结果:11 22 (33, 44, 55)

t = (11, 22, 33, 44, 55)
index(*t)  # <==>index(11, 22, 33, 44, 55)
# 输出结果:11 22 (33, 44, 55)
"""
*在实参中得使用:把容器类型的数据一一打散,然后传递给形参,字典是传键
"""

  1. **的使用
dict = {'username': 'kevin', 'age': '18', 'gender': 'male'}
# index(username='kevin', age='age', gender='male')
# """
# **在实参中得使用:把字典内的元素打散,然后以关键字的形式一一传入
# """
index(**dict)  # <==> index(username='kevin', age='age', gender='male')
"""
**在实参中得使用:把字典内的元素打散,然后以关键字的形式一一传入
"""

2.5 命名关键字参数(了解)

单独一个*的使用

# 单独一个*,表示星号后面的参数统统按照关键字的形式传参
def register(name, age, *, sex='male', height):
    print('Name:%s,Age:%s,Sex:%s,Height:%s' % (name, age, sex, height))

register('kevin', 20, sex='male', height='180')
# 输出结果:
# Name:kevin,Age:20,Sex:male,Height:180

# register('kevin',20, 'male','180')  # ×, 不能以这种方式传参

* 、 ** 的区别

1.概念

  • *args:接收任意数量的位置参数,在函数内部打包成一个元组(Tuple)
  • **kwargs:接收任意数量的关键字参数,在函数内部打包成一个字典(Dictionary)。
def func(a, b, *args, **kwargs):
    print(f"普通参数: a={a}, b={b}")
    print(f"位置可变参数: {args}")
    print(f"关键字可变参数: {kwargs}")

func(1, 2, 3, 4, 5, name="Tom", score=99)

"""
输出结果:
普通参数: a=1, b=2
位置可变参数: (3, 4, 5)
关键字可变参数: {'name': 'Tom', 'score': 99}
"""

2.解包

  • 和 ** 不仅可以用来打包(接收)参数,还可以用来解包(传递)参数。
def introduce(name, age, city):
    print(f"姓名: {name}, 年龄: {age}, 城市: {city}")

# 解包列表/元组 (用 *)
my_list = ["Alice", 25, "Beijing"]
introduce(*my_list)  # 等价于 introduce("Alice", 25, "Beijing")

# 解包字典 (用 **)
my_dict = {"name": "Bob", "age": 30, "city": "Shanghai"}
introduce(**my_dict) # 等价于 introduce(name="Bob", age=30, city="Shanghai")

3.我定一个函数的时候,把一个列表作为某一个参数的默认值。这个时候是有好处还是坏处,分别是什么呀?

  • 坏处:经典的“可变默认参数陷阱”
    Python 函数的默认参数是在函数定义时(而不是每次调用时)被创建并保存在内存中的。这意味着,所有未传入该参数的函数调用,都会共享同一个列表对象。
def add_item(item, lst=[]):
    lst.append(item)
    return lst

print(add_item(1))  # 输出: [1]
print(add_item(2))  # 输出: [1, 2] ??? 为什么 2 跑到了 1 后面?
print(add_item(3))  # 输出: [1, 2, 3]

为什么会这样?
因为 lst=[] 这个空列表只在函数定义时创建了一次。

  • 第一次调用 add_item(1) 时,往这个默认列表里加了 1。

  • 第二次调用 add_item(2) 时,Python 发现你没传列表,于是复用了那个已经包含 1 的列表,接着在里面加 2。

  • 结果就是:每次调用都在修改同一个全局列表,导致数据互相污染。

  • 好处:
    如果从极客或底层设计的角度来看,它确实有一个非常罕见的好处:跨调用保持状态(记忆功能)。
    因为默认参数在内存中是持久的,你可以利用这个特性实现一些不需要全局变量的“状态记忆”。

# 利用陷阱实现一个简易的计数器
def counter(n, cache=[]):
    cache.append(n)
    return len(cache)

print(counter(10))  # 1
print(counter(20))  # 2
print(counter(30))  # 3

# 注:虽然能实现,但在实际工程中,我们通常会用类 class 或闭包来实现状态记忆,而不是利用这个语言特性的副作用。
  • 正确的替代方案(业界标准写法)
    如果你希望每次调用函数时,如果不传列表,就默认使用一个全新的空列表,请永远使用 None 作为默认值:
def add_item(item, lst=None):
    # 如果调用时没传列表,就在函数内部创建一个新列表
    if lst is None:
        lst = []  
        
    lst.append(item)
    return lst

print(add_item(1))  # 输出: [1]
print(add_item(2))  # 输出: [2] 完美!每次都是干净的列表

总结:
除非你非常清楚自己在做什么并且刻意需要这种“记忆”行为,否则永远不要将列表、字典等可变对象作为函数的默认参数。用 None 代替是唯一正确的做法。

在定义函数的时候,会不会给某一个参数设置默认值?

  1. 默认值是做什么用的?
    默认值的核心作用是提供“兜底”选项,增加函数的灵活性。
    它允许调用者在调用函数时,只传入那些“必须改变”的参数,而忽略那些“通常不需要改变”的参数。这能极大简化代码,让函数调用看起来更清爽。

  2. 在什么情况下传值?在什么情况下不传值?
    简单来说,取决于当前的业务需求是否和默认值一致:

  • 不传值(使用默认值):当你的需求正好是常规情况、通用情况时。
  • 传值(覆盖默认值):当你有特殊需求,需要改变函数的某种默认行为时。
posted @ 2023-02-27 20:58  星空看海  阅读(206)  评论(0)    收藏  举报