函数进阶知识
函数名只是一个指向函数的变量
在python中,一切皆对象。函数名只是一个指向函数的变量,为了验证这一点,我们可以查看修改和删除函数名对程序的影响:
def someFunc(): # someFunc只是一个变量名,它指向了我们创建的函数
print("Now in a function")
if __name__ == '__main__':
anotherFunc = someFunc # 用另一个变量名指向刚创建的函数
anotherFunc() # 输出Now in a function
# 删除了一个指向函数的变量名,对函数本身并没有影响;
# 因为还有一个变量anotherFunc指向函数,因此函数本体并不会被回收
del someFunc
anotherFunc() # 还是输出Now in a function
可以看到,在python中函数名和一个普通变量一样,只不过它指向了一组语句。
嵌套函数和变量作用域
在函数内是可以定义另一个函数的,这叫做嵌套函数。作用域是程序运行时变量可被访问的范围,定义在函数内的变量是局部变量,局部变量的作用范围只能是函数内部范围内,它不能在函数外引用。根据这个原理,对于嵌套函数来说,在内层的函数可以访问外层函数定义的变量。
如下例所示:
def nested_func():
msg = "This is a message"
# 在函数内部定义一个函数,打印消息
def print_message():
# 在嵌套函数内层可以访问外层函数的变量
print(msg)
# 调用打印消息的函数
print_message() # output: This is a message
if __name__ == '__main__':
nested_func()
将函数作为返回值
函数由于是一个对象,它是可以作为另一个函数的返回值或者作为参数传递给其他函数。
def nested_func(msg):
# 在函数内部定义一个函数,打印消息
def print_message():
# 在嵌套函数内层可以访问外层函数的变量
print(msg)
return print_message
if __name__ == '__main__':
msg = "This is a message"
aFunction = nested_func(msg) # 将aFunction指向了nested_Func(msg)的返回值,即print_message
aFunction() # 等同于调用了print_message()
这里将返回的函数赋值给另一个函数,这样可以实现对内部函数从外部进行传值。
闭包(Closure)
什么是闭包
两个嵌套的函数,内部函数使用外部函数的变量。这套变量+内部函数整体,就叫做闭包。
闭包避免了使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关连起来。这一点与面向对象编程是非常类似的,在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。
为什么需要闭包
刚才提到过,闭包可以实现对内部函数从外部进行传值。实现这种功能,有两套方案:使用类和使用闭包。使用闭包的方案我们前面已经介绍过,我们下面看看如何用类实现这个功能:
class someClass(object):
def __call__(self, msg):
print(msg)
if __name__ == '__main__':
msg = "This is a message"
aFunction = someClass()
aFunction(msg) # 输出 This is a message
这里就实现了和上面用嵌套函数一样的功能,在这里这个aFunction
虽然是一个实例对象,但是我们定义了__call__
方法之后,它也可以像函数一样被调用。
这种实现方式的缺陷在于,python3的类默认会继承object(Python2 略有不同,继承了object的叫做新式类,没有继承的叫做经典类),它里面自带了一系列我们用不到的方法,会占用相对较大的空间。
print(dir(aFunction))
# 输出结果:
# ['__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
而闭包则更加精简,占用的空间更小。
装饰器的内部执行原理
什么是装饰器
装饰器就是一种闭包,只是这个闭包的函数名等于原先的函数名,从而改变了原先函数的指向。
def my_decorator(func):
def print_msg():
print("new message has been added!")
func()
return print_msg
def plain_print_msg():
print("entering plain print msg")
if __name__ == '__main__':
# plain_print_msg作为实参被传给my_decorator
# aFunc指向my_decorator的返回值,也就是print_msg
aFunc = my_decorator(plain_print_msg)
# 相当于在指向print_msg,其内部的func = plain_print_msg
aFunc()
print("-" * 10)
# 将原先的函数名plain_print_msg直接指向my_decorator的返回值
# 可以看到和上面部分的代码实现的功能是相同的
plain_print_msg = my_decorator(plain_print_msg)
plain_print_msg()
可以看到,执行下面两行代码之后,函数名称并没有变, 但是却指向了一个新的函数,在新的函数内,除了执行原函数之外,还加了一些其他的功能,这就是装饰器的实现原理。
在实际使用装饰器时,我们并不需要手动修改函数名的指向,而是可以由@
运算符来进行代劳,上面的代码可以改写如下:
def my_decorator(func):
def print_msg():
print("new message has been added!")
func()
return print_msg
@my_decorator
def plain_print_msg():
print("entering plain print msg")
if __name__ == '__main__':
plain_print_msg()
这里的@my_decorator
和我们上面手动实现的代码功能是一样的。
为什么需要使用装饰器
那么在开发过程中进行功能扩充时,为什么不直接修改函数源码,而要使用装饰器呢?
开放封闭
原则:已经实现的功能代码不允许修改,但是可以被扩展。也即已经实现的功能代码块封闭,而对扩展开发开放。这是因为在比较大的系统中,可能有很多模块使用同一个函数,如果为了修改功能,直接对函数进行修改,那么其他地方对函数的引用可能会出错。而使用了装饰器之后,可以在装饰器内梳理代码逻辑,这样不该动其他地方的代码也可以正常加上新功能。
装饰器什么时候开始生效的
装饰器的生效不会等到函数进行调用,而是在解释器读到装饰符开始,就会对函数进行装饰。为了说明这一点,我们可以看下面这一段代码:
def my_decorator(func):
print("entering my decorator!")
def print_msg():
print("new message has been added!")
func()
return print_msg
@my_decorator
def plain_print_msg():
print("entering plain print msg")
if __name__ == '__main__':
pass # 注意这里并没有执行函数调用
输出结果:
entering my decorator!
可以看到函数并没有被调用,但是装饰器已经被加装在函数之上了。
对带参数的函数进行装饰
通过上面对函数的学习,我们已经知道装饰器的工作原理了,因此如果需要对带参数的函数进行装饰,无非就是在闭包的内层函数上加上相应的参数。示例如下:
import time
def time_wrapper(func):
def timer(cnt):
start_time = time.time()
func(cnt)
end_time = time.time()
print("time consumed by %s: %s second" % (func.__name__, str(end_time - start_time)))
return timer
@time_wrapper
def count1(cnt):
lst = []
for i in range(cnt):
lst.insert(0, i)
@time_wrapper
def count2(cnt):
lst = []
for i in range(cnt):
lst.append(i)
if __name__ == '__main__':
count1(10000)
count2(10000)
对有返回值的函数进行装饰
在了解了装饰器的原理之后,我们就应该知道如果原函数带有返回值的话,我们需要在装饰器的什么地方进行返回了:
def my_decorator(func):
print("entering my decorator!")
def print_msg():
print("new message has been added!")
return func() # 将函数的返回值作为返回值
return print_msg
@my_decorator
def plain_print_msg():
print("entering plain print msg")
return "OK" # 函数带有返回值,指示打印状态
if __name__ == '__main__':
# 这里给的实际上是print_msg()的返回值,也就是func()
# 形参func = plain_print_msg,因此返回值和原函数plain_print_msg的返回值是相同的
ret = plain_print_msg()
print(ret)
多个装饰器装饰同一个函数
当有多个装饰器装饰同一个函数时,最靠近函数的(位于程序最下端的)装饰器最先装饰,然后依次由近及远进行装饰。而调用闭包时,由远及近进行调用。
def decorator1(func):
print("entering decorator1")
def add_msg():
print("using decorator1: add_msg")
func()
return add_msg
def decorator2(func):
print("entering decorator2")
def add_another_msg():
print("using decorator2: add_another_msg")
return add_another_msg
@decorator1 # 距离函数较远的装饰器后装饰,先调用(包在外层)
@decorator2 # 距离函数较近的装饰器先装饰,后调用(包在内层)
def plain_print_msg():
print("entering plain print msg")
return "OK" # 函数带有返回值,指示打印状态
if __name__ == '__main__':
plain_print_msg()
输出的结果:
entering decorator2
entering decorator1
using decorator1: add_msg
using decorator2: add_another_msg
用类实现装饰器
明白了装饰器的实现原理之后,就可以知道用类也是可以实现装饰器的。这就是我们之前讲的用类实现闭包功能的@
装饰符版本。
class Test():
def __init__(self, func):
self._func = func
def __call__(self, *args, **kwargs):
print("----调用装饰器----")
self._func(*args, **kwargs)
@Test
def my_fun(a, b, c):
print(a, b, c)
if __name__ == '__main__':
my_fun(10, 20, 'main')
输出结果:
----调用装饰器----
10 20 main
带参数的装饰器
当装饰器带有参数时,调用过程分为两步:
- 调用装饰器函数(闭包外层的函数)并将参数作为实参传递
- 用上一步的返回值当作装饰器对函数进行装饰
因此需要给装饰器加上参数时,我们要对闭包进行再一层包装,在最外层传递入装饰器中传进来的参数。
一个对操作进行权限检查的例子如下:
def get_level(level):
def level_check_wrapper(func):
def level_check():
print("正在检查权限:")
if level == 1:
print("权限等级为1")
return func()
elif level == 2:
print("权限等级为2")
return func()
return level_check
return level_check_wrapper
@get_level(1)
def low_level_op():
print("低级权限操作")
print([i for i in range(5)])
@get_level(2)
def high_level_op():
print("高级权限操作")
print([item for item in "abcdefgh"])
if __name__ == '__main__':
low_level_op()
high_level_op()
网友评论