【Python基础】自定义函数、闭包

函数是 Python 程序中不可或缺的一部分。一个规范的 Python 程序,除非代码量很少(比如 10 行、20 行以下),基本都应该由多个函数组成,这样的代码才更加模块化、规范化。

函数基础

简单来说,函数就是为了实现某一功能的代码段,只要写好以后,就可以重复利用。Python 中的函数大概是下面的这种形式:

def name(param1, param2, ..., paramN):
    statements
    return/yield value # optional

其中:

  • def 是函数的声明,和其他需要编译的语言(比如 C 语言)不一样的是,def 是可执行语句,这意味着函数直到被调用前,都是不存在的。当程序调用函数时,def 语句才会创建一个新的函数对象,并赋予其名字。
  • name 是指函数的名称;
  • 括号里面的 param1, param2, …, paramN 则是函数的参数,可以设定默认值,如果参数 param 没有传入,则参数为默认值;而如果传入了参数 param,其就会覆盖默认值。;
  • statements则表示函数的主体部分,可以执行相应的语句;
  • 在函数最后,可以使用 return 或 yield 返回调用结果,也可以不返回。

需要注意,主程序调用函数时,必须保证这个函数此前已经定义过,不然就会报错。但是,如果在函数内部调用其他函数,函数间哪个声明在前、哪个在后就无所谓,因为 def 是可执行语句,函数在调用之前都不存在,我们只需保证调用时,所需的函数都已经声明定义。

# 在主程序中调用函数时,必须保证这个函数此前已经定义过,不然就会报错。
my_func('hello world')
def my_func(message):
    print('Got a message: {}'.format(message))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-9ec00f181704> in <module>
----> 1 my_func('hello world')
      2 def my_func(message):
      3     print('Got a message: {}'.format(message))

NameError: name 'my_func' is not defined


# 在函数内部调用其他函数,函数间哪个声明在前、哪个在后就无所谓
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

另外,Python 和其他语言相比的一大特点是,Python 是 dynamically typed 的,变量可以接受任何数据类型(整型,浮点,字符串等等)。对函数参数来说,这一点同样适用。Python 不用考虑输入的数据类型,而是将其交给具体的代码去判断执行,同样的一个函数(比如下面的相加函数 my_sum()),可以同时应用在整型、列表、字符串等等的操作中。这也是 Python 和其他语言,比如 Java、C 等很大的一个不同点。当然,Python 这种方便的特性,在实际使用中也会带来诸多问题。因此,必要时请在函数开头加上数据的类型检查isinstance())。

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

print(my_sum(3, 5))
8

print(my_sum([1, 2], [3, 4])) # 也可以把列表作为参数来传递,表示将两个列表相连接
[1, 2, 3, 4]

print(my_sum('hello ', 'world')) # 也可以把字符串作为参数传递,表示字符串的合并拼接
hello world

print(my_sum([1, 2], 'hello')) # 但如果两个参数的数据类型不同,比如一个是列表、一个是字符串,两者无法相加,那就会报错
TypeError: can only concatenate list (not "str") to list

函数嵌套

所谓的函数嵌套,就是指函数里面又有函数,这么做主要有下面两个方面的作用:

  • 第一,函数的嵌套能够保证内部函数的隐私。内部函数只能被外部函数所调用和访问,不会暴露在全局作用域,因此,如果你的函数内部有一些隐私数据(比如数据库的用户、密码等),不想暴露在外,那你就可以使用函数的的嵌套,将其封装在内部函数中,只通过外部函数来访问。
  • 第二,合理的使用函数嵌套,能够提高程序的运行效率。比如下面这个例子:
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))
120

这里,我们使用递归的方式计算一个数的阶乘。因为在计算之前,需要检查输入是否合法,所以,写成函数嵌套的形式,输入是否合法就只用检查一次。而如果我们不使用函数嵌套,那么每递归调用一次函数,便会检查一次输入,这是没有必要的,也会降低程序的运行效率。实际工作中,如果遇到相似的情况,当函数开头的输入检查不是很快,且还会耗费一定的资源的时候,那运用函数的嵌套就十分必要了。

函数变量作用域

