06-02 函数的参数

一、形参与实参介绍

1、引入

  • 函数的参数分为2大类,一个是函数定义阶段,另一个是函数调用阶段。

2、什么是形参?

"""
定义阶段的参数。
在定义函数阶段,定义的形式参数,称之为形参。形参相当与变量名
"""

3、什么是实参?

"""
调用阶段的参数。
在调用函数阶段传入的值称之为实际参数,简称实参。实参相当于变量值。
"""

4、什么时候形参会申请内存空间?

"""
函数调用阶段,传参的时候,形参就会申请内存空间。这和变量的定义一样,我们所知只有一个变量名,并没有申请内存空间,当有变量值与之绑定的时候,这个时候就会发生申请内存空间把值存入。
"""

5、调用函数时2者的关系

"""
1、绑定关系:在调用阶段,实参的值(变量值)会绑定给形参(变量名)。
2、使用范围:且含有这种绑定关系的参数只能在函数体内使用
3、生效时间:实参与形参的绑定关系在函数调用时生效,函数调用结束后解除绑定关系
"""
# 实参是传入的值,但值可以是一下形式(小结:可以这么理解只要实参传入的结果最终结果是一个值就可以)
# 形式一:
func(1, 2)

# 形式二:
a = 1
b = 2
func(a, b)

# 形式三:
func(int('1'), 2)
func(func1(1, 2), func2(2, 3), 333)

二、形参于实参具体使用

1、位置参数:位置形参、位置实参

"""
按照从左到右的顺序依次定义的参数称之为位置参数
"""
# 位置参数分成2种:
# 1、位置形参:大前提:函数定义阶段。按照从左到右的顺序直接定义的“变量名”。特点:必须被传值,多个一个不行,少一个也不行。
def func(x, y):
    print(x, y)


# 多一个不行
# func(1, 2, 3)  # 报错:类型错误(TypeError: func() takes 2 positional arguments but 3 were given)

# 少一个不行
# func(1, )  # 报错:类型错误(TypeError: func() missing 1 required positional argument: 'y')


# 2、位置实参:大前提:函数调用阶段。按照从左到右的顺序依次传入值。特点:按照顺序与形参一一对应传值。(真正按照顺序是在传参的时候,且实参必须遵形参的规则)
func(1, 2)  # 1给了x, 2给了y。返回结果:1 2
func(2, 1)  # 2给了x, 1给了y。返回结果:2 1

2、关键字参数:关键字实参

"""
关键字实参:大前提:在函数调用阶段。按照key=value的形式传值。特点:指名道姓的给某个形参传值,可以完全不参照顺序。
"""
# 关键字实参:大前提:在函数调用阶段。按照key=value的形式传值。特点:指名道姓的给某个形参传值,可以完全不参照顺序。(实参必须遵形参的规则)
def func(x, y):
    print(x, y)

func(y=2, x=1)
func(x=1, y=2)

# 位置实参与关键字实参传值混合使用:
# 1、位置实参必须放在关键字实参之前
func(1, y=2)    # 1给了x, 2给了y。返回结果:1 2
# func(y=2, 1)  # 报错,语法错误:SyntaxxError

# 2、不能给同一个形参重复传值
# func(1, y=2, x=3) # 1给了x, 2给了y,3又给了x重复传值。报错(TypeError)

3、默认参数:指的是默认形参

"""
默认形参:大前提:定义函数阶段。就已经被赋值的形参。特点:在定义阶段就已经被赋值,意味着在调用的阶段可以不为其传值(不传也行,传也行)。
"""
# 一、介绍
def func(x, y=3):
    print(x, y)
func(x=1)  # 1 3
func(x=1, y=444)  # y有默认值,但是你就就想传,就以你传的为准。 返回结果:1 444

# 二、应用场景:用在经常不变,大多数情况都是一样得情况。比如用户注册,用户没有设置密码,那么我们给一个默认密码。这个时侯就用到了默认参数。
def register(name, age, password='123'):
    print(name, age, password)

