一、函数
在编程的语境下,函数(function)指的是一个有命名的、执行某个计算的语句序列(sequence of statements)。 在定义一个函数的时候,你需要指定函数的名字和语句序列。 之后,你可以通过这个名字“调用(call)”该函数。
1.函数调用
>>> type(42)
<class 'int'>
函数的名字是 type。
括号中的表达式被称为这个函数的 实参(argument)。
函数执行的结果,就是实参的类型。
函数“接受(accept)”实参,然后“返回(return)”一个结果。 该结果也被称为返回值(return value)。
2.新建函数
目前为止,我们只使用了Python自带的函数, 但是创建新函数也是可能的。 一个函数定义(function definition)指定了新函数的名称 以及当函数被调用时执行的语句序列。
下面是一个示例:
def print_lyrics():
print("I'm a lumberjack, and I'm okay.")
print("I sleep all night and I work all day.")
def 是一个关键字,表明这是一个函数定义。函数的名字是 print_lyrics。
函数的命名规则与变量名相同:字母、数字以及下划线是合法的, 但是第一个字符不能是数字。不能使用关键字作为函数名,并应该避免 变量和函数同名。
函数名后面的圆括号是空的,表明该函数不接受任何实参。
Python 的函数具有非常灵活多样的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。从简到繁的参数形态如下:
1)位置参数 (positional argument)
有时也称必备参数,指的是必须按照正确的顺序将实际参数传到函数中。调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致,指定的实际参数的数量,必须和形式参数的数量一致(传多传少都不行)
实参和形参数量必须一致
2)默认参数 (default argument)
为参数设置默认值,即在定义函数时,直接给形式参数指定一个默认值。这样的话,即便调用函数时没有给拥有默认值的形参传递参数,该参数可以直接使用定义函数时设置的默认值。
#str1没有默认参数,str2有默认参数
def dis_str(str1,str2 = "http://c.biancheng.net/python/"):
print("str1:",str1)
print("str2:",str2)
dis_str("http://c.biancheng.net/shell/")
dis_str("http://c.biancheng.net/java/","http://c.biancheng.net/golang/")
运行结果为:
str1: http://c.biancheng.net/shell/
str2: http://c.biancheng.net/python/
str1: http://c.biancheng.net/java/
str2: http://c.biancheng.net/golang/
3)可变参数 (variable argument)
顾名思义,可变参数就是传入的参数个数是可变的,可以是 0, 1, 2 到任意个,是不定长的参数。
def functionname(arg1, arg2=v, *args):
"函数_文档字符串"
function_suite
return [expression]
*args - 可变参数,可以是从零个到任意个,自动组装成元组。
加了星号(*)的变量名会存放所有未命名的变量参数。
def printinfo(arg1, *args):
print(arg1)
for var in args:
print(var)
printinfo(10) # 10
printinfo(70, 60, 50)
# 70
# 60
# 50
4)关键字参数 (keyword argument)
def functionname(arg1, arg2=v, *args, **kw):
"函数_文档字符串"
function_suite
return [expression]
**kw - 关键字参数,可以是从零个到任意个,自动组装成字典。
5)命名关键字参数 (name keyword argument)
def functionname(arg1, arg2=v, *args, *, nkw, **kw):
"函数_文档字符串"
function_suite
return [expression]
*, nkw - 命名关键字参数,用户想要输入的关键字参数,定义方式是在nkw 前面加个分隔符 *。
如果要限制关键字参数的名字,就可以用「命名关键字参数」
使用命名关键字参数时,要特别注意不能缺少参数名。
def printinfo(arg1, *, nkw, **kwargs):
print(arg1)
print(nkw)
print(kwargs)
printinfo(70, nkw=10, a=1, b=2)
# 70
# 10
# {'a': 1, 'b': 2}
printinfo(70, 10, a=1, b=2)
# TypeError: printinfo() takes 1 positional argument but 2 were given
没有写参数名nwk,因此 10 被当成「位置参数」,而原函数只有 1 个位置函数,现在调用了 2 个,因此程序会报错。
6)参数组合
在 Python 中定义函数,可以用位置参数、默认参数、可变参数、命名关键字参数和关键字参数,这 5 种参数中的 4 个都可以一起使用,但是注意,参数定义的顺序必须是:
位置参数、默认参数、可变参数和关键字参数。
位置参数、默认参数、命名关键字参数和关键字参数。
要注意定义可变参数和关键字参数的语法:
*args 是可变参数,args 接收的是一个 tuple
**kw 是关键字参数,kw 接收的是一个 dict
加了星号(*)的变量名会存放所有未命名的变量参数。
命名关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。定义命名关键字参数不要忘了写分隔符 *,否则定义的是位置参数。
函数定义的第一行被称作函数头(header); 其余部分被称作函数体(body)。 函数头必须以冒号结尾,而函数体必须缩进。 按照惯例,缩进总是4个空格。 函数体能包含任意条语句。
打印语句中的字符串被括在双引号中。单引号和双引号的作用相同;大多数人使用单引号,上述代码中的情况除外,即单引号(同时也是撇号)出现在字符串中时。
所有引号(单引号和双引号)必须是“直引号(straight quotes)”,它们通常位于键盘上Enter键的旁边。像这句话中使用的‘弯引号(curly quotes)’,在Python语言中则是不合法的。
在交互模式下键入函数定义,每空一行解释器就会打印三个句点(…), 让你知道定义并没有结束。
>>> def print_lyrics():
... print("I'm a lumberjack, and I'm okay.")
... print("I sleep all night and I work all day.")
...
为了结束函数定义,你必须输入一个空行。
定义一个函数会创建一个 函数对象(function object),其类型是 function:
>>> print(print_lyrics)
<function print_lyrics at 0xb7e99e9c>
>>> type(print_lyrics)
<class 'function'>
调用新函数的语法,和调用内建函数的语法相同:
>>> print_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
2.定义和使用
def print_lyrics():
print("I'm a lumberjack, and I'm okay.")
print("I sleep all night and I work all day.")
def repeat_lyrics():
print_lyrics()
print_lyrics()
repeat_lyrics()
该程序包含两个函数定义:print_lyrics和repeat_lyrics。
函数定义和其它语句一样,都会被执行,但是其作用是创建函数对象。 函数内部的语句在函数被调用之前,是不会执行的,而且函数定义不会产生任何输出。
在运行函数之前,必须先创建这个函数。换句话说,函数定义必须在其第一次被调用之前执行。
3.执行流程
语句执行的顺序, 这也被称作执行流程(flow of execution)。
执行流程总是从程序的第一条语句开始,自顶向下,每次执行一条语句。
,函数不被调用的话,函数内部的语句是不会执行的。
函数调用像是在执行流程上绕了一个弯路。 执行流程没有进入下一条语句,而是跳入了函数体,开始执行那里的语句,然后再回到它离开的位置。
每次一个函数执行完成时, 程序会回到调用它的那个函数原来执行的位置。当到达程序的结尾时,程序才会终止。
4.形参和实参
在函数内部,实参被赋给称作形参(parameters)的变量。
一个接受一个实参的函数:
def print_twice(bruce):
print(bruce)
print(bruce)
这个函数将实参赋给名为 bruce 的形参。当函数被调用的时候,它会打印形参(无论它是什么)的值两次。
该函数对任意能被打印的值都有效。
>>> print_twice('Spam')
Spam
Spam
>>> print_twice(42)
42
42
>>> print_twice(math.pi)
3.14159265359
3.14159265359
组合规则不仅适用于内建函数,而且也适用于开发者自定义的函数(programmer-defined functions),因此我们可以使用任意类型的表达式作为print_twice的实参:
>>> print_twice('Spam '*4)
Spam Spam Spam Spam
Spam Spam Spam Spam
>>> print_twice(math.cos(math.pi))
-1.0
-1.0
在函数被调用之前,实参会先进行计算,因此在这些例子中, 表达式’Spam '*4和 math.cos(math.pi) 都只被计算了一次。
你也可以用变量作为实参:
>> michael = 'Eric, the half a bee.'
>>> print_twice(michael)
Eric, the half a bee.
Eric, the half a bee.
传递的实参名(michael)与形参的名字(bruce)没有任何关系。 这个值在传入函数之前叫什么都没有关系;只要传入了print_twice函数,我们将所有人都称为 bruce 。
变量和形参都是局部的
当你在函数里面创建变量时,这个变量是局部的(local), 也就是说它只在函数内部存在。例如:
def cat_twice(part1, part2):
cat = part1 + part2
print_twice(cat)
该函数接受两个实参,拼接(concatenates)它们并打印结果两次。 下面是使用该函数的一个示例:
>>> line1 = 'Bing tiddle '
>>> line2 = 'tiddle bang.'
>>> cat_twice(line1, line2)
Bing tiddle tiddle bang.
Bing tiddle tiddle bang.
当cat_twice结束时,变量 cat 被销毁了。 如果我们试图打印它,我们将获得一个异常:
>>> print(cat)
NameError: name 'cat' is not defined
形参也都是局部的。例如,在print_twice函数的外部并没有 bruce 这个变量。
5.为什么写函数?
为什么值得将一个程序分解成多个函数。 原因包括以下几点:
创建一个新的函数可以让你给一组语句命名, 这可以让你的程序更容易阅读和调试。
通过消除重复的代码,函数精简了程序。
将一个长程序分解为多个函数,可以让你一次调试一部分,然后再将它们组合为一个可行的整体。
设计良好的函数经常对多个程序都有帮助。一旦你写出并调试好一个函数,你就可以重复使用它。
二、Lambda 表达式
1.匿名函数的定义
在 Python 里有两类函数:
第一类:用 def 关键词定义的正规函数
第二类:用 lambda 关键词定义的匿名函数
lambda 表达式,又称匿名函数,常用来表示内部仅包含 1 行表达式的函数。如果一个函数的函数体仅有 1 行表达式,则该函数就可以用 lambda 表达式来代替。
python 使用 lambda 关键词来创建匿名函数,而非def关键词,它没有函数名,其语法结构如下:
lambda argument_list: expression
lambda - 定义匿名函数的关键词。
argument_list - 函数参数,它们可以是位置参数、默认参数、关键字参数,和正规函数里的参数类型一样。
:- 冒号,在函数参数和表达式中间要加个冒号。
expression - 只是一个表达式,输入函数参数,输出一些值。
注意:
expression 中没有 return 语句,因为 lambda 不需要它来返回,表达式本身结果就是返回值。
匿名函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
def sqr(x):
return x ** 2
print(sqr)
# <function sqr at 0x000000BABD3A4400>
y = [sqr(x) for x in range(10)]
print(y)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
lbd_sqr = lambda x: x ** 2
print(lbd_sqr)
# <function <lambda> at 0x000000BABB6AC1E0>
y = [lbd_sqr(x) for x in range(10)]
print(y)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
sumary = lambda arg1, arg2: arg1 + arg2
print(sumary(10, 20)) # 30
func = lambda *args: sum(args)
print(func(1, 2, 3, 4, 5)) # 15
2.匿名函数的作用
lambda 表达式,其就是简单函数(函数体仅是单行的表达式)的简写版本。相比函数,lamba 表达式具有以下 2 个优势:
对于单行函数,使用 lambda 表达式可以省去定义函数的过程,让代码更加简洁;
对于不需要多次复用的函数,使用 lambda 表达式可以在用完之后立即释放,提高程序执行的性能。
三、练习题
1.怎么给函数编写⽂档?
函数的说明文档,本质就是一段字符串,只不过作为说明文档,字符串的放置位置是有讲究的,函数的说明文档通常位于函数内部、所有代码的最前面。
#定义一个比较字符串大小的函数
def str_max(str1,str2):
'''
比较 2 个字符串的大小
'''
str = str1 if str1 > str2 else str2
return str
help(str_max)
#print(str_max.__doc__)
程序执行结果为:
Help on function str_max in module __main__:
str_max(str1, str2)
比较 2 个字符串的大小
2.怎么给函数参数和返回值注解?
3.闭包中,怎么对数字、字符串、元组等不可变元素更新。
4.分别根据每一行的首元素和尾元素大小对二维列表 a = [[6, 5], [3, 7], [2, 8]] 排序。(利用lambda表达式)
5.利用python解决汉诺塔问题?
6.有a、b、c三根柱子,在a柱子上从下往上按照大小顺序摞着64片圆盘,把圆盘从下面开始按大小顺序重新摆放在c柱子上,尝试7.用函数来模拟解决的过程。(提示:将问题简化为已经成功地将a柱上面的63个盘子移到了b柱)