Python学习-修饰器 - itemgetter的妙用

下面这篇对装饰器讲的很好,懂了。

http://python.jobbole.com/85056/

《简单 12 步理解 Python 装饰器》

 

使用装饰器非常简单(见步骤10),但是写装饰器却很复杂。

 

2. 作用域

在 Python 函数中会创建一个新的作用域。Python 高手也称函数有自己的命名空间。也就是说,当在函数体中遇到变量时,Python 会首先在该函数的命名空间中寻找变量名。Python 有几个函数用来查看命名空间。下面来写一个简单函数来看看局部变量和全局变量的区别。

>>> a_string = "This is a global variable"
>>> def foo():
...     print locals()
>>> print globals() # doctest: +ELLIPSIS
{..., 'a_string': 'This is a global variable'}
>>> foo() # 2
{}

内建函数 globals 返回一个包含所有 Python 能识别变量的字典。(为了更清楚的描述,输出时省略了 Python 自动创建的变量。)在注释 #2 处,调用了 foo 函数,在函数中打印局部变量的内容。从中可以看到,函数 foo 有自己单独的、此时为空的命名空间。

 

3. 变量解析规则

Python 的作用域规则是, 变量的创建总是会创建一个新的局部变量但是变量的访问(包括修改)在局部作用域查找然后是整个外层作用域来寻找匹配。

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

>>> a_string = "This is a global variable"
>>> def foo():
...     a_string = "test" # 1
...     print locals()
>>> foo()
{'a_string': 'test'}
>>> a_string # 2
'This is a global variable'

注:要加上global关键字才行。

 

6. 内嵌函数

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

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

 

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

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

这意味着可以将函数当做实参传递给函数,或者在函数中将函数作为返回值返回。如果你从未想过这样使用,请看下面的可执行代码

>>> 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

但是,将函数作为值返回会怎样?思考下面代码:

>>> 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 会被重新定义,但是如果函数 ouer 没有返回 inner,当 inner 超出 outer 的作用域,inner 的生命周期将结束。

在 #2 处将获得返回值即函数 inner,并赋值给新变量 foo。可以看到如果鉴定 foo,它确实包含函数 inner,通过使用调用操作符(双括号,还记得吗?)来调用它。虽然看起来可能有点怪异,但是目前为止并没有什么很难理解的,对吧?hold 住,因为接下来会更怪异!

 

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...>,)

 

foo()

1

 

返回的 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

 

闭包是强大的技术——在某些方面来看可能感觉它有点像面向对象技术:outer 作为 inner的构造函数,有一个类似私有变量的 x。闭包的作用不胜枚举——如果你熟悉 Python中 sorted 函数的参数 key,也许你已经写过 lambda 函数通过第二项而非第一项来排序一些列表。也可以写一个 itemgetter 函数,接收一个用于检索的索引并返回一个函数,然后就能恰当的传递给 key 参数了。

 

找到了一个示例,如下:

a = [1,2,3] 
>>> b=operator.itemgetter(1)      //定义函数b,获取对象的第1个域的值
>>> b(a) 

2

>>> b=operator.itemgetter(1,0)  //定义函数b,获取对象的第1个域和第0个的值
>>> b(a) 
(2, 1)

要注意,operator.itemgetter函数获取的不是值,而是定义了一个函数,通过该函数作用到对象上才能获取值。

sorted函数用来排序,sorted(iterable[, cmp[, key[, reverse]]])

其中key的参数为一个函数或者lambda函数。所以itemgetter可以用来当key的参数

a = [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

根据第二个域和第三个域进行排序

sorted(students, key=operator.itemgetter(1,2))

 

但是这么用闭包太没意思了!让我们再次从头开始,写一个装饰器。

9. 装饰器

装饰器其实就是一个以函数作为参数并返回一个替换函数的可执行函数。

 

>>> def outer(some_func):
...     def inner():
...         print "before some_func"
...         ret = some_func() # 1
...         return ret + 1
...     return inner
>>> def foo():
...     return 1
>>> decorated = outer(foo) # 2
>>> decorated()
before some_func
2

 

下面的Wrapper就是装饰器:

>>> def wrapper(func):
...     def checker(a, b): # 1
...         if a.x < 0 or a.y < 0:
...             a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
...         if b.x < 0 or b.y < 0:
...             b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
...         ret = func(a, b)
...         if ret.x < 0 or ret.y < 0:
...             ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
...         return ret
...     return checker
>>> add = wrapper(add)
>>> sub = wrapper(sub)
>>> sub(one, two)
Coord: {'y': 0, 'x': 0}
>>> add(one, three)
Coord: {'y': 200, 'x': 100}

 

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

Python 2.4 通过在函数定义前添加一个装饰器名和 @ 符号,来实现对函数的包装。在上面代码示例中,用了一个包装的函数来替换包含函数的变量来实现了装饰函数。

>>> @ wrapper 
... def add(a, b): 
...     return Coordinate(a.x + b.x, a.y + b.y)

相当于:

>>> add = wrapper(add)

 

11. args 和 *kwargs

>>> def one(*args):
...     print args # 1
>>> one()
()
>>> one(1, 2, 3)
(1, 2, 3)
>>> def two(x, y, *args): # 2
...     print x, y, args
>>> two('a', 'b', 'c')
a b ('c',)

 

也可以像下面这样传参:

>>> def add(x, y):
...     return x + y
>>> lst = [1,2]
>>> add(lst[0], lst[1]) # 1
3
>>> add(*lst) # 2
3

 

对于 **

>>> def foo(**kwargs):
...     print kwargs
>>> foo()
{}
>>> foo(x=1, y=2)
{'y': 2, 'x': 1}
>>> dct = {'x': 1, 'y': 2}
>>> def bar(x, y):
...     return x + y
>>> bar(**dct)
3

 

12. 更通用的装饰器

用学到的新知识,可以写一个记录函数参数的装饰器。为简单起见,仅打印到标准输出:

>>> def logger(func):
...     def inner(*args, **kwargs): #1
...         print "Arguments were: %s, %s" % (args, kwargs)
...         return func(*args, **kwargs) #2
...     return inner

注意在 #1 处函数 inner 接收任意数量和任意类型的参数,然后在 #2 处将他们传递给被包装的函数。这样一来我们可以包装或装饰任意函数,而不用管它的签名。

>>> @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

 

每一个函数的调用会有一行日志输出和预期的返回值。

再聊装饰器

如果你一直看到了最后一个实例,祝贺你,你已经理解了装饰器!你可以用新掌握的知识做更多的事了。

你也许考虑需要进一步的学习:Bruce Eckel 有一篇很赞的关于装饰器文章,他使用了对象而非函数来实现了装饰器。你会发现 OOP 代码比纯函数版的可读性更好。Bruce 还有一篇后续文章 providing arguments to decorators,用对象实现装饰器也许比用函数实现更简单。最后,你可以去研究一下内建包装函数 functools,它是一个在装饰器中用来修改替换函数签名的装饰器,使得这些函数更像是被装饰的函数。

 

functools,用于高阶函数:指那些作用于函数或者返回其它函数的函数,通常只要是可以被当做函数调用的对象就是这个模块的目标。

下次再看哈。

 

posted @ 2017-01-19 19:28  blcblc  阅读(427)  评论(0编辑  收藏  举报