register('三炮', 18)  # 三炮 18 123
register('四炮', 18)  # 四炮 18 123
register('五炮', 18)  # 五炮 18 123
register('六炮', 18, password='111')  # 六炮 18 111   

# 三、位置形参与默认形参的综合运用:  
# 1、顺序问题:位置形参必须在默认形参左边
# def func(y=2, x):  # 报错,语法错误(SyntaxError)
#     pass

# 2、默认参数的值是在什么阶段被赋予的?赋予时间:默认参数的值是在函数定义阶段就被赋予的。 赋予结果:被赋予的是值得内存地址
# 强调内容:👇
"""
内存地址是什么?
	内存地址是对值得一种引用。
内存地址的传递 == 引用传递:
	有的人说python所有的传递,参数的传递也好、变量的传递也好,所有的传递都是内存地址的传递,在python中所有得传递传的起始不是值,都是内存地址的传递。
	又有的人说内存地址就是基于值得引用,所以有的人会说,python的传递都是“引用传递”。他说指的应用传递,其实都是内存地址的传递。
"""
# 示例一:
m = 2
def func(x, y=m):  # y=m这里把m得内存地址给了y,也就是把值2得内存地址给了y。
    print(x, y)  # 1 2
m = 3333333333  # 定义func阶段已经过去了,m自己在那里玩
func(1)

# 示例二:
m = [111, ]
def func(x, y=m):  # y=m这里y拿到的是m对应的内存地址,而我们在外面定义的m是一个容器类型列表,所以y拿到的就是一个容器的内存地址,这个内存地址与上面定义的m绑定内存地址都是一致的,同一个内存地址。当我们下面用m.append操作时,把值追加到了m中,所以y中所关联的的值也会改变。
    print(x, y)  #  [111, 333]
m.append(333)
func(1)
print(m)  # [111, 333]

# 3、默认值使用类型:默认值可以被指为任意数据类型,但是并不推荐使用可变类型(补充:定义函数默认值形参最理想的结果就是预知它能拿到的结果,也可以说默认值的输出必须是靠谱的。)
# 需求:传入的值生成一个列表,且每次调用的结果都要拿到上一次操作结果继续。
def func(x, y, z, li=[]):
    li.append(x)
    li.append(y)
    li.append(z)
    print(li)
func(1, 2, 3)  # [1, 2, 3]
func(4, 5, [6, 7])  # [1, 2, 3, 4, 5, [6, 7]]

# 需求:传入的值生成一个列表,且每次调用的结果都是必须保证独立的列表。如果用户传入列表,那么就按照用户的为准。
def func(x, y, z, li=None):
    if li is None:
        li = []
    li.append(x)
    li.append(y)
    li.append(z)
    print(li)
func(1, 2, 3)  # [1, 2, 3]
func(4, 5, [6, 7])  # [4, 5, [6, 7]]  

4、可变长度的参数(*与**的用法)

  • 可变成长度的位置参数:值得是可变长度的位置形参
"""
引入可变长度的意义所在:
	可变长度指的是在调用函数时,传入的值(也就是实参)的个数不固定。注意:实参是用来为形参传值的,所以争对溢出的实参必须要有对应的形参来接收。(必须要有对应的可以接收位置的形参
"""

"""
*号使用方法:
	*形参名:用来接收溢出的位置实参。溢出的位置实参会被*号保存成元组的形式,赋值给紧跟其后的形参名。
*号功能介绍:
	*后跟的可以是任意名字,但是为了规范性*后面跟的得变量名要使用args(提示:以后看到*后面的args,就直接看成元组)
"""
#  一、*号用在形参中:可以接收位置实参溢出的值,保证位置实参传值的可扩展性。
# 1、基本使用介绍
def func(x, y, *z):
    print(x, y, z)
func(1, 2, 3, 4, 5, 6)  # 3456是多出来的实参,返回结果:1 2 (3, 4, 5, 6)

