Python-函数
文章目录
- Python-函数
- 什么是函数
- Python函数的定义
- Python函数的调用
- 形式参数和实际参数
- 位置参数
- 关键字参数
- 默认参数
- 可变参数
- 逆向参数传递
- 函数返回多个值
- 递归函数
- 偏函数
- 全局变量和局部变量
- Python函数内用同名全局变量
- 局部函数
- 闭包
- lambda表达式(匿名函数)
- 字符串代码执行
- 函数式编程(map()、filter()和reduce())
- 内置函数一览表
- 函数注解
什么是函数
Python 中函数的应用非常广泛,前面章节中我们已经接触过多个函数,比如 input() 、print()、range()、len() 函数等等,这些都是 Python 的内置函数,可以直接使用。
除了可以直接使用的内置函数外,Python 还支持自定义函数,即将一段有规律的、可重复使用的代码定义成函数,从而达到一次编写、多次调用的目的。
举个例子,前面学习了 len() 函数,通过它我们可以直接获得一个字符串的长度。我们不妨设想一下,如果没有 len() 函数,要想获取一个字符串的长度,该如何实现呢?请看下面的代码:
n=0
for c in "http://c.biancheng.net/python/":
n = n + 1
print(n)
要知道,获取一个字符串长度是常用的功能,一个程序中就可能用到很多次,如果每次都写这样一段重复的代码,不但费时费力、容易出错,而且交给别人时也很麻烦。
所以 Python 提供了一个功能,即允许我们将常用的代码以固定的格式封装(包装)成一个独立的模块,只要知道这个模块的名字就可以重复使用它,这个模块就叫做函数(Function)。
比如,在程序中定义了一段代码,这段代码用于实现一个特定的功能。问题来了,如果下次需要实现同样的功能,难道要把前面定义的代码复制一次?如果这样做实在太傻了,这意味着每次当程序需要实现该功能时,都要将前面定义的代码复制一次。正确的做法是,将实现特定功能的代码定义成一个函数,每次当程序需要实现该功能时,只要执行(调用)该函数即可。
其实,函数的本质就是一段有特定功能、可以重复使用的代码,这段代码已经被提前编写好了,并且为其起一个“好听”的名字。在后续编写程序过程中,如果需要同样的功能,直接通过起好的名字就可以调用这段代码。
下面演示了如何将我们自己实现的 len() 函数封装成一个函数:
#自定义 len() 函数
def my_len(str):
length = 0
for c in str:
length = length + 1
return length
#调用自定义的 my_len() 函数
length = my_len("http://c.biancheng.net/python/")
print(length)
#再次调用 my_len() 函数
length = my_len("http://c.biancheng.net/shell/")
print(length)
如果读者接触过其他编程语言中的函数,以上对于函数的描述,肯定不会陌生。但需要注意的一点是,和其他编程语言中函数相同的是,Python 函数支持接收多个( ≥0 )参数,不同之处在于,Python 函数还支持返回多个( ≥0 )值。
比如,上面程序中,我们自己封装的 my_len(str) 函数,在定义此函数时,我们为其设置了 1 个 str 参数,同时该函数经过内部处理,会返回给我们 1 个 length 值。
通过分析 my_len() 函数这个实例不难看出,函数的使用大致分为 2 步,分别是定义函数和调用函数。接下来一一进行详细的讲解。
Python函数的定义
定义函数,也就是创建一个函数,可以理解为创建一个具有某些用途的工具。定义函数需要用 def 关键字实现,具体的语法格式如下:
def 函数名(参数列表):
//实现特定功能的多行代码
[return [返回值]]
其中,用 [] 括起来的为可选择部分,即可以使用,也可以省略。
此格式中,各部分参数的含义如下:
- 函数名:其实就是一个符合 Python 语法的标识符,但不建议读者使用 a、b、c 这类简单的标识符作为函数名,函数名最好能够体现出该函数的功能(如上面的 my_len,即表示我们自定义的 len() 函数)。
- 形参列表:设置该函数可以接收多少个参数,多个参数之间用逗号( , )分隔。
- [return [返回值] ]:整体作为函数的可选参参数,用于设置该函数的返回值。也就是说,一个函数,可以用返回值,也可以没有返回值,是否需要根据实际情况而定。
注意: 在创建函数时,即使函数不需要参数,也必须保留一对空的“()”,否则 Python 解释器将提示“invaild syntax”错误。另外,如果想定义一个没有任何功能的空函数,可以使用 pass 语句作为占位符。
例如,下面定义了 2 个函数
#定义个空函数,没有实际意义
def pass_dis():
pass
#定义一个比较字符串大小的函数
def str_max(str1,str2):
str = str1 if str1 > str2 else str2
return str
另外值得一提的是,函数中的 return 语句可以直接返回一个表达式的值,例如修改上面的 str_max() 函数:
def str_max(str1,str2):
return str1 if str1 > str2 else str2 # str1 > str2 满足返回str1 否则返回str2
该函数的功能,和上面的 str_max() 函数是完全一样的,只是省略了创建 str 变量,因此函数代码更加简洁。
Python函数的调用
调用函数也就是执行函数。如果把创建的函数理解为一个具有某种用途的工具,那么调用函数就相当于使用该工具。函数调用的基本语法格式如:[返回值] = 函数名([形参值])
其中,函数名即指的是要调用的函数的名称;形参值指的是当初创建函数时要求传入的各个形参的值。如果该函数有返回值,我们可以通过一个变量来接收该值,当然也可以不接受。
需要注意的是,创建函数有多少个形参,那么调用时就需要传入多少个值,且顺序必须和创建函数时一致。即便该函数没有参数,函数名后的小括号也不能省略。
例如,我们可以调用上面创建的 str_max() 函数:
strmax = str_max(1,2)
print(strmax) # 2
对于上面程序中调用 str_max() 函数,由于当初定义该函数为其设置了 2 个参数,因此这里在调用该参数,就必须传入 2 个参数。同时,由于该函数内部还使用了 return 语句,因此我们可以使用 strmax 变量来接收该函数的返回值。
形式参数和实际参数
形式参数: 在定义函数时,函数名后面括号中的参数就是形式参数,例如:
#定义函数时,这里的函数参数 obj 就是形式参数
def demo(obj):
print(obj)
实际参数:在调用函数时,函数名后面括号中的参数称为实际参数,也就是函数的调用者给函数的参数。例如:
a = "C语言中文网"
#调用已经定义好的 demo 函数,此时传入的函数参数 a 就是实际参数
demo(a)
实参和形参的区别,就如同剧本选主角,剧本中的角色相当于形参,而演角色的演员就相当于实参。明白了什么是形参和实参后,再来想一个问题,那就是实参是如何传递给形参的呢?
Python 中,根据实际参数的类型不同,函数参数的传递方式可分为 2 种, 分别为值传递和引用(地址)传递
- 值传递:适用于实参类型为不可变类型(字符串、数字、元组);
- 引用(地址)传递:适用于实参类型为可变类型(列表,字典);
值传递和引用传递的区别是,函数参数进行值传递后,若形参的值发生改变,不会影响实参的值;而函数参数继续引用传递后,改变形参的值,实参的值也会一同改变。
位置参数,有时也称必备参数,指的是必须按照正确的顺序将实际参数传到函数中,换句话说,调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。
在调用函数,指定的实际参数的数量,必须和形式参数的数量一致(传多传少都不行),否则 Python 解释器会抛出 TypeError 异常,并提示缺少必要的位置参数。
在调用函数时,传入实际参数的位置必须和形式参数位置一一对应
关键字参数
目前为止,我们使用函数时所用的参数都是位置参数,即传入函数的实际参数必须与形式参数的数量和位置对应,而我们还有一种办法可以避免牢记参数位置的麻烦,令函数的调用和参数传递更加灵活方便。
关键字参数是指使用形式参数的名字来确定输入的参数值。通过此方式指定函数实参时,不再需要与形参的位置完全一致,只要将参数名写正确即可。因此,Python 函数的参数名应该具有更好的语义,这样程序可以立刻明确传入函数的每个参数的含义。例如,在下面的程序中就使用到了关键字参数的形式给函数传参:
def dis_str(str1,str2):
print("str1:",str1)
print("str2:",str2)
#位置参数
dis_str("python","python1")
#关键字参数
dis_str("python",str2="python1")
dis_str(str2="python",str1="python")2
可以看到,在调用有参函数时,既可以根据位置参数来调用,也可以使用关键字参数来调用。在使用关键字参数调用时,可以任意调换参数传参的位置。
当然还可以如上案例一样使用位置参数和关键字参数混合传参的方式。但需要注意,混合传参时关键字参数必须位于所有的位置参数之后。也就是说,如下代码是错误的:
# 位置参数必须放在关键字参数之前,下面代码错误
dis_str(str1="python1","python2") # Python 解释器会报如下错误: SyntaxError: positional argument follows keyword argument
默认参数
我们知道,在调用函数时如果不指定某个参数,Python 解释器会抛出异常。为了解决这个问题,Python 允许为参数设置默认值,即在定义函数时,直接给形式参数指定一个默认值。这样的话,即便调用函数时没有给拥有默认值的形参传递参数,该参数可以直接使用定义函数时设置的默认值。
Python 定义带有默认值参数的函数,其语法格式如下:
def 函数名(...,形参名,形参名=默认值):
代码块
注意,在使用此格式定义函数时,指定有默认值的形式参数必须在所有没默认值参数的最后,否则会产生语法错误。下面程序演示了如何定义和调用有默认参数的函数:
#str1没有默认参数,str2有默认参数
def dis_str(str1,str2 = "python"):
print("str1:",str1)
print("str2:",str2)
dis_str("python1")
dis_str("python2")
上面程序中,dis_str() 函数有 2 个参数,其中第 2 个设有默认参数。这意味着,在调用 dis_str() 函数时,我们可以仅传入 1 个参数,此时该参数会传给 str1 参数,而 str2 会使用默认的参数
当然在调用 dis_str() 函数时,也可以给所有的参数传值,这时即便 str2 有默认值,它也会优先使用传递给它的新值。同时,结合关键字参数,以下 3 种调用 dis_str() 函数的方式也是可以的:
dis_str(str1 = "shell")
dis_str("shell1",str2 = "shell2")
dis_str(str1 = "shell3",str2 = "shell4")
再次强调,当定义一个有默认值参数的函数时,有默认值的参数必须位于所有没默认值参数的后面。因此,下面例子中定义的函数是不正确的:
#语法错误
def dis_str(str1="http://c.biancheng.net/python/",str2,str3):
pass
显然,str1 设有默认值,而 str2 和 str3 没有默认值,因此 str1 必须位于 str2 和 str3 之后。
有读者可能会问,对于自己自定义的函数,可以轻易知道哪个参数有默认值,但如果使用 Python 提供的内置函数,又或者其它第三方提供的函数,怎么知道哪些参数有默认值呢?Pyhton 中,可以使用“函数名.__defaults__”查看函数的默认值参数的当前值,其返回值是一个元组。以本节中的 dis_str() 函数为例,在其基础上,执行如下代码:
print(dis_str.__defaults__)
可变参数
可变参数又称为不定长参数,即传入函数中的实际参数可以是任意多个, 在python中可变参数有两种:
- 形参前添加一个
*
默认是tuple类型 - 形参前添加一个
**
默认是dict类型
注意: 可变参数必须在所有参数后面
注意: *args,**kwargs
这两种可变参数在同一个函数里只能各使用一次
演示:
def par(A,*args):
print(A)# 1
print(args) # (2, 3, 4)
if __name__ == '__main__':
par(1, 2, 3, 4)
def par(A,**kwargs):
print(A)# 1
print(kwargs) # {'B': 1, 'C': 2}
if __name__ == '__main__':
par(1,B=1,C=2)
逆向参数传递
直接将列表、元组、字典作为函数参数,Python 会将其进行拆分,把其中存储的元素按照次序分给函数中的各个形参。在以逆向参数收集的方式向函数参数传值时,Pyhon 语法规定,当传入列表或元组时,其名称前要带一个 *
`号,当传入字典时,其名称前要带有 2 个 **
`号。
将列表按照顺序映射到参数上
def par(A,args):
print(A) # A
print(args) #B
if __name__ == '__main__':
data = ["A", "B"]
# data = ("A", "B")
par(*data)
使用字典方式给同名参数赋值
def par(A,B):
print(A)# 1
print(B)# 2
if __name__ == '__main__':
data = {"A": 1, "B": 2}
par(**data)
在Python中,我们可以通过多种方法,从一个函数中返回多个值。列如: 返回一个列表,返回一个字典,返回一个对象等,都能实现返回多个值,但是在python中还提供了简单的方法使用return直接可以返回多个值,会将多个值字典组织为元组的方式返回,接收的时候也需要使用多个变量进行接收, 演示如下:
# x 除以 y 的余数与商的函数
def F1(x, y):
a = x % y
b = (x - a) / y
return a,b # 也可以写作 return (a, b)
if __name__ == '__main__':
c, d = F1(9, 4) # 也可以写作 (c , d) = F1 ( 9, 4 )
print(c,"----", d) # 1 ---- 2.0
递归函数
从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事!故事是什么呢?『从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事!故事是什么呢?』……这也许是最经典(口耳相传)的童谣了,充分展现了自然语言的魅力及其无限可能性,可以永远以这种递归的方式继续下去。。。
俄文艺理论家车尔尼雪夫斯基曾说过:艺术来源于生活,却又高于生活!
生活如此,编程世界亦如此 - 没有生活原形或者现象,何来程序创作的源头和灵感?正因此,Python 中出现了这样一种函数 - 递归函数。
大多数情况下,我们见到的是一个函数调用其他函数。除此之外,**函数还可以自我调用,这种类型的函数被称为递归函数。 **递归的一个视觉效果呈现 - 捧着画框的蒙娜丽莎:
递归(Recursion),在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。
在使用递归时,需要注意以下几点:
- 递归就是在过程或函数里调用自身
- 必须有一个明确的递归结束条件,称为递归出口。
注意: 切勿忘记递归出口,避免函数无限调用。
大多数学过数学、计算机科学或者读过编程相关书籍的人,想必都会遇到阶乘由于简单、清晰,因此其常被用作递归的示例。除了阶乘以外,还有很多算法可以使用递归来处理,例如:斐波那契数列、汉诺塔等。
递归阶乘实现代码
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n - 1)
if __name__ == '__main__':
i = factorial(5)
print(i)
为了明确递归步骤,对 5
进行过程分解:
递归的优缺点:
从“编程之美”的角度来看,引用一句伟大的计算机编程名言:迭代者为人,递归者为神。
优点:
- 递归使代码看起来更加整洁、优雅
- 可以用递归将复杂任务分解成更简单的子问题
- 使用递归比使用一些嵌套迭代更容易
缺点:
- 递归的逻辑很难调试、跟进
- 递归调用的代价高昂(效率低),因为占用了大量的内存和时间。
- 递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了
注意: 绝大多数递归,都可以使用循环实现, 即使递归代码很简洁,能不用尽量不使用递归
偏函数
简单说 partial 把某个函数的某个参数固定,从而构造出一个新的函数来。返回的这个新函数对象是 partial 对象,
在一些情况下, 我们在设计 Python 函数的时候, 会给它设定非常丰富的功能, 这样我们就可以定义一个非常强大的函数。 与此同时带来的问题是使用上的不方便, 因为有可能我们需要传递非常多的参数才能换成我们想要的功能。这时候 partial 函数就可以让我们在这个强大的函数中派生出对应的具体功能。
需要导入的包 from functools import partial
语法为:functools.partial(func, /, *args, **keywords)
偏函数的使用:
def getInfo(country,city,name,age):
print(f'我叫{name},我是{country}人,我家乡是{city},我今年{age}岁了!')
getInfo('中国','西安','飞',18) -- 正常调用
--使用偏函数, 可以把固定的几个参数给设置默认值,之后函数调用的时候就不需要在填充了
getXianIifo = partial(getInfo,country='中国',city='西安') # 设置默认填充前两个(关键字参数设置)
getXianIifo(name='小明',age=19)
getXianIifo(name='小熊',age=20)
def demo(x, y, z):
print(x,y,z)
new_demo = partial(demo,1) # 位置参数设置(从左到右)
new_demo(2,3)
注意: 如果使用关键字参数设置偏函数的默认参数那么之后使用偏函数的时候也必须使用关键字参数,同理位置参数也一样,两者不能混搭
全局变量和局部变量
所谓作用域(Scope),就是变量的有效范围,就是变量可以在哪个范围以内使用。有些变量可以在整段代码的任意位置使用,有些变量只能在函数内部使用,有些变量只能在 for 循环内部使用。
变量的作用域由变量的定义位置决定,在不同位置定义的变量,它的作用域是不一样的。本节我们只讲解两种变量,局部变量和全局变量。
在函数内部定义的变量,它的作用域也仅限于函数内部,出了函数就不能使用了,我们将这样的变量称为局部变量(Local Variable)。
要知道,当函数被执行时,Python 会为其分配一块临时的存储空间,所有在函数内部定义的变量,都会存储在这块空间中。而在函数执行完毕后,这块临时存储空间随即会被释放并回收,该空间中存储的变量自然也就无法再被使用。列:
def demo():
add = "python"
print("函数内部 add =",add)
demo()
print("函数外部 add =",add) # 报错 NameError: name 'add' is not defined
可以看到,如果试图在函数外部访问其内部定义的变量,Python 解释器会报 NameError 错误,并提示我们没有定义要访问的变量,这也证实了当函数执行完毕后,其内部定义的变量会被销毁并回收。
值得一提的是,函数的参数也属于局部变量,只能在函数内部使用。例如:
def demo(name,add):
print("函数内部 name =",name)
print("函数内部 add =",add)
demo("Python教程","http://c.biancheng.net/python/")
print("函数外部 name =",name) # NameError: name 'name' is not defined
print("函数外部 add =",add)
除了在函数内部定义变量,Python 还允许在所有函数的外部定义变量,这样的变量称为全局变量(Global Variable)。
和局部变量不同,全局变量的默认作用域是整个程序,即全局变量既可以在各个函数的外部使用,也可以在各函数内部使用。
定义全局变量的方式有以下 2 种:
在函数体外定义的变量,一定是全局变量,例如:
add = "shell"
def text():
print("函数体内访问:",add)
text()
print('函数体外访问:',add)
在函数体内定义全局变量。即使用 global 关键字对变量进行修饰后,该变量就会变为全局变量。例如:
def text():
global add # 定义全局变量
add= "java"
print("函数体内访问:",add)
text()
print('函数体外访问:',add)
注意: 在使用 global 关键字修饰变量名时,不能直接给变量赋初值,否则会引发语法错误。
获取指定作用域范围中的变量
在一些特定场景中,我们可能需要获取某个作用域内(全局范围内或者局部范围内)所有的变量,Python 提供了以下 3 种方式:
globals() 函数为 Python 的内置函数,它可以返回一个包含全局范围内所有变量的字典,该字典中的每个键值对,键为变量名,值为该变量的值。
# 全局变量
Pyname = "Python教程"
Pyadd = "python"
def text():
# 局部变量
Shename = "shell教程"
Sheadd= "shell"
print(globals())
注意: globals() 函数返回的字典中,会默认包含有很多变量,这些都是 Python 主程序内置的,可以不用理会它们。
可以看到,通过调用 globals() 函数,我们可以得到一个包含所有全局变量的字典。并且,通过该字典,我们还可以访问指定变量,甚至如果需要,还可以修改它的值。例如,在上面程序的基础上,添加如下语句:
print(globals()['Pyname']) # 获取全局变量字段中指定变量值
globals()['Pyname'] = "Python入门教程" # 修改指定变量的值
print(Pyname)
locals() 函数也是 Python 内置函数之一,通过调用该函数,我们可以得到一个包含当前作用域内所有变量的字典。这里所谓的“当前作用域”指的是,在函数内部调用 locals() 函数,会获得包含所有局部变量的字典;而在全局范文内调用 locals() 函数,其功能和 globals() 函数相同。
# 全局变量
Pyname = "Python教程"
Pyadd = "http://c.biancheng.net/python/"
def text():
# 局部变量
Shename = "shell教程"
Sheadd = "shell"
print("函数内部的 locals:")
print(locals())
if __name__ == '__main__':
text()
print("函数外部的 locals:")
print(locals())
当使用 locals() 函数获取所有全局变量时,和 globals() 函数一样,其返回的字典中会默认包含有很多变量,这些都是 Python 主程序内置的,不用理会它们。
注意: 当使用 locals() 函数获得所有局部变量组成的字典时,可以向 globals() 函数那样,通过指定键访问对应的变量值,但无法对变量值做修改。
vars() 函数也是 Python 内置函数,其功能是返回一个指定 object 对象范围内所有变量组成的字典。如果不传入object 参数,vars() 和 locals() 的作用完全相同。
Pyname = "Python教程"
Pyadd = "http://c.biancheng.net/python/"
class Demo:
name = "Python 教程"
add = "http://c.biancheng.net/python/"
if __name__ == '__main__':
print("有 object:")
print(vars(Demo))
print("无 object:")
print(vars())
Python函数内用同名全局变量
在python之中变量是有作用域之分的,定义在外部,也就是主程序结构之中的变量可以在任意的地方被任意对象所引用。与之相反的则是定义在函数或者是循环结构之中的局部变量
在python程序之中变量命名是具有唯一性的,也就是变量名不能够和关键字、内置函数、标准库以及已经定好的变量相同。所以当我们在函数之中定义一个变量时,如果名称和全局变量名相同就会出现同名局部变量覆盖了同名全局变量的情况
想要在函数内部使用全局同名变量那么需要使用global示例如下:
name = 'Charlie'
def test ():
global name # 如果不加global,则会报错,因为name是全局变量
print(name)
name = 'Slote' # 设置全局变量的值
if __name__ == '__main__':
test()
print(name)
局部函数
通过前面的学习我们知道,Python 函数内部可以定义变量,这样就产生了局部变量,有读者可能会问,Python 函数内部能定义函数吗?答案是肯定的。Python 支持在函数内部定义函数,此类函数又称为局部函数。
那么,局部函数有哪些特征,在使用时需要注意什么呢?接下来就给读者详细介绍 Python 局部函数的用法。
首先,和局部变量一样,默认情况下局部函数只能在其所在函数的作用域内使用。举个例子:
#全局函数
def outdef ():
#局部函数
def indef():
print("http://www.123.com/")
#调用局部函数
indef()
#调用全局函数
outdef()
就如同全局函数返回其局部变量,就可以扩大该变量的作用域一样,通过将局部函数作为所在函数的返回值,也可以扩大局部函数的使用范围。例如,修改上面程序为:
#全局函数
def outdef ():
#局部函数
def indef():
print("调用局部函数")
#调用局部函数
return indef
#调用全局函数
new_indef = outdef()
调用全局函数中的局部函数
new_indef()
因此,对于局部函数的作用域,可以总结为:如果所在函数没有返回局部函数,则局部函数的可用范围仅限于所在函数内部;反之,如果所在函数将局部函数作为返回值,则局部函数的作用域就会扩大,既可以在所在函数内部使用,也可以在所在函数的作用域中使用。
另外值得一提的是,如果局部函数中定义有和所在函数中变量同名的变量,也会发生“遮蔽”的问题。例如:
#全局函数
def outdef ():
name = "所在函数中定义的 name 变量" # 和局部函数同名变量
#局部函数
def indef():
print(name) # 报错: UnboundLocalError: local variable 'name' referenced before assignment
name = "局部函数中定义的 name 变量"
indef()
#调用全局函数
outdef()
因为函数内部的函数同名变量会把外面函数的变量覆盖了,那么就导致 print(name)找不到name这个变量了,
由于这里的 name 变量也是局部变量,因此前面章节讲解的 globals() 函数或者 globals 关键字,并不适用于解决此问题。这里可以使用 Python 提供的 nonlocal 关键字。
修改上面程序为:
#全局函数
def outdef ():
name = "所在函数中定义的 name 变量"
#局部函数
def indef():
nonlocal name #获取外部函数的变量,一直找到最顶层的函数,如果没找到那么就报错
print(name)
#修改name变量的值
name = "局部函数中定义的 name 变量"
indef()
#调用全局函数
outdef()
闭包
闭包,又称闭包函数或者闭合函数,其实和前面讲的嵌套函数类似,不同之处在于,闭包中外部函数返回的不是一个具体的值,而是一个函数。一般情况下,返回的函数会赋值给一个变量,这个变量可以在后面被继续执行调用。
# 闭包函数,其中 exponent 称为自由变量
def nth_power(exponent):
def exponent_of(base):
return base ** exponent
return exponent_of # 返回值是 exponent_of 函数
if __name__ == '__main__':
square = nth_power(2) # 计算一个数的平方
print(square(2)) # 计算 2 的平方
cube = nth_power(3) # 计算一个数的立方
print(cube(2)) # 计算 2 的立方
看到这里,读者可能会问,为什么要闭包呢?上面的程序,完全可以写成下面的形式:
def nth_power_rewrite(base, exponent):
return base ** exponent
上面程序确实可以实现相同的功能,不过使用闭包,可以让程序变得更简洁易读。设想一下,比如需要计算很多个数的平方,那么读者觉得写成下面哪一种形式更好呢?
# 不使用闭包
res1 = nth_power_rewrite(base1, 2)
res2 = nth_power_rewrite(base2, 2)
res3 = nth_power_rewrite(base3, 2)
# 使用闭包
square = nth_power(2)
res1 = square(base1)
res2 = square(base2)
res3 = square(base3)
显然第二种方式表达更为简洁,在每次调用函数时,都可以少输入一个参数。其次,和缩减嵌套函数的优点类似,函数开头需要做一些额外工作,当需要多次调用该函数时,如果将那些额外工作的代码放在外部函数,就可以减少多次调用导致的不必要开销,提高程序的运行效率。
lambda表达式(匿名函数)
lambda 表达式,又称匿名函数,常用来表示内部仅包含 1 行表达式的函数。如果一个函数的函数体仅有 1 行表达式,则该函数就可以用 lambda 表达式来代替。
lambda 表达式的语法格式如:name = lambda [list] : 表达式
`
其中,定义 lambda 表达式,必须使用 lambda 关键字;[list] 作为可选参数,等同于定义函数是指定的参数列表;value 为该表达式的名称。
该语法格式转换成普通函数的形式,如下所示:
def name(list):
return 表达式
name(list)
显然,使用普通方法定义此函数,需要 3 行代码,而使用 lambda 表达式仅需 1 行。
举个例子,如果设计一个求 2 个数之和的函数,使用普通函数的方式,定义如下:
def add(x, y):
return x+ y
print(add(3,4))
由于上面程序中,add() 函数内部仅有 1 行表达式,因此该函数可以直接用 lambda 表达式表示:
add = lambda x,y:x+y
print(add(3,4)
可以这样理解 lambda 表达式,其就是简单函数(函数体仅是单行的表达式)的简写版本。相比函数,lamba 表达式具有以下 2 个优势:
- 对于单行函数,使用 lambda 表达式可以省去定义函数的过程,让代码更加简洁;
- 对于不需要多次复用的函数,使用 lambda 表达式可以在用完之后立即释放,提高程序执行的性能。
字符串代码执行
eval() 和 exec() 函数的功能是相似的,都可以执行一个字符串形式的 Python 代码(代码以字符串的形式提供),相当于一个 Python 的解释器。二者不同之处在于,eval() 执行完要返回结果,而 exec() 执行完不返回结果
eval() 函数的语法格式为:eval(expression, globals=None, locals=None, /)
`
exec() 函数的语法格式如下:exec(expression, globals=None, locals=None, /)
可以看到,二者的语法格式除了函数名,其他都相同,其中各个参数的具体含义如下:
- expression:这个参数是一个字符串,代表要执行的语句 。该语句受后面两个字典类型参数 globals 和 locals 的限制,只有在 globals 字典和 locals 字典作用域内的函数和变量才能被执行。
- globals:这个参数管控的是一个全局的命名空间,即 expression 可以使用全局命名空间中的函数。如果只是提供了 globals 参数,而没有提供自定义的 __builtins__,则系统会将当前环境中的 __builtins__ 复制到自己提供的 globals 中,然后才会进行计算;如果连 globals 这个参数都没有被提供,则使用 Python 的全局命名空间。
- locals:这个参数管控的是一个局部的命名空间,和 globals 类似,当它和 globals 中有重复或冲突时,以 locals 的为准。如果 locals 没有被提供,则默认为 globals。
注意,__builtins__ 是 Python 的内建模块,平时使用的 int、str、abs 都在这个模块中。通过 print(dic[“__builtins__”]) 语句可以查看 __builtins__ 所对应的 value。
eval() 和 exec() 函数的应用场景
在使用 Python 开发服务端程序时,这两个函数应用得非常广泛。例如,客户端向服务端发送一段字符串代码,服务端无需关心具体的内容,直接跳过 eval() 或 exec() 来执行,这样的设计会使服务端与客户端的耦合度更低,系统更易扩展。
需要注意的是,在使用 eval() 或是 exec() 来处理请求代码时,函数 eval() 和 exec() 常常会被黑客利用,成为可以执行系统级命令的入口点,进而来攻击网站。解决方法是:通过设置其命名空间里的可执行函数,来限制 eval() 和 exec() 的执行范围。
使用演示
# 将字符串转成相应的对象(如list、tuple、dict和string之间的转换)
a = "[[1,2], [3,4], [5,6], [7,8], [9,0]]"
b = eval(a) # [[1, 2], [3, 4], [5, 6], [7, 8], [9, 0]]
a = "{1:'xx',2:'yy'}"
c = eval(a) # {1: 'xx', 2: 'yy'}
a = "(1,2,3,4)"
d = eval(a) # (1, 2, 3, 4)
x = 10
def func():
y = 20 # 局部变量y
a = eval("x+y") # x本地没有就调用全局变量
print("a:",a)
b = eval("x+y",{"x":1,"y":2}) # 定义局部变量,优先调用
print("b:",b)
c = eval("x+y",{"x":1,"y":2},{"y":3,"z":4}) # 第一个{"x":1,"y":2}全局变量globals ,第二个{"y":3,"z":4}局部变量locals.,
print("c:",c)
d = eval("print(x,y)")
print("d:",d) # 对于变量d,因为print()函数不是一个计算表达式,因此没有返回值
func()
注意: eval()函数只能计算单个表达式的值,而exec()函数可以动态运行代码段 , eval()函数可以有返回值,而exec()函数返回值永远为None
函数式编程(map()、filter()和reduce())
所谓函数式编程,是指代码中每一块都是不可变的,都由纯函数的形式组成。这里的纯函数,是指函数本身相互独立、互不影响,对于相同的输入,总会有相同的输出。
除此之外,函数式编程还具有一个特点,即允许把函数本身作为参数传入另一个函数,还允许返回一个函数。
def compute(f, s): #f为函数对象,s为系列对象
return f(s)
compute(min,(1,5,3,2))
map() 函数的基本语法格式如下:
map(function, iterable)
其中,function 参数表示要传入一个函数,其可以是内置函数、自定义函数或者 lambda 匿名函数;iterable 表示一个或多个可迭代对象,可以是列表、字符串等。 map() 函数的功能是对可迭代对象中的每个元素,都调用指定的函数,并返回一个 map 对象。注意,该函数返回的是一个 map 对象,不能直接输出,可以通过 for 循环或者 list() 函数来显示。
# 对列表中的每个元素乘以 2
listDemo = [1, 2, 3, 4, 5]
new_list = map(lambda x: x * 2, listDemo)
print(list(new_list))
# 两个列表相加
listDemo1 = [1, 2, 3, 4, 5]
listDemo2 = [3, 4, 5, 6, 7]
new_list = map(lambda x,y: x + y, listDemo1,listDemo2)
print(list(new_list))
# 批量执行函数
def multiply(x):
return (x * x)
def add(x):
return (x + x)
funcs = [multiply, add] # 将函数添加到列表中
for i in range(10):
value = map(lambda x: x(i), funcs)# 将参数传达到所有函数里
print(list(value))
注意,由于 map() 函数是直接由用 C 语言写的,运行时不需要通过 Python 解释器间接调用,并且内部做了诸多优化,所以相比其他方法,此方法的运行效率最高。
filter()函数的基本语法格式如下:
filter(function, iterable)
此格式中,funcition 参数表示要传入一个函数,iterable 表示一个可迭代对象。filter() 函数的功能是对 iterable 中的每个元素,都使用 function 函数判断,并返回 True 或者 False,最后将返回 True 的元素组成一个新的可遍历的集合。
# 获取余数是0的
listDemo = [1, 2, 3, 4, 5]
new_list = filter(lambda x: x % 2 == 0, listDemo)
print(list(new_list))# [2, 4]
# 获取第一个列表减去第二个列表大于0的
listDemo = [1, 2, 3, 4, 5]
new_list = map(lambda x,y: x-y>0,[3,5,6],[1,5,8] )
print(list(new_list)) # [True, False, False]
number_list = range(-10, 5)
print("number_list: ",type(number_list))
# 这个filter类似于⼀个for循环, 但它是⼀个内置函数, 并且更快。
less_than_zero = filter(lambda x: x < 0, number_list)
print(less_than_zero)
print(list(less_than_zero))
reduce() 函数通常用来对一个集合做一些累积操作,其基本语法格式为:
reduce(function, iterable)
`其中,function 规定必须是一个包含 2 个参数的函数;iterable 表示可迭代对象。
注意,由于 reduce() 函数在 Python 3.x 中已经被移除,放入了 functools 模块,因此在使用该函数之前,需先导入 functools 模块。
import functools
if __name__ == '__main__':
listDemo = [1, 2, 3, 4, 5]
product = functools.reduce(lambda x, y: x * y, listDemo)
print(product)
通常来说,当对集合中的元素进行一些操作时,如果操作非常简单,比如相加、累积这种,那么应该优先考虑使用 map()、filter()、reduce() 实现。另外,在数据量非常多的情况下(比如机器学习的应用),一般更倾向于函数式编程的表示,因为效率更高。
当然,在数据量不多的情况下,使用 for 循环等方式也可以。不过,如果要对集合中的元素做一些比较复杂的操作,考虑到代码的可读性,通常会使用 for 循环。
内置函数一览表
需要注意的是,开发者不建议使用以上内置函数的名字作为标识符使用(作为某个变量、函数、类、模板或其他对象的名称),虽然这样做 Python 解释器不会报错,但这会导致同名的内置函数被覆盖,从而无法使用。
函数注解
def add(x: int, y: int) -> int:
return x+y
初步看上去,与python2相比,这个函数在定义上,多了3个int。前两个int表示输入数据的类型,"->"符号后面的int,表示返回值类型。在python中,是不用强制申明变量类型的。python3的这种新语法,貌似可以直接申明变量类型?
这种“申明函数参数中变量类型,与返回值类型”的语法,就是python3所谓的“函数注解”。 注解,只是给函数参数和返回值添加说明而已不是强制要求的。并且,“函数注解”的目的,是给人看的,不是给解释器看。所以,哪怕类型使用错误,编译器也不会报错,举例如下:
def add(x: int, y: int) -> int:
return x+y
z = add(3.14, 1.23)
用python3.0以上的版本运行这段程序,并不会报错,即便实参是浮点型,而形参要求是整型。最终z的值是4.37。Python 解释器并不会因为这些注解而提供额外的校验,没有任何的类型检查工作。也就是说,这些类型注解加不加,对你的代码来说没有任何影响。
“函数注解”的作用,仅仅是:方便程序员查看,IDE的设计者可以用来做代码检查。
我们如何得到某一个函数的类型注解呢?其实很简单,使用__annotations__
就可以,比如上面定义的函数add
,运行如下代码:
add.__annotations__ # 得到的结果就是{'return': int, 'x': int, 'y': int}。