Python 函数

函数是对程序逻辑进行结构化或过程化的一种编程方法。能将整块代码巧妙地隔离成易于管理的小块,把重复代码放到函数中而不是进行大量的拷贝--这样既能节省空间,也有助于保持一致性,因为你只需改变单个的拷贝而无须去寻找再修改大量复制代码的拷贝。

Python 内置函数

https://docs.python.org/zh-cn/3/library/functions.html

自定义函数组成

def func(var1){
xxx     #函数体
return yyy #函数返回值
}

def:函数声明的关键字
函数名:函数的名称,日后根据函数名调用函数
函数体:函数中进行一系列的逻辑计算
参数:为函数体提供数据
返回值:当函数执行完毕后,可以给调用者返回数据。

一个函数,如果函数名后紧跟一对括号,相当于现在我就要调用这个函数,如果不跟括号,相当于只是一个函数的名字,里面存了函数所在位置的引用(引用:内存中开辟一些空间,存下这个函数的代码、内部的局部变量)。

形式参数:

1. 位置参数

位置参数必须以在被调用函数中定义的准确顺序来传递。另外,没有任何默认参数(见下一个部分)的话,传入函数(调用)的参数的精确的数目必须和声明的数字一致。

1 #!/usr/bin/env python
2 
3 def func1(x,y,z):
4     print(x,y,z)
5 func1(1,2,3)
6 func1(1,'xxx',[1,2,3])
View Code

2. 默认参数

对于默认参数如果在函数调用时没有为参数提供值则使用预先定义的的默认值。

1 def func2(x,y=2,z=4):
2     print(x,y,z)
3 func2(1,2,3)
4 func2(1,2)
5 func2(1,z=2)
View Code

3. 可变长度的参数

可能会有需要用函数处理可变数量参数的情况。这时可使用可变长度的参数列表。变长的参数在函数声明中不是显式命名的,因为参数的数目在运行时之前是未知的(甚至在运行的期间,每次函数调用的参数的数目也可能是不同的),这和常规参数(位置和默认)明显不同,常规参数都是在函数声明中命名的。由于函数调用提供了关键字以及非关键字两种参数类型,python 用两种方法来支持变长参数。可变长的参数元组必须在位置和默认参数之后 。

3.1 非关键字可变长参数(元组) 

def function_name([formal_args,] *vargs_tuple):
    "function_documentation_string"
    function_body_suite

星号(*)操作符之后的形参将作为元组传递给函数,元组保存了所有传递给函数的"额外"的参数(匹配了所有位置和具名参数后剩余的)。如果没有给出额外的参数,元组为空。 

 1 def func3(arg1, arg2='defaultB', *theRest):
 2     'display regular args and non-keyword variable args'
 3     print('formal arg 1:',arg1)
 4     print('formal arg 2:',arg2)
 5     print('formal arg 3:',[ i for i in theRest])
 6     
 7 func3(1,)
 8 func3(1,'xxx')
 9 func3(1,'xxx',1,2,3,4,5)
10 li1 = [1,2,3,4,5]
11 func3(1,'xxx',*li1)
View Code
3.2 关键字变量参数(Dictionary) 

def function_name([formal_args,][*vargst,] **vargsd):
    “function_documentation_string”
    function_body_suite

为了区分关键字参数和非关键字非正式参数,使用了双星号(**)。 **是被重载了的以便不与幂运算发生混淆。关键字变量参数应该为函数定义的最后一个参数,带**。

 1 def func4(arg1,age=18,**theRest):
 2     'display 2 regular args and keyword variable args' 
 3     print('formal arg 1:',arg1)
 4     print('formal arg 2:',age)
 5     for eachXtrArg in theRest.keys():
 6         print('Xtra arg %s: %s' % (eachXtrArg, str(theRest[eachXtrArg])))
 7 
 8 func4(2,)
 9 func4(2,name='nb',addr='gz')
10 func4(2,age=3,name='nb',addr='gz')
11 dic1 = {'age':3,'name':'nb','addr':'gz'}
12 func4(2,**dic1)
View Code

匿名函数与 lambda

python 允许用 lambda 关键字创造匿名函数。匿名是因为不需要以标准的方式来声明,比如说,使用 def 语句。(除非赋值给一个局部变量,这样的对象也不会在任何的名字空间内创建名字.)然而,作为函数,它们也能有参数。一个完整的 lambda“语句”代表了一个表达式,这个表达式的定义体必须和声明放在同一行。我们现在来演示下匿名函数的语法:
lambda [arg1[, arg2, ... argN]]: expression
参数是可选的,如果使用的参数话,参数通常也是表达式的一部分。lambda 表达式返回可调用的函数对象。 

1 func5 = lambda x,y=2:x**y
2 print(func5(8))
View Code

内建函数

1. filter()

filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。

该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。

1 print(list(filter(lambda x:x%2==0,[1,2,3,4,5,6])))
2 str1='Hello world hello Python'
3 print(list(filter(lambda x:x.istitle(),str1.split(' '))))
View Code

2. map()

map() 会根据提供的函数对指定序列做映射。

第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。

1 print(list(map(lambda x:x**2,(1,2,3,4,5,6))))
View Code

3.reduce()

在 Python3 中,reduce() 函数已经被从全局名字空间里移除了,它现在被放置在 functools 模块里,如果想要使用它,则需要通过引入 functools 模块来调用 reduce() 函数:

1 from functools import reduce
2 print(reduce(lambda x,y:x+y,range(1,100)))
View Code

4. zip()

zip()函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存。我们可以使用 list() 转换来输出列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用*号操作符,可以将元组解压为列表。

