python编程中的函数_11.Python编程:函数

当同一个功能、同一套算法等在多个地方重复使用,就要考虑把其抽成一个函数,在需要的地方调用即可。这样可以提高代码的复用性,也可便于维护代码。其实前面在学习循环等知识点时,已经接触到了函数。本文将详细学习Python3中的函数这一块内容,具体包括:函数的定义、调用、参数的分类、作用域等。

函数的定义规格及格式及调用

python3中定义一个函数需要遵循以下规则:

函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。

任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。

函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。

函数内容以冒号起始,并且缩进。

return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。

Python 定义函数使用 def 关键字,一般格式如下:

def 函数名(参数列表):

函数体

实例:输入一个一元二次方程的系数,如:ax^2 + bx + c中的a,b,c,画出一个一元二次的方程的图像

# 输入一个一元二次方程的系数,如:ax^2 + bx + c中的a,b,c,画出一个一元二次的方程的图像

import math

import matplotlib.pyplot as plt

import numpy as np

# 定义函数1,用来接收一个一元二次方程的系数

def the_power_function(a1=1, b=0.0, c=0.0):

# 调整系数符号

b0 = fix_symbol(b)

c0 = fix_symbol(c)

# 调整bx

bx = ''

if b0 != '':

bx = b0 + 'x'

# 组装表达式

funcExpress = str(a1) + 'x' + r"^2" + bx + c0

print("一元二次函数是:" + funcExpress)

startPoint = -( b/2/a1 + 5)

endPoint = 5 - b/2/a1

u = np.arange(startPoint, endPoint, 0.01)

y1 = [[math.pow((a + b/2/a1), 2) + (c - b*b/4/a1)] for a in u]

plt.plot(u, y1, linewidth = 1, color="#FF0000", label= funcExpress)

plt.legend(loc = "lower right")

plt.grid(True)

plt.show()

# 定义函数2:一个修正系数符号的函数

def fix_symbol(c):

if c < 0:

return str(c)

elif c > 0:

return "+" + str(c)

else:

return ""

# 调用函数

the_power_function(1, 4, -3.8)

运行结果:

一元二次函数是:1x^2+4x-3.8

绘制的函数图像是:

一元二次函数:1x^2+4x-3.8

解析:

上面实例中定义了2个函数:

函数1.接收一个一元二次方程的系数并绘制函数图像的函数,形参列表是方程的系数,并提供了默认参数。当用户什么也不传,或者某一参数不传时,就会使用提供的默认参数。

函数2:用来修正方程系数的函数

2.函数的调用:在需要调用函数地方,直接写上已定义好的函数名,并按照函数签名(函数的名、形参列表)传入需要的参数即可。

3.对于函数1调用时,如果参数不传,就会使用默认参数了。关于函数参数,接下来会有详细的介绍。

4.关于画图,后面会有专门章节学习。

参数的分类

定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了。对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解。

Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。

1.普通参数

普通参数是在调用函数时,要严格按照参数的类型、个数传入的,否则会报TypeError异常。

2.默认参数

当某个参数没有传入实参时,会使用函数定义时的参数的值,即默认参数。上面一元二次函数例子中,如果二次项系数a1、一次项系数b、常数项c都不传入,则按照def the_power_function(a1=1, b=0.0, c=0.0):函数定义时的a1=1, b = 0.0, c = 0.0。得到一元二次函数: 1x^2。

# 上接

# 不传入参数

the_power_function()

运行结果:一元二次函数是:1x^2

一元二次函数:1x^2的图像

从上面的例子可以看出,默认参数可以简化函数的调用。设置默认参数时,有几点要注意:

一是普通参数在前,默认参数在后,否则Python的解释器会报错(思考一下为什么默认参数不能放在普通参数前面);

二是如何设置默认参数。

当函数有多个参数时,把调用函数时必须传入的参数放前面,其他参数可以使用默认值的写在后面。这种可以使用默认值的参数就可以作为默认参数。

使用默认参数有什么好处?最大的好处是能降低调用函数的难度。

也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。比如调用the_power_function(b=4, a1=3, c=9.9),意思是,b、a1、c参数用传进去的值,其他默认参数继续使用默认值

