Python学习之路-02函数的参数

在学习廖雪峰的Python3语法的时候,看到函数的参数这一小节,有一点小感受,加上知识点也挺多的,便打算写一篇博客记录一下。

函数定义

def functionName(parameterList):
    pass # 函数体

解释

  • def:定义函数的关键词
  • functionName:函数名
  • parameterList:参数列表

示例

def power(x):
    return x * x

多参函数

在参数列表定义多个参数,调用函数时按顺序依次输入参数值。

示例

def power(x, n):
    return x ** n
print(power(2, 3))

运行结果

8

默认参数

引言

当我们调用函数时,对某些参数经常使用的同一个值:比如当我们调用函数‘求一个数的幂’时,很多情况下只是想求这个数的平方根,每次都传2比较很麻烦,这个时候就可以用到默认参数了。

示例

def power(x,  n = 2):
    return x ** n
print(power(3))
print(power(3,3))

运行结果

9
81

注意事项:默认参数必须定义在必选参数后(理由类似C#参数数组只能在最后)

问题来了,当我在一个函数中定义了多个默认参数,在调用的时候只有一部分我不想使用默认值,该怎么办?
别急,Python给出了解决方案:当不按顺序提供部分默认参数时,在参数前加上参数名

def student(name, age = 18, city = 'BeiJin'):
    return 'My name is %s, I am %s years old, I am from %s.' % (name, age, city)
print(student('A'))
print(student('B', hometown = 'ShangHai'))
print(student('C', age = 20))

运行结果

My name is A, I am 18 years old, I am from BeiJin.
My name is B, I am 18 years old, I am from ShangHai.
My name is C, I am 20 years old, I am from BeiJin.

思维扩展

能不能通过在参数前加上参数名,然后不按顺序提供参数?
让我们拿先前定义的power来试试
print(power(n = 2, 3))
编译器甩了我们一个报错
SyntaxError: positional argument follows keyword argument
x也标明参数名试试
print(power(n = 2, x = 3))
成功得到结果9

得到结论:在每个参数前加参数名可以不按顺序提供参数  

emmm,感觉也没什么用。

默认参数的一个坑

在廖雪峰的教程里有这么一段:

def add_end(L=[]):
    L.append('END')
    return L

我刚刚看到这一段的时候,是很感兴趣的[1],因为如果你在用C#这样写:

public void Test<T>(List<T> l = new List<T>())
{
}

编译器会甩你一个报错:“l”的默认参数值必须是编译时常量
报错界面
出现这个差异的原因是C#是一门编译型语言,而Python是一门解释型语言。
在这里简单解释一下编译型语言和解释型语言。

  • 编译型语言:在运行代码前,首先需要经过一个编译的过程,将源代码编译生成机器语言的文件,计算机运行的是这个可执行文件。
  • 解释型语言: 代码在运行过程中被专门的解释器逐句翻译,即使是同样的代码,每次运行都需要翻译一次。

说远了,话题拉回来,让我们来说一说默认参数的这个坑。
运行add_end(),好像并未有什么问题。
但是当这个方法被再次调用的时候,问题来了。
我们的期望值是:['END']
我们得到的实际值是:['END', 'END']

为什么会出现两个"END"?

廖雪峰在教程中给出了解释,在这里我也在解释一遍:
Python有一个说法:万物皆对象。函数也是一个对象,可以赋值给变量,可以当参数传进函数里。所以函数在定义时,默认参数L的值就被算出来了:L指向一个List:[ ]
当我们改变这个List的值,比如函数add_end()中的L.append('END')时,L还是指向原来那个List,但是这个List已经被修改过了,我们调用append()函数在其中加入了值“END”,所以当我们在二次调用函数时,是向['END']里添加值“END”。
本来想灵魂画师的,最后还是决定老老实实用绘图工具
图解原因
定义默认参数要牢记一点:默认参数必须指向不变对象!
修正上面的示例,这里再次引用廖雪峰的代码

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

在C#中,我们也是这么做的

public void Test<T>(List<T> l = null)
{
    if (l == null) l = new List<T>();
}

感觉这个坑我并不容易踩到啊,我本来的编程习惯就是这样写的。(手动幽灵)

可变参数

引言

当我们要传入多个参数时,比如求任意个数的合:sum = a + b + c + ……
因为参数数量不确定,所以我们只能定义参数为一个list或者tuple:

def summation(nums):
    sum = 0
    for num in nums:
        sum += num
    return sum
print(summation((1, 2, 3)))
print(summation([1, 2, 3]))

运行结果

6
6

但这样很不方便,我们在每次传参之前还要定义list或者tuple。
如同上面提到的C#中的参数数组,Python中也有类似的语法:可变参数。可以解决这个问题。

示例

def summation(*nums):
    sum = 0
    for num in nums:
        sum += num
    return sum
print(summation(1, 2, 3))

运行结果

6

也可以这样传参

nums = [1, 2, 3]
print(summation(*nums))

或者

nums = (1, 2, 3)
print(summation(*nums))

那么当我们定义可变参数的时候,传进来的参数是存入一个list呢,还是一个tuple?
可以把上面的代码稍微改动一下

def output(*string):
    print(string)
nums_tuple = (1, 2, 3)
nums_list = [1, 2, 3]
output(1, 2, 3)
output(*nums_tuple)
output(*nums_list)

运行结果

(1, 2, 3)
(1, 2, 3)
(1, 2, 3)

不管我们是直接传参,还是传入list或者tuple,输出的都是一个tuple,至于不是list的原因我认为是因为tuple是不可更改的吧,安全性更高。

对于调用函数时的这个*,廖雪峰给出的解释是:*nums表示把nums这个list的所有元素作为可变参数传进去。
最开始我是觉得没什么,但是后面看到他在讲参数组合时通过在一个tuple前加"*"将tuple里面的部分值赋值给位置参数,剩余部分赋值给可变参数,我目前有点没搞懂这是怎么一回事[2],望大佬指教。

关键字参数

引言

可变参数允许用户在调用函数时传入0个或任意数量个参数然后根据函数定义的参数列表生成一个tuple。但在某些场合,我们使用这些参数时,需要一个对应提示字段。

比如,我们想输出一个角色的信息。对于一个角色而言,必定会有的属性有:名称,等级,评级,可能有的属性有:武器,装备。对于一定会有的名称,等级和评级,可以直接定义为位置变量,但是对于可有可无的武器和装备,该怎么在调用函数的时候输入呢。

根据已学的这点语法,我能想到的,只有以下几种:
一. 全部定义为普通的位置参数,如果没有值就传空,在函数里一次判空处理

def role(name, level, rank, weapon, stigmata_upper, stigmata_middle, stigmata_lower):
    print("%s资料\t%s阶%s级" % (name, rank, level))
    if weapon is not None:
        print("武器\t%s" % weapon)
    if stigmata_upper is not None:
        print("上位圣痕\t%s" % stigmata_upper)
    if stigmata_middle is not None:
        print("中位圣痕\t%s" % stigmata_middle)
    if stigmata_lower is not None:
        print("下位圣痕\t%s" % stigmata_lower)
    print()
role('神恩颂歌', 80, 'S', None, None, None, None)
role('白骑士·月光', 80, 'SS', '能量跃迁者', None, None, None)
role('异度黑核侵蚀', 80, 'SSS', '火刀·真田', '薛定谔(上)', '普朗克(中)', '杏·玛尔(下)')

运行结果

神恩颂歌资料	S阶80级

白骑士·月光资料	SS阶80级
武器	能量跃迁者

异度黑核侵蚀资料	SSS阶80级
武器	火刀·真田
上位圣痕	薛定谔(上)
中位圣痕	普朗克(中)
下位圣痕	杏·玛尔(下)

但是,大量的重复代码,写死的参数以及对应的提示字段,既不方便后期维护,也不方便函数调用。

二. 传入一个dict,利用dict的属性:一个键值对为一个元素,元素的数量可变,解决了参数数量和对应提示字段写死的问题。
让我们修改上面的函数

def role(name, level, rank, other):
    print("%s资料\t%s阶%s级" % (name, rank, level))
    for key in other:
        print("%s\t%s" % (key, other[key]))
    print()
role('神恩颂歌', 80, 'S', {})
role('白骑士·月光', 80, 'SS', {'武器': '能量跃迁者'})
role('异度黑核侵蚀', 80, 'SSS', {'武器': '火刀·真田', '上位圣痕': '薛定谔(上)', '中位圣痕': '普朗克(中)', '下位圣痕': '杏·玛尔(下)'})

运行结果并没有发生改变,我们成功的解决了参数数量和提示字段被写死的问题。
但是即使这样还是有点麻烦,我们需要再传参时定义一个dict。那么有没有这个一个特性能让我们省事呢?
当然有啦,好消息!好消息!
嘛,其实Python已经为我们准备好了这个特性:关键字参数。

示例[3]

def role(name, level, rank, **other):
    print("%s资料\t%s阶%s级" % (name, rank, level))
    for key in other:
        print("%s\t%s" % (key, other[key]))
    print()
role('神恩颂歌', 80, 'S')
role('白骑士·月光', 80, 'SS', 武器 = '能量跃迁者')
role('异度黑核侵蚀', 80, 'SSS', 武器 = '火刀·真田', 上位圣痕 = '薛定谔(上)', 中位圣痕 = '普朗克(中)', 下位圣痕 = '杏·玛尔(下)')

在定义函数时在参数名前加上'**',在调用函数时,会将所有传入的参数(一定要标明参数名,不然key值会缺失)组装成一个dict。
当已经定义好一个dict时也可以通过类似可变参数的方法传值:

kw = {'武器': '火刀·真田', '上位圣痕': '薛定谔(上)', '中位圣痕': '普朗克(中)', '下位圣痕': '杏·玛尔(下)'}
role('异度黑核侵蚀', 80, 'SSS', **kw)

注意事项:函数中的other的值和kw的值最开始是一致的,但other的值仅仅是复制了一份kw的值,二者并不指向同一个dict,对other的改动不会影响到kw。

命名关键字参数

引言

关键字参数固然好用,但是实际使用中可能传入的参数的Key是有穷的,但我们在调用函数时可能会因为种种原因传入了一个本不该传入的Key-Value键值对。
想要限制关键字参数的名字,可以尝试使用命名关键字参数。

示例

def example(a, b, *, c, d):
    print(a, b, c, d)
example(1, 2, c = 3, d = 4)

运行结果

1 2 3 4

命名关键字参数需要在参数列表中添加字符'*'做为分隔符,凡是声明在分隔符后的参数都会被视为命名关键字参数。

但可变参数的关键字符也是''呀,二种语法不能一起使用吗?事实上是可以共存的,函数会将可变参数的''即视为可变参数的关键字符,也会视为命名关键字参数的分隔符,在这个可变参数后声明的所有参数都会被视为命名关键字参数。

同时,命名关键字参数和关键字参数一样,在调用函数传值时一定要标明参数名,否则运行会报错。

def example(a, b, *e, c, d):
    print(a, b, e, c, d)
example(1, 2, 3, 4, c = 5, d = 6)

运行结果

1 2 (3, 4) 5 6

命名关键字参数也可以设置默认参数

def example(a, b, *e, c = 5, d):
    print(a, b, e, c, d)
example(1, 2, 3, 4, d = 6)
example(1, 2, 3, 4, 5, c = 6, d = 7)

运行结果

1 2 (3, 4) 5 6
1 2 (3, 4, 5) 6 7

组合使用

对于Python的函数,可以同时使用所有类型的参数,但要注意先后顺序:
位置参数->默认参数->可变参数->关键字参数和命名关键字参数

灵活使用可以做到很多效果,但同时也要注意代码可读性,不能盲目追求代码精简。

对于任意Python函数,都可以这样定义参数列表:

def functionName(*args, **kw):
    pass

但是这样的可读性很差,并不推荐使用。

因为技术还不够,想不出什么太好的示例代码,在这里给出廖雪峰对于参数组合的示例:

def f1(a, b, c = 0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c = 0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
f1(1, 2)
f1(1, 2, c = 3)
f1(1, 2, 3, 'a', 'b')
f1(1, 2, 3, 'a', 'b', x = 99)
f2(1, 2, d = 99, ext = None)
args1 = (1, 2, 3, 4)
kw1 = {'d': 99, 'x': '#'}
f1(*args1, **kw1)
args2 = (1, 2, 3)
kw2 = {'d': 88, 'x': '#'}
f2(*args2, **kw2)

运行结果

a = 1 b = 2 c = 0 args = () kw = {}
a = 1 b = 2 c = 3 args = () kw = {}
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

  1. 以前从来没有写过解释型语言。 ↩︎

  2. 如果说是按顺序填空赋值,我可以理解。但是我有点搞不懂“所有元素作为可变参数”这句话:可变参数成功赋值给位置参数。 ↩︎

  3. 在写到一半才意识到这个例子可能没找好,但又不想改了……最开始担心不能在调用函数的时候使用中文参数名,但是在Pycharm上测试的结果是没有问题。就是不知道Python有没有什么规约会限制,比如只能用英文。我觉得把关键字参数生成的dict的key来当提示语句并不是不可以,因为最开始创造这门语言的人用的应该是英文,但是在国内实际开发环境中应该是用中文来做提示字段。使用关键字参数的可行性我现在也说不准。 ↩︎

posted @ 2018-09-25 20:35  丿唐宋乄元明清  阅读(74)  评论(0)    收藏  举报