自定义函数

声明/定义与调用

在调用自定义的函数之前,要先定义或者声明,

def func(param): 
	statement 
	return value
func(param)

但是,如果我们在函数内部调用其他函数,函数间哪个声明在前、哪个在后就无所谓,因为 def 是可执行语句,函数在调用之前都不存在,我们只需保证调用时,所需的函数都已经声明定义:

def my_func(message):
    my_sub_func(message) # 调用my_sub_func()在其声明之前不影响程序执行
    
def my_sub_func(message):
    print('Got a message: {}'.format(message))

my_func('hello world')

# 输出
Got a message: hello world

多态

函数的参数可以是多种类型,在函数定义时可以不定义参数类型,根据实际的参数类型进行相应的操作,比如:

def my_sum(a, b):
    return a + b

result = my_sum(3, 5)
print(result)

# 输出
8

上面这个函数的输入参数如果是字符串,可以实现字符串的拼接,如果是list,可以实现list的拼接

print(my_sum([1, 2], [3, 4]))
# 输出
[1, 2, 3, 4]

如果两个参数的数据类型不同,比如一个是列表、一个是字符串,两者无法相加,那就会报错。
因此,在实际使用中必要时要在开头加上数据的类型检查。

函数嵌套

在一个函数的定义中可以加入另一个函数的定义,如:

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))

这样做的好处是:1、函数内部定义的函数不能被其他外部函数访问,提高安全性;2、可以减少冗余代码,提高代码运行效率,比如如果在函数开始时需要做大量的数据类型检查,可以采用这种形式,不用每次运行这个函数时都检查一次数据类型。
如上面的函数实现阶乘的功能,如果用正常的递归函数实现(如下所示),会导致每次调用这个函数都做一次validation check,如果采用内部嵌套函数的形式,就只需检查一次。

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' )
    ...
    if input <= 1:
    	return 1
    return input * factorial(input - 1)

变量作用域

全局定义的变量作用域是全局,局部函数内定义的变量作用域在函数内。
注意:在局部函数中不可修改全局变量,如果要修改,要加上global关键字。

MIN_VALUE = 1
def validation_check(value):
    global MIN_VALUE
    ...
    MIN_VALUE += 1
    ...
validation_check(5)

如果遇到函数内部局部变量和全局变量同名的情况,那么在函数内部,局部变量会覆盖全局变量,比如下面这种:

MIN_VALUE = 1
def validation_check(value):
    MIN_VALUE = 3
    ...

在validation_check函数内部,MIN_VALUE为3,在函数外部,MIN_VALUE还是1

对于嵌套函数来说,内部函数可以访问外部函数定义的变量,但是无法修改,若要修改,必须加上 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

此时X已经被内部函数修改为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

上述函数计算X的N次幂,nth_power的返回值是函数exponent_of,square = nth_power(2)代表N固定为2,再调用square(2)代表X为2,输出为2的平方=4
也可以用常规写法:

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)
...

另外,如果函数开头需要做一些额外工作,而又需要多次调用这个函数时,将那些额外工作的代码放在外部函数,就可以减少多次调用导致的不必要的开销,提高程序的运行效率。