# 2、应用场景:写一个求和功能,无论用户传几个数,都把这些数字加起来。
# 不规范
# def my_sum(*nums_tuple):  
#     res = 0
#     for num in nums_tuple:
#         res += num
#	  return res

# 规范
def my_sum(*args):
    res = 0
    for num in args:
        res += num
    return res

res = my_sum(1, 2, 3, 4 , 5, 6, 7, 8, 9)  
print(res)  # 45

# 二、*号用在实参中:*会把后面的多个值打散。打散成位置实参(本质原理可以理解为for循环取值)
# 拓展:*可以打散字典,但是只是把字典的key打散,当作位置实参传值。
def func(x, y, z):
    print(x, y, z)
func(*[1, 2, 3])  # 1 2 3 

# 三、形参与实参中都带*:(提示:只要是可以被for循环的,都可以是用*打散)
def func(x, y, *args):
    print(x, y , args)
func(1, 2, [3, 4, 5])   # 1, 2, [(3, 4, 5),]。分析:把1给了x,把2给了y。把[3, 4, 5]给了*号,*号在把[3, 4, 5]按照顺序存入元组中,交个args。args就是:([3, 4, 5], )

func(1, 2, *[3, 4, 5])  # 1, 2, (3, 4, 5)。分析:把1给了x,把2给了y,*号把[3, 4, 5]打散成按照顺序放置的位置实参,把3,4,5给了*号,*号将其按照顺序存入元组中。args就是:(3,4,5)

func(*'hello')  # h e ('l', 'l', 'o') 分析:*号把'hello'打散成按照顺序放置的位置实参 h,e,l,l,o, 接着把‘h’给了x,把‘e’给了y。剩下的‘l,l,o’给了*号。*号将其按照顺序存入元组中。args就是:(l,l,o)
  • 可变长度的关键字参数:值得是可变长度的关键字形参
"""
**使用方法:
	**形参名:用来接收溢出的关键字实参。**会将溢出的关键字实参保存成字典格式,然后赋值给紧跟其后的形参名。
**功能介绍:	
	**后跟的可以跟任意名字,但是为了规范性**后面跟的得变量名要使用kwargs(提示:以后看到**后面的后面跟的得变量名要使用kwargs,就直接看成字典)
"""
# 一、**用在形参中:可以接收关键字实参溢出得值,保证关键字传值得可扩展性
def func(x, y, **kwargs):
    print(x, y, kwargs)
func(1, y=2, a=3, b=4, c=5)  # 注意:y=2并不是溢出的。abc才是,返回结果:1 2 {'a': 3, 'b': 4, 'c': 5}


# 二、**用在实参中(注意:**后面跟的只能是字典),实参中带**,**后的值会被打散成关键字实参
def func(x, y, z):
    print(x, y, z)
func(*{'x': 1, 'y': 2, 'z': 3})  # 注意:一个*号可以打散字典,但是打散以后打散的是字典的key,因为我们之前说过*号类似于for循环,而for循环默认循环的就是字典的key,所以实际传参可以看成fuc('x', 'y', 'z')。打印结果:x y z

func(**{'x': 1, 'y': 2, 'z': 3})  # **将字典打散,打散以后的结果func('x'=1, 'y'=2, 'z'=3)。打印结果:1 2 3
    
# 错误:对定义的位置形参传值,多一个不行少一个不行
# func(**{'x': 1, 'y': 2})  # 打散以后func(x=1, y=2),传值不对等

# 错误:打散后形成的关键字参数不匹配
func(**{'x': 1, 'a': 2, 'y': 3})  # 打散以后func(x=1, a=2, y=3),没有a这个形参可以指定。

# 三、实参中与形参中都带**
def func(x, y, **kwargs):
    print(x, y, kwargs)
