登录  | 加入社区

黑狼游客您好!登录后享受更多精彩

只需一步,快速开始

新浪微博登陆

只需一步, 快速开始

查看: 941|回复: 0

教程|一步步分析轻松Python装饰器,只必要12步轻松搞定! ...

[复制链接]

364

主题

11

帖子

0

现金

黑狼菜鸟

Rank: 1

积分
24
发表于 2018-10-4 00:05:42 | 显示全部楼层 |阅读模式 来自 江苏徐州
vMgmQ8k8zPFKmFb7.jpg
要搞定装饰器必要你相识一些函数式编程的概念,固然另有明白在python中界说和调用函数相干语法的一些特点。
我没法让装饰器变得简朴,但是通过一步步的分析,我大概可以或许让你在明白装饰器的时间更自大一点。由于装饰器很复杂,这篇文章将会很长(本身都说很长,还敢这么多废话blablabla...前戏就不继承翻译直接省略了)
1. 函数

在python中,函数通过 def关键字、函数名和可选的参数列表界说。通过 return关键字返回值。我们举例来阐明怎样界说和调用一个简朴的函数:

  • >>> def foo():
  • ...     return 1
  • >>> foo()
  • 1
方法体(固然多行也是一样的)是必须的,通过缩进来表现,在方法名的背面加上双括号 ()就可以或许调用函数
2. 作用域

在python中,函数会创建一个新的作用域。python开辟者大概会说函数有本身的定名空间,差不多一个意思。这意味着在函数内部遇到一个变量的时间函数会优先在本身的定名空间内里去探求。让我们写一个简朴的函数看一下 当地作用域 和 全局作用域有什么差别:

  • >>> a_string = "This is a global variable"
  • >>> def foo():
  • ...     print locals()
  • >>> print globals()
  • {..., 'a_string': 'This is a global variable'}
  • >>> foo()
内置的函数 globals返回一个包罗全部python表明器知道的变量名称的字典(为了干净和洗的白白的,我省略了python自行创建的一些变量)。在#2我调用了函数 foo 把函数内部 当地作用域内里的内容打印出来。我们可以或许看到,函数 foo有本身独立的定名空间,固然临时定名空间内里什么都还没有。
3. 变量剖析规则

固然这并不是说我们在函数内里就不能访问表面的全局变量。在python的作用域规则内里,创建变量肯定会肯定会在当前作用域里创建一个变量,但是访问大概修改变量时会先在当前作用域查找变量,没有找到匹配变量的话会依次向上在闭合的作用域内里举行检察找。以是假如我们修改函数 foo的实现让它打印全局的作用域里的变量也是可以的:

  • >>> a_string = "This is a global variable"
  • >>> def foo():
  • ...     print a_string
在#1处,python表明器会实验查找变量 a_string,固然在函数的 当地作用域内里是找不到的,以是接着会去上层的作用域内里去查找。
但是另一方面,如果我们在函数内部给全局变量赋值,效果却和我们想的不一样:

  • >>> a_string = "This is a global variable"
  • >>> def foo():
  • ...     a_string = "test"
我们可以或许看到,全局变量可以或许被访问到(假如是可变数据范例(像list,dict这些)乃至可以或许被更改)但是赋值不可。在函数内部的#1处,我们现实上 新创建了一个局部变量, 隐蔽全局作用域中的同名变量。我们可以通过打印出局部定名空间中的内容得出这个结论。我们也能看到在#2处打印出来的变量 a_string的值并没有改变。
4. 变量生存周期

值得留意的一个点是,变量不但是生存在一个个的定名空间内,他们都有本身的生存周期,请看下面这个例子:

  • >>> def foo():
  • ...     x = 1
  • >>> foo()
  • >>> print x
1处发生的错误不但仅是由于 作用域规则导致的(只管这是抛出了NameError的错误的缘故原由)它还和python以及别的许多编程语言中函数调用实现的机制有关。在这个地方这个实行时间点并没有什么有用的语法让我们可以或许获取变量 x的值,由于它这个时间压根不存在!函数 foo的定名空间随着函数调用开始而开始,竣事而烧毁。
5. 函数参数

