网易游戏面试经验(二)
前言
在之前我写过一篇博客,讲述了python的闭包和装饰器,python的装饰器一直是面试热点,也是python很重要的特性之一,不过我认为闭包是装饰器的基础,比装饰器拥有更宽广的概念和作用,所以如果面试官问我关于装饰器的问题,我都会从闭包的角度去分析装饰器,关于这个大块面试官问了我如下几个问题:什么是装饰器?什么是闭包?闭包和装饰器的作用?闭包和装饰器的使用场景?下面从这几个问题出发,在前一篇博客的基础之上更加深刻的探讨关于闭包和装饰器的知识
正文
关于闭包的概念,我从网上找了如下几种说法:
- This technique of using the values of outside parameters within a dynamic function is called closures
- a closure is simply a function with free variables, where the bindings for all such variables are known in advance
- a closure is a function (object) that remembers its creation environment (enclosing scope)
- This technique by which some data gets attached to the code is called closure in Python
- A closure occurs when a function has access to a local variable from an enclosing scope that has finished its execution
上面的话语基本涵盖了闭包的特性,关于这些语句的理解,我还是先列举一个非常简单的例子:
# coding="utf-8"
def fun(msg):
def nested_fun():
print msg
return nested_fun
new_fun = fun("i like python")
new_fun()
上面的代码首先定义了一个函数fun,在这个函数之中再定义了一个嵌套函数,之后返回了这个函数,在这个函数之外,重新将fun函数包括msg参数赋值给了new_fun,之后执行new_fun()函数,运行这段代码,可以发现在console中打印出了i love python这句话。上面的代码有两个注意点:1.函数也是一种对象,可以作为参数返回和传递;2.new_fun=fun("i like python")这句代码其实是将new_fun指向了fun函数的返回值nested_fun函数,在执行new_fun函数的时候其实在执行了nested_fun函数,在这个函数中打印出的msg对象其实来自于上层函数,并不是自己的本地变量,为什么还会打印出来呢?所以这就是闭包,根据这个例子可以很好的体会上面列出的几种说法。
在python中闭包所满足的条件为:
- 必须拥有嵌套函数
- 嵌套函数调用定义在闭包函数里面的值
- 闭包函数必须返回嵌套函数
装饰器和闭包可以实现一定的封装功能,将函数或者变量封装起来,同时利用他们可以减少代码冗余,减少一些重复工作量。下面举一个例子,如果现在我们想实现一个简单的乘法函数,比如times2(n)函数就是给n*2,times5(n)实现给n乘以5,这个功能可以使用默认值很容易的实现,代码如下:
def times2(n, x=2):
return n * x
print times2(3)
但是这有个明显的缺点,每个times()函数都需要进行定义细节,现在可以用闭包来实现,代码如下:
def multiplier(n):
def multi(x):
return x * n
return multi
times2 = multiplier(2)
times5 = multiplier(5)
print times2(4)
print times5(4)
这段代码的美妙大家可以好好体会一下。
说完了闭包,再说一说装饰器,装饰器就是用函数或者类去封装(装饰)另一个对象,装饰器支持和被封装的对象相同的接口,所以被装饰的对象甚至感觉不到自己被装饰了,在python中,装饰器更多的是一个函数将另一个函数作为参数,他会利用闭包来实现整个装饰功能,和上面举得闭包的例子不同的是,装饰器利用的是闭包可以捕获函数。
装饰器的常用场景如下:
- 注入参数
- 记录函数行为
- 预处理/后处理
- 修改调用时的上下文
下面简单的列举一个记录函数行为的装饰器的使用:
def logging(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print func.__name__, args, kwargs
return res
return wrapper
@logging
def fib(n):
if n == 1 or n == 0:
return 1
return fib(n-1) + fib(n-2)
print fib(10)
上面的装饰器代码实现了打印被装饰的函数的执行情况,大家可以自己运行。
总结
语言的美会得到认可和传播,比如lamdba函数,了解了语言的特性之后,可以极大的提高工作效率,增加代码的美感,提高代码的健壮性。