Python Decorator 的一些小细节/坑

Decorator 的本质是什么?

decorator 本质就是一个接收对象对象(对,是个对象,而不是大多数人认为的函数),更多的资料可以参照 理解Python的装饰器 | Darkof

1
2
3
4
5
final_func = decorator(wrapped_function) # 与注释部分的实质是一致的。
@decorator
def wrapped_func(*args, **kwargs):
pass

被装饰的函数与之前相比,改变了什么?

行为

这个是最显而易见的,装饰器可以在原函数执行之前或之后添加额外的行为

函数本身的属性

如果你简单的实现了下面的 decorator 会改变什么呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def foo(func):
def wrapped(*args, **kwargs):
print "in decorator"
return func(*args, **kwargs)
return wrapped
class bar(object): # 原谅我用小写的 bar :)
def __init__(self, func):
self.func= func
def __call__(self, *args, **kwargs):
print "in decorator"
return self.func(*args, **kwargs)
@foo
def f1(a):
print a
print f1.__name__
#输出: 'wrapped'
@bar
def f2(a):
print a
print f2.__name__
# AttributeError Traceback (most recent call last)
# <ipython-input-9-dff5600c49e8> in <module>()
# ----> 1 f2.__name__
#
# AttributeError: 'bar' object has no attribute '__name__'

从上面我们看出来,函数的 __name__ 属性也发生了变化,这也是为什么我们推荐装饰器的时候使用 fucntools.wraps

1
2
3
4
5
6
7
8
import functools
def foo(func):
@functools.wraps(func)
def wrapped(*args, **kwargs):
print "in decorator"
return func(*args, **kwargs)
return wrapped

wraps 会把原函数的属性赋给新的 wrapped 这个函数(主要会同步的属性为 __name__, __module__, __doc__, __dict__, 当然,你也可以添加你希望同步的属性)

函数的参数

是的,很少有人会注意到被装饰过会,函数接收的参数也会产生变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
inspect.getargspec(f1)
# 输出 ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None)
inspect.getargspec(f2)
# TypeError Traceback (most recent call last)
# <ipython-input-16-bff760b02fba> in <module>()
# ----> 1 inspect.getargspec(f2)
# /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/inspect.pyc in getargspec(func)
# 814 func = func.im_func
# 815 if not isfunction(func):
# --> 816 raise TypeError('{!r} is not a Python function'.format(func))
# 817 args, varargs, varkw = getargs(func.func_code)
# 818 return ArgSpec(args, varargs, varkw, func.func_defaults)
# TypeError: <__main__.bar object at 0x108729f50> is not a Python function

f1 接收的函数名从 a 变成了 args 和 kwargs, f2 干脆就拿不到了,这也意味着其实装饰器并不能做到 works anywhere(毕竟有很多装饰器会通过参数来判断这是不是一个 classmethod,然后两个装饰器混用可能会导致其中一个失效)

装饰 classmethod/staticmethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import functools
def foo(func):
def wrapped(*args, **kwargs):
print "in decorator"
return func(*args, **kwargs)
return wrapped
class Bar(object):
@foo
@classmethod
def duck(cls):
print 'Yooooooooooo!'
Bar.duck()
# ---------------------------------------------------------------------------
# TypeError Traceback (most recent call last)
# <ipython-input-19-ddf54c241cc4> in <module>()
# ----> 1 Bar.duck()
#
# TypeError: unbound method wrapped() must be called with Bar instance as first argument (got nothing instead)

当我们尝试用之前实现的 decorator 来装饰 classmethod 的时候,会遇到 TypeError,原因是 classmethod/staticmethod 本质是一个 Descriptor 而非 function,我们在一开始提到这样一句话:

decorator 本质就是一个接收对象对象

在前面也聊到了,decorator 是个对象,可能是个 class 或是 function, 那么他接收的是什么呢,很多人认为 decorator 接受的是函数,然而严格来说,decorator 接收的是一个对象

