由浅到深 理解Python 装饰器

2 篇文章 0 订阅
1 篇文章 0 订阅

作为一名自学Python的小白,在学习装饰器上面也算走了不少的坑,简单把学习的路径跟大家分享。

深入理解装饰器大致可以分为以下12个步骤:

1.函数

在Python中,使用def 和函数名以及可选参数列表来定义函数。函数使用用return返回值:

简单的例子如下:


2.作用域

在Python函数中会创建一个新的作用域。即函数自己的命名空间。换句话说,当在函数中遇到变量是,Python会首先在该函数的命名空间中寻找变量名。Python 有几个函数可以用来查看命名空间。

简单的例子如下:

运行结果:

{ . . . , 'temp_string' : 'This is a global variable' }

{ }

内建函数globals 返回一个包含所有Python能识别变量的字典为了能够更加清楚的描述,输出是省略了Python自动创建的变量。helloworld()函数的输出时候一个空的字典,证明函数helloworld有自己单独的、此时为空的命名空间。

3.变量解析规则

Python的作用域规则是,变量的创建总是会创建一个新的局部变量但是变量的访问在局部作用域查找然后是整个外层作用域来寻找匹配。所以如果修改helloworld函数来打印全部变量,结果将是我们希望的那样:

>>>temp_string = "This is a global variable"
>>> def helloworld():
...     print temp_string # 1
>>> helloworld()
This is a global variable

在#1处,Python在函数foo中搜索局部变量temp_string,但是没有找到,然后继续搜索同名的全局变量。

另一方面,如果尝试在函数里给全局变量赋值,结果并不是我们想要的那样:

>>>temp_string = "This is a global variable"
>>> def helloworld():
...     print temp_string # 1
>>> helloworld()
This is a global variable

{'temp_string':'test' }

>>>temp_string

'This is a global variable'

从上面可知,全部变量可以被访问,但是不能被赋值。在函数#1处,实际上是常见了一全局变量相容的名值和局部变量,并且“覆盖”了全局变量。

4.变量生命周期

值得注意的是,变量不仅是在命名空间中有效,他们也有生命周期。

我们来看下面的小例子:



运行结果:

Traceback ( most recent call last ) :
   . . .
NameError : name 'x' is not defined

这个问题不仅仅是因为作用域规则,业余Python和很多其他语言中的函数调用的实现有关。没有任何语法可以下该出取得变量新的值----它确确实实不存在!函数helloworld的命名空间在每次函数被调用时重新创建,在函数结束时销毁。

5.函数的实参和形参

Python允许向函数传递参数。形参名在函数里为局部变量。即函数的参数可以有名称和位置。也就是说着这其中的不同取决于是函数定义还是函数调用。可以对用位置形参定义的函数传递关键字实参,反过来也可行!

具体的小例子如下:

>>> def foo(x, y=0): # 1
...     return x - y
>>> foo(3, 1) # 2
2
>>> foo(3) # 3
3
>>> foo() # 4
Traceback (most recent call last):
  ...
TypeError: foo() takes at least 1 argument (0 given)
>>> foo(y=1, x=3) # 5
2


6.内嵌函数

Python允许创建内嵌函数。即可以在函数内部生命函数并且所有的作用域和生命周期规则仍然适用。



以上代码看起来有些复杂,但它仍是易于理解的。来看 #1 —— Python 搜索局部变量 x 失败,然后在属于另一个函数的外层作用域里寻找。变量 x 是函数 outer 的局部变量,但函数 inner 仍然有外层作用域的访问权限(至少有读和修改的权限)。在 #2 处调用函数 inner。值得注意的是,inner 在此处也只是一个变量名,遵循 Python 的变量查找规则——Python 首先在 outer 的作用域查找并找到了局部变量 inner

7.函数是Python中的一级对象

在Python中有个常识:函数和其他任何东西一样,都是对象。函数包含变量,他并不那么特殊。

>>> issubclass(int, object) # all objects in Python inherit from a common baseclass
True
>>> def foo():
...     pass
>>> foo.__class__ # 1
<type 'function'>
>>> issubclass(foo.__class__, object)
True


可能你从未考虑过函数可以有属性---但是函数在Python中,和其他任何东西一样都是对象。即在pyhon中函数知识常规的值,就像其他任意类型的值一样。这意味着可以将函数当属实参传递给函数,或者在函数中奖函数作为返回值返回,如果你从未向所这样使用,请看下面的可执行代码:

>>> def add(x, y):
...     return x + y
>>> def sub(x, y):
...     return x - y
>>> def apply(func, x, y): # 1
...     return func(x, y) # 2
>>> apply(add, 2, 1) # 3
3
>>> apply(sub, 2, 1)
1


从#3处可以看出在python中吧函数作为值传参并没有特别的语法,和其他便令一样,函数民就是变量标签。

可能是之前见过这种写法---Python使用函数作为实参,常见的操作如:通过传递一个函数给key参数,来自定义使用内奸函数sorted.但是,将函数作为值返回会怎样。请看下面的例子:

>>> def outer():
...     def inner():
...         print "Inside inner"
...     return inner # 1
...
>>> foo = outer() #2
>>> foo # doctest:+ELLIPSIS
<function inner at 0x...>
>>> foo()
Inside inner


这看起来可能会有点怪,在#1处返回一个其实是函数标签的变量inner,页没有什么特殊的原发函数outer返回看并没有被嗲用的函数inner.还记得变量的生命周期吗?每次调用函数outer的时候,函数inner 会被重新定义,但是如果函数outer没有返回inner,当inner超出outer的作用域,inner的生命周期将结束。

8.闭包

先看一个小例子:

