python学习分享之函数参数详解-有趣的形参与实参

大家好,我是小眼睛优粥面,今天跟小伙伴聊天的时候说到python的函数,大家说python函数的参数形式简直太多了,有时候记起来慢,用起来又有点乱,什么关键字参数、序列实参、不定形参巴拉巴拉的一大堆。正好今天简单总结下python函数形参与实参的几种奇妙用法,欢迎大家交流分享(码字不易,希望大家标明出处),有不对的地方也请大家指正,万分感谢啦^ ^ 

0. 文章目录

目录

0. 文章目录

1. 函数的定义

2. 函数的实参

2.1. 位置实参

2.2. 关键字实参

2.3. 序列实参

2.4. 字典实参

3. 函数的形参,约束实参的传递方式

3.1. 位置形参

3.2. 默认形参

3.3. 不定长形参

3.3.1. 星号元组形参

3.3.2. 双星号字典形参

3.4. 命名关键字形参

3.5. 形参的位置关系

4. 函数参数的总结


1. 函数的定义

首先,说到函数的参数,我们有必要先回忆一下什么是函数?函数一段具有特定功能,与主程序相隔离,且易于复用的程序代码。(重点的理解标注的内容)。

同时,以至于函数是如何调用的、函数的形参、实参是什么,这些内容不一一详细赘述,就是长成以下这个样子。

# 函数定义,用于封装一个特定的功能,表示一个功能或者行为。
def func_name(形式参数):
    函数体

# 函数调用,函数是可以重复执行的语句块,可以重复调用。
func_name(实际参数)

2. 函数的实参

2.1. 位置实参

那么,我们就先从实际参数开始聊起吧,位置实参最为简单也最常见一种实参形式,所谓位置实参就是实参与形参的位置依次对应,就如张导电影的名字一样《一个也不能少》,同样,一个也不能多,就像这样:

def func_name(p1, p2, p3):
    print("p1:", p1)
    print("p2:", p2)
    print("p3:", p3)

# 函数调用:位置形参必须按照位置顺序传递参数
func_name(1, 2, 3)

'''
输出结果:
p1: 1
p2: 2
p3: 3
'''

2.2. 关键字实参

关键字实参:实参按照名称与形参进行对应,这个也很好理解,传参的时候加上“形参名”即可。什么意思呢?其实就是在告诉你函数的 “调用者” 给你 “制作者” 传递信息时不用再按照 “位置关系” 一一对应,可以按照名字(关键字)去对应啦,既然这样那他自然就可以改变传参的位置了。就像这样:

def func_name(p1, p2, p3):
    print("p1:", p1)
    print("p2:", p2)
    print("p3:", p3)

# 函数调用:关键字形参可以不按顺序传递
func_name(p2=1, p3=3, p1=2)

'''
输出结果:
p1: 2
p2: 1
p3: 3
'''

好吧,不知道你们看到这里有没有疑惑,它有啥用呢?我们平时写代码的时候难道就按照 “位置” 去传递参数不香吗?而且写得内容还少,何必罗里吧嗦的去写这么一大堆关键字呢?恩,你想的没错,这个不是他应用的重点,这块留个疑问,你继续看,我们到后面会讲到。

2.3. 序列实参

序列实参是用序列容器的思想去进行传参,python容器都有哪些是有序的呢?我们都知道有 “字符串、列表、元组”,Python中可以把实参用 * 号将序列容器拆解,将元素与形参依次对应。这块重点在拆分,即将实参按照位置进行拆分给函数。注意:序列实参传参的时候要遵循位置关系,还是那句话 “一个也不能多,一个也不能少”,其实他还是 “位置实参” 的一个扩展的高级应用,如下面所示:

def func_name(p1, p2, p3):
    print(p1, p2, p3, sep=",")

# 将实参用*将序列容器拆解后与形参的位置依次对应
str_data = "123"
func_name(*str_data)

list_data = [4, 5, 6]
func_name(*list_data)

tuple_data = (7, 8, 9)
func_name(*tuple_data)