当我们知道 classmethod/staticmehtod 是 Descriptor 之后就很容易的知道如何写一个装饰 Descriptor(当然,你需要先了解什么是 Descriptor)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class bar(object): # 原谅我用小写的 bar :)
def __init__(self, func):
self.func= func
def __call__(self, *args, **kwargs):
print "in decorator"
return self.func(*args, **kwargs)
class foo(object):
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
func = self.func.__get__(instance, owner)
return bar(func)
def __call__(self, *args, **kwargs):
return bar(self.func)(*args, **kwargs)
class Duck(object):
@foo
@staticmethod
def fly():
print 'fly'
@foo
@classmethod
def run(cls):
print 'run'
@foo
def stop(self):
print 'stop'
Duck.fly()
# 输出
# in decorator
# fly
Duck.run()
# 输出
# in decorator
# run
Duck().stop()
# 输出
# in decorator
# stop

理解Python的装饰器

原文出自stackoverflow,英文好的同学可以直接戳这里,如果是人人看的那么可能排版不太好,可以戳这里

Python的函数也是对象

为了理解装饰器,你必须首先要理解python里的函数也是对象。这是一个很重要的概念,我们可以来看个小例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def shout(word="yes"):
return word.capitalize()+"!"
print shout()
# 输出 : 'Yes!'
# 作为一个对象,你可以像其他对象一样,将他赋给一个变量
scream = shout
# 注意我们没有使用shout() 因为我们不是在调用shout这个函数,
# 我们把shout这个函数传递给给scream
# 这意味着我们可以调用scream来代替调用shoot
print scream()
# 输出 : 'Yes!'
# 不仅如此,你删掉shout, 仍然可以调用scream
del shout
try:
print shout()
except NameError, e:
print e
#outputs: "name 'shout' is not defined"
print scream()
# 输出: 'Yes!'

我们先把上面的概念记下,过会我们会再回来。