>>> def outer():
...     x = 1
...     def inner():
...         print x # 1
...     return inner
>>> foo = outer()
>>> foo.func_closure # doctest: +ELLIPSIS
(<cell at 0x...: int object at 0x...>,)

Python中的一切东西都按照作用于规则运行--- x是函数outer中的一个局部变量,当函数inner在#1处打印x时,Python在inner中搜索局部变量但是没有找到,然后在外层作用域及函数outer中搜索找到了变量x.

但如果从变量的生命周期角度来看应该如何呢?变量x对函数outer来说数局部变量,即只有当outer运行时它才存在。只有当outer返回后才能调用inner,所以一句Python运行机制,在调用inner时x就应该不存在了,那么这里应该有某种运行错误出现。

结果并非如此,返回的inner函数正常运行。Python支持一种名为函数闭包的特性,意味着在非全局作用域定义的inner含在在定义时记得外层命名空间是怎样的。inner函数报喊了外层作用域变量,通过查看他的func_closure属性可以看出这种函数闭包特性。

记住---每次调用outer时,函数inner都会被重新定义。此时x的值没有变化,所以返回的每个inner函数和其他的inner函数运行结果相同,但是如果沙宏伟做一点修改呢?

>>> def outer(x):
...     def inner():
...         print x # 1
...     return inner
>>> print1 = outer(1)
>>> print2 = outer(2)
>>> print1()
1
>>> print2()
2


从这个示例可以看到闭包---函数记住其外层作用域的事实---可以用来构建本质上有一个硬编码参数的自定一函数,虽然没有直接给inner函数传参1或者2,但是构建了能“记住”该打印什么书的inner函数自定义版本。


闭包是强大的技术---在某些方面来看可能感觉它有点像面向对象技术:outer作为inner的构造函数,有一个类似私有变量的x.闭包的作用不再多说。但是这么用闭包太没意思了,让我们开始写装饰器。

9.装饰器

装饰器其实就是一个一函数作为参会并返回一个替换函数的可执行函数。让我们从简单的开始,直到能写出实用的装饰器。


运行结果:
before some _func
2

看出来什么了吗?首先我们定义了一待单个参数some_func的名为outer的函数,然后在outer内部定义了一个内嵌函数inner。inner函数将打印一行字符串然后调用some_func,并获取返回值。在每次偶尔被调用时,some_func的值可能都会不同,但不论some_func是什么函数,都将调用它。最后,inner返回some_func()的返回值加1.

我们可以说decorated是foo的装饰版----即foo加上一些东西。事实上,如果写了一个实用的装饰器,可能会想到用装饰器版来代替foo,这样就总能得到“附带其他东西”的foo版本。用不着学习任何新的语法,通过将包含函数的变脸重新复制就能轻松做到这一点:

>>> foo = outer(foo)
>>> foo # doctest: +ELLIPSIS
<function inner at 0x...>

现在任意调用foo()都不会得到原来的foo,而是新的装饰器版。

10:函数装饰器@符号的应用

别的不多说,我们先看一个例子:


Python通过在函数定义前添加一个装饰器名和@符号,来实现对函数的包装。这种模式可以随时用来包装任意函数。使用@可以随时用来包装任意函数,使其看起来更加明确。

11.args和*kwargs

上面我们利用语法糖写了一个简单的装饰器,但是它只适用于特定类型的函数---不带参数的函数。如何定义多个参数,并继续将参数传递给闭包中的函数呢。如何实现一个为每次被装饰函数的调用添加内容,但不改变不装饰的函数的装饰器呢?如何让装饰器接收它装饰的任何函数的调用信息,并且在代用这些函数时传递给该装饰器的任何参数都传递给他们呢?恰巧,Python对这种特性提供了语法支持,请先去阅读Python Tutorial以做详细的了解。

不多说,直接看代码:


这里*args可以表示在调用函数从迭代器中取出位置参数,也可以表示在定义函数是接收额外的位置参数。**kwargs表示所有未捕获的关键字参数将会被存储在字典kwargs中。

12.在类中使用装饰器

类中使用装饰器与前面的使用并没有太大的差别,但是会让自己的函数显得更加的简洁。简单的例子分享给大家》

import sys

def propget(func):
	locals = sys._getframe(1).f_locals
	name = func.__name__
	prop = locals.get(name)

	if not isinstance(prop,property):
		prop = property(func,doc = func.__doc__)
	else:
		doc = prop.__doc__ or func.__doc__
		prop = property(func,prop.fsert,prop.fdel,doc)
	return prop

def propset(func):
	locals = sys._getframe(1).f_locals
	name = func.__name__
	prop = locals.get(name)
	if not isinstance(prop,property):
		prop = property(None,func,doc = func.__doc__)
	else:
		doc = prop.__doc__ or func.__doc__
		prop = property(prop.fget,func,prop.fdel ,doc)
	return prop

def propdel(func):
	locals = sys._getframe(1).f_locals
	name = func.__name__
	prop = locals.get(name)
	if not isinstance(prop,property):
		prop = property(None,None,func,doc = func.__doc__)
	else:
		prop = property(prop.fget,prop.fset,func,prop.__doc__)
	return prop

#These can be used like this
class Example(object):
	@propget
	def myattr(self):
		return self._half * 2

	@propset
	def myattr(self,value):
		self._half = value / 2

	@propdel
	def myattr(self):
		del self._half
也许你想学习更多的内容有兴趣的可以参看 https://wiki.python.org/moin/PythonDecoratorLibrary
最后,你可以去研究一下内建包装函数 functools,它是一个在装饰器中用来修改替换函数签名的装饰器,使得这些函数更像是被装饰的函数。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值