装饰器 属于 metaprogramming 的一种,是在编译时一段程序尝试去修改另外一段程序的部分内容。
基础知识
在 python 中,everything 都是 objects。
Names 只是 tags bound to these objects。
甚至 classes,functions 都是 objects。
例如:
a = math.sin
,则a
就用了同sin
一样的功能;
然后math.sin
甚至可以指向其他函数,例如math.sin = func
,则math.sin
就变成了func
函数的功能
而此时a
仍然具有sin
函数的功能
函数可以作为参数传递给其他函数,例如map
, filter
, reduce
.
这些接受函数作为参数的函数被称为 higher order functions.
函数也可以作为返回值被返回,例如在上一节中提到的 Closure。
回到 Decorators
functions 和 methods 都被称为 callable,因为他们都可以被执行。
实际上,任何 object,只要实现了__call__()
方法,都是一个 callable。
所以,Decorator 就是一个返回 callable 的 callable。
Decorator 使用函数作为参数,增加一些功能做成一个新的函数,然后返回它。
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
def ordinary():
print("I am ordinary")
>>> ordinary()
I am ordinary
>>> # let's decorate this ordinary function
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary
在上面的例子中,make_pretty()
就是一个Decorator。
Decorator 就像一个 wrapper 一样。
Decorator 就好像把传入的函数 “decorate/装饰” 了一番,然后又吐了出来。
@Decorator
python 中提供了一个更简单的使用 decorator 的方法,就是@符号:
@make_pretty
def ordinary():
print("I am ordinary")
就等同于
def ordinary():
print("I am ordinary")
ordinary = make_pretty(ordinary)
注意,下面的例子中,重新赋值后的 ordinary 已经不是 def 定义的ordinary 了,而是一个经过 decorator 修饰的 ordinary。
因此上面的例子中,@符号直接创造了经过修饰的 ordinary,省去了中间的形式。
为带参数的函数进行“装饰”
例子
def divide(a, b):
return a/b
上面的函数显然有缺陷,在 b == 0 时会 raise ZeroDivisionError,因此我们可以对之进行“装饰”:
def smart_divide(func):
def inner(a,b):
print("I am going to divide",a,"and",b)
if b == 0:
print("Whoops! cannot divide")
return
return func(a,b)
return inner
@smart_divide
def divide(a,b):
return a/b
经过修饰后,divide
在当 b == 0 时会警告,变成了一个没有返回值的函数(返回 None);在b != 0 时返回正常的结果,又是一个有返回值的函数。
当使用者调用 divide(n / m)
时,实际上是在调用一个内部函数 inner(n / m)
。
而该内部函数使用了原始的、未被“修饰”的函数,即作为参数传入的 func
,即写在@smart_divide下面的那个形式。
通用 decorator
你会发现,实际上inner()
函数使用了和原始的被修饰函数同样的参数。根据这个原理,我们可以写出更通用的格式:
def works_for_all(func):
def inner(*args, **kwargs):
print("I can decorate any function")
return func(*args, **kwargs)
return inner
那么这个 decorator 可以“修饰”任何函数。
Chaining Decorators
被“修饰”的函数可以再次被“修饰”,就成了 decorator 嵌套:
def star(func):
def inner(*args, **kwargs):
print("*" * 30)
func(*args, **kwargs)
print("*" * 30)
return inner
def percent(func):
def inner(*args, **kwargs):
print("%" * 30)
func(*args, **kwargs)
print("%" * 30)
return inner
@star
@percent
def printer(msg):
print(msg)
printer("Hello")
输出结果是:
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
注意,@star 和 @percent 的前后顺序很重要!不然就是不一样的结果。
带有参数的 decorator
有时候你会看到下面的形式:
@decorator(param)
def func():
pass
不要怕,根据其原理,那么就相当于:
def func():
pass
func = decorator(param)(func)
可见,decorator
本身就是一个返回 Decorator 的 Decorator 函数。其形式诸如:
def decorator(argument):
def real_decorator(function):
def wrapper(*args, **kwargs):
funny_stuff()
something_with_argument(argument)
function(*args, **kwargs)
more_funny_stuff()
return wrapper
return real_decorator
几个跟 Class 相关的常用 Decorator
1. @classmethod 和 @staticmethod
- 在类中,normal method 也称为 instance method,定义 instance method 时,需要默认给定一个 self 参数,代表了实例本身。
class Date(object):
def __init__(self, day=0, month=0, year=0):
self.day = day
self.month = month
self.year = year
>>> date1 = Date(6, 11, 2017)
- 在类中,还有一类 method 称为 class method,定义 class method 时,需要默认给定一个 cls 参数,代表了类本身。
...
@classmethod
def from_string(cls, date_as_string):
day, month, year = map(int, date_as_string.split('-'))
date1 = cls(day, month, year)
return date1
date2 = Date.from_string('11-09-2012')
因为在 python 中没有 重载/Overloading,所以不能用 date2 = Date('11-09-2012')
的形式定义另外一个构造函数,因此需要类提供新的方法来进行“构造”。
在上面的例子中,@classmethod 为该类引入了一个新方法 from_string
。
@classmethod 修饰符是必须的,否则,如果没有这个修饰符,即使你为 from_string
写了 cls
作为一参,它实际上仍然是意义上的 self
,并且你只能通过 = Date().from_string(...
的形式使用它。
- 在类中,还有一类 method 是 static method,它对应于 C++ 中的 static method,其实就是一个普通的函数,只不过被包含在了类中,此时”类”的作用仅仅是作为一个 naming space.
...
@staticmethod
def is_date_valid(date_as_string):
day, month, year = map(int, date_as_string.split('-'))
return day <= 31 and month <= 12 and year <= 3999
# usage:
is_date = Date.is_date_valid('11-09-2012')
同样,该方法不需要任何类的实例就可以直接使用。
当然,对于 class method 和 static method,你通过类的实例使用也是没问题的,即 is_date = Date().is_date_valid('11-09-2012')
也合法,但没人这样用,因为它使得 class method 和 static method 的含义变得模糊了。
2. @property
见下一节