装饰器
装饰器是python语言中的语法糖,可以通过装饰器对函数的功能进行拓展。
为什么需要装饰器
我们假设你的程序实现了say_hello()和say_goodbye()两个函数。
1 2 3 4 5 6 7 8 9 10 11
| def say_hello(): print("hello!")
def say_goodbye(): print("hello!")
if __name__ == '__main__': say_hello() say_goodbye()
|
假设上述代码中的say_goodbye函数出现了bug,为了之后能更好的维护,现在需要在调用方法前记录函数调用名称,以定位出错位置。
1 2 3 4 5
| [DEBUG]: Enter say_hello() Hello!
[DEBUG]: Enter say_goodbye() Goodbye!
|
实现方式:
1 2 3 4 5 6 7 8 9 10 11
| def say_hello(): print("[DEBUG]: enter say_hello()") print("hello!") def say_goodbye(): print("[DEBUG]: enter say_goodbye()") print("hello!") if __name__ == '__main__': say_hello() say_goodbye()
|
对上述代码进行优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def debug(): import inspect caller_name = inspect.stack()[1][3] print("[DEBUG]: enter {}()".format(caller_name))
def say_hello(): debug() print("hello!")
def say_goodbye(): debug() print("goodbye!")
if __name__ == '__main__': say_hello() say_goodbye()
|
上述代码需要在每个业务函数里都要调用一下debug()函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?
那么装饰器这时候应该登场了。
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。
如何实现一个装饰器
在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def debug(func): def wrapper(): print("[DEBUG]: enter {}()".format(func.__name__)) return func() return wrapper
def say_hello(): print("hello!")
say_hello = debug(say_hello)
say_hello()
|
上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def debug(func): def wrapper(): print("[DEBUG]: enter {}()".format(func.__name__)) return func()
return wrapper
@debug def say_hello(): print("hello!")
say_hello()
|
这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper接受和原函数一样的参数,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def debug(func): def wrapper(something): print("[DEBUG]: enter {}()".format(func.__name__)) return func(something)
return wrapper
@debug def say(something): print("hello {}!".format(something))
say('顾安')
|
这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数了。
1 2 3 4 5 6 7 8 9 10 11 12 13
| def debug(func): def wrapper(*args, **kwargs): print("[DEBUG]: enter {}()".format(func.__name__)) return func(*args, **kwargs) return wrapper
@debug def say(something): print("hello {}!".format(something))
say('顾安')
|
带参数的装饰器
假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。
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
| def logging(level): def wrapper(func): def inner_wrapper(*args, **kwargs): print("[{level}]: enter function {func}()".format( level=level, func=func.__name__)) return func(*args, **kwargs) return inner_wrapper return wrapper
@logging(level='INFO') def say(something): print("say {}!".format(something))
@logging(level='DEBUG') def do(something): print("do {}...".format(something))
if __name__ == '__main__': say('hello') do("my work")
|
是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG'),它其实是一个函数,会马上被执行,只要它返回的结果是一个装饰器时,那就没问题。细细再体会一下。
基于类的装饰器
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()方法,那么这个对象就是callable的。
1 2 3 4 5 6 7
| class Test: def __call__(self): print('call me!')
t = Test() t()
|
像__call__这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。
回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Logging: def __init__(self, func): self.func = func
def __call__(self, *args, **kwargs): print("[DEBUG]: enter function {func}()".format( func=self.func.__name__)) return self.func(*args, **kwargs)
@Logging def say(something): print("say {}!".format(something))
say('木木')
|
带参数的类装饰器
如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接收的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接收一个函数并返回一个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Logging: def __init__(self, level='INFO'): self.level = level
def __call__(self, func): def wrapper(*args, **kwargs): print("[{level}]: enter function {func}()".format( level=self.level, func=func.__name__)) func(*args, **kwargs)
return wrapper
@Logging(level='INFO') def say(something): print("say {}!".format(something))
say('木木')
|
property属性
@property - 简介
什么是property属性?
一种用起来像是使用的实例属性一样的特殊属性,可以对应于某个方法。
1 2 3 4 5 6 7 8 9 10 11 12
| class Foo: def func(self): pass
@property def prop(self): pass foo_obj = Foo() foo_obj.func() foo_obj.prop
|
1 2 3 4 5 6 7 8
| class Goods: @property def money(self): return 100
goods = Goods() print(goods.money)
|
property属性的定义和调用要注意一下几点:
- 定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
- 调用时,无需括号
简单的示例
对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据 这个分页的功能包括:
- 根据用户请求的当前页和总数据条数计算出 m 和 n
- 根据m 和 n 去数据库中请求数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Pager: def __init__(self, current_page): self.current_page = current_page self.per_items = 10
@property def start(self): val = (self.current_page - 1) * self.per_items return val
@property def end(self): val = self.current_page * self.per_items return val
p = Pager(1) print(p.start) print(p.end)
|
- Python的property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。
property属性的两种方式
- 装饰器 即:在方法上应用装饰器
- 类属性 即:在类中定义值为property对象的类属性
装饰器方式
在类的实例方法上应用@property装饰器
Python中的类有经典类和新式类,新式类的属性比经典类的属性丰富。( 如果类继object,那么该类是新式类 )
经典类,具有一种@property装饰器:
1 2 3 4 5 6 7 8 9
| class Goods: @property def price(self): return 100 obj = Goods() result = obj.price print(result)
|
新式类,具有三种@property装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Goods(object): @property def price(self): print('@property')
@price.setter def price(self, value): print('@price.setter')
@price.deleter def price(self): print('@price.deleter')
obj = Goods() print(obj.price) obj.price = 123 del obj.price
|
注意:
- 经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法
- 新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法
由于新式类中具有三种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
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
| class Goods(object):
def __init__(self): self.original_price = 100 self.discount = 0.8
@property def price(self): new_price = self.original_price * self.discount return new_price
@price.setter def price(self, value): self.original_price = value
@price.deleter def price(self): del self.original_price
obj = Goods() print(obj.price) obj.price = 200 print(obj.price) del obj.price
|
类属性方式,创建值为property对象的类属性
- 当使用类属性的方式创建property属性时,
经典类和新式类无区别
1 2 3 4 5 6 7 8 9 10
| class Goods: def get_price(self): return 100
price = property(get_price)
obj = Goods() result = obj.price print(result)
|
property方法中有个四个参数
- 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
- 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
- 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
- 第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Foo(object): def get_bar(self): print("getter...") return 'a...'
def set_bar(self, value): """必须两个参数""" print("setter:", value) return 'set value' + value
def del_bar(self): print("deleter...") return 'b...'
BAR = property(get_bar, set_bar, del_bar, "description...")
obj = Foo()
print(obj.BAR) obj.BAR = "c" desc = Foo.BAR.__doc__ print(desc) del obj.BAR
|
由于类属性方式创建property属性具有3种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
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
| class Goods(object):
def __init__(self): self.original_price = 100 self.discount = 0.8
def get_price(self): new_price = self.original_price * self.discount return new_price
def set_price(self, value): self.original_price = value
def del_price(self): del self.original_price
PRICE = property(get_price, set_price, del_price, '价格属性描述...')
obj = Goods() print(obj.PRICE) obj.PRICE = 200 print(obj.PRICE) del obj.PRICE
|
@property - 应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Money(object): def __init__(self): self.__money = 0
def get_money(self): return self.__money
def set_money(self, value): if isinstance(value, int): self.__money = value else: print("error:不是整型数字")
money = Money() print(money.get_money()) money.set_money(10) print(money.get_money())
|
- 使用property升级getter和setter方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Money(object): def __init__(self): self.__money = 0
def get_money(self): return self.__money
def set_money(self, value): if isinstance(value, int): self.__money = value else: print("error:不是整型数字")
money = property(get_money, set_money)
money_obj = Money() money_obj.money = 100 print(money_obj.money)
|
- 使用property取代getter和setter方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Money(object): def __init__(self): self.__money = 0
@property def money(self): return self.__money
@money.setter def money(self, value): if isinstance(value, int): self.__money = value else: print("error:不是整型数字")
money_obj = Money() money_obj.money = 100 print(money_obj.money)
|