Python 函数中变量的作用域和其他语言类似。如果变量是在函数内部定义的,就称为局部变量,只在函数内部有效。一旦函数执行完毕,局部变量就会被回收,无法访问;相对应的,全局变量则是定义在整个文件层次上的,可以在文件内的任何地方被访问,当然在函数里面也可以读取到全局变量的。不过,我们不能在函数内部随意改变全局变量的值。

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check():
    ...
    MIN_VALUE += 1
    ...
    
validation_check()
---------------------------------------------------------------------------
UnboundLocalError: local variable 'MIN_VALUE' referenced before assignment

这是因为,Python 的解释器会默认函数内部的变量为局部变量,但是又发现局部变量 MIN_VALUE 并没有声明,因此就无法执行相关操作。所以,如果一定要在函数内部改变全局变量的值,就必须加上 global 这个关键字

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check():
    global MIN_VALUE
    ...
    MIN_VALUE += 1
    ...
validation_check()
print(MIN_VALUE) 
2

这里的 global 关键字,并不表示重新创建了一个全局变量 MIN_VALUE,而是告诉 Python 解释器,函数内部的变量 MIN_VALUE,就是之前定义的全局变量,并不是新的全局变量,也不是局部变量。这样,程序就可以在函数内部访问全局变量,并修改它的值了。

另外,如果遇到函数内部局部变量和全局变量同名的情况,那么在函数内部,局部变量会覆盖全局变量,而当退出到函数外部时,该变量还是定义全局变量时的值。

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check():
    MIN_VALUE = 3
    print(MIN_VALUE) 
    
validation_check()
3
print(MIN_VALUE)
1

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

闭包

闭包其实和刚刚讲的嵌套函数类似,不同的是,这里外部函数返回的是一个函数,而不是一个具体的值。返回的函数通常赋于一个变量,这个变量可以在后面被继续执行调用。

  • 使用闭包的一个原因,是让程序变得更简洁易读。
# 不使用闭包
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)
...
  • 其次,闭包使得局部变量在函数外被访问成为可能,避免了使用全局变量。比如下面的例子中,一般情况下,函数中的局部变量仅在函数的执行期间可用,一旦 print_msg() 执行过后,我们会认为 msg变量将不再可用。然而,在这里我们发现 print_msg 执行完之后,在调用 another 的时候 msg 变量的值正常输出了,这就是闭包的作用。这里的 another 就是一个闭包,闭包本质上是一个函数,它由两部分组成,printer() 函数和变量 msg。闭包使得变量的值始终保存在内存中。
def print_msg(): # 外层函数
    msg = "zen of python"
    def printer(): # 内层函数        
        print(msg)
    printer()
    
print_msg()
# 输出
zen of python

# 使用闭包
def print_msg(): # 外层函数
    msg = "zen of python"
    def printer(): # 内层函数
        print(msg)
    return printer # 内部函数 printer() 直接作为返回值

another = print_msg()
another()
  • 还有,闭包允许将函数与其所操作的某些数据(环境)关连起来。这一点与面向对象编程是非常类似的,在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。一般来说,当对象中只有一个方法时,这时使用闭包是更好的选择,比用类来实现更优雅。
def adder(x):
    def wrapper(y):
        return x + y
    return wrapper

adder5 = adder(5)
adder5(10)
15
adder5(6)
11
  • 和上面讲到的嵌套函数优点类似,当函数开头需要做一些额外工作,而你又需要多次调用这个函数时,将那些额外工作的代码放在外部函数,就可以减少多次调用导致的不必要的开销,提高程序的运行效率。

  • 另外,闭包还常常和装饰器(decorator)一起使用。

小结

  1. Python 中函数的参数可以接受任意的数据类型,使用起来需要注意,必要时请在函数开头加入数据类型的检查;
  2. Python 中函数的参数可以设定默认值;
  3. 嵌套函数的使用,能保证数据的隐私性,提高程序运行效率;
  4. 合理地使用闭包,则可以简化程序的复杂度,提高可读性。

参考

《Python核心技术与实战》

一步一步教你认识Python闭包:https://zhuanlan.zhihu.com/p/26934085

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值