'''
输出结果:
1,2,3
4,5,6
7,8,9
'''

2.4. 字典实参

上面说完了 “序列实参” ,那对应的势必会有 “散列实参” 的容器传参,python中字典就是无序的散列,那就是 “字典实参” ,Python中可以将实参用 ** 将字典拆解后与形参的名字进行对应。重点还是在拆分上。同样,字典实参需要根据键值对与名字相对应,类似于上面讲的 “关键字实参”,所以关键字实参有的特性它都有,如不按照顺序传递等等,直接上代码吧。

def func_name(p1, p2, p3):
    print("p1:", p1)
    print("p2:", p2)
    print("p3:", p3)

dict_data = {'p3':3, 'p1':1, 'p2':2}
func_name(**dict_data)

'''
输出结果:
p1: 1
p2: 2
p3: 3
'''

3. 函数的形参,约束实参的传递方式

3.1. 位置形参

位置形参这个上面基本上也讲过了,调用的时候实参必填,且需要按照名称顺序排列,这个我们天天用,不多讲了。

3.2. 默认形参

所谓默认形参,即在调用函数时,具有默认值的参数如果没有传入值,则会直接只用该形参的默认值。也很简单,与上面的 “位置形参” 相反,即参数值 “可选” ,直接看代码。

def func_name(p1=1, p2=2, p3=3):
    print("p1:", p1)
    print("p2:", p2)
    print("p3:", p3)

func_name()

'''
输出结果:
p1: 1
p2: 2
p3: 3
'''

这个语法比较好理解没什么可细说的。但是有一点,还记得上面 2.2章节讲到的 关键字实参 吗?是不是还留有一点 “它到底干什么用?” 的悬念?这块来了,关键字实参可以不按照固定顺序来传递,那么它最主要的目的就是为了实现“只传递其中之一或几个参数,而不是全部都传递”,说白了就是我要实现给某个特定的数据传参。所以这个时候就要配合上默认形参来使用了,就像下面这样:

def func_name(p1=1, p2=2, p3=3):
    print("p1:", p1)
    print("p2:", p2)
    print("p3:", p3)

# 位置实参适合全给,而关键字实参适合不全给,而是指定给某一个。
func_name(4, p3=5)

'''
输出结果:
p1: 4
p2: 2
p3: 5
'''

但注意一点:默认参数必须自右至左依次存在,如果一个参数有默认值,则其右侧所有参数都必须有默认参数。因为这样写,Python他根本不知道这个“2”你是填还是不填,即不能像下面这样写:

def func_name(p1=1, p2, p3=3):
    print("p1:", p1)
    print("p2:", p2)
    print("p3:", p3)

func_name(2)

'''
输出结果:
File "C:/text.py", line 1
  def func_name(p1=1, p2, p3=3):
                 ^
SyntaxError: non-default argument follows default argument
'''

3.3. 不定长形参

重点来了,这个比较重要,不定长形参分为两类:星号元组形参双星号字典形参,我们着重介绍一下这里。

3.3.1. 星号元组形参

所谓星号元组形参,即可以将多个位置实参合并为一个元组。重点在于合并,即这里参数可以传多个或者不传,最后都会被合并成一个元组给函数。注意这个星号和上面的序列实参的一个 * 没有任何关系,大家不要记混了。另外还有一点,在Python中大家约定俗成的星号元组形参用args去作为形参,即arguments(多个参数)的意思。

def func_name(*args):
    print("args:", args)

# 星号元组形参,实参数量可以不固定
func_name()
func_name(1, 2, 3)

'''
输出结果:
args: ()
args: (1, 2, 3)
'''

这里有几点非常关键的注意事项需要注意,(1)形参中只能有一个 “星号元组形参” 。(2)调用的时候必须用 “位置实参” ,不支持 “关键字实参” ,如下所示:

# 这样写python不知道用哪几个合并成哪个
def func_name1(*args, *p2):
    print("args:", args)
    print("p2:", p2)

func_name1(1, 2, 3)