func(y=222, x=111, a=333, b=444)  # a、b作为溢出的关键字参数,传给**,由**接收以后转化成字典格式{'a': 333, 'b': 444}交给kwargs.返回结果:111 222 {'a': 333, 'b': 444}
func(**{'y': 222, 'x': 111, 'a': 333, 'b': 444})  # 由**打散变成func(y=222, x=111, a=333, b=444),再传给**,由**接收以后转化成字典格式{'a': 333, 'b': 444}交给kwargs.返回结果:111 222 {'a': 333, 'b': 444}
        

# 四、形参中**与*混用:*args必须在**kwargs之前
'''
作用:
	1、主要用来增加扩展性 
	2、形参精简,不冗余
'''
def func(x, *args, **kwargs): 
	print(x)  # 1
	print(args)   # (2, 3, 4)       
	print(kwargs)    # {'y': 6, 'z': 7} 
func(1, 2, 3, 4, y=6, z=7)  # 位置实参1,把值15的内存地址传递给x,然后位置实参溢出的交给args:(2, 3, 4)。关键字实参没有匹配的,接着把关键字实参溢出的交给kwargs: {'y': 6, 'z': 7}  

5、装饰器铺垫:了解在调用外层函数给内层函数传值的意义

# 1、刨析出在调用外层函数给内层函数传值的限制及扩展性差的问题        
def index(x, y, z):       
	print('index====>', x, y, z)
def wrapper(a, b, c):  # wrapper的参数受制于index
	index(a, b, c)  # index应该怎么变,也就是说wrapper参数就得怎么变
wrapper(1, 2, 3)  # 这里为wrapper传的参数,是给index用的


# 2、需求:我想调用的函数的格式,原封不动的当作实参转嫁给里面的函数,我们就用下面的这种格式:(提示:wrapper中传参取决于index中的形式参数的限)
def index(x, y, z):       
	print('index====>', x, y, z)
def wrapper(a, b, c):  # wrapper的参数受制于index
	index(a, b, c)  # index应该怎么变,也就是说wrapper参数就得怎么变
wrapper(1, 2, 3)  # 这里为wrapper传的参数,是给index用的  

# 3、魔鬼练习
def index(x, y, z):
    print('=====>', x, y, z)


def wrapper(*args, **kwargs):
    index(*args, **kwargs)


# 打印1,2,3
wrapper(1, 2, 3)  # 第一步:wrapper(*(1, 2, 3), **{}) 第二步:index(1, 2, 3)
wrapper(1, 2, z=3)  # 第一步:wrapper(*(1, 2), **{'z': 3}) 第二步:index(1, 2, z=3)
wrapper(1, z=3, y=2)  # 第一步:wrapper(*(1, ), **{'z': 3, 'y': 2}) 第二步:index(1, y=2, z=3)
wrapper(y=2, z=3, x=1)  # 第一步:wrapper(*(), **{'y': 2, 'z': 3, 'x': 1}) 第二步:index(y=2, z=3, x=1)
# wrapper([1, 2, 3])  # 报错:第一步:wrapper(*([1, 2, 3]), **{}) 第二步:index([1, 2, 3])
wrapper(*[1, 2, 3])  # 第一步:wrapper(1, 2, 3) 第二步:wrapper(*(1, 2, 3), **{}) 第三步: index(1, 2, 3)
wrapper(*{1: None, 2: None, 3: None})  # 第一步:wrapper(1, 2, 3) 第二步:wrapper(*(1, 2, 3), **{}) 第三步: index(1, 2, 3)
wrapper(**{'x': 1, 'y': 2, 'z': 3})  # 第一步:wrapper(x=1, y=2, z=3) 第二步:wrapper(*(), **{'x': 1, 'y': 2, 'z': 3}) 第三步: index(x=1, y=2, z=3)
wrapper(**{'y': 2, 'z': 3, 'x': 1})  # 第一步:wrapper(y=2, z=3, x=1) 第二步:wrapper(*(), **{'y': 2, 'z': 3, 'x': 1}) 第三步: index(y=2, z=3, x=1)
wrapper(*[1], **{'y': 2, 'z': 3})  # 第一步:wrapper(1, y=2, z=3) 第二步:wrapper(*(1, ), **{'y': 2, 'z': 3}) 第三步: index(1, y=2, z=3)
wrapper(*[1], *{2: None, 3: None})  # 第一步:wrapper(1, y=2, z=3) 第二步:wrapper(*(1, ), **{'y': 2, 'z': 3}) 第三步: index(1, y=2, z=3)
wrapper('1', *{2: None}, z=3)  # 第一步:wrapper(1, 2, z=3) 第二步:wrapper(*(1, 2), **{'z': 3}) 第三步: index(1, y=2, z=3)
wrapper(*{1}, *{2: None}, z=3)  # 第一步:wrapper(1, 2, z=3) 第二步:wrapper(*(1, 2), **{'z': 3}) 第三步: index(1, 2, z=3)
wrapper(*(1,), *'23')   # 第一步:wrapper(1, 2, 3) 第二步:wrapper(*(1, 2, 3), **{}) 第三步: index(1, 2, 3)
"""
*和**什么时候使用?:
1、当一个函数中包裹着另一个函数,我们通过外面的函数名调用里面被包裹的函数。
	用在外层函数的参数中
	用在内层函数的调用中。
2、或者为了增加函数代码的可扩展性,函数参数冗余的问题,我们使用*和**的形参的形式。

规则总结:
1、定义函数时:看到*args,就把args看成实参传值溢出以后的元组。看到**kwargs,就把kwargs看成实参传值溢出以后的字典
2、调用函数时: 看到*,就把它后面的对象当作位置实参传参时的形式。。看到**,就把它后面的对象当作关键字实参传参时的形式。
3、如果需要调用外层函数控制给内层函数传值,使用*args **kwargs的形式的参数。
4、调用外层函数传的值,要遵守内层函数定义时的参数传参规则。

使用总结:只看被外层函数包裹的内层函数定义时参数的规则。调用外层函数传什么值,内层函数调用就接收什么值。
"""

