我们前面用的代码都是比较简单的脚本,而实际工作中是没有人把整个一个功能从头写到尾按顺序堆到一块的。一个规范的值得借鉴的Python程序,除非代码量很少(10行20行左右)应该由多个函数组成,这样的代码才更加的模块化、规范化。
函数的基础知识这里就不详细说明了,这里讲一些其他的内容!
一.多态
我们先看一个这样的函数
def fun(a,b): return a+b print(fun(1,2)) print(fun('1','2'))
运行后会发现效果是不一样的。Python不用考虑输入数据的类型,而是将其交给具体的代码去判断执行,同样一个函数可以同时用在int、str等数据类型的操作中。这种行为就称为多态。这也是Python和Java,C等很大的一个不同点。注意这是一个中性的特点,无所谓优略,但在必要时需要在开头加上数据的类型检查。
二.嵌套
Python函数的另一大特性就是支持函数的嵌套
def fun1(): print('in fun1') def fun2(): #fun2是嵌套在fun1内部的 print('in fun2') fun2() fun1()
这里再fun1内部定义了fun2,在调用fun1时,先打印‘in fun1’,然后fun1内部调用fun2,在打印‘in fun2’。这样做有什么好处呢?
嵌套的优点
1.函数的嵌套可以保证内部函数的隐私。内部函数只能被外部函数调用或访问,不会被暴露在全局作用域。如果函数内部有一些隐私数据(数据库的用户、密码等)不想被暴露在外部,就可以使用函数的嵌套。将其封装在函数内部,只通过外部函数来访问
def connect_DB(): def get_DB_configuration(): host = '' username = '' password = '' return host,username,password conn = connertor.connect(get_DB_configuration()) #这里所有的用户信息是不会被暴露出来的 return conn #函数只返回连接状态
这里的get_DB_configuration()只能在connect_DB函数内部被调用,在全局内部是无法调用的。
2.合理的使用函数的嵌套,能够提高程序的运行效率。比如我们想算一个数的阶乘,在计算前需要判定传递的参数是否合法
def factorial(input): if not isinstance(input,int): raise Exception('input must be an integer.') if input < 0: raise Exception('input must be greater or equal to 0') def inner_factorial(input): if input <=1: return 1 return input*inner_factorial(input-1) return inner_factorial(input) print(factorial(3))
这样通过函数的嵌套我们在检查数据的合法性时只执行了一次,而如果我们不用嵌套的化每次递归都要进行一次判定,会降低程序的运行效率。在工作中会经常遇到这种情况,那么运用嵌套就是非常必要的了。
三.函数变量作用域
函数的变量作用域和其他语言类似,如果变量在函数内部,就是局部变量,只在函数内部有效,一旦函数执行完毕,局部变量就会被回收,无法访问。
相对应的,全局变量则是定义在整个文件层次上的,例如这样
MAX = 10 MIN = 1 def fun(): if MAX > MIN: print(MAX) fun()
但是我们不能随意改变变量的值,例如函数里出现这样就会报错
MAX = 10 MIN = 1 def fun(): MAX+=1 fun()
UnboundLocalError: local variable 'MAX' referenced before assignment
因为Python解释器会默认函数内部的变量为局部变量,调用时发现MAX并没有被声明,就会抛出异常。那如果我们在函数中定义了个变量名称和全局变量名字一样会怎么样呢?
A = 123 def fun(): A = 456 print('in fun A:{}'.format(A)) print('before call fun A:{}'.format(A)) fun() print('after call fun A:{}'.format(A))
before call fun A:123 in fun A:456 after call fun A:123
但还是要注意,在函数内如果先调用了全局变量是不能重新声明一个和它名字相同的变量的,也不能重新改变它的值。而假如需要对这个变量进行改变时,就需要加上关键字Global。
A = 123 def fun(): global A print(A) A = 456 print('in fun A:{}'.format(A)) print('before call fun A:{}'.format(A)) fun() print('after call fun A:{}'.format(A))
before call fun A:123 123 in fun A:456 after call fun A:456
切记切记,这个方法一定要慎用。因为一旦函数被调用变量的值就会被更改。如果哪次在哪次调用中被修改成不需要的值,要追溯起来时相当麻烦的。
还有就是嵌套函数,内部函数可以访问外部的变量而不能改变其值。要想改变值的话在内部函数用关键字nonlocal声明变量
def fun1(): x = 'local' def fun2(): nonlocal x #一定要在一开始声明 print('before change x:{}'.format(x)) x = 'inside' print('after change x:{}'.format(x)) fun2() print('after call fun2 x:{}'.format(x)) fun1()
before change x:local
after change x:inside
after call fun2 x:inside
四.闭包、闭包、闭包
闭包(closure)是这节课里最重要却右不好理解的内容。闭包和嵌套类似,不过这里的外部函数返回的是一个函数而不是具体的值,返回的函数通常赋予一个变量,这个变量可以在后面被继续执行调用。
举个例子,我们想计算一个数的n次幂,用闭包可以这么写
def nth_power(exponent): def exponnet_of(base): return base**exponent return exponnet_of #返回值是一个函数 square = nth_power(2) #计算一个数的平方 cube = nth_power(3) #计算一个数的立方 print(square(3)) print(cube(3))
这里的外部函数nth_power()的返回值是函数exponnet_of(),在执行完
square = nth_power(2)
cube = nth_power(3)
以后外部函数的参数exponent是会被内部函数exponnet_of()记住的,之后我们调用时程序就能顺利的输出结果。
这么看起来,我们也可以把程序写成这样的
def nth_power_rewrite(base,exponnet): return base**exponnet
其实也是可以的,但是使用了闭包可以使程序变得更简洁易读。比如我们需要算很多个数的平方,就成这样的了
不用闭包 res1 = nth_power_rewrite(1,2) res2 = nth_power_rewrite(2,2) res3 = nth_power_rewrite(3,2) res4 = nth_power_rewrite(4,2) #使用闭包 squre = nth_power(2) res1 = square(1) res2 = square(2) res3 = square(3) res4 = square(4)
首先看来,闭包在每次调用函数时都少数如一个参数,更加简洁。
其次,和前面的嵌套类似,函数开开始需要做一些额外的工作,而需要多次调用这个函数时,就可以把这些额外的工作放在外部函数中,可以减少多次调用导致的不必要的开销。
另外一点以后会讲,闭包常常和装饰器(decorator)一起使用。