当我们的python程式越来越复杂的时候,我们希望将一部分功能相近或重复的程式进行多次调用而不用写重复代码,这些小程式就是函数,函数是一种名称独立的程式片段,可以接受任何形态的参数,处理完成后也可以输出任何形式的结果。
python的函数定义(def)
在python中使用def来定义一个函数,函数命名规则和变量相同(数字,字母,下划线组成,数字不打头),函数定义时可以在括号里写明进入函数的多个参数,并可对参数进行赋初值,当没有提供此参数时将以初始值运行,传输参数会按照定义的顺序进行,也可以使用关键字索引(参数名称)来设定特定参数,使用return返回运算结果(可返回多个值),如下为自定义函数范例:
def hello(name='ric',age=18):
print(f"{name} {age}!")
return f"{name} {age}!"
a=hello(name='hello',age='word')
print(a)
需要注意的是定义函数的代码一定要在调用代码之前,否则会发生错误
函数内也可以再嵌套函数,python代码的变量有层级关系,定义在内部的变量不能被外部调用(称为作用域),定义在外部的变量在函数内部未定义时可以使用,变量作用的核心思想未:由内向外寻找,直到最外层无变量则报错。
python函数的通用运算子(*args,**kwargs)
有时候我们建立函数时并不知道用户会传多少参数,就会使用到*args 和 **kwargs,这里的英文名称只是“变量名称”,可以自由变换,重点在于前方的一个星号和两个星号。
当函数参数为args(一个星号*)时,则传入的所有参数都会被组合成tuple类型
当函数参数为kwargs(两个星号**)时,则传入的所有[带有关键字引数]的参数,都会被组合成字典形式。当*args和**kwargs同时出现时,有关键字索引的会被转换成字典,其余的转换成tuple类型,通用运算子范例如下:
def test1(*args):#一个星号的运算子,全部是tuple类型,args是变量名
print(args)
test1(1,2,3,'a','b','c')
def test2(**kwargs): #两个星号的运算子,带有关键字的全部为字典,kwargs是变量名
print(kwargs)
test2(name='ric',age=28,like='deeplearn')
函数内部可以写pass来进行占位,执行到pass时不会进行任何操作,但是会满足python的严格缩进
python的匿名函数(lambda)
python中允许匿名函数lambda,匿名函数不需要定义函数名称,只能有一行运算式,执行完成后自动回传结果(一般函数使用return才能回传结果),lambda函数也可以有多个参数,也可以使用for循环语句和if条件判断语句,如下范例代码:
y = lambda n: [i if i%2==0 else 100 for i in range(n)] #使用for循环和if判定生成list
print(y(5))
值得一提的是lambda函数可以搭配map方法(将指定的函数,依次作用于可迭代对象的每个元素,并返回一个迭代器对象),filter方法(过滤筛选的作用),sorted方法(对集合进行排序)来使用。
python的递归函数(recursion)
python程式运行时,有时无法使用单纯的循环来解决问题,这个时候就要使用到递归功能,当“一个函数在执行中不断调用自己”的特定就称之为递归,其特点为自身调用自己,具备函数停止条件。如下为一个简单递归函数范例:
def a(n): # 建立函数 a,带有参数 n
if n 0 or n 1: # 如果 n 等于 0 或 1
return 1 # 回传结果 1
else:
return n + a(n-1) # 回传
print(a(3)) # 执行结果为 6 ( 3+2+1 )
需要注意的是python递归函数减少了程式复杂度,但是会消耗更多内存,python设定了递归最大为3000次,超过就会发生错误而强制停止。
python递归可以很好的解决一些带有重复性质的数学问题,比如求阶乘,斐波那契数列,最大公因数等。
python生成器(generator)
当python程序需要迭代的内容非常大的串列时,往往需要消耗不少的电脑内存,这世如果改用生成器(generator)的方式,就能产生更好的效能。
生成器(generator)是一个python序列制作物件,可以用来迭代一个很大的序列,在迭代过程中产生的值是动态的,不需要将整个序列存储在内存中。生成器是记录产生值的方法,而不是记录值,且生成器中产生的值只能用一次,所以无法重新启动或获取,生成器的语法和串列生成器类似,差别在于串列是中括号[],生成器是小括号(),执行后会产生一个生成器物件object而不是串列,其语法如下:
b = (i for i in range(10)) # 生成器表示式
print(b) #这里的b是物件,可以使用for取出内容,但是只能取出一次
值得一提的是,如果一个函数中包含“yield 陈述式”,那么这个函数就会变成一个生成器(generator函数),generator函数和普通的执行流程不同,普通函数是顺序执行,遇到return就返回,而generator函数则是在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yiled语句处继续执行。范例代码如下:
def f():
yield(1) # 使用 yield
yield(2)
yield(3)
g = f() # 赋值给变量 g
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3
为什么上方要使用g = f() 呢,因为调用generator函数会建立一个generator物件,多次调用会创建多个相互独立的generator,如果不将其赋值给变量,那么永远只能取出第一个值。
python的装饰器(decorator)
python的装饰器是一个可以让程式代码达到既精简,又漂亮的写法,提升程式可读性同时用起来也简单。
装饰器是python的一种程式设计模式,装饰器本质上是一个python函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数或者类对象,有了装饰器,就可以抽离与函数功能本身无关的程式代码,放到装饰器中并继续重复使用。在python中使用@符号作为装饰器使用的语法糖符号(语法糖指的是将复杂程式包起来的糖衣,也就是简化写法),我们可以在需要装饰的函数的前面用@函数 来进行装饰,这里的函数会包在需装饰函数外面先执行,其结果可套用在需装饰函数上,范例代码如下:
def a(func):
print('a is run!')
return func
@a# 裝飾器寫在 b 的前面,表示裝飾 b
def b():
print('b is run!')
b()
这里的@a出现在b函数定义前面,表示函数b有一个装饰器a,在调用b函数时会先运行a函数。如果a函数有return参数,可以直接被b进行接收使用,如果遇到被装饰的函数有参数,也可以将参数传入装饰器(注意参数名在函数中将会变化,但是本体不变)。
python的闭包(Closure )
闭包从字面翻译就是封闭的包裹,在包裹外的人,无法拿到里面的东西,在包裹里面则可以。闭包可以保存在函数作用范围内的状态,不会受到其他函数的影响,且无法从其他函数取得闭包的数据,也可以避免建立许多全局变量互相干扰。
我们来看看维基百科的闭包定义“在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。”,这里其实说明了闭包的定义和作用,闭包就是能读取外部函数变量的函数,闭包可以将外层函数的局部变量和外层函数外部连接起来,闭包可以将外层函数的变量持久的保存在内存中。范例代码如下:
def a(msg):
i = '!!!' # ------------------------ 闭包开始
def b(): # A 函式内定义了 B 函式
print(msg + i) # B 函式使用了 A 函式的变量
return b # 将 B 函式当作回传值 ------- 闭包结束
s = a('hello')
s()
python还有一个概念是作用域 (scope),作用域表示变量,常量,函数或其他定义语句被存取得到的范围,python共有四种作用域,從內而外分別是 Local ( 區域 )、Enclosing ( 閉包外函式 )、Global ( 全域 ) 和 Built-in ( 內置預設 ),内部作用域无法影响到外部作用域。有时候定义函数里面的函数时会遇到变量名称冲突的问题,我们可以用自由变量( nonlocal)来进行声明后则可以在局部使用变量。