函数的参数详解
1. 参数引入
在了解函数的初步使用之后,我们来聊一下函数的参数,比如我们需要用Python来对不同的用户发送邮件,此时加入发送邮件的代码如下所示:
def sendEmail():
pass
那么针对不同用户,我们自然而然想到使用参数,用户信息传参,然后根据参数去发送邮件;如下所示:
v1 = "424662508@qq.com"
v2 = "424662509@qq.com"
v3 = "wupeiqi@live.com"
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
def send_email(xx):
# ### 1.邮件内容配置 ###
msg = MIMEText("约吗", 'html', 'utf-8')
msg['From'] = formataddr(["alex", "yangliangran@126.com"])
msg['Subject'] = "180一晚"
# ### 2.发送邮件 ###
server = smtplib.SMTP_SSL("smtp.126.com")
server.login("yangliangran@126.com", "LAYEVIAPWQAVVDEP")
server.sendmail("yangliangran@126.com", xx, msg.as_string())
server.quit()
send_email("424662508@qq.com")
send_email("424662509@qq.com")
send_email("wupeiqi@live.com")
如果不使用函数的参数那么就需要同样的代码写多次,代码冗余过高,违背函数的初衷,也充分体现函数参数的作用。
2. 参数的使用
在Python中,形参(形式参数)是函数定义时声明的参数,用于接收函数调用时传递的实参(实际参数)的值。形参是函数定义的一部分,用于定义函数的输入。
实参是函数调用时传递给函数的具体数值或对象。实参是函数调用的一部分,用于向函数传递数据。
形参和实参之间的关系是,当函数被调用时,实参的值会被传递给对应的形参,函数内部可以使用形参来访问和处理实参的值。
例如,下面是一个函数定义的例子:
def greet(name):
print("Hello, " + name + "!")
在这个例子中,name
是一个形参,用于接收函数调用时传递的实参的值。当函数被调用时,可以传递一个具体的名字作为实参,例如:
greet("Alice")
在这个函数调用中,“Alice” 是一个实参,它的值会被传递给 name
形参,函数内部会打印出 “Hello, Alice!”。
总结起来,形参是函数定义时声明的参数,用于接收实参的值;实参是函数调用时传递给函数的具体数值或对象。
在定义函数时,如果在括号中添加变量
,我们称它为函数的形式参数:
# ###### 定义有三个参数的函数(a1/a2/a3一般称为形式参数-形参) #####
def func(a1,a2,a3):
print(a1+a2+a3)
# 执行函数并传入参数(执行函数传值时一般称为实际参数-实参)
func(11,22,33)
# 执行函数并传入参数
func(9,2,103)
- 位置传参
def add(n1,n2):
print(n1+n2)
add(1,22)
- 关键字传参
def add(n1,n2):
print(n1+n2)
add(n1=1,n2=22)
"""
1. 形参
2. 实参
3. 位置传参
4. 关键字传参
"""
# ###### 定义有三个参数的函数(a1/a2/a3一般称为形式参数-形参) #####
def func(a1, a2, a3):
print(a1 + a2 + a3)
# 执行函数并传入参数(执行函数传值时一般称为实际参数-实参)
func(11, 22, 33)
# 执行函数并传入参数
func(9, 2, 103)
# 执行函数
func(a1=99, a2=88, a3=1)
func(a1=99, a3=1, a2=88)
3. 参数的分类
3.1 默认参数
def func(a1, a2, a3=10):
print(a1 + a2 + a3)
# 位置传参
func(8, 19)
func(1, 2, 99)
# 关键字传参(位置和关键混合时,关键字传参要在后面)
func(12, 9, a3=90)
func(12, a2=9, a3=90)
func(a1=12, a2=9, a3=90)
file_object = open("xxx.txt")
3.2 动态参数
- **
def func(**kwargs):
print(kwargs) # 字典类型 {"n1":"alex"} {"n1":"alex","age":"18","email":"xxxx"} {}
# 只能按关键字传参
func(n1="alex")
func(n1="alex",age=18)
func(n1="alex",age=18,email="xx@live.com")
- ,*
def func(*args,**kwargs):
print(args,kwargs) # (22,33,99) {}
func(22,33,99)
func(n1="alex",age=18)
func(22,33,99,n1="alex",age=18)
func()
提示:是否还记得字符串格式化时的format功能。
v1 = "我叫{},今年{},性别{}".format("alex",18,"男")
v2 = "我叫{name},今年{age},性别{gender}".format(name="alex",age=18,gender="男")
注意事项(不重要,听过一遍即可)
# 1. ** 必须放在 * 的后面
def func1(*args, **kwargs):
print(args, **kwargs)
# 2. 参数和动态参数混合时,动态参数只能放在最后。
def func2(a1, a2, a3, *args, **kwargs):
print(a1, a2, a3, args, **kwargs)
# 3. 默认值参数和动态参数同时存在
def func3(a1, a2, a3, a4=10, *args, a5=20, **kwargs):
print(a1, a2, a3, a4, a5, args, kwargs)
func3(11, 22, 33, 44, 55, 66, 77, a5=10, a10=123)
小结
1、*args
中args
是元组,可以通过args
元组获取所有位置参数
2、**kwargs
中的kwargs
是字典,可以通过kwargs
获取所有关键字参数
3、在函数的形参中,如果同时有*args
和**kwargs
,*args
必须在**kwargs
前面
Python中,函数的参数分为位置参数、可变参数、关键字参数、命名关键字参数。args
代表可变参数,可以接收0个或任意多个参数,当不确定调用者会传入多少个位置参数时,就可以使用可变参数,它会将传入的参数打包成一个元组。kwargs
代表关键字参数,可以接收用参数名=参数值的方式传入的参数,传入的参数的会打包成一个字典。定义函数时如果同时使用args
和kwargs
,那么函数可以接收任意参数。
4. 参数拓展
参数内存地址相关【面试题】
参数传递:函数调用参数的传递方式是值传递还是引用传递?
- 不可变类型:值传递
n = 100
def fn(n2):
n2 += 1
print("n2:", n2)
fn(n)
print("n:", n)
- 可变类型:引用传递
d = {'age': 100}
def fn2(d2):
d2['age'] += 1
print("d2:", d2)
fn2(d)
print("d:", d)
在开始开始讲参数内存地址相关之前,我们先来学习一个技能:如果想要查看下某个值的在内存中的地址?
v1 = "alex"
addr = id(v1)
print(addr) # 140691049514160
v1 = [11,22,33]
v2 = [11,22,33]
print( id(v1) )
print( id(v2) )
v1 = [11,22,33]
v2 = v1
print( id(v1) )
print( id(v2) )
记住一句话:函数执行传参时,传递的是内存地址。
def func(data):
print(data, id(data)) # alex 140247057684592
v1 = "alex"
print(id(v1)) # 140247057684592
func(v1)
面试题:请问Python的参数默认传递的是什么?
Python参数的这一特性有两个好处:
- 节省内存
- 对于可变类型且函数中修改元素的内容,所有的地方都会修改。可变类型:列表、字典、集合。
# 可变类型 & 修改内部修改
def func(data):
data.append(999)
v1 = [11,22,33]
func(v1)
print(v1) # [11,22,33,666]
# 特殊情况:可变类型 & 重新赋值
def func(data):
data = ["alex","alex"]
v1 = [11,22,33]
func(v1)
print(v1) # [11,22,33]
# 特殊情况:不可变类型,无法修改内部元素,只能重新赋值。
def func(data):
data = "alex"
v1 = "alex"
func(v1)
其他很多编程语言执行函数时,默认传参时会将数据重新拷贝一份,会浪费内存。
提示注意:其他语言也可以通过 ref 等关键字来实现传递内存地址。
当然,如果你不想让外部的变量和函数内部参数的变量一致,也可以选择将外部值拷贝一份,再传给函数。
import copy
# 可变类型 & 修改内部修改
def func(data):
data.append(999)
v1 = [11, 22, 33]
new_v1 = copy.deepcopy(v1) # 拷贝一份数据
func(new_v1)
print(v1) # [11,22,33]
函数的返回值也是是内存地址
def func():
data = [11, 22, 33]
return data
v1 = func()
print(v1) # [11,22,33]
上述代码的执行过程:
- 执行
func
函数 data = [11, 22, 33]
创建一块内存区域,内部存储[11,22,33]
,data变量指向这块内存地址。return data
返回data指向的内存地址- v1接收返回值,所以 v1 和 data 都指向
[11,22,33]
的内存地址(两个变量指向此内存,引用计数器为2) - 由函数执行完毕之后,函数内部的变量都会被释放。(即:删除data变量,内存地址的引用计数器-1)
所以,最终v1指向的函数内部创建的那块内存地址。
def func():
data = [11, 22, 33]
return data
v1 = func()
print(v1) # [11,22,33]
v2 = func()
print(v2) # [11,22,33]
上述代码的执行过程:
- 执行
func
函数 data = [11, 22, 33]
创建一块内存区域,内部存储[11,22,33]
,data变量指向这块内存地址 1000001110。return data
返回data指向的内存地址- v1接收返回值,所以 v1 和 data 都指向
[11,22,33]
的内存地址(两个变量指向此内存,引用计数器为2) - 由函数执行完毕之后,函数内部的变量都会被释放。(即:删除data变量,内存地址的引用计数器-1)
所以,最终v1指向的函数内部创建的那块内存地址。(v1指向的1000001110内存地址)
- 执行
func
函数 data = [11, 22, 33]
创建一块内存区域,内部存储[11,22,33]
,data变量指向这块内存地址 11111001110。return data
返回data指向的内存地址- v2接收返回值,所以 v1 和 data 都指向
[11,22,33]
的内存地址(两个变量指向此内存,引用计数器为2) - 由函数执行完毕之后,函数内部的变量都会被释放。(即:删除data变量,内存地址的引用计数器-1)
所以,最终v1指向的函数内部创建的那块内存地址。(v1指向的11111001110内存地址)
def func():
data = [11, 22, 33]
print(id(data))
return data
v1 = func()
print(v1, id(v1)) # [11,22,33]
v2 = func()
print(v2, id(v1)) # [11,22,33]
参数的默认值【面试题】
这个知识点在面试题中出现的概率比较高,但真正实际开发中用的比较少。
def func(a1,a2=18):
print(a1,a2)
原理:Python在创建函数(未执行)时,如果发现函数的参数中有默认值,则在函数内部会创建一块区域并维护这个默认值。
- 执行函数未传值时,则让a2指向 函数维护的那个值的地址。
func("root")
- 执行函数传值时,则让a2指向新传入的值的地址。
func("admin",20)
在特定情况【默认参数的值是可变类型 list/dict/set】 & 【函数内部会修改这个值】下,参数的默认值 有坑 。
- 坑
# 在函数内存中会维护一块区域存储 [1,2,666,666,666] 100010001
def func(a1,a2=[1,2]):
a2.append(666)
print(a1,a2)
# a1=100
# a2 -> 100010001
func(100) # 100 [1,2,666]
# a1=200
# a2 -> 100010001
func(200) # 200 [1,2,666,666]
# a1=99
# a2 -> 1111111101
func(99,[77,88]) # 66 [177,88,666]
# a1=300
# a2 -> 100010001
func(300) # 300 [1,2,666,666,666]
- 大坑
# 在内部会维护一块区域存储 [1, 2, 10, 20,40 ] ,内存地址 1010101010
def func(a1, a2=[1, 2]):
a2.append(a1)
return a2
# a1=10
# a2 -> 1010101010
# v1 -> 1010101010
v1 = func(10)
print(v1) # [1, 2, 10]
# a1=20
# a2 -> 1010101010
# v2 -> 1010101010
v2 = func(20)
print(v2) # [1, 2, 10, 20 ]
# a1=30
# a2 -> 11111111111 [11, 22,30]
# v3 -> 11111111111
v3 = func(30, [11, 22])
print(v3) # [11, 22,30]
# a1=40
# a2 -> 1010101010
# v4 -> 1010101010
v4 = func(40)
print(v4) # [1, 2, 10, 20,40 ]
- 深坑
# 内存中创建空间存储 [1, 2, 10, 20, 40] 地址:1010101010
def func(a1, a2=[1, 2]):
a2.append(a1)
return a2
# a1=10
# a2 -> 1010101010
# v1 -> 1010101010
v1 = func(10)
# a1=20
# a2 -> 1010101010
# v2 -> 1010101010
v2 = func(20)
# a1=30
# a2 -> 11111111111 [11,22,30]
# v3 -> 11111111111
v3 = func(30, [11, 22])
# a1=40
# a2 -> 1010101010
# v4 -> 1010101010
v4 = func(40)
print(v1) # [1, 2, 10, 20, 40]
print(v2) # [1, 2, 10, 20, 40]
print(v3) # [11,22,30]
print(v4) # [1, 2, 10, 20, 40]
总结
Python函数的参数用于接收输入的值,并在函数体内进行处理。函数参数的作用有以下几点:
-
传递数据:函数参数允许我们将数据传递给函数,供函数内部使用。通过参数,我们可以在函数内部访问和操作外部的数据。
-
指定参数类型:参数可以用来指定函数所接受的数据类型。这使得函数更加严谨,在调用时会进行类型检查,确保传递的数据符合函数预期的类型。
-
设置默认值:通过给参数设置默认值,我们可以在函数调用时对参数进行省略。这样可以增加函数的灵活性,并简化代码。默认值使得函数可以适应多种场景,同时允许用户提供自定义的值。
-
可变参数:Python提供了可变参数的机制,允许函数接受不定数量的参数。可变参数可以接受任意数量的参数,将其封装为一个集合,在函数内部以列表或元组的形式进行处理。这对于需要处理不定数量的数据的情况非常有用。
-
关键字参数:通过关键字参数,可以在函数调用时指定参数的名称。这样可以提高代码的可读性,并允许参数的顺序灵活变化。关键字参数还可以与默认值一起使用,实现参数的可选性。
Python函数参数的应用场景包括但不限于:
- 函数调用时传递不同的参数值,实现不同的函数行为。
- 函数接受用户输入,进行处理并返回结果。
- 函数接受多个参数,计算、处理或转换这些参数。
- 函数接受可变数量的参数,处理任意数量的数据。
- 函数接受关键字参数,以提高代码的可读性。
- 函数参数用于配置函数的行为,如设定默认值、控制算法等。
在函数内部访问和操作外部的数据。
-
指定参数类型:参数可以用来指定函数所接受的数据类型。这使得函数更加严谨,在调用时会进行类型检查,确保传递的数据符合函数预期的类型。
-
设置默认值:通过给参数设置默认值,我们可以在函数调用时对参数进行省略。这样可以增加函数的灵活性,并简化代码。默认值使得函数可以适应多种场景,同时允许用户提供自定义的值。
-
可变参数:Python提供了可变参数的机制,允许函数接受不定数量的参数。可变参数可以接受任意数量的参数,将其封装为一个集合,在函数内部以列表或元组的形式进行处理。这对于需要处理不定数量的数据的情况非常有用。
-
关键字参数:通过关键字参数,可以在函数调用时指定参数的名称。这样可以提高代码的可读性,并允许参数的顺序灵活变化。关键字参数还可以与默认值一起使用,实现参数的可选性。
Python函数参数的应用场景包括但不限于:
- 函数调用时传递不同的参数值,实现不同的函数行为。
- 函数接受用户输入,进行处理并返回结果。
- 函数接受多个参数,计算、处理或转换这些参数。
- 函数接受可变数量的参数,处理任意数量的数据。
- 函数接受关键字参数,以提高代码的可读性。
- 函数参数用于配置函数的行为,如设定默认值、控制算法等。
以上是Python函数参数的基本作用和应用场景的简要总结。不同的应用场景可能需要不同的参数类型和设置,具体的使用方式需要根据实际需求和函数设计进行选择。