6、命名关键字参数(了解)

# 1、介绍:在定义函数时,*后定义的形参,称之为命名关键字形参。特点:命名关键字实参必须按照key=value的形式为其传值。
def func(x, y, *, a, b):  # a,b都称之为命名关键字形参
    print(x, y)  # 1 2
    print(a, b)  # 111 222
func(1, 2, a=111, b=222)

# 2、区分命名关键字参数的默认值与默认形参:命令关键字后面的参数设置默认值,只是仅仅的设置默认值,没有位置形参与默认形参的顺序位置之分。默认形参则必须跟在位置形参之后。
def func(x, y=2, *, a=3, b):
    print(x, y, a, b)
func(1, 2, b=4)   # 1 2 3 4

# 3、只要在*号后面无论是单个的*,还是*arges。z都是命名关键字形参。
def func(x, *args, a=444, b):
    print(x, args, a, b)
func(1, 2, 3, b=555)   # 1 (2, 3) 444 555

7、组合使用(了解)

# 1、形参组合使用顺序:位置形参、默认形参、*args(可变长度的位置行形参)、命名关键字形参、**kwargs(可变长度的关键字形参)
def func(x, y=222, *args, z, **kwargs):
    print(x)  # 1
    print(y)  # 2,这里默认参数失去了意义
    print(args)  # (2, )
    print(z)  # 4
    print(kwargs)  # {'a': 5, 'b' 6}


func(1, 2, 3, z=4, a=5, b=6)  
    
# 实参组合使用顺序:位置实参、关键字实参
def func(x, y, z, a, b, c):
    print(x)  # 111
    print(y)  # 222
    print(z)  # 333
    print(a)  # 444
    print(b)  # 555
    print(c)  # 666
func(111, *[222, 333], a=444, **{'b': 555, 'c': 666})
func(111,  222, 333, a=444, c=666, b=555)
func(*[111, 222], 333, **{'b': 555, 'c': 666}, a=444)
posted @ 2020-03-18 21:03  给你加马桶唱疏通  阅读(136)  评论(0编辑  收藏  举报