'''
输出结果:
File "C:/test.py", line 1
    def func_name1(*args, *p2):
                          ^
SyntaxError: invalid syntax
'''
def func_name1(*args):
    print(args)

# 只支持”位置实参“
func_name1(p1=1, p2=2, p3=3)

'''
输出结果:
Traceback (most recent call last):
  File "C:/test.py", line 1, in 
    func_name1(p1=1, p2=2, p3=3)
TypeError: func_name1() got an unexpected keyword argument 'p1'
'''

好,说完了他的语法,我们举个 “栗子” 很好的说明他的具体应用。这是一个栗子:定义一个函数,计算多个数的累加和。

def multiply_plus(*args):
    sum = 1
    for num in args:
        sum += num
    return sum

print(multiply_plus(1, 3, 4, 5, 6))
print(multiply_plus(15, 13, 44, 51, 23, 53, 21, 1, 3, 21))

'''
输出结果:
20
246
'''

3.3.2. 双星号字典形参

双星号字典形参也属于不定长形参,即可以将多个关键字实参合并为一个字典。和星号元组实参不一样,这个用的是关键字实参,直接上代码吧。

def func_name(**kwargs):
    print("kwargs:", kwargs)

func_name()
func_name(p1=1, p2=2, p3=3)

'''
输出结果:
kwargs: {}
kwargs: {'p1': 1, 'p2': 2, 'p3': 3}
'''

学习了这两个形参模式,星号元组形参是位置实参合并,双星号字典形参是关键字实参合并,我们如果大胆的把他们组合在一起,那就能实现所有参数的形式无限的 “组合大招模式”,哈哈哈。就像下面这样。

def func_name(*args, **kwargs):
    print("args:", args)
    print("kwargs:", kwargs)
    print("=================")

func_name()
func_name(1, 2, 3, 4, 5, 6)
func_name(p1=1, p2=2, p3=3, p4=4, p5=5, p6=6)
func_name(1, 2, 3, p4=4, p5=5, p6=6)

'''
输出结果:
args: ()
kwargs: {}
=================
args: (1, 2, 3, 4, 5, 6)
kwargs: {}
=================
args: ()
kwargs: {'p1': 1, 'p2': 2, 'p3': 3, 'p4': 4, 'p5': 5, 'p6': 6}
=================
args: (1, 2, 3)
kwargs: {'p4': 4, 'p5': 5, 'p6': 6}
=================
'''

3.4. 命名关键字形参

这个东西我们平时在自己写代码的时候可能用的比较少,但如果大家经常会看源码函数的时候,这块经常会出现,也是比较重点的一个点,大家平时学到这里的时候往往会比较蒙,不太能很好的理解这个到底有什么用?所以,要讲起来可能会很长,我们放到所有的内容的最后来讲一下。

命名关键字形参往往会和其他形参相混淆,因为他可以和任何形参结合使用,只不过他出现在一个上面提到过的关键点之后,那就是 “星号元组形参” ,即出现在*args之后的位置形参,应该叫做:命名关键字形参,其是为了约束实参必须是关键字实参。如下所示:

# 其中,p1和p2是命名关键字形参,限制其实参必须为关键字实参
def func_name1(*args, p1, p2):
    print(args)
    print(p1)
    print(p2)

func_name1(1,2,3, p1=2, p2=3)

===============================
输出结果:
(1, 2, 3)
2
3

语法还有另外一种写法,args我们可以省略,注意这里省略之后就不能再给 * 号传参了。

def func_name1(*, p1, p2):
    print(args)
    print(p1)
    print(p2)

func_name1(p1=2, p2=3)

===============================
输出结果:
2
3

大家又要想了,这样写有什么意义呢?不用星号传参不就可以了吗?恩,确实,我开始也是这么想的,后来我学到了一位大神的讲解后明白了这一点。我们看这么一个例子,在日常开发中往往会遇到这样的情况,其中:p1是必须要提供的信息,p2是可选的信息,就像下面,如果按照第一种写法调用func_name(1, 2),其实p1和p2的主次是不清晰的。为了让你分清楚p1是主要参数,p2为次要参数是修饰p1用的,你需要的时候才用,且必须加上参数的关键字,所以python在中间加上一个 * 号,如果你中间不加 * 号的话,那么第一种调用就是支持的。

