Python中的装饰器(decorator)与描述器(descriptor)

python

装饰器(decorator)和描述器(descriptor)是Python中两个重要的概念,理解它们是深入理解Python的关键,因为这是很多特性的基础,包括:函数、方法、属性、类方法、静态方法和父类的引用等等(Python如何实现property、classmethod和staticmethod)。

本文重点在于探讨decorator和descriptor特性的细节。


装饰器(decorator)

装饰器可以认为是面向切面编程模式(AOP aspect-oriented programming)的一种实现,可以对原有功能做扩展或者彻底的改变,而不改变外部的调用方式。

语法分析

下面来看一个例子,fPrint函数实现了一个打印的功能,现在需要在不改变fPrint的前提下,为打印内容增加一个前缀,以方便阅读

def header(func):
    def wrapper(msg):
        print('log: ', end='')
        func(msg)

    return wrapper

@header
def fPrint(msg):
    print(msg)

# 调用打印
fPrint('msg')

# 打印内容
>>>> log: msg

从结果上来看fPrint('msg')的结果:先调用了header然后调用了fPrint。我们注意到,fPrint的函数定义前面,多了@header,装饰器语法实际上是将fPrint的实现做了替换,所以我们调用的不再是原始的那个fPrint。装饰器语法实际上等价于:

...

def fPrint(msg):
    print(msg)
    
fPrint = header(fPrint)

# 调用打印
fPrint('msg')

# 打印内容
>>>> log: msg

带有自定义入参的装饰器

通常装饰器函数是以被装饰函数作为入参,我们要手动传入参数该怎么办?记住一点装饰器语法实际是函数地址的替换,从最简单的装饰器(fPrint = header(fPrint))入手,推测一下自定义入参等价形式:fPrint = header(param)(fPrint),根据等价形式实现装饰器:

def header(ipt):
    def decorator(func):
        def wrapper(msg):
            print('{} log: '.format(ipt), end='')
            func(msg)
        return wrapper

    return decorator

@header('header')
def fPrint(msg):
    print(msg)

# 调用打印
fPrint('msg')

# 打印内容
>>>> header log: msg

关于语法的一些理解:当@header后面省略参数时,则以后面的函数定义(fPrint)为参数来完成函数替换。有参数时,执行后的结果为一个函数,再以后面的函数定义(fPrint)为参数完成函数替换,即fPrint = header('header')(fPrint)

装饰器函数写起来不是很好理解,尤其是对于含有自定义参数的装饰器,这里建议,先写出要实现的装饰器的等价形式,依此来写装饰器函数

装饰器类的定义

装饰器可以是一个函数,也可以是一个class, fPrint作为__init__函数的参数传入,fPrint = header(fPrint),这时fPrint已经不再是函数而是header的一个实例,至于有什么用等下篇再谈。

class header(object):
    def __init__(self, func):
        self.f = func

@header
def fPrint(msg):
    print(msg)

描述器(descriptor)

描述器是任何一个实现了描述器协议(__get__(), __set__(), 或者 __delete__())的对象,当描述器作为class属性时,对这个属性的访问便会触发相应的方法。通常使用a.b的语法来获取、赋值、删除一个class属性时,会在A.__dict__中查找属性b,如果b是一个描述器,相应的描述器方法就会被调用。

描述器的实现

以下方法仅适用于,一个描述器作为class属性时,这一点需要特别注意,如果作为实例属性,对应方法是不会被触发的
在以下的例子中,"属性"指的是class属性,即type(a).__dict__中的某个元素。

object.__get__(self, instance, owner=None)
当通过类或者实例获取属性时__get__方法时会被调用,可选的owner参数是持有者类(以a.b为例,ownerA),当通过实例获取属性时,instance为对应实例(以a.b为例,instancaa),通过持有者类获取时(A.b),instanceNone,这个方法应该返回一个计算好的属性值或者抛出AttributeError异常

object.__set__(self, instance, value)
通过持有者类的实例对一个属性设置新的值时会调用__set__方法,注意的是,添加set或者delete方法会使得描述器的类型变为数据型描述器,查看“描述器的调用”获取更多细节。

object.__delete__(self, instance)
通过持有者的实例来del一个属性时会调用__delete__方法

object.__set_name__(self, owner, name)
描述器被创建并添加到持有者类时被调用,这个调用不会自动进行,需要显式调用,以告知描述器已被持有者持有

class A:
    pass

descr = custom_descriptor()
A.attr = descr
descr.__set_name__(A, 'attr')

描述器方法的调用

首先,关注一下普通属性的调用优先级:
一个属性可以是class属性,也可以是实例属性,回顾一下属性调用的优先级:
a.x调用优先级为:a.__dict__['x']type(a).__dict__['x'],所以当一个class属性不存在同名的实例属性时,通过class和实例都可以调用。

描述器是基于属性的调用来发挥作用的,对于一个描述器(它是一个class 属性)在不存在实例属性x的情况下,a.x等价于A.x等价于type(a).__dict__['x']

  1. 以下是触发描述器__get__方法的情景:

    • 直接调用:
      x.__get__(a)

    • 通过实例调用:
      a.x

    • 通过类调用:
      A.x

  2. 以下是触发描述器__set__方法的情景:

class A(object):
    pass

class Descriptor(object):
    def __set__(self, instance, value):
        pass

A.b = Descriptor()
a = A()

# 用class调用不会触发__set__
# A.b = 'b'

# 实例调用才会触发__set__
a.b = 'b'
  1. 以下是触发描述器__delete__方法的情景:
class A(object):
    pass

class Descriptor(object):
    def __set__(self, instance, value):
        pass

    def __delete__(self, instance):
        pass

a = A()
A.b = Descriptor()
del a.b

数据型和非数据型描述器

  • 数据型描述器:
    定义了__set____get__方法,这种描述器可以监听读和写

  • 非数据型描述器:
    只定义了__get__方法,只能监听属性的读取操作。

二者的区别在于,以a.b为例:
对于一个非数据型描述器b,如果给a设置了一个实例属性b,根据属性读取的优先级,a.b读取的将是a的实例属性b,描述器的__get__方法将不会被触发。而对于数据型描述器,即使存在一个同名的实例属性,a.b访问的仍然是描述器,数据型描述器不会被实例属性覆盖。

class DataDescriptor(object):
    def __set__(self, instance, value):
        print('data descriptor set')

    def __get__(self, instance, owner=None):
        return 'data descriptor get'

class NonDataDescriptor(object):

    def __get__(self, instance, owner=None):
        return 'non-data descriptor get'

class A(object):
    dataDescriptor = DataDescriptor()
    nonDataDescriptor = NonDataDescriptor()
    pass

a = A()

print('a.dataDescriptor: {}'.format(a.dataDescriptor))
print('a.nonDataDescriptor: {}'.format(a.nonDataDescriptor))

a.dataDescriptor = 11

# 非数据型描述器会被实例属性覆盖
a.nonDataDescriptor = 22 

print('a.dataDescriptor: {}'.format(a.dataDescriptor))
print('a.nonDataDescriptor: {}'.format(a.nonDataDescriptor))

>>> a.dataDescriptor: data descriptor get
>>> a.nonDataDescriptor: non-data descriptor get
>>> data descriptor set
>>> a.dataDescriptor: data descriptor get
>>> a.nonDataDescriptor: 22

你可能感兴趣的:(Python中的装饰器(decorator)与描述器(descriptor))