the_power_function(b=4, a1=3, c=9.9)

运行结果:

一元二次函数是:3x^2+4x+9.9

一元二次函数是:3x^2+4x+9.9

注意:

默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下:

先定义一个函数,传入一个list,添加一个END再返回:

# 默认参数的坑

# 自定义一个list的拼接函数,参数是一个可变的、默认长度为0的list

def the_end(my_list=[]):

my_list.append("The End")

return my_list

list1 = ["a", "b", "c"]

# 传入实参list1

the_end(list1)

print(list1)

运行结果:

['a', 'b', 'c', 'The End']

一切正常,正如所期望的结果一样。接着传入实参list2,并打印list2:

list2 = ["1", "2", "3"]

# 传入实参list2

the_end(list2)

print(list2)

运行结果:

['1', '2', '3', 'The End']

此时,运行结果仍与预期一样。再次尝试:不传参数,使用默认的参数[]:

# 不传参数,使用默认的参数[]

print(the_end())

运行结果:

['The End']

到目前为止,无论传入新的list,还是使用默认的参数值,运行结果都如预期。但是,倘若对同一个list调用两次,具体操作如下:

# 自定义一个list的拼接函数,参数是一个可变的、默认长度为0的list

def the_end(my_list=[]):

my_list.append("The End")

return my_list

# 测试连续调用两次结尾函数:

print(the_end())

print(the_end())

运行结果:

[ 'The End']

['The End', 'The End']

思考:这是为什么呢?

原因解释如下:

Python函数在定义的时候,默认参数my_list的值就被计算出来了,即[],因为默认参数my_list也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

定义默认参数要牢记一点:默认参数必须指向不变对象!

优化如下:

# 默认参数的坑

# 自定义一个list的拼接函数,参数是一个可变的、默认长度为0的list

def the_end(my_list = None):

if my_list is None:

my_list = []

my_list.append("The End")

return my_list

# 测试连续调用两次结尾函数:

print(the_end())

print(the_end())

运行结果:

['The End']

['The End']

这样修改后,多次调用也没关系了。

为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。

补充:可更改(mutable)与不可更改(immutable)对象

在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。

不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。

可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。

python 函数的参数传递:

不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。

可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响

python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。

3.可变参数

在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,当然还可以是0个。在其它高级编程语言,如Java,C等都支持该语法,这在一定程度上大大提高了函数的灵活性。

我们以数学题为例子,给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……。

要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:

# 定义一个求平方的和的函数

def get_sum(nums):

total = 0

for i in nums:

total = total + i * i

print(total)

return total

但是调用的时候,需要先组装出一个list或tuple:

# 但是调用的时候,需要先组装出一个list或tuple:

nums1 = [1, 2, 3, 4]

# 调用求平方的和的函数

get_sum(nums1)

运行结果:

30

如果利用可变参数,调用函数的方式可以简化成这样:

get_sum(1, 2, 3, 4)

为了支持这种方便快捷的调用函数,我们把函数的参数改为可变参数:

# 定义一个求平方和的函数(可变参数形式)

def get_sum_mutable_params(*nums):

total = 0

for i in nums:

total = total + i * i

print(total)

return total

定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:

# 调用函数

get_sum_mutable_params(1, 2, 3, 4)

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

运行结果:

30

140

如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:

nums1 = [1, 2, 3, 4]

# 调用函数

get_sum_mutable_params(nums1[0], nums1[1], nums1[2], nums1[3])

运行结果:30

这种写法当然是可行的,问题是太繁琐,所以Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:

nums1 = [1, 2, 3, 4]

# 使用*将一个list或tuple中的元素,传入给可变参数

get_sum_mutable_params(*nums1)

运行结果:30

*nums1 表示把nums1这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。

提示:可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。

4.关键字参数

上面提到: 可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

比如,大家在某网站注册账号时,除了必填信息(如:注册账号、登录密码)外,还有一些是可选填项(如:爱好,地区等)。

# 关键字参数

def enrollment(account, pwd, **kws):

print('account:', account, ' pwd:', pwd, ' other:', kws)

enrollment("15639067890@163.com", r'******')

enrollment("15639067890@163.com", r'******', city="北京")

运行结果:

account: 15639067890@163.com pwd: ****** other: {}

account: 15639067890@163.com pwd: ****** other: {'city': '北京'}

说明:函数enrollment()除了必选参数account和pwd外,还接受关键字参数kws。在调用该函数时,可以只传入必选参数。也可以传入任意个数的关键字参数:

# 关键字参数

def enrollment(account, pwd, **kws):

print('account:', account, ' pwd:', pwd, ' other:', kws)

enrollment("15639067890@163.com", r'******')

enrollment("15639067890@163.com", r'******', city="北京")

enrollment("15639067891@163.com", r'***', city="广州", sex='male')

enrollment("15639067892@163.com", r'*****', city="上海", name='Wang DaChui')

运行结果:

# 只传入必传参数

account: 15639067890@163.com pwd: ****** other: {}

# 既有必传参数,又有关键词参数

account: 15639067890@163.com pwd: ****** other: {'city': '北京'}

account: 15639067891@163.com pwd: *** other: {'city': '广州', 'sex': 'male'}

account: 15639067892@163.com pwd: ***** other: {'city': '上海', 'name': 'Wang DaChui'}

关键字参数有什么用?它可以扩展函数的功能。比如,在enrollment()函数里,我们保证能接收到account和pwd这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了账号和密码是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。

和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:

# 关键字参数

def enrollment(account, pwd, **kws):

print('account:', account, ' pwd:', pwd, ' other:', kws)

# 一个字段

other = {'city' : '深圳', 'job' : '程序员'}

enrollment("15639067890@163.com", r'******', city=other['city'], job=other['job'])

运行结果:

account: 15639067890@163.com pwd: ****** other: {'city': '深圳', 'job': '程序员'}

当然,上面复杂的调用可以用简化的写法:

# 关键字参数

def enrollment(account, pwd, **kws):

print('account:', account, ' pwd:', pwd, ' other:', kws)

other = {'city' : '深圳', 'job' : '程序员'}

# 常规调用形式:

enrollment("15639067890@163.com", r'******', city=other['city'], job=other['job'])

# 简化调用形式

enrollment("15639067893@163.com", r'******', **other)

运行结果:

account: 15639067890@163.com pwd: ****** other: {'job': '程序员', 'city': '深圳'}

account: 15639067893@163.com pwd: ****** other: {'job': '程序员', 'city': '深圳'}

说明:

**other表示把other这个dict的所有key-value用关键字参数传入到函数的**kws参数,kws将获得一个dict,注意kws获得的dict是other的一份拷贝,对kws的改动不会影响到函数外的other。

命名关键字参数

对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。但是调用者仍可以传入不受限制的关键字参数。

# 关键字参数

def enrollment(account, pwd, **kws):

if 'sex' in kws:

# 有sex

pass

if 'city' in kws:

# 有city

pass

print('account:', account, ' pwd:', pwd, ' other:', kws)

但是调用者仍可以传入不受限制的关键字参数:

enrollment("15639067891@163.com", r'***', city="广州", sex='male')

enrollment("15639067892@163.com", r'*****', city="上海", name='Wang DaChui')

运行结果:

account: 15639067891@163.com pwd: *** other: {'city': '广州', 'sex': 'male'}

account: 15639067892@163.com pwd: ***** other: {'city': '上海', 'name': 'Wang DaChui'}

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和sex作为关键字参数。这种方式定义的函数如下。

# 命名关键字参数

def enrollment(account, pwd, *, city, sex):

print('account:', account, ' pwd:', pwd, ' city:', city, 'sex:', sex)

# 调用函数

enrollment("15639067891@163.com", r'***', city="广州", sex='male')

运行结果:

account: 15639067891@163.com pwd: *** city: 广州 sex: male

和关键字参数kw不同,命名关键字参数需要一个特殊分隔符,后面的参数会被视为命名关键字参数。在调用函数时,关键词参数不能多不能少,要严格按照定义时的参数名。

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错。由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数。

# 命名关键字参数

def enrollment(account, pwd, *, city, sex):

print('account:', account, ' pwd:', pwd, ' city:', city, 'sex:', sex)

# 少传入命名关键词参数

enrollment("15639067891@163.com", r'***', "广州")