# (1)普通参数方法
def func_name01(p1, p2):
    print("p1:", p1)
    print("p2:", p2)

func_name01(1, 2)

===============================
输出结果:
p1: 1
p2: 2

# (2)关键字形参方法
def func_name02(p1, *, p2):
    print("p1:", p1)
    print("p2:", p2)
    print("args:", args)

func_name02(1, p2=2)

===============================
输出结果:
p1: 1
p2: 2

可能你还是不太明白,那我们看一个非常好的例子,一个我们python中最常用的函数:print的源码(python3)。

def print(self, *args, sep=' ', end='\n', file=None): # known special case of print
    """
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
    
    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.
    """
    pass

其中,*args表示print中可以包含多个打印参数,如:print(1,2,3,4,5)。而sep=' ', end='\n',就是上面说到的次要条件了,这样可读性也很高。想一下如果没有 “命名关键字形参” 这项技术,我们调用print代码时候会是如何?就像下面这样,python根本不知道你那个是sep,哪个是end了

# (1)有命名关键字形参方法调用print函数
print(1, 2, 3, 4, sep="+", end="...")

# (2)没有命名关键字形参方法调用print函数
print(1, 2, 3, 4, "+", "...")

好吧,想一下我们调用的print函数为什么必须要用命名关键字形参,我想你就会明白他的应用价值了。如果你还是不理解,哈哈,没关系,记住就行了。

3.5. 形参的位置关系

有一点还需要注意一下,那就是在多种形参同时出现的时候,记住一个书写顺讯问题,很关键哦。参数自左至右的顺序为:位置形参 --> 星号元组形参 --> 命名关键字形参 --> 双星号字典形参,我们还是看一个 “栗子”。

def func_name(p1, p2='', *args, p3=0, **kwargs):
    '''
    :param p1:位置形参(必填)
    :param p2:位置形参+默认形参(选填)
    :param args:星号元组形参(位置实参数量无限)
    :param p3:命名关键字形参+默认形参(按要求填且可选)
    :param kwargs:双星号字典形参(关键字实参数量无限)
    :return:
    '''
    print("p1:", p1)
    print("p2:", p2)
    print("args:", args)
    print("p3:", p3)
    print("kwargs:", kwargs)

# 组合形参函数调用
func_name(1, 2, 3, 4, 5, p3=6, a=1, b=2, c=3)

'''
输出结果:
p1: 1
p2: 2
args: (3, 4, 5)
p3: 6
kwargs: {'a': 1, 'b': 2, 'c': 3}
'''

4. 函数参数的总结

好了,我们现在把所有的函数参数都说完了。总结一下吧。

函数的参数分为两种:实际参数和形式参数,实际参数的核心目的是为了和形式参数进行对应,它包括:位置实参、关键字实参、序列实参、字典实参。形式参数的核心目的是为了约束,包括:位置形参、默认形参、不定长形参和命名关键字形参。总结一下如下表。

函数参数的总结
类型 名称 关键点 示例
实际参数 位置实参 顺序 函数名(数据1, 数据2)
关键字实参 名字 函数名(参数名1=数据1, 参数名2=数据2)
序列实参 函数名(*序列)
字典实参 函数名(**字典)
形式参数 位置形参 必填 def 函数名(参数名1,函数名2):
默认形参 可选 def 函数名(参数名1=数据1,函数名2=数据2):
不定长形参 星号元组形参 def 函数名(*args):
双星号字典形参 def 函数名(**kwargs):
命名关键字形参 必须是关键字实参 def 函数名(*args,参数名1,函数名2):
def 函数名(*,参数名1,函数名2):
形参顺序 位置形参 --> 星号元组形参 --> 命名关键字形参 --> 双星号字典形参

你可能感兴趣的:(Python,python)