Python里函数的另外一个特性:函数可以定义在另外一个函数内(函数中定义函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def talk():
# 你可以在函数“talk”中定义一个函数
def whisper(word="yes"):
return word.lower()+"..."
# 然后在“talk”函数里直接使用“whisper”
print whisper()
# 你调用了内部定义了“whisper”的talk函数,
# 每一次调用talk,talk中的whisper也会被调用
talk()
# 输出:
# "yes..."
# 但是在talk函数之外并不存在whisper:
try:
print whisper()
except NameError, e:
print e
# 输出 : "name 'whisper' is not defined”

函数引用

从上面的例子可以看出函数是对象,因此:

  • 可以传递给变量
  • 可以在另一个函数中定义
  • 这意味着一个函数可以返回另外一个函数(返回值是一个函数),我们看一下例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def getTalk(type="shout"):
# 我们接着定义函数
def shout(word="yes"):
return word.capitalize()+"!"
def whisper(word="yes") :
return word.lower()+"...";
# 然后返回定义的两个函数中的一个
if type == "shout":
# 我们不使用(),因为在这里我们不是在调用它
# 返回一个函数对象
return shout
else:
return whisper
# 你怎么使用这个东西?
# 得到一个函数对象然后传递给一个变量
talk = getTalk()
# 你可以看到这里的talk是一个函数对象
print talk
# 输出 : <function shout at 0xb7ea817c>
# 这个对象是函数返回的一个对象
print talk()
# 输出 : Yes!
# 你也可以直接用一种很难看的方式直接调用
print getTalk("whisper")()
# 输出 : yes…

既然你可以返回一个函数,那么你也可以传递一个函数对象作为参数

1
2
3
4
5
6
7
8
def doSomethingBefore(func):
print "I do something before then I call the function you gave me"
print func()
doSomethingBefore(scream)
# 输出:
# I do something before then I call the function you gave me
# Yes!

你应该已经能够理解装饰器了。装饰器就是一种包装,能够让你在不改变函数本身的情况下,在其之前或之后执行代码

手工实现装饰器

你会如何实现一个装饰器呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 一个装饰器是一个能够接收另外一个函数作为参数的函数
def my_shiny_new_decorator(a_function_to_decorate):
# 装饰器在内部定义了一个函数:the_wrapper_around_the_original_function
# 这是一个对原始函数(a_function_to_decorate)进行包装的函数
# 所以它可以在原始函数(a_function_to_decorate)之前或者之后执行一些代码
def the_wrapper_around_the_original_function():
# 在这里实现一些你希望在原函数执行前执行的代码
print "Before the function runs"
# 在这里调用原函数
a_function_to_decorate()
# 在这里实现一些需要在原函数执行后执行的代码
print "After the function runs"
# 在这时,a_function_todecorate还没有被执行(这是定义过程)
# 我们返回我们刚刚创建的装饰器函数:the_wrapper_around_the_original_function
# 这个装饰器包括了原函数以及我们在其之前和之后需要执行的代码
return the_wrapper_around_the_original_function
# 现在来考虑创建一个你不会再直接调用的函数
def a_stand_alone_function():
print "I am a stand alone function, don't you dare modify me"
a_stand_alone_function()
# 输出: I am a stand alone function, don't you dare modify me
# 现在通过装饰它来扩展它的行为
# 只需要将它传给装饰器,它就会动态的被装饰
# 然后你会得到一个新的可以使用的函数
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
# 输出:
# Before the function runs
# I am a stand alone function, don't you dare modify me
# After the function runs

现在,你每次想调用 a_stand_alone_function,都通过调用a_stand_alone_function_decorated来代替。

这个很好解决, 通过 my_shiny_new_decorator 返回的函数来重写 a_stand_alone_function_decorated

1
2
3
4
5
6
a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#输出:
# Before the function runs
# I am a stand alone function, don't you dare modify me
# After the function runs# And guess what? That's EXACTLY what decorators do!

 

揭开装饰器的面纱(好吧,我承认这个副标题略二)

之前的例子,我们通过使用装饰器语法来实现:

1
2
3
4
5
6
7
8
9
@my_shiny_new_decorator
def another_stand_alone_function():
print "Leave me alone"
another_stand_alone_function()
#输出:
#Before the function runs
#Leave me alone
#After the function runs

就这没简单,

@decorator等同于:

1
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

 

装饰器是 设计模式中装饰者模式的python实现。这些经典的设计模式都 被内嵌在python中来简化开发,就像iterators(迭代器)一样。

当然,你可以同时使用多个装饰器(手动装饰器实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def bread(func):
def wrapper():
print "</''''''>"
func()
print "<______/>"
return wrapper
def ingredients(func):
def wrapper():
print "#tomatoes#"
func()
print "~salad~"
return wrapper
def sandwich(food="--ham--"):
print food
sandwich()
#输出: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#输出:
#</''''''>
# #tomatoes#
# --ham--
# ~salad~
#<______/>

使用python语法糖实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@bread
@ingredients
def sandwich(food="--ham--"):
print food
sandwich()
#输出:
#</''''''>
# #tomatoes#
# --ham--
# ~salad~
#<______/>
The order you set the decorators MATTERS:
@ingredients
@bread
def strange_sandwich(food="--ham--"):
print food
strange_sandwich()
#输出:
##tomatoes#
#</''''''>
# --ham--
#<______/>
# ~salad~

 

接下来我们来看一些关于装饰器的高级用法

向被装饰的函数传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 这不是什么黑魔法,你只需像装饰器传递参数
def a_decorator_passing_arguments(function_to_decorate):
def a_wrapper_accepting_arguments(arg1, arg2):
print "I got args! Look:", arg1, arg2
function_to_decorate(arg1, arg2)
return a_wrapper_accepting_arguments
# 当你调用由装饰器返回的函数,
# 也就是你在调用a_wrapper_accepting_arguments这个函数,
# 把参数传递给wrapper然后再让它把参数传递给被装饰的原函数
@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
print "My name is", first_name, last_name
print_full_name("Peter", "Venkman")
# 输出:
#I got args! Look: Peter Venkman
#My name is Peter Venkman

装饰对象的方法

在Python中对象和方法是很相似的,不同之处在于方法需要将当前对象的因作为第一个参数传递进去。这意味考虑到self之后,你用相同的办法可以为方法创建装饰器,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def method_friendly_decorator(method_to_decorate):
def wrapper(self, lie):
lie = lie - 3 # very friendly, decrease age even more :-)
return method_to_decorate(self, lie)
return wrapper
class Lucy(object):
def __init__(self):
self.age = 32
@method_friendly_decorator
def sayYourAge(self, lie):
print "I am %s, what did you think?" % (self.age + lie)
l = Lucy()
l.sayYourAge(-3)
#输出: I am 26, what did you think?

当然,如果你想创建一个通用与函数(function)与方法(method)的装饰器,不需要考虑参数如何,只要用args和*kwargs就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def a_decorator_passing_arbitrary_arguments(function_to_decorate):
# 装饰器接收所有任何参数
def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
print "Do I have args?:"
print args
print kwargs
# 然后解压缩参数,在这是*args和**kwargs
# 如果你对压缩和解压缩参数不熟悉,
# 看这:http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
function_to_decorate(*args, **kwargs)
return a_wrapper_accepting_arbitrary_arguments
@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
print "Python is cool, no argument here."
function_with_no_argument()
#输出:
#Do I have args?:
#()
#{}
#Python is cool, no argument here.
@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
print a, b, c
function_with_arguments(1,2,3)
#输出:
#Do I have args?:
#(1, 2, 3)
#{}
#1 2 3
@a_decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, platypus="Why not ?"):
print "Do %s, %s and %s like platypus? %s" %
(a, b, c, platypus)
function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!")
#输出:
#Do I have args ? :
#('Bill', 'Linus', 'Steve')
#{'platypus': 'Indeed!'}
#Do Bill, Linus and Steve like platypus? Indeed!
class Mary(object):
def __init__(self):
self.age = 31
@a_decorator_passing_arbitrary_arguments
def sayYourAge(self, lie=-3): # You can now add a default value
print "I am %s, what did you think ?" % (self.age + lie)
m = Mary()
m.sayYourAge()
#输出:
# Do I have args?:
#(<__main__.Mary object at 0xb7d303ac>,)
#{}
#I am 28, what did you think?

 

向装饰器传递参数

现在你可能想知道如何向装饰器本身传递参数。你可能不解为什么要这么做,因为之前的装饰器必须接受一个函数作为参数,但不能把被装饰的函数直接传递给装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 装饰器是个普通的函数
def my_decorator(func):
print "I am a ordinary function"
def wrapper():
print "I am function returned by the decorator"
func()
return wrapper
# 所以你可以直接调用而不不使用@
def lazy_function():
print "zzzzzzzz"
decorated_function = my_decorator(lazy_function)
# 输出: I am a ordinary function
# 它输出 "I am a ordinary function", 因为你调用了它
@my_decorator
def lazy_function():
print "zzzzzzzz"
# 输出: I am a ordinary function

my_decorator的调用和使用@my_decorator是十分相似的。你让通过被变量标志的my_decorator来让python调用它。这很重要,因为这个标志可以让你直接指出装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def decorator_maker():
print "I make decorators! I am executed only once: "+
"when you make me create a decorator."
def my_decorator(func):
print "I am a decorator! I am executed only when you decorate a function."
def wrapped():
print ("I am the wrapper around the decorated function. "
"I am called when you call the decorated function. "
"As the wrapper, I return the RESULT of the decorated function.")
return func()
print "As the decorator, I return the wrapped function."
return wrapped
print "As a decorator maker, I return a decorator"
return my_decorator
# 我们来创建一个装饰器.
new_decorator = decorator_maker()
# 输出:
# I make decorators! I am executed only once: when you make me create a decorator
# As a decorator maker, I return a decorator
# 然后我们来装饰一个函数
def decorated_function():
print "I am the decorated function."
decorated_function = new_decorator(decorated_function)
# outputs:
# I am a decorator! I am executed only when you decorate a function.
# As the decorator, I return the wrapped function
# 我们调用一下这个被装饰过的方法:
decorated_function()
#输出:
# I am the wrapper around the decorated function. I am called when you call the
# decorated function.
# As the wrapper, I return the RESULT of the decorated function.
# I am the decorated function.

再让我们跳过中间变量来实现同样的事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def decorated_function():
print "I am the decorated function."
decorated_function = decorator_maker()(decorated_function)
# 输出:
# I make decorators! I am executed only once: when you make me create a decorator
# As a decorator maker, I return a decorator
# I am a decorator! I am executed only when you decorate a function.
# As the decorator, I return the wrapped function.
# 最后:
decorated_function()
# 输出:
# I am the wrapper around the decorated function. I am called when you call the
# decorated function.
# As the wrapper, I return the RESULT of the decorated function.
# I am the decorated function.

让我们通过更简洁方式来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@decorator_maker()
def decorated_function():
print "I am the decorated function."
#输出:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.
#然后就到最后了,调用:
decorated_function()
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

你觉得怎么样,我们通过@来进行函数调用

回到向装饰器传递参数这个问题上。如果我们使用函数来产生装饰器,我们也可以向这个函数传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
print "I make decorators! And I accept arguments:", decorator_arg1, decorator_arg2
def my_decorator(func):
# 向这传递参数是一种闭包特性
# 如果你不喜欢这种闭包特性,你可以把它传给一个变量
# 或者看看这:http://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python
print "I am the decorator. Somehow you passed me arguments:", decorator_arg1, decorator_arg2
# 不要弄混 装饰器的参数 和 被装饰的方法的参数
def wrapped(function_arg1, function_arg2) :
print ("I am the wrapper around the decorated function.n"
"I can access all the variablesn"
"t- from the decorator: {0} {1}n"
"t- from the function call: {2} {3}n"
"Then I can pass them to the decorated function"
.format(decorator_arg1, decorator_arg2,
function_arg1, function_arg2))
return func(function_arg1, function_arg2)
return wrapped
return my_decorator
@decorator_maker_with_arguments("Leonard", "Sheldon")
def decorated_function_with_arguments(function_arg1, function_arg2):
print ("I am the decorated function and only knows about my arguments: {0}"
" {1}".format(function_arg1, function_arg2))
decorated_function_with_arguments("Rajesh", "Howard")
#输出:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function.
#I can access all the variables
# - from the decorator: Leonard Sheldon
# - from the function call: Rajesh Howard
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard

像这样,一个需要参数的装饰器,参数可以以变量的形式传递进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
c1 = "Penny"
c2 = "Leslie"
@decorator_maker_with_arguments("Leonard", c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
print ("I am the decorated function and only knows about my arguments:"
" {0} {1}".format(function_arg1, function_arg2))
decorated_function_with_arguments(c2, "Howard")
#输出:
#I make decorators! And I accept arguments: Leonard Penny
#I am the decorator. Somehow you passed me arguments: Leonard Penny
#I am the wrapper around the decorated function.
#I can access all the variables
# - from the decorator: Leonard Penny
# - from the function call: Leslie Howard
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Leslie Howard

 

 

做个练习:一个用来装饰装饰器的装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def decorator_with_args(decorator_to_enhance):
# 我们像之前那样传递参数
def decorator_maker(*args, **kwargs):
# 创建一个只接受函数作为参数的函数
# 但是能够使用maker传递的参数
def decorator_wrapper(func):
# 我们将返回原始的装饰器,就像返回一个普通的函数一样
# IS JUST AN ORDINARY FUNCTION (which returns a function).
# 需要注意的是装饰器必须有自己的标识符,否则无法工作
return decorator_to_enhance(func, *args, **kwargs)
return decorator_wrapper
return decorator_maker

然后可以这么使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 你创造了了一个将作为装饰器使用的函数,然后向里面插入了一个装饰器
# 别忘了,插入的装饰器的标识符是 "decorator(func, *args, **kwargs)"
@decorator_with_args
def decorated_decorator(func, *args, **kwargs):
def wrapper(function_arg1, function_arg2):
print "Decorated with", args, kwargs
return func(function_arg1, function_arg2)
return wrapper
# 然后你可以通过被装饰的装饰器来装饰你的函数
@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
print "Hello", function_arg1, function_arg2
decorated_function("Universe and", "everything")
#输出:
#Decorated with (42, 404, 1024) {}
#Hello Universe and everything
# 酷不?

我知道,你上次有这种感觉,应该是听到别人说:“要理解递归,你要先理解递归”的时候。但是掌握了刚才讲的这些之后是不是很爽。

装饰器范例

  • 装饰器是Python2.4出现的新特性,确保你的代码跑在2.4或者更高版本上
  • 记住装饰器会延缓函数调用
  • 不能取消装饰。有一些方法能创建可以取消的装饰器,但木有人用。一个函数被装饰了,就是被装饰了。
  • 装饰器装饰函数后,会增加debug难度
    Python2.5通过提供functool模块来解决了上述的最后一个问题。functool.wraps会拷贝被装饰函数的名字 模块 文档等到他的包装器中,而functools.wraps本身也是一个装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 直接用装饰器会显得很杂乱
def bar(func):
def wrapper():
print "bar"
return func()
return wrapper
@bar
def foo():
print "foo"
print foo.__name__
#outputs: wrapper
# "functools" 有助于解决这个问题
import functools
def bar(func):
# 我们说的包装器(wrapper)是用来包装func的
# 下面是见证奇迹的时刻
@functools.wraps(func)
def wrapper():
print "bar"
return func()
return wrapper
@bar
def foo():
print "foo"
print foo.__name__
#输出: foo

 

如何有效的使用装饰器

也许你会有个疑问:我可以用装饰器做什么,它看起来很好很强大,但是如果有个实际例子会更好。

关于在什么情况下使用有很多种可能,传统的用法是当你需要扩展一个外部库函数的行为或者处于debug的需要(但你不想对其直接做做更改,因为这只是临时的),你可以使用一个装饰器来扩展许多函数,不要重写每个函数(DRY原则),举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def benchmark(func):
"""
A decorator that prints the time a function takes
to execute.
"""
import time
def wrapper(*args, **kwargs):
t = time.clock()
res = func(*args, **kwargs)
print func.__name__, time.clock()-t
return res
return wrapper
def logging(func):
"""
A decorator that logs the activity of the script.
(it actually just prints it, but it could be logging!)
"""
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print func.__name__, args, kwargs
return res
return wrapper
def counter(func):
"""
A decorator that counts and prints the number of times a function has been executed
"""
def wrapper(*args, **kwargs):
wrapper.count = wrapper.count + 1
res = func(*args, **kwargs)
print "{0} has been used: {1}x".format(func.__name__, wrapper.count)
return res
wrapper.count = 0
return wrapper
@counter
@benchmark
@logging
def reverse_string(string):
return str(reversed(string))
print reverse_string("Able was I ere I saw Elba")
print reverse_string("A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!")
#输出:
#reverse_string ('Able was I ere I saw Elba',) {}
#wrapper 0.0
#wrapper has been used: 1x
#ablE was I ere I saw elbA
#reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {}
#wrapper 0.0
#wrapper has been used: 2x
#!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A

 

而使用装饰器的好处是是不需要做多余的重写就能立刻使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@counter
@benchmark
@logging
def get_random_futurama_quote():
import httplib
conn = httplib.HTTPConnection("slashdot.org:80")
conn.request("HEAD", "/index.html")
for key, value in conn.getresponse().getheaders():
if key.startswith("x-b") or key.startswith("x-f"):
return value
return "No, I'm ... doesn't!"
print get_random_futurama_quote()
print get_random_futurama_quote()
#输出:
#get_random_futurama_quote () {}
#wrapper 0.02
#wrapper has been used: 1x
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#wrapper has been used: 2x
#Curse you, merciful Poseidon!

 

 

Python本身提供了许多装饰器:property, staticmethod等等。Django使用装饰器来捕获和查看权限等,装饰器用途广泛

 

 

原文出自:http://stackoverflow.com/questions/739ddd654/how-can-i-make-a-chain-of-function-decorators-in-python/1594484#1594484

mysql offset 为什么这么慢。。。

之前从来没觉得 offset 有什么坑,也没有细想过 mysql 的 offset 的实现原理。

直到这周打算把 4000w+ 的数据热到 redis 中,写了一个脚本, 主要的代码大概如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from redis import Redis
from sqlalchemy import select
import table # Mysql Table Object
redis_cli = Redis(xxxxxxxxxxx)
CUR = 0
MAX = 40000000
while CUR <= MAX:
query = text("""SELECT id FROM example
LIMIT CUR, 1000
ORDER BY id DESC""")
result = table.execute(query).fetchall()
pipe = redis_cli.pipeline() # 使用 pipeline 来减少连接开销
for item in result:
pipe.set(item.id, 'foo')
pipe.execute()
CUR += 1000

开始执行大概下午 6 点左右,然后我就去吃饭逗猫写代码又睡了一觉。

上午 11 点左右来公司发现,才完成了 1000w 左右的数据,内心是崩溃的。。。。。

看了一眼 slow log,一次 Mysql 的查询需要 40s, 然后开始查一些资料找原因,发现 offset/limit 根本无法用到 index 机制,而是读整张表,然后数到需要便宜的位置,所以上面的代码到 1000w 时, mysql 会按照 id 的顺序逐条累加,一直找到第 1000w 的位置(至于为什么不通过 index 来直接找到 id 为 10000000 的数据,原因很简单,id 为 10000000 的数据并不已经代表是第 1000w 条数据,中间有可能会有数据被删除使得 id 非连续)。

找到了原因重写了一把脚本,把 offset 改成 where 就解决了这个问题,然后用了半个小时就跑完了数据- -。

thumbnail

Python 中的「全局变量」的小细节

前几天被同学问了一个问题,为什么自己修改修改了全局变量但是没有生效,例子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File: foo.py
l = 10
def bar():
global l
l = 20
#-----------------------------
# File: main.py
from foo import l, bar
if __name__ == '__main__':
print l
bar()
print l
输出:
10
10

所以我们还是要弄明白什么是全局变量。。。

Python 有全局变量么

有 - -|||。。。。。。
有两种情况是全局变量:

  • 在当前文件中的最外层作用于声明的变量为全局变量
  • 用 global 声明的变量为全局变量

Python 的「全局变量」的作用域是多大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# foo.py
l = 10
def bar():
global b
b = 1
--------------------------
# In ipython
In [1]: import foo
In [2]: 'l' in foo.__dict__
Out[2]: True
In [3]: 'b' in foo.__dict__
Out[3]: False
In [4]: foo.bar()
In [5]: 'b' in foo.__dict__
Out[5]: True

从上面的代码里我们得到以下几个结论

  • 全局变量的作用域被绑定到所在文件之下
  • 即使是先加载再声明全局变量,依然会绑定到该文件之中

为什么我不能跨文件修改全局变量

其实,正确的来说,这个问题问的并没有切中要点,在最初的代码中,我们无法修改 l 的主要原因是 import 的特性导致

The from form does not bind the module name: it goes through the list of identifiers, looks each one of them up in the module found in step (1), and binds the name in the local namespace to the object thus found.

所以当你执行这个语句

1
from foo import l

本质是在当前的 Namespace 中声明了变量 l, 并将 l 指向 foo 这个 module 中的 l 所指向的对象。

当你执行 foo.bar() 的时候,将 foo 中的 l 改为了 20, 但是 main.py 中 l 扔指向 10,所以并没有实现夸文件修改.
同理,在 main.py 中修改 l 也不会影响 foo.l

最后的最后

虽然前面举了例子,有分析了原理,但是其实就像是聊屠龙术的运行原理(更何况「全局变量」连屠龙书都算不上,顶多算是这个图里的 IE

除非是用来定义常量,否则不要在 Python 里用全局变量。