运行结果:

Traceback (most recent call last):

File "F:/python_projects/09argument.py", line 103, in

enrollment("15639067891@163.com", r'***', "广州")

TypeError: enrollment() takes 2 positional arguments but 3 were given

意思是:enrollment()需要 2 个位置参数,但是有3个被传入了。

此时,如果少传入了命名关键词参数,则报错:TypeError: enrollment() missing 1 required keyword-only argument:xxx。例如:

# 命名关键字参数

def enrollment(account, pwd, *, city, sex):

print('account:', account, ' pwd:', pwd, ' city:', city, 'sex:', sex)

# 少传入命名关键词参数

enrollment("15639067891@163.com", r'***', city="广州")

运行结果:

Traceback (most recent call last):

File "F:/python_projects/09argument.py", line 102, in

enrollment("15639067891@163.com", r'***', city="广州")

TypeError: enrollment() missing 1 required keyword-only argument: 'sex'

意思是:缺少了必须的关键词参数:sex

如果多传入了命名关键词参数,则报错:TypeError: enrollment() got an unexpected keyword argument :xxx。例如:

# 命名关键字参数

def enrollment(account, pwd, *, city, sex):

print('account:', account, ' pwd:', pwd, ' city:', city, 'sex:', sex)

# 多传入关键词参数

enrollment("15639067892@163.com", r'*****', city="上海", sex='male', name='Wang DaChui')

运行结果:

Traceback (most recent call last):

File "F:/python_projects/09argument.py", line 105, in

enrollment("15639067892@163.com", r'*****', city="上海", sex='male', name='Wang DaChui')

TypeError: enrollment() got an unexpected keyword argument 'name'

意思是:调用enrollment()时,不期望有的(多余的)关键词参数:name

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

# 命名关键字参数

def enrollment(account, pwd, *other, city, sex):

print('account:', account, ' pwd:', pwd, 'other :', other, 'city:', city, 'sex:', sex)

other = [2, 4, 5]

# 调用形式

enrollment("15639067893@163.com", r'******', *other, city='北京', sex='male')

运行结果:

account: 15639067893@163.com pwd: ****** other : (2, 4, 5) city: 北京 sex: male

这里的*other,已经是一个可变参数了,所以后面跟着的命名关键字参数就不再需要一个特殊分隔符了。使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:

命名关键字参数可以有缺省值,从而简化调用:

# 命名关键字参数

def enrollment(account, pwd, *other, city='北京', sex):

print('account:', account, ' pwd:', pwd, 'other :', other, 'city:', city, 'sex:', sex)

other = [2, 4, 5]

# 调用形式

enrollment("15639067893@163.com", r'******', *other, city='北京', sex='male')

# 使用默认关键词参数的值

enrollment("15639067894@163.com", r'****', *other)

运行结果:

account: 15639067893@163.com pwd: ****** other : (2, 4, 5) city: 北京 sex: male

account: 15639067894@163.com pwd: **** other : (2, 4, 5) city: 北京 sex: male

如果,命名关键词没有给初始值,则调用时必须传入。否则,会报错缺少必须的关键词参数。

# 命名关键字参数

def enrollment(account, pwd, *other, city='北京', sex):

print('account:', account, ' pwd:', pwd, 'other :', other, 'city:', city, 'sex:', sex)

other = [2, 4, 5]

# 调用形式

enrollment("15639067893@163.com", r'******', *other, city='北京', sex='male')

# 使用默认关键词参数的值

enrollment("15639067894@163.com", r'****', *other)

运行结果:

Traceback (most recent call last):

account: 15639067893@163.com pwd: ****** other : (2, 4, 5) city: 北京 sex: male

File "F:/python_projects/09argument.py", line 125, in

enrollment("15639067894@163.com", r'****', *other)

TypeError: enrollment() missing 1 required keyword-only argument: 'sex'

参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

小结

Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。

1.默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!

2.要注意定义可变参数和关键字参数的语法:

*args是可变参数,args接收的是一个tuple;

**kw是关键字参数,kw接收的是一个dict。

3.以及调用函数时如何传入可变参数和关键字参数的语法:

可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3));

关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})。

使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

4.命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。

5.定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值