赞
踩
我们都知道一个规范的Python程序,除非代码量太少,否则都应该让程序由多个函数组成,这样的代码才更加的规模化、模块化。
Python本身自带很多的内置函数,例如open()、len()、int()等等,太多了,但即使有大量的内置函数,仍然需要我们自己根据具体的场景,自定义某个函数。
函数基础
函数说白了就是为了实现某一个功能的代码块,写好之后就可以复用。先看一段代码:
- def my_func(message):
- print('Got a message: {}'.format(message))
-
- # 调用函数 my_func()
- my_func('Hello World')
- # 输出
- Got a message: Hello World
所以最后就是下面这种形式:
- def name(param1, param2, ..., paramN):
- statements
- return/yield value # optional
def是可执行语句,这意味着函数直到被调用前,都是不存在的,当程序调用函数时,def语句才会创建一个新的函数对象,并赋予其名字。
再看个例子:
- def my_sum(a, b):
- return a + b
-
- result = my_sum(3, 5)
- print(result)
-
- # 输出
- 8
这里定义了一个名为my_sum的函数,有a,b两个参数,函数返回的是a+b这个结果,例如传参3和5,最后结果为8。
另外Python函数的参数可以设定默认值,比如:
- def func(param = 0):
- ...
这样这个函数,在调用func()这个函数时,如果没有给param传参,则参数的默认值就为0,如果给param传了参数,则会覆盖默认值,使用的是传入的参数。
我们都知道Python是动态类型的语言,它可以接受任何的数据类型(整型,浮点型,字符串等等),对函数的参数而言,也是适用的,比如刚刚的my_sum函数,我们可以计算两个数字相加,也可以把列表或字符串作为参数来传递:
- print(my_sum([1, 2], [3, 4]))
-
- # 输出
- [1, 2, 3, 4]
-
- print(my_sum('hello ', 'world'))
-
- # 输出
- hello world
当然传递的两个参数一定要类型相同,否则一个数字,一个字符串肯定是要报错的。
所以这就是Python的多态行为。在实际运用中,这种特性会让Python非常方便,但同时可能会有诸多问题,所以最好在运用前,把数据类型检查好。
Python函数的另一大特性就是支持函数的嵌套,就是函数里边又有函数:
- def f1():
- print('hello')
- def f2():
- print('world')
- f2()
-
- f1()
-
- # 输出
- hello
- world
这里是f1()函数内部又定义了f2()函数,所以在调用f1()函数时,会先打印字符串‘hello’,再在f1()函数内部调用f2(),打印出字符串‘world’。
话说回来,函数的嵌套有什么用处?
第一,函数的嵌套可以保证内部函数的隐私,因为内部函数只能被外部函数调用和访问,不会暴露全局的作用域,比方说函数内部有一些隐私数据(数据库的用户和密码等),你不想暴露在外,就可以使用函数嵌套这种方式,把它封装到函数的内部,比如:
- def connect_DB():
- def get_DB_configuration():
- ...
- return host, username, password
- conn = connector.connect(get_DB_configuration())
- return conn
这里get_DB_configuration()函数就是内部函数,它不能在connect_DB()这个函数以外调用,直接调用就会报错:
- get_DB_configuration()
-
- # 输出
- NameError: name 'get_DB_configuration' is not defined
这样的话,我们只能靠外部的函数来访问内部的函数,程序的安全性便有了提高。
第二,合理使用嵌套函数,能够提升程序的运行效率,比如:
- def factorial(input):
- # validation check
- 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(5))
这里是使用递归的方式计算一个数的阶乘,因为在计算之前,需要检查输入的合法性,所以如果写成嵌套的形式,输入的合法性只需要检查一次,如果不使用嵌套的话,那每调用一次递归就会检查一次,这显然是没有必要的,降低程序的运行效率。
函数变量的作用域
Python函数变量的作用域跟其他语言差不多,如果变量是在函数内部定义的,那么就只有在函数内部有效,被称为局部变量。一旦函数执行完毕后,变量就会被Python回收。
相应的全局变量则是定义在整个文件层次上的,比如:
- MIN_VALUE = 1
- MAX_VALUE = 10
- def validation_check(value):
- if value < MIN_VALUE or value > MAX_VALUE:
- raise Exception('validation check fails')
这里的MIN_VALUE,MAX_VALUE就是全局变量,可以在文件内的任何地方被访问,函数的内部也可以,但是,我们不能在函数内部随意更改全局变量的值。例如下面:
- MIN_VALUE = 1
- MAX_VALUE = 10
- def validation_check(value):
- ...
- MIN_VALUE += 1
- ...
- validation_check(5)
-
- ##程序报错信息
- UnboundLocalError: local variable 'MIN_VALUE' referenced before assignment
这是因为Python的解释器会默认把函数内部的变量作为局部变量,也就是说,全局变量MIN_VALUE 和MAX_VALUE在没有在函数内部被声明的情况下,在函数内部被访问就会当成是局部变量,所以,如果想要改变全局变量MIN_VALUE 和MAX_VALUE的值,就要加上global这个声明:
- MIN_VALUE = 1
- MAX_VALUE = 10
- def validation_check(value):
- global MIN_VALUE
- ...
- MIN_VALUE += 1
- ...
- validation_check(5)
这个global关键字不是创建一个名为MIN_VALUE 和MIN_VALUE的全局变量 ,而是告诉Python解释器这个函数内部被访问的MIN_VALUE 和MAX_VALUE就是之前定义的全局变量,不是新的全局变量,也不是局部变量。这样声明之后,我们就可以在函数内部去访问并修改它的值了。
类似的,对于嵌套函数来说,内部函数可以访问外部函数的变量,但是无法修改,若要修改需加上nonlocal这个关键字:
- def outer():
- x = "local"
- def inner():
- nonlocal x # nonlocal 关键字表示这里的 x 就是外部函数 outer 定义的变量 x
- x = 'nonlocal'
- print("inner:", x)
- inner()
- print("outer:", x)
- outer()
- # 输出
- inner: nonlocal
- outer: nonlocal
还有如果不加上nonlocal这个关键字,内部函数的变量和外部函数的变量同名,则内部函数的变量会覆盖外部函数的变量。
- def outer():
- x = "local"
- def inner():
- x = 'nonlocal' # 这里的 x 是 inner 这个函数的局部变量
- print("inner:", x)
- inner()
- print("outer:", x)
- outer()
- # 输出
- inner: nonlocal
- outer: local
闭包
啥是闭包尼?其实跟嵌套函数很类似,不一样的就是,外部函数不是返回的一个具体的值,而是返回的是一个函数,返回的函数通常赋予了变量,这个变量可以在后面被继续调用。
- def nth_power(exponent):
- def exponent_of(base):
- return base ** exponent
- return exponent_of # 返回值是 exponent_of 函数
-
- square = nth_power(2) # 计算一个数的平方
- cube = nth_power(3) # 计算一个数的立方
- square
- # 输出
- <function __main__.nth_power.<locals>.exponent(base)>
-
- cube
- # 输出
- <function __main__.nth_power.<locals>.exponent(base)>
-
- print(square(2)) # 计算 2 的平方
- print(cube(2)) # 计算 2 的立方
- # 输出
- 4 # 2^2
- 8 # 2^3
例如上边这个计算一个数的n次幂的例子,外部函数nth_power()的返回值是函数exponent_of(),在执行完square = nth_power(2)和cube = nth_power(3)后,外部函数nth_power()的参数exponent,仍然会被内部函数exponent_of()记住,这样在调用square(2)和cube(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)
- ...
肯定是第二种,所以闭包的有点可嵌套函数类似,比方说在函数的开头需要做一下额外的工作,但这个工作我只需要做一次就够了,当我用普通函数定义时,我每调用一次函数,这个额外的工作便会重新工作一次,那如果我把这些额外的工作放在外部函数里,就可以有效的减少因为多次调用而浪费不必要的计算开销。
其实闭包还经常跟装饰器一起使用,但装饰器的使用以后再讲,所以这一点就暂时不提了。
所以,Python函数的自定义函数你了解的差不多了吗?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。