python答应我们向函数通报参数,参数会酿成当地变量存在于函数内部。

  • >>> def foo(x):
  • ...     print locals()
  • >>> foo(1)
  • {'x': 1}
在Python里有许多的方式来界说和通报参数,完备版可以检察 python官方文档。我们这里大略的阐明一下:函数的参数可以是必须的 位置参数大概是可选的 定名,默认参数

  • >>> def foo(x, y=0):
在#1处我们界说了函数 foo,它有一个位置参数 x和一个定名参数 y。在#2处我们可以或许通过通例的方式来调用函数,只管有一个定名参数,但参数依然可以通过位置通报给函数。在调用函数的时间,对于定名参数 y我们也可以完全不管就像#3地方示的一样。假如定名参数没有吸收到任何值的话,python会主动利用声明的默认值也就是 0。必要留意的是我们不能省略第一个位置参数 x, 否则的话就会像#5地方示发生错误。
现在还算简便清楚吧, 但是接下来大概会有点令人狐疑。python支持函数调用时的定名参数(个人以为应该是定名实参)。看看#5处的函数调用,我们通报的是两个定名实参,这个时间由于著名称标识,参数通报的次序也就不消在意了。
固然相反的环境也是精确的:函数的第二个形参是 y,但是我们通过位置的方式通报值给它。在#2处的函数调用 foo(3,1),我们把 3通报给了第一个参数,把 1通报给了第二个参数,只管第二个参数是一个定名参数。
桑不起,感觉用了好大一段才说清晰这么一个简朴的概念:函数的参数可以有 名称和 位置。这意味着在函数的界说和调用的时间会稍稍在明白上有点儿差别。我们可以给只界说了位置参数的函数通报定名参数(实参),反之亦然!假如以为不敷可以检察官方文档
6. 嵌套函数

Python答应创建嵌套函数。这意味着我们可以在函数内里界说函数而且现有的作用域和变量生存周期仍旧实用。

  • >>> def outer():
  • ...     x = 1
  • ...     def inner():
  • ...         print x
这个例子有一点儿复杂,但是看起来也还行。
想一想在#1发生了什么:python表明器需找一个叫 x的当地变量,查找失败之后会继承在上层的作用域内里探求,这个上层的作用域界说在别的一个函数内里。对函数 outer来说,变量 x是一个当地变量,但是如先条件到的一样,函数 inner可以访问封闭的作用域(至少可以读和修改)。
在#2处,我们调用函数 inner,非常紧张的一点是, inner也仅仅是一个遵照python变量剖析规则的变量名,python表明器会优先在 outer的作用域内里对变量名 inner查找匹配的变量.
7. 函数是python天下里的一级类对象

显而易见,在python里函数和其他东西一样都是对象。(此处应该高声歌唱)啊!包罗变量的函数,你也并不是那么特别!

  • >>> issubclass(int, object)
你大概从没有想过,你界说的函数居然会有属性。没办法,函数在python内里就是对象,和其他的东西一样,大概如许形貌会太学院派太官方了点:在python里,函数只是一些平凡的值而已和其他的值一毛一样。
这就是说你尅一把函数想参数一样通报给其他的函数大概说从函数了内里返回函数!假如你从来没有这么想过,那看看下面这个例子:

  • >>> def add(x, y):
  • ...     return x + y
  • >>> def sub(x, y):
  • ...     return x - y
  • >>> def apply(func, x, y):
这个例子对你来说应该不会很希奇。 add和 sub黑白常平凡的两个python函数,担当两个值,返回一个盘算后的效果值。
在#1处你们能看到预备吸收一个函数的变量只是一个平凡的变量而已,和其他变量一样。
在#2处我们调用传进来的函数:" ()代表着调用的操纵而且调用变量包罗的值。
在#3处,你们也能看到通报函数并没有什么特别的语法。"函数的名称只是很其他变量一样的表标识符而已。你们大概看到过如许的举动:"python把频仍要用的操纵酿成函数作为参数举行利用,像通过通报一个函数给内置排序函数的 key参数从而来自界说排序规则。那把函数当做返回值回事如许的环境呢:

  • >>> def outer():
  • ...     def inner():
  • ...         print "Inside inner"
  • ...     return inner
