python系列6:python中的装饰器和回调函数

1. 函数嵌套与装饰器

在python中,函数可以作为参数传递。装饰器可以很方便的给原函数增加一些功能。使用装饰器必须和内嵌包装函数一起。

简单来说在f2上@f1等价于执行f2 = f1(f2)

注意这里是f2而不是f2(),也就是说是函数名作为参数传递,不需要执行f2();而f1是f1(),所以f1在使用装饰器的地方相当于调用过一次。如果f1是一个类,那么相当于这里要调用一次构造函数__init__和回调函数__call___。
下面举几个例子:

1.1 单纯嵌套

def f1(g):
	print("in f1")
	return g

@f1
def f2():
	print("in f2")

f2()
f2()

在这个例子中@f1等价于f2 = f1(f2)= f2,此时打印了"in f1";接着执行两遍f2()就是打印两遍"in f2"。最终输出为:

in f1
in f2
in f2

1.2 内嵌包装函数,保证装饰器调用

def f1(g):
	print("in f1")
	def m():
		print("in m")
		return g()
	return m

@f1
def f2():
	print("in f2")

f2()
f2()

在这个例子中@f1等价于f2 = f1(f2)= m,此时打印了"in f1";接着执行两遍f2()等价于执行m(),而m()返回f2()。因此返回了两遍"in m in f2"。最终输出为:

in f1
in m
in f2
in m
in f2

1.3 装饰器带参数,需要两层内嵌函数

def f1(arg):
    print("in f1")
    def m(g):
        print("in m")
        def n():
            print(arg)
            print("in n")
            return g()
        return n
    return m

@f1("para")
def f2():
    print("in f2")

f2()
f2()

在这个例子中@f1(“para”)等价于f2 = f1(“para”)(f2)=m(f2)=n,此时打印了"in f1 in m";注意n()返回g(),因此接着执行两遍f2()等价于执行两遍n()+g()。这个例子用了2层包装函数,最终输出为:

in f1
in m
para
in n
in f2
para
in n
in f2

1.4 函数带参数,需要使用args列表

def f1(func):
    print("in f1")
    def m(a, b):
        print("in m")
        print(func(a, b))
    return m
 
@f1
def f2(a, b):
    print("f2(%s,%s) called." % (a, b))
    return a + b
 
f2(1, 2)
f2(3, 4)

这里,@f1表示f2=f1(f2)=m,因此f2(a,b)=m(a,b)。
如果参数列表个数不确定怎么办?那就需要用到*args或者**kargs了,前者将参数打包成tuple,后者打包成dict。下面是个例子:

def f1(func):
    print("in f1")
    def m(*args):
        print("in m")
        print(args)
        print(func(*args))
    return m
 
@f1
def f2(a, b):
    print("f2(%s,%s) called." % (a, b))
    return a + b

@f1
def f3(a, b, c):
    print("f3(%s,%s,%s) called." % (a, b, c))
    return a + b + c
 
f2(1, 2)
f3(3, 4, 5)

返回

in f1
in f1
in m
(1, 2)
f2(1,2) called.
3
in m
(3, 4, 5)
f3(3,4,5) called.
12

1.5 类作为装饰器

class f1:
    def __init__(self,para):
        self.para = para
        print("in __init__")

    def __call__(self,p):
        print ("in __call__")
        def m():
            print("in m")
            return p(self.para)
        return m
 
@f1("parameters")
def f2(s):
    print(s)
    print ("in f2")

f2()
f2()

在这里,@f1(“para”) 等价于f2= f1(“para”)(f2)=m,先执行f1的构造函数和回调函数。注意m返回f2(“para”),因此执行两遍f2()变为执行两遍m()+f2(“para”)

2. 回调函数和闭包

上面涉及到回调函数和闭包两个概念
回调函数是在每次调用类的时候启动,可以使实例像函数一样调用,比如f1是类f的一个实例,那么f1(a,b)等于f1.__call__(a,b)。
python系列6:python中的装饰器和回调函数_第1张图片
回调实际上有两种:阻塞式回调和延迟式回调。两者的区别在于:阻塞式回调里,回调函数的调用一定发生在起始函数返回之前;而延迟式回调里,回调函数的调用有可能是在起始函数返回之后。

闭包指的是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。简单来说,当定义内部嵌套函数时,返回的函数参数会自动定义__closure__属性,里面定义了一个元组用于存放所有的外面变量。

2.1 最简单的闭包例子

先看一个最简单的例子:

def f(a,b):
    def m():
        return a+b
    return m

a = f(4,5)
for c in  a.__closure__:
    print(c.cell_contents)

f中嵌套了函数m,因此返回函数赋值给a后,有了__closure__参数列表,其值等于内部函数m引用的外部函数f的参数列表的值,结果为:

4
5

2.2 装饰器和闭包

拿1.1节的例子来做示例:

def f1(g):
    def m():
        print("in m")
        return g()
    return m

@f1
def f2():
    print("in f2")

f2.__closure__[0].cell_contents()

@f1等价于执行f2 = f1(f2),f1内部有一个嵌套函数,因此f1(f2)返回的函数m有了__closure__参数列表,第一个参数的值是原始的f2函数。最后输出

in f2

2.3 带参数的装饰器和闭包

以1.3节的例子为基础:

def f1(arg):
    def m(g):
        def n():
            k = arg
            return g()
        return n
    return m

@f1("para")
def f2():
    print("in f2")

print(f2.__closure__[0].cell_contents)
f2.__closure__[1].cell_contents()

这里n是最内部嵌套函数,形成了一个闭包。@f1(“para”)返回n的clusure函数包含了arg和g两个参数。上面的例子输出为:

para
in f2

2.4 回调函数和闭包

最后来看回调函数的闭包。以1.5节为基础例子:

class f1:
    def __init__(self,para):
        self.para = para

    def __call__(self,p):
        def m():
            return p(self.para)
        return m
 
@f1("parameters")
def f2(s):
    print(s)
    print ("in f2")

f2.__closure__[0].cell_contents("abc")
print(f2.__closure__[1].cell_contents.para)

f1在回调的时候包含了嵌套函数,因此在执行f2= f1(“para”)(f2)的时候,传入了p和self两个参数(注意p在前面),因此输出为:

abc
in f2
parameters

你可能感兴趣的:(python系列)