怎么在Python装饰器中自定义功能呢?用这种方法让你“随心所欲”

本文始发于我的公众号:TechFlow,原创不易,求个关注程序员


今天是Python专题的第14篇文章,咱们继续装饰器的话题,来看看怎么给装饰器包装方法,实现更多灵活的操做。web

在以前的文章当中,咱们实现了对装饰器赋予参数,从而能够经过传入不一样的参数来控制装饰器中的逻辑。这样作能够大大地增长装饰器的灵活性,可是仍然不足以解决全部的问题。编程

若是咱们面临一个变更很频繁的业务,之后也许须要加上一些当前想不到的逻辑,这个时候就没有办法仅仅经过参数来控制了。那么有没有办法不只仅是传入参数,而是能够给装饰器添加不一样的逻辑呢?app

固然是有的,可是这个操做比较复杂,让咱们抽丝剥茧,一点一点来吃透它。框架

setattr和getattr操做

首先咱们来看下setattr和getattr这两个方法,attr是attribute的缩写,也就是属性的意思。咱们搞明白了这个单词的意思以后就简单了,根据字面能够理解到,这两个方法一个是设置属性一个是获取属性编辑器

是的,就是这么简单,没错。函数式编程

其中getattr尤为简单,基本上等价于使用.去获取属性。函数

咱们来看一个最简单的例子,咱们先建立一个类,而后给它附上一个属性。工具

class A:
 def __init__(self):  self.name = 'hello' 复制代码

以后,咱们可使用getattr方法去得到它的name属性:学习

a = A()
getattr(a, 'name') 复制代码

有get天然就有set,咱们也能够经过setattr为它附上新的属性。第二个参数是新增的属性名称,第三个参数是属性的值。

setattr(a, 'age', 18)
复制代码

这样,当咱们去执行a.age的时候,就会得到18。这里要注意的是,咱们只是单纯地为a这个实例建立了新的属性,并无更改A这个类中的定义。因此其余A这个类的实例并不会受到影响,另外若是咱们将多个值赋值给了同一个属性名会发生覆盖,也就是后面的覆盖前面的。

属性这个词在Python中的定义是比较宽泛的,除了变量能够称做是属性,函数也同样能够做为属性。也就是说咱们除了能够添加一个变量以外,也能够添加一个函数

咱们来看个例子:

def print_log():
 print('This is a log') 复制代码

这是一个简单的demo方法,咱们经过setattr将它赋值给实例a,那么咱们就能够在实例a中调用它了。

不只仅如此,类也同样能够经过setattr方法设置。

理解了setattr和getattr的用法以后,咱们不由有一个问题,咱们经过.操做不香吗,为何还要搞一个setattr和getattr出来呢?

若是咱们本身写代码写着玩,固然是用.操做更方便,但若是是实际的开发场景。颇有可能咱们须要添加的属性的名称是个变量,而不是写死的,也就是说是可配置的。这个时候就不能经过.了,咱们考虑问题的时候不能仅仅从功能入手,也须要思考一下它的使用场景。

为装饰器定义属性

setattr咱们都已经熟悉了,接下来回到正题。Python当中一切都是对象,一样函数也是对象。既然函数也是对象,那么咱们就能够给函数也设置属性。装饰器的本质就是函数,因此咱们能够给装饰器内包装的函数也设置属性,为了方便你们理解,我先不用setattr,让你们看看单纯的带属性的装饰器是什么样的。

def decorate(func):
 logmsg = func.__name__   @wraps(func)  def wrapper(*args, **kwargs):  print(logmsg)  return func(*args, **kwargs)   def set_message(newmsg):  nonlocal logmsg  logmsg = newmsg   wrapper.set_message = set_message  return wrapper 复制代码

若是咱们把set_message这个方法拿掉的话,它就是一个普普统统的装饰器。set_message方法当中,咱们使用nonlocal关键字修改了logmsg这个变量的值,而这个值会在装饰器的包装函数当中用到。也就是说咱们经过调用set_message方法,能够修改这个装饰器的运行结果和逻辑。

这里,咱们没用装饰器,而是简单地使用了.关键字来对它进行了赋值。仍是和以前说的同样,这样固然是能够的,可是若是咱们想要配置这个name就作不到了。最多见的场景就是区分线上和测试环境,一种作法是在接口的名字以前加上一个标识,好比线上是online,测试环境是test或者是dev。经过这种方法区分不一样环境的逻辑。

因此比较好的方法是将这个逻辑也写成一个装饰器,将被包装的方法做为参数传入。若是你看明白了上一篇文章,熟悉装饰器传参的话,这段代码对你来讲应该很简单。

def attach(obj):
 @wraps(obj)  def wrapper(func):  setattr(obj, func.__name__, func)  return func  return wrapper 复制代码

有了attach这个装饰器以后,咱们只须要给set_message这个方法加上注解,将被包装的函数做为参数传入便可。

 @attach(wrapper)
 def set_message(newmsg):  nonlocal logmsg  logmsg = newmsg 复制代码

若是只是想要实现功能,而不追求规范的话,可使用partial来简化代码,减小它的层次结构:

def attach(obj, func=None):
 if func is None:  return partial(attach_wrapper, obj)  setattr(obj, func.__name__, func)  return func 复制代码

这样写也是能够work的,只要熟悉partial的用法,应该也不难理解。

让函数随心所欲

若是你是一个程序员,你面临一个变更很频繁的业务,你没法预知以后的需求状况,想要代码有足够大的机动余地,这个时候能够利用强大的setattr给程序留一个“后门”,方便后面临时修改。

具体的作法其实很简单,咱们在装饰器当中定义一个dict,用来存储自定义的函数。再实现一个set_func方法将自定义的函数存储进这个dict当中,只有就能够经过参数,在不修改装饰器的状况下自由变动装饰器内的逻辑了。

咱们来看代码:

def decorate(func):
 func_dict = {}   @wraps(func)  def wrapper(*args, **kwargs):  # 经过key来选择应该调用哪个函数做为装饰器的逻辑  if kwargs.get('key') is not None:  func_dict[kwargs['key']](*args, **kwargs)  return func(*args, **kwargs)   # 将函数名和函数做为参数传入,存储在dict中  @attach(wrapper)  def set_func(func_name, func):  nonlocal func_dict  func_dict[func_name] = func   return wrapper 复制代码

咱们再来看一个使用的例子:

def test(*args, **kw):
 print('test') add.set_func('test', test) add(3, 4, key='test') 复制代码

这样,咱们就把test方法中的逻辑放入了装饰器当中,只有咱们须要,咱们还能够写出其余的方法,来自定义咱们对装饰器的需求,而又不须要修改装饰器内部的逻辑。不只如此,咱们还能够在主体函数的先后都加上这样的逻辑,真的能够说是随心所欲了。

固然通常状况下咱们用不到这样的骚操做,可是可以写出来或者说看懂这样的功能,那就说明关于装饰器的理解已经算是入门了。

结尾

装饰器能够说是函数式编程在Python当中最重要的使用渠道,在许多Python工具和框架当中大量使用。其实咱们学习的并不只仅是装饰器的一两种奇淫技巧,也是函数式编程的一些思想和理念。当咱们将这些理念理解深入了以后,不只仅是Python,一样能够在许多其余的领域得到日新月异的进步。

各位看官大人,赏个关注吧~

相关文章
相关标签/搜索