这个例子看起来大概会更加的希奇。在#1处我把恰恰是函数标识符的变量 inner作为返回值返回出来。这并没有什么特别的语法:"把函数 inner返回出来,否则它根本不大概会被调用到。"还记得变量的生存周期吗?每次函数 outer被调用的时间,函数 inner都会被重新界说,假如它不被当做变量返回的话,每次实行事后它将不复存在。
在#2处我们捕捉住返回值 - 函数 inner,将它存在一个新的变量 foo里。我们可以或许看到,当对变量 foo举行求值,它确实包罗函数 inner,而且我们可以或许对他举行调用。初次看起来大概会以为有点希奇,但是明白起来并不困难是吧。对峙住,由于希奇的迁移转变立刻就要来了(嘿嘿嘿嘿,我笑的并不猥琐!)
8. 闭包

我们先不急着界说什么是闭包,先来看看一段代码,仅仅是把上一个例子简朴的调解了一下:

  • >>> def outer():
  • ...     x = 1
  • ...     def inner():
  • ...         print x
在上一个例子中我们相识到, inner作为一个函数被 outer返回,生存在一个变量 foo,而且我们可以或许对它举行调用 foo()。不外它会正常的运行吗?我们先来看看作用域规则。
全部的东西都在python的作用域规则下举行工作:" x是函数 outer里的一个局部变量。当函数 inner在#1处打印 x的时间,python表明器会在 inner内部查找相应的变量,固然会找不到,以是接着会到封闭作用域内里查找,而且会找到匹配。
但是从变量的生存周期来看,该怎么明白呢?我们的变量 x是函数 outer的一个当地变量,这意味着只有当函数 outer正在运行的时间才会存在。根据我们已知的python运行模式,我们没法在函数 outer返回之后继承调用函数 inner,在函数 inner被调用的时间,变量 x早已不复存在,大概会发生一个运行时错误。
千万没想到,返回的函数 inner居然可以或许正常工作。Python支持一个叫做 函数闭包的特性,用人话来讲就是,嵌套界说在 非全局作用域内里的函数可以或许记着它在被界说的时间它所处的封闭定名空间。这可以或许通过检察函数的 func_closure属性得出结论,这个属性内里包罗封闭作用域内里的值(只会包罗被捕获到的值,好比 x,假如在 outer内里还界说了其他的值,封闭作用域内里是不会有的)
记着,每次函数 outer被调用的时间,函数 inner都会被重新界说。如今变量 x的值不会变革,以是每次返回的函数 inner会是同样的逻辑,如果我们轻微改动一下呢?

  • >>> def outer(x):
  • ...     def inner():
  • ...         print x
从这个例子中你可以或许看到 闭包 - 被函数记着的封闭作用域 - 可以或许被用来创建自界说的函数,本质上来说是一个 硬编码的参数。究竟上我们并不是通报参数 1大概 2给函数 inner,我们现实上是创建了可以或许打印各种数字的各种自界说版本。
闭包单独拿出来就是一个非常强盛的功能, 在某些方面,你大概会把它当做一个雷同于面向对象的技能: outer像是给 inner服务的构造器, x像一个私有变量。利用闭包的方式也有许多:你假如认识python内置排序方法的参数 key,你说不定已经写过一个 lambda方法在排序一个列表的列表的时间基于第二个元素而不是第一个。如今你说不定也可以写一个 itemgetter方法,吸收一个索引值来返回一个完善的函数,通报给排序函数的参数 key
不外,我们如今不会用闭包做这么low的事(⊙o⊙)…!相反, 让我们再爽一次,写一个高大上的 装饰器!
9. 装饰器

装饰器实在就是一个闭包,把一个函数当做参数然后返回一个替换版函数。我们一步步从简到繁来瞅瞅:

  • >>> def outer(some_func):
  • ...     def inner():
  • ...         print "before some_func"
  • ...         ret = some_func()