1 a = [1,2,3]
2 b = ['a','b','c']
3 c = ['hello','world','hello','python']
4 
5 print(list(zip(a,b)))
6 print(list(zip(a,c)))
7 #x,y = zip(*zip(a,b))
8 x,y = zip(*zip(a,c))
9 print(x,y)
View Code

变量的作用域

变量的作用域(变量的可见性)是定义为其声明在程序里的可应用范围,变量可以是局部域或者全局域。

全局变量的一个特征是除非被删除掉,否则它们的存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的。

局部变量就像它们存放的栈,暂时地存在,仅仅只依赖于定义它们的函数现阶段是否处于活动(加载到内存)。当一个函数调用出现时,其局部变量就进入声明它们的作用域。在那一刻,一个新的局部变量名为那个对象创建了,一旦函数完成,框架被释放,变量将会离开作用域。

 1 def func6():
 2     local_var1 = "hello Python!"
 3     global local_var2    #使用global关键字将局部变量声明为全局变量
 4     local_var2 = "HELLO PYTHON!"
 5     global_var1 = "HELLO WORLD"    #修改全局变量
 6     print("函数func6中局部变量local_var1 is:",local_var1)
 7     print("函数func6中全局变量global_var1 is:",global_var1)
 8     print(local_var2)
 9 
10 if __name__ == "__main__":
11     global_var1 = "hello world!"
12     print("main函数中全局变量global_var1 is:",global_var1)
13     #print(local_var1)    #NameError
14     #print(local_var2)    #NameError
15     func6() 
16     #print(local_var1)    #NameError
17     print(local_var2)     #正常访问
View Code

闭包

闭包(Closure):在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量(自由变量),并且外函数的返回值是内函数的引用。这样就构成了一个闭包。

自由变量:定义在外部函数内的但由内部函数引用或者使用的变量被称为自由变量。

一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

闭包将内部函数自己的代码和作用域以及外部函数的作用结合起来。闭包的词法变量不属于全局名字空间域或者局部的--而属于其他的名字空间。闭包对于安装计算,隐藏状态,以及在函数对象和作用域中随意地切换是很有用的。

 1 #out() 为外部函数
 2 def out(var):
 3     num = 20
 4     #inner() 为内部函数
 5     def inner():
 6         #内部函数中访问自由变量
 7         print(var+num)
 8     #外函数的返回值是内函数的引用
 9     return inner
10 
11 # 在这里我们调用外函数传入参数10
12 # 此时外函数两个临时变量 num是20 var是10 ,并创建了内函数,然后把内函数的引用返回存给了func7
13 # 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数
14 func7=out(10)
15 # 调用内部函数,确认下内部函数是不是能使用外部函数的临时变量
16 # func7存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数
17 func7()
View Code

修改自由变量

在基本的python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:

1. global声明全局变量

2. 全局变量是可变类型数据的时候可以修改

在闭包内函数也是类似的情况.在内函数中想修改闭包变量(外函数绑定给内函数的局部变量)的时候:

1.在python3中,可以用nonlocal 关键字声明一个变量,表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。

2.在python2中,没有nonlocal这个关键字,我们可以把闭包变量改成可变类型数据进行修改,比如列表。

 1 def out(var):
 2     num = 20
 3      
 4     #python2 修改自由变量方式
 5     tmp = [var]    
 6     
 7     def inner():
 8         #内部函数修改自由变量
 9         #python3 修改自由变量的方式
10         nonlocal num
11         #python2 修改自由变量方式
12         num += 1
13         tmp[0] += 1
14         print(num+tmp[0])
15     return inner
16 
17 func8 = out(10)  
18 func8()  
View Code

还有一点需要注意:使用闭包的过程中,一旦外函数被调用一次返回了内函数的引用,虽然每次调用内函数,是开启一个函数执行过后消亡,但是闭包变量实际上只有一份,每次开启内函数都在使用同一份闭包变量

 1 def out(var1):
 2     def inner(var2):
 3         nonlocal var1
 4         var1 += var2 
 5         print(var1)
 6     return inner
 7 
 8 func9 = out(10)
 9 func9(20)
10 func9(30)
View Code

闭包用途

1. 装饰器

2. 面向对象。经历了上面的分析,我们发现外函数的临时变量送给了内函数。大家回想一下类对象的情况,对象有好多类似的属性和方法,所以我们创建类,用类创建出来的对象都具有相同的属性方法。闭包也是实现面向对象的方法之一。在python当中虽然我们不这样用,在其他编程语言入比如JavaScript中,经常用闭包来实现面向对象编程

3. 实现单利模式。其实这也是装饰器的应用。单利模式毕竟比较高大,,需要有一定项目经验才能理解单利模式到底是干啥用的,我们就不探讨了。

递归

如果函数包含了对其自身的调用, 该函数就是递归的。 

递归特点:

1. 必须有一个明确的结束条件
2. 问题规模每递归一次读应该比上一次的问题规模有所减少
3. 效率低

 1 def fibo1(n):
 2     x = y = 1;
 3     for i in range(n-1):
 4        x,y=y,x+y
 5     return x
 6 print(fibo1(10))
 7 
 8 def fibo(n):
 9    if n == 1 or n == 2:
10        return 1
11    return fibo(n-2) + fibo(n-1)
12 print(fibo(5))
13 
14 def fibo2(n):
15     fibs = []
16     x = y = 1
17     if n == 1:
18         return [1] 
19     if n == 2:
20         return [1,1]
21     for i in range(n):
22         fibs.append(x)
23         x,y=y,x+y
24     return fibs
25 print(fibo2(10))
View Code
posted on 2020-06-02 21:05  伊葭言  阅读(197)  评论(0)    收藏  举报