python函数装饰器的实现_python 函数的深入理解与装饰器实现

函数装饰与语法糖 @ 在说函数的装饰之前,先说说内嵌函数的运行机制。 内嵌函数 内嵌函数就是在函数中定义的函数。 内嵌函数,除非明确的调用,它是不会按照函数中的代码顺序运行的。比如下面的代码中 def foo(): def bar(): print "bar() is running" print

函数装饰与语法糖 @

在说函数的装饰之前,先说说内嵌函数的运行机制。

内嵌函数

内嵌函数就是在函数中定义的函数。

内嵌函数,除非明确的调用,它是不会按照函数中的代码顺序运行的。比如下面的代码中

def foo():

def bar():

print "bar() is running"

print "foo() is running"

运行上述代码,不会输出 "bar() is running" ,原因就是内嵌函数必须要在函数中显式的调用,否则它不会按照脚本语言的规则,按顺序执行的。

下面是上述代码的修改版:

def foo():

def bar():

print "bar() is running"

bar()

print "foo() is running"

运行上面的代码,得到的输出结果如下:

>>> foo()

bar() is running

foo() is running

内嵌函数,作用域仅限于它定义所在的外层函数。

所以,不能从外部被调用。下面的调用就会报错。

>>> bar()

Traceback (most recent call last):

File "", line 1, in

NameError: name 'bar' is not defined

>>> foo().bar()

foo() is running

Traceback (most recent call last):

File "", line 1, in

AttributeError: 'NoneType' object has no attribute 'bar'

函数的理解与装饰

在 python 中,函数也是对象,既然是对象,那么就可以传递、赋值给其他变量并被调用。

这就是函数能够被装饰的基础。装饰一个函数,这个时候,内嵌函数就派上了用场了。

通常情况下,我们直接调用一个函数。现在假设我们想要在函数调用的前后打印两个日志,记录这个函数被调用了。一般的方法就是在函数的入口和出口调用两次打印语句,这样做也行,但是如果我们不是一个函数,而是几个,几十个呢?

所以,如果一个函数 foo() 传递给另外一个函数 bar(),函数 bar() 做的事是打印一条入口日志,然后调用 foo(),然后再打印一条出口日志,这样来说,不就行了么?即 bar(foo),那么实现的代码就可以这样写:

def bar(func):

print "before call %s" % func.__name__

func()

print "after call %s" % func.__name__

def foo():

print "called foo()"

上面的代码就可以采用 bar(foo) 来调用,实现了打印日志的目的。那么我们在我们已经调用了 foo() 的地方,是不是都要改成 bar(foo) 的形式呢?

这样做也太脏了。那么是不是我们在 bar() 中加一个返回,赋值给被传入的函数,是不是就行了呢?先看下面的代码,和上面相比,只是在 bar() 中增加了一个返回。

def bar(func):

print "before call %s" % func.__name__

func()

print "after call %s" % func.__name__

return func

def foo():

print "called foo()"

我们使用如下调用方式:

foo = bar(foo)

foo()

不起作用!单独调用的 foo() 并没有按照我们期望的那样,在输出 foo() 的内容前先输出 bar() 的两个打印语句。问题出在哪里?

分析上面的代码,可以看出来,在 bar() 中的 return 语句,返回的 func 在 bar() 中的传入、传出都没有一点改变,所以上述方法不起作用。

也就是说,如果要使一个函数 foo() 传入另外一个函数 bar(),返回时函数 foo() 被修改了,就要求 bar() 中的代码能够修改 foo() 的运行形式,在返回时 foo() 已经不是原来传入时的 foo() 了,而是被修改过的函数了。

自然的(其实是不自然的,看了书和网上的代码才知道的)我们就想到了内嵌函数,我们在内嵌函数中调用传入的函数,并且返回内嵌函数,这样就达到了修改传入函数的的目的。看下面的例子:

def bar(foo):

def wrap():

print "before call %s" % func.__name__

foo()

print "after call %s" % func.__name__

return wrap

def foo():

print "called foo()"

现在我们再使用如下的调用方法:

foo = bar(foo)

foo()

输出如下内容

before call foo

called foo

after call foo

现在,我们看到,我们修改函数的运行方式的目的已经达到了,那么,我们是不是要在每一个我们需要进行 bar() 修改的地方都来一句类似于 foo = bar(foo) 的调用呢?

不是的,我们有语法糖 "@" 符号!使用上面的例子,我们可以使用下面的方法来修改函数:

@bar

def foo():

print "called foo()"

这样的效果和上面的一致的。

稍微复杂点的例子(有参数)

先说一下 python 中实现变参的两种方式

python 中的变参

被装饰的函数如果自身是有参数的怎么办呢?如何来写一个通用的可处理参数的包装函数?

python 中提供了两种可变参数, *args, **kwargs,形参名字随便取的,不过为了简洁与明晰表达意思,所以一般都用 args, kwargs。

*args 表示的是:无关键字的,参数长度可变的参数列表(非python 意义上的list());

**kwargs 表示的是:有关键字的,参数长度可变的参数列表(非python 意义上的list());

通俗点说,一个函数定义了 *args 这样的参数,那么就可以直接传递值或者一个列表的值进去;如果定义了 **kwargs 这样的参数,就可以传递类似字典参数进去。

比如有下面的函数:

def foo(*args, **kwargs):

for arg in args:

print arg

for k,v in kwargs.items():

print k,v

调用方法就可以使用:

foo(1, 2, 3, d = 4, e = 5, f = 6)

args = [1, 2, 3]

kwargs = {"d":4, "e":5, "f":6}

foo(*args, **kwargs)

foo(*args)

foo(**kwargs)

foo(1, 2) #args

foo(d = 4, e = 5, f = 6) # kwargs

上面就是可变参数的实现和调用方法。

函数装饰器中的参数

我们要编写一个通用点的装饰器,就不能假设被装饰的函数的参数的是什么样的,所以就需要用到上述的两种变参方法。下面是修改后的例子:

def bar(foo):

def wrap(*args, **kwargs):

print "before call %s" % func.__name__

foo(*args, **kwargs)

print "after call %s" % func.__name__

return wrap

@bar

def foo(arg0 = None, arg1 = None):

print "called foo()", arg0, arg1

结语

理解装饰器的关键在于理解内嵌函数的运行机制以及 python 中函数也是对象这个事实。

装饰器主要用在哪些地方呢?都有些什么作用?python 自带的 property, staticmethond, classmethod 的使用场景以及作用是什么?请看本博接下来的文章。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值