细致看看上面这个装饰器的例子。们界说了一个函数 outer,它只有一个 some_func的参数,在他内里我们界说了一个嵌套的函数 inner。 inner会打印一串字符串,然后调用 some_func,在#1处得到它的返回值。在 outer每次调用的时间 some_func的值大概会不一样,但是不管 some_func的之怎样,我们都会调用它。末了, inner返回 some_func() + 1的值 - 我们通过调用在#2处存储在变量 decorated内里的函数可以或许看到被打印出来的字符串以及返回值 2,而不是盼望中调用函数 foo得到的返回值 1
我们可以以为变量 decorated是函数 foo的一个装饰版本,一个增强版本。究竟上假如计划写一个有效的装饰器的话,我们大概会想乐意用装饰版本完全代替原先的函数 foo,如许我们总是会得到我们的"增强版" foo。想要到达这个结果,完全不必要学习新的语法,简朴地赋值给变量 foo就行了:

  • >>> foo = outer(foo)
  • >>> foo
如今,任何怎么调用都不会牵涉到原先的函数 foo,都会得到新的装饰版本的 foo,如今我们照旧来写一个有效的装饰器。
想象我们有一个库,这个库可以或许提供雷同坐标的对象,大概它们仅仅是一些x和y的坐标对。不外惋惜的是这些坐标对象不支持数学运算符,而且我们也不能对源代码举行修改,因此也就不能直接参加运算符的支持。我们将会做一系列的数学运算,以是我们想要可以或许对两个坐标对象举行符合加减运算的函数,这些方法很轻易就能写出:
假如不巧我们的加减函数同时也必要一些界限查抄的举动那该怎么办呢?搞欠好你只可以或许对正的坐标对象举行加减操纵,任何返回的值也都应该是正的坐标。以是如今的盼望是如许:

  • >>> one = Coordinate(100, 200)
  • >>> two = Coordinate(300, 200)
  • >>> three = Coordinate(-100, -100)
  • >>> sub(one, two)
  • Coord: {'y': 0, 'x': -200}
  • >>> add(one, three)
  • Coord: {'y': 100, 'x': 0}
我们盼望在不更改坐标对象 one, two, three的条件下 one减去 two的值是 {x: 0, y: 0}, one加上 three的值是 {x: 100, y: 200}。与其给每个方法都加上参数和返回值界限查抄的逻辑,我们来写一个界限查抄的装饰器!

  • >>> def wrapper(func):
  • ...     def checker(a, b):
这个装饰器能想先前的装饰器例子一样举行工作,返回一个颠末修改的函数,但是在这个例子中,它可以或许对函数的输入参数和返回值做一些非常有效的查抄和格式化工作,将负值的 x和 y更换成 0。 
显而易见,通过如许的方式,我们的代码变得更加简便:将界限查抄的逻辑隔离到单独的方法中,然后通过装饰器包装的方式应用到我们必要举行查抄的地方。

别的一种方式通过在盘算方法的开始处和返回值之前调用界限查抄的方法也可以或许到达同样的目标。但是不可置否的是,利用装饰器可以或许让我们以最少的代码量到达坐标界限查抄的目标。究竟上,假如我们是在装饰本身界说的方法的话,我们可以或许让装饰器应用的更加有逼格。
10. 利用 @ 标识符将装饰器应用到函数

Python2.4支持利用标识符 @将装饰器应用在函数上,只必要在函数的界说前加上 @和装饰器的名称。在上一节的例子里我们是将本来的方法用装饰后的方法取代:

  • >>> add = wrapper(add)
这种方式可以或许在任何时间对恣意方法举行包装。但是假如我们自界说一个方法,我们可以利用 @举行装饰:
必要明确的是,如许的做法和先前简朴的用包装方法替换原有方法是一毛一样的, python只是加了一些语法糖让装饰的举动更加的直接明白和优雅一点。
11. args and *kwargs

