文章目录
一、函数定义
内建函数:内建函数也叫内置函数,即系统已经定义好的函数,开发者可以直接调用。类似pow(2,4)
等等
1. 函数的定义
函数定义的一般格式如下:
def 函数名(参数列表):
函数体
例如:
def hello():
print("hello")
print("world!")
以上实例定义的hello()函数虽然不包含任何参数,但是函数名后的一对括号是不能省略的。在实际应用中,稍复杂的函数通常都会包含一个或多个参数。
以下代码定义了无任何操作的空函数nop()。
def nop():
pass
在Python代码中,pass语句通常可以用来作为占位符,表示什么操作都不执行。比如在项目起始阶段,如果还没想好函数具体实现时,可以先放置一个pass语句,让代码先成功运行起来。待项目框架搭建完毕后,在进行相应的具体实现。
通常情况下,在Python语言中定义一个具有特定功能的函数需要符合以下规则:
- 函数代码块以def关键字开头,后接函数标识符名称和形参列表;
- 任何传入的参数和自变量必须放在圆括号内;
- 函数的第一行语句可以选择性地使用文档字符串(即函数说明);
- 函数内容以冒号起始,并且严格统一缩进;
- 函数都有返回值,默认返回None。
2. 形参和实参
在编程语言中,函数定义时用的是形参,调用时用的是实参
- 形参(parameter),全称为"形式参数",不是实际存在的变量,又称虚拟变量。形参是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
- 实参(argument),全称为"实际参数",是在调用时传递给函数的参数。实参可以是常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。
- 形参和实参的功能是数据传送。
以计算面积的函数为例:
# 计算矩形面积的函数area()
def area(width, height):
return width * height
# 调用area函数
w = 4
h = 9
print("with=", w, "height=", h, "area=", area(w, h))
上述代码中,函数area()定义处的width和height就是形式参数,函数体外定义的变量w和h是实际参数。可以看到,把实参w和h传入函数体后,就把相应的值赋值给了形参width和height。形参width和height的作用域只限于area()函数体内,而实参w和h作用域则根据外部调用处的设置而定。
对于函数形参列表,默认情况下函数调用时的参数值与参数列表声明中定义的顺序是一致。Python语言也允许函数调用时参数顺序与声明时不一致,即显示指明关键字参数,并根据参数的指定进行赋值。
def func(x, y):
print('x+y=', x + y)
print('x*y=', x * y)
# 等效于func(x=3,y=2),也等效于func(3,2)
func(y=2, x=3)
上述代码中,函数func()定义时形式参数的顺序是func(x, y),但是调用时实际参数的顺序确是func(y = 1, x = 2)。这是因为Python语言中提供了一种关键字参数的机制,可以给开发者提供更大的灵活性。
3. 函数的返回值
函数的返回值是函数执行完成后,系统根据函数的具体定义返回给外部调用者的值。
在Python语言中,当函数运行到return语句时即执行完毕,同时将结果返回。因此,可以在函数内部通过条件判断和循环设置实现较复杂的逻辑,并返回预期的结果。如果没有return语句,函数体内所有语句执行完毕后默认返回None。
# 函数定义
def add(x, y):
print('x+y=', x + y)
return x + y
# 函数调用
result = add(y=1, x=2)
print(result)
上述代码中,定义的add()函数返回“x+y”的运算结果。可以看到,调用该函数后,把该函数的返回值赋值给了变量result ,最后输出了变量result 的值。
另外需要注意的是,在Python语言中,函数也可以有多个返回值,例如:
# 函数定义
def add(x, y):
print('x+y=', x + y)
print('x*y=', x * y)
return x + y, x * y
# 函数调用
a, b = add(y=1, x=2)
print(a, b)
上述代码中,定义的add()函数有连个返回值,分别是“x+y”和“x*y”。可以看到,调用该函数后,把该函数的返回值分别赋值给变量a,b,最后输出了变量a和变量b的值。
注意: 返回值和接收变量的对应关系,是按照顺序一一对应的
二、函数分类
1. 内置函数
Python语言中自带的函数叫做内建函数,这些内建函数对大部分常用操作进行有效封装,可以直接调用,为开发提供了极大便利。由于内建函数是Python语言内置的函数,因此不需要导入任何函数库即可直接调用
在Python语言中,还可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”,如下代码所示。
a = abs
print(a(-1))
这里需注意,abs没有小括号,因此加了小括号相当于调用函数了。
2. 自定义函数
在Python语言中,内建函数可以直接使用,第三方函数需要使用import命令导入相应的库才能使用。对于自定义函数,其定义和调用可以在同一个文件中,也可分离成不同的文件。
from test import hello
hello()
上述代码演示了函数的定义和调用不在一个文件的情形。首先,将hello()函数定义好并保存为test.py文件,然后使用Python语言的import指令“from test import hello”将该文件导入,可以调用hello()函数了。导入时需要注意test是文件名并且不含.py扩展名。
三、函数参数
1. 参数种类
函数参数分为可变类型和不可变类型,其调用结果是不同的。
- 可变类型:类似c++的引用传递,如列表、字典等。如果传递的参数是可变类型,则在函数内部对传入参数的修改会影响到外部变量。
- 不可变类型:类似c++的值传递,如整数、字符串、元组等。如果传递的参数是不可变类型,则在函数内部对传入参数的修改不会影响到外部变量。
想要查看一个变量是否可变,可以传入函数,该函数先打印形参id,然后在函数内修改形参(整型、字符串可以再赋值,列表可以append…),再次打印形参id,对比两个id的值,相同则是可变类型,不同则是不可变类型
2. 默认参数
编写函数时,可给每个形参指定默认值。在调用函数时,如果给形参提供了实参,Python语言将使用指定的实参值;否则,将使用形参的默认值。给形参指定默认值后,可在函数调用中省略相应的实参。
使用默认值可简化函数调用,还可清楚地指出函数的典型用法。
如下方式调用describe_student()函数会出现错误。
def describe_student(person_name, student_age):
"函数功能:显示学生的信息"
print("my name is ", person_name)
print(person_name + "is" + student_age + "years old")
describe_student('Jack')
提示错误信息:
Traceback (most recent call last):
File "D:/python_demo/demo_2.py", line 88, in <module>
describe_student('Jack')
TypeError: describe_student() missing 1 required positional argument: 'student_age'
上述代码中,提示的错误信息很明确,就是调用函数describe_student()时缺少了一个位置参数student_age。这个时候,默认参数就排上用场了
若大部分学生的年龄为18岁,开发者可以把第二个参数student_age的默认值设定为18,这样,当开发者调用describe_student(Jack)时,相当于调用describe_student(Jack,18) ,如下代码所示。
def describe_student(person_name, student_age='18'):
"函数功能:显示学生的信息"
print("my name is ", person_name)
print(person_name + "is" + student_age + "years old")
describe_student('Jack')
describe_student('Jack', '18')
结果:
my name is Jack
Jackis18years old
my name is Jack
Jackis18years old
对于年龄不是18岁的学生,就必须明确地传入student_age,如describe_student(‘Herbie’,19)。从上面的例子可以看出,默认参数可以简化函数的调用。
要注意的是,设置默认参数时,必选参数在前,默认参数在后,否则Python语言的解释器会报错。
默认参数很有用,但使用时要牢记一点,默认参数必须指向不可变对象,否则会出现错误,如下代码所示。
def test_add(a=[]):
a.append('END')
return a
print(test_add([1, 2, 3]))
print(test_add(['a', 'b', 'c']))
print(test_add())
print(test_add())
print(test_add())
结果:
[1, 2, 3, 'END']
['a', 'b', 'c', 'END']
['END']
['END', 'END']
['END', 'END', 'END']
从上述代码可以看出,默认参数是空列表[],但是函数test_add()似乎每次都“记住了”上次添加了’END’后的list。这是因为在Python语言中,函数在定义的时候,默认参数H的值就被计算出来了,即[]。因为默认参数H也是一个变量,它指向对象[]。每次调用该函数,如果改变了H的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
开发者也可以用None这个不可变对象来解决报错问题,如下代码所示。
def test_add(H=None):
if H is None:
H = []
H.append('END')
return H
print(test_add())
print(test_add())
结果:
['END']
['END']
3. 不定长参数
在Python语言中,可以在函数参数前面添加“*”号把该参数定义为不定长参数;可以看出,不定长参数的使用使得calc()函数定义和调用都变得简洁,实例如下所示:
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n
return sum
print(calc(1, 2, 3, 4))
print(calc())
num = [1, 2, 3]
print(calc(*num))# 此处传参也用的不定长参数
结果:
10
0
6
4. 关键字参数
关键字实参是传递参数时使用“名称–值”对的方式,在实参中将名称和值关联起来。
关键字实参让开发者无需考虑函数调用中的实参顺序,清楚地指出了函数调用中各个值的用途。
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra)
结果:
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。
以person()函数为例,我们希望检查是否有city和job参数:
def person(name, age, **kw):
if 'city' in kw:
# 有city参数
pass
if 'job' in kw:
# 有job参数
pass
print('name:', name, 'age:', age, 'other:', kw)
但是调用者仍可以传入不受限制的关键字参数
person(‘Jack’, 24, city=‘Beijing’, addr=‘Chaoyang’, zipcode=123456)
5. 命名关键字参数
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:
def person(name, age, *, city, job):
print(name, age, city, job)
和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。
调用方式如下:
person('Jack', 24, city='Beijing', job='Engineer')
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
def person(name, age, *args, city, job):
print(name, age, args, city, job)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
person('Jack', 24, 'Beijing', 'Engineer')
结果:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given
由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数。
命名关键字参数可以有缺省值,从而简化调用:
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
person('Jack', 24, job='Engineer')
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个作为特殊分隔符。如果缺少,Python解释器将无法识别位置参数和命名关键字参数
def person(name, age, city, job):
# 缺少 *,city和job被视为位置参数
pass
6. 参数组合
在Python语言中定义函数,开发者可以组合使用这些参数(必选参数、默认参数、可变参数、关键字参数和命名关键字参数)。注意参数定义是有顺序的。定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
比如要定义一个函数,包含上述若干种参数,如下代码所示。
def func(a, b, c=0, *args, **kw):
print('a=', a, 'b=', b, 'c=', c, 'args=', args, 'kw=', kw)
print(func(1, 2))
# 输出结果:a= 1 b= 2 c= 0 args= () kw= {}
print(func(1, 2, 3))
# 输出结果:a= 1 b= 2 c= 3 args= () kw= {}
print(func(1, 2, 3, 'a', 'b'))
# 输出结果:a= 1 b= 2 c= 3 args= ('a', 'b') kw= {}
print(func(1, 2, 3, 'a', 'b', x=4))
# 输出结果:a= 1 b= 2 c= 3 args= ('a', 'b') kw= {'x': 4}
args = (1, 2, 3, 4)
kw = {'x': 5}
print(func(*args, **kw))
# 输出结果:a= 1 b= 2 c= 3 args= (4,) kw= {'x': 5}
大概也许可能在定义函数的时候可变参数后面也可以定义必选参数,但是传参的时候必须以关键字参数传入
四、函数式编程
函数式编程中的“函数”不是指计算机中的函数,而是指数学中的函数,即自变量的映射。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数。
1. 高阶函数
接受函数为参数,或者把函数作为结果返回的函数称为高阶函数。
2. 匿名函数
所谓匿名函数,即不再使用def语句这样标准形式定义的函数。Python语言经常使用lambda来创建匿名函数。lambda 只是一个表达式,函数体比def定义的函数体要简捷。lambda函数的语法如下所示。
lambda [arg1[,arg2],....argn]]:expression
lambda函数拥有自己的名字空间,且不能访问自有参数列表之外或全局名字空间里的参数
对于复杂函数或者函数体体量大的函数,最好不要用lambda函数,会增加代码的阅读难度,使代码晦涩难懂。