zoukankan      html  css  js  c++  java
  • python编程之数据描述器

      第一次实际接触描述符是在我们项目中我们需要实现自己的Manger管理器从而在查询时加上特定条件,所以去看了Django ORM的源码就打开了对数据描述符的大门。

      要入门描述符,我们首先要知道几个前提的知识:

    1. 什么是描述器?
    2. 什么是数据描述器?
    3. 什么是非数据描述器?
    4. 属性被调用时,属性访问的顺序?

    01、什么是描述器

      python官方的解释是:一般地,一个描述器是一个包含 “绑定行为” 的对象,对其属性的访问被描述器协议中定义的方法覆盖。这些方法有:__get__()__set__() 和 __delete__()。如果某个对象中定义了这些方法中的任意一个,那么这个对象就可以被称为一个描述器。

      通俗的讲,描述器其实就是一个类, 这个类至少要实现__get__()__set__() 和 __delete__()中的一个。

    class Descriptor:
        def __init__(self, default):
            self.value = default
            self.attr_dict = dict()
    
        def __get__(self, instance, owner):
            # 在这里instance等于Digit的实例子, owner等于Digit
            return self.attr_dict[instance]
    
        def __set__(self, instance, value):
            self.attr_dict[instance] = value
    
    
    class Digit:
        num = Descriptor(0)
    
        def __init__(self, num):
            self.num = num

      在上图代码中 Descriptor 类实现了__get__()__set__() 和 __delete__()中的一个,所以是一个描述器。

    02、什么是数据描述器

      数据描述器:只要实现了__get__()__set__() 和 __delete__()中的任意两个,就称为数据描述器。

    03、什么是非数据描述器

      非数据描述器: 只是实现了__get__()__set__() 和 __delete__()中的一个,就称为非数据描述器。

    04、属性被调用时,属性访问的顺序

      ① __getattribute__(), 无条件调用
      ② 数据描述符:由 ① 触发调用 (若人为的重载了该 __getattribute__() 方法,可能会调职无法调用描述符)
      ③ 实例对象的字典(若与描述符对象同名,会被覆盖哦)
      ④ 类的字典
      ⑤ 非数据描述符
      ⑥ 父类的字典
      ⑦ __getattr__() 方法
     
      根据这个顺序我们知道,如果描述器是一个数据描述器时,当描述器的属性名和实例的属性重名时,如上代码中Digit类属性num和实例属性num重名了, 这个时候如果Digit类的实例访问num属性,其实是访问数据描述器的__get__放法的。自然如果Descriptor描述器如果只实现了__get__方法即为非数据描述器,这是Digit类的实例访问num属性,访问的即是实例自己的num属性。并且根据这个属性访问顺序我们可以知道,如果重写__getattribute__()是可以让数据描述失效的,这个大家可以测试一下。
     
     
      当我们了解了上述知识后,我们就想知道这个描述器有怎样的应用场景呢?
    python官方的应用场景有:函数、属性、静态方法和类方法,也就是python的这些底层实现是用到了描述器的,后续我们将自己来实现python自带的property描述器。
    我们平时代码中的应用场景有:数据校验,还有ORM Manager的只让类调用,不能是实例调用。
     

    05、ORM中ManagerDescriper的实现

      我们在使用Django ORM语句对数据库进行操作时,都是用类.objects.get()为什么不能是instance.objects.get()呢?这是因为ManagerDescriper描述器中对调用者做了限制,看代码。
    class ManagerDescriptor:
    
        def __init__(self, manager):
            self.manager = manager
    
        def __get__(self, instance, cls=None):
            if instance is not None:           # 这里表示不允许用实例去调用onjects只能用类调用
                raise AttributeError("Manager isn't accessible via %s instances" % cls.__name__)
    
            if cls._meta.abstract:
                raise AttributeError("Manager isn't available; %s is abstract" % (
                    cls._meta.object_name,
                ))
    
            if cls._meta.swapped:
                raise AttributeError(
                    "Manager isn't available; '%s.%s' has been swapped for '%s'" % (
                        cls._meta.app_label,
                        cls._meta.object_name,
                        cls._meta.swapped,
                    )
                )
    
            return cls._meta.managers_map[self.manager.name]

      这里真的设计的真的很精妙,可能很多朋友看到这有点懵逼objects是什么?ManagerDescriptor是怎样被调用的,因为本节主讲描述器相关的知识,所以这些可去查询ORM相关的源码。

    06、数据校验

      当我做教学管理系统时,我们有一个学生类用于记录每个学生每科的成绩,我们要求记录记录的成绩不能是负数,这时我们学生类的代码可能如下:

    class Student:
    
        def __init__(self, chinese, math):
            self.chinese = chinese if chinese >= 0 else 0
            self.math = math if chinese >= 0 else 0

      试想如果是这种写法的话,如果我们对于分数多几个校验条件,或者多几个科目,这个代码整体将会非常的臃肿和难看。

     

      这时我们的脑海可能立马闪现出用python自带的property来装饰科目属性,就可以省去大量的重复代码了,这样就有我们的2.0代码

    class Student:
    
        def __init__(self, chinese, math):
            self._chinese = None
            self._math = None
            self.chinese = chinese
            self.math = math
    
        @property
        def math(self):
            return self._math
    
        @math.setter
        def math(self, value):
            if value >= 0:
                self._math = value
            else:
                self._math = 0
    
        @property
        def chinese(self):
            return self._chinese
    
        @chinese.setter
        def chinese(self, value):
            if value >= 0:
                self._chinese = value
            else:
                self._chinese = 0
    
    
    c = Student(1, -1)

      这时我们如果有多种校验时能让代码简洁很多,可是还是没能解决多科目的情况下重复代码的问题,并且带来一个额外的开销是要创建每个科目的额外变量来存储分数。

      这时就是我们的描述器上场了。

    class Score:
    
        def __init__(self):
            self.score = dict()
    
        def __get__(self, instance, owner):
            if not self.score[instance]:
                return 0
            return self.score[instance]
    
        def __set__(self, instance, value):
            if value >= 0:
                self.score[instance] = value
            else:
                self.score[instance] = 0
    
    
    class Student:
        chinese = Score()
        math = Score()
    
        def __init__(self, chinese, math):
            self.chinese = chinese
            self.math = math
    
    
    c = Student(1, -1)
    print(c.math)

      这里我们用Score描述器去存储各科目的分数,完美的解决了多科目时的数据校验问题, 不过它也有局限性,这里能这样用的前提是, 每个科目的检验方式是相同的,不然就得新建一个描述器去储存分数值,在描述器中我们用instance作为字典的键,这是有一定局限性的,如果instance的类是一个list类型等可变类型就不能把instance作为键。

      看到这里有没有发现,有点像ORM的Model类了,哈哈,自己去看看源码。

    07、用描述器实现property

    class Property:
        def __init__(self, fget=None, fset=None, fdelete=None):
            self.fget = fget
            self.fset = fset
            self.fdel = fdelete
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            if self.fget is None:
                raise AttributeError("unreadable attribute")
            return self.fget(instance)
    
        def __set__(self, instance, value):
            if self.fset is None:
                raise AttributeError("can't set attribute")
            self.fset(instance, value)
    
        def __delete__(self, instance):
            if self.fdel is None:
                raise AttributeError("can't delete attribute")
            self.fdel(instance)
    
        def getter(self, func):
            return type(self)(func, self.fset, self.fdel)
    
        def setter(self, func):
            return type(self)(self.fget, func, self.fdel)
    
        def deleter(self, func):
            return type(self)(self.fget, self.fset, func)
    
    
    class Student:
    
        def __init__(self, chinese, math):
            self._chinese = None
            self._math = None
            self.chinese = chinese
            self.math = math
    
        @Property
        def math(self):
            return self._math
    
        @math.setter
        def math(self, value):
            if value >= 0:
                self._math = value
            else:
                self._math = 0
    
        @Property
        def chinese(self):
            return self._chinese
    
        @chinese.setter
        def chinese(self, value):
            if value >= 0:
                self._chinese = value
            else:
                self._chinese = 0
    
    
    c = Student(1, -1)
    print(c.math)
    c.math = 1

    上面代码中的Property是python代码的实现,发现它本质上用的就是描述器。

    其他python底层方法实现可以参考官网https://docs.python.org/zh-cn/3/howto/descriptor.html

  • 相关阅读:
    Ubuntu下systemd服务的配置
    编译压缩代码 MFCompress-src-1.01 :对‘***’未定义的引用
    德尔福 基础
    德尔福 XE5 安卓权限设置
    德尔福 XE5 安卓调试
    复制任意文件或文件夹到剪贴板
    无法完成安装:'Cannot access storage file '/
    Centos7.4安装kvm虚拟机(使用virt-manager管理)
    MSYS2 使用
    线程
  • 原文地址:https://www.cnblogs.com/lifei01/p/13204435.html
Copyright © 2011-2022 走看看