我们已经完成了一个有效的装饰器,但是由于硬编码的缘故原由它只能应用在一类详细的方法上,这类方法吸收两个参数,通报给闭包捕捉的函数。假如我们想实现一个可以或许应用在任何方法上的装饰器要怎么做呢?再好比,假如我们要实现一个能应用在任何方法上的雷同于计数器的装饰器,不必要改变原有方法的任何逻辑。这意味着装饰器可以或许担当拥有任何署名的函数作为本身的被装饰方法,同时可以或许用通报给它的参数对被装饰的方法举行调用。
非常偶合的是Python恰好有支持这个特性的语法。可以阅读 Python Tutorial 获取更多的细节。当界说函数的时间利用了 *,意味着那些通过位置通报的参数将会被放在带有 *前缀的变量中, 以是:

  • >>> def one(*args):
  • ...     print args
第一个函数 one只是简朴地讲任何通报过来的位置参数全部打印出来而已,你们可以或许看到,在代码#1处我们只是引用了函数内的变量 args*args仅仅只是用在函数界说的时间用来表现位置参数应该存储在变量 args内里。Python答应我们订定一些参数而且通过 args捕捉其他全部剩余的未被捕获的位置参数,就像#2地方示的那样。
*操纵符在函数被调用的时间也能利用。意义根本是一样的。当调用一个函数的时间,一个用 *标记的变量意思是变量内里的内容必要被提取出来然后当做位置参数被利用。同样的,来看个例子:

  • >>> def add(x, y):
  • ...     return x + y
  • >>> lst = [1,2]
  • >>> add(lst[0], lst[1])
1处的代码和2处的代码所做的事变实在是一样的,在#2处,python为我们所做的事实在也可以手动完成。这也不是什么坏事, *args要么是表现调用方法大的时间额外的参数可以从一个可迭代列表中取得,要么就是界说方法的时间标记这个方法可以或许担当恣意的位置参数。
接下来提到的 **会稍多更复杂一点, **代表着键值对的参数字典,和 *所代表的意义相差无几,也很简朴对不对:

  • >>> def foo(**kwargs):
  • ...     print kwargs
  • >>> foo()
  • {}
  • >>> foo(x=1, y=2)
  • {'y': 2, 'x': 1}
当我们界说一个函数的时间,我们可以或许用 **kwargs来表明,全部未被捕捉的关键字参数都应该存储在 kwargs的字典中。如前所诉, argshe kwargs并不是python语法的一部门,但在界说函数的时间,利用如许的变量名算是一个不成文的约定。和 *一样,我们同样可以在界说大概调用函数的时间利用 **

  • >>> dct = {'x': 1, 'y': 2}
  • >>> def bar(x, y):
  • ...     return x + y
  • >>> bar(**dct)
  • 3
12. 更通用的装饰器

有了这招新的技能,我们随任意便就可以写一个可以或许记载下通报给函数参数的装饰器了。先来个简朴地把日记输出到界面的例子:

  • >>> def logger(func):
  • ...     def inner(*args, **kwargs):
请留意我们的函数 inner,它可以或许担当恣意数目和范例的参数并把它们通报给被包装的方法,这让我们可以或许用这个装饰器来装饰任何方法。

  • >>> @logger
  • ... def foo1(x, y=1):
  • ...     return x * y
  • >>> @logger
  • ... def foo2():
  • ...     return 2
  • >>> foo1(5, 4)
  • Arguments were: (5, 4), {}
  • 20
  • >>> foo1(1)
  • Arguments were: (1,), {}
  • 1
  • >>> foo2()
  • Arguments were: (), {}
  • 2
任意调用我们界说的哪个方法,相应的日记也会打印到输出窗口,和我们预期的一样。
译者:寒寻 
译文:http://www.cnblogs.com/imshome/p/8327438.html 
原文:http://dzone.com/articles/understanding-python
喜好分享or




上一篇:【Php】php零底子全套视频教程(附课程源码和实战项目) ...
下一篇:Python 底子教程:小白怎样处置惩罚非常?
您需要登录后才可以回帖 登录 | 加入社区

本版积分规则

 

QQ|申请友链|小黑屋|手机版|Hlshell Inc. ( 豫ICP备16002110号-5 )

GMT+8, 2024-5-15 13:13 , Processed in 0.060676 second(s), 47 queries .

HLShell有权修改版权声明内容,如有任何爭議,HLShell將保留最終決定權!

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表