ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

图灵星环:Python 属性描述符:从原理到 ORM 实践

图灵星环:Python 属性描述符:从原理到 ORM 实践 绝大部分的开发者都熟练地使用了这个符号, 还熟练地使用了类属性约束这种手段, 也熟练地进行了类型校验的操作, 同时也熟练地定义了ORM中的字段, 但是极少有人知道这些事情的背后核心原因都是属性描述符。属性描述符其实是面向对象体系里面那些特别基础的东西, 它就是最底层的基础设施。它本来就是一个原生的机制, 专门用来实现属性拦截、读写控制、数据校验还有自动映射以及懒加载这些功能的。我们说得简单一点, 描述符这个玩意儿是可以直接把一个类属性的读取操作、赋值操作以及删除操作这三个方面全都接管的。这样一来, 就给了普通的属性一种能加入自定义逻辑的能力。这样做的好处就是, 不需要在每一个业务类里面都一遍又一遍地去写什么校验代码、映射代码还有取值代码了。在实际的开发工作过程中 , 那些被叫作是 、 的语法结构, 还有那些被标识为插槽变量的东西, 以及那些ORM框架里的字段设计, 它们全都是基于描述符协议这个机制来实现的。如果想要能够读懂那些比较高级的语法特性, 如果想要手动去编写一个轻便轻量级的ORM系统, 如果想要实现代码之间那种优雅合理的解耦效果, 那么就必须要把属性描述符的基本原理给彻底搞明白。这是一篇完全用文字构成的深度教程。我们先把描述符的核心协议、分类的规则, 还有优先级的机制, 以及需要避开的那些坑点, 都从零开始进行拆解。接着, 我们会将这些知识最终应用到ORM核心原理的实际操作中去, 从而完整地串联起从底层原理到工程实践运用的整个完整过程。一、关于属性描述符, 它有一个核心性的定义, 并且存在一个协议相关的体系。1.所谓的属性描述符, 具体来说究竟是什么呢?任意一类, 只要在该类中实现了用于描述符协议中的任意一种魔法方法, 这个类的实例就会变成一个属性描述符。当这个实例作为另一个类的类属性存在的时候, 它就会自动接管这个属性的所有读取数据、写入数据以及删除数据的操作。描述符这种东西是典型的「属性代理模式」, 它不会去修改宿主类在调用时候的方式, 但是它却能够全权去掌控属性的底层逻辑, 从而实现逻辑和业务之间的解耦。1.关于完整描述符协议中那三个核心的方法, 这里对它们进行了非常完整的详细叙述和介绍说明。这个描述符协议里面是包含了三个核心方法的, 这三个方法正好对应了属性的三种操作行为。所以说是所有那些高级特性最基础的东西。第一点就是方法, 它的主要任务是负责那些属于属性读取的操作。也就是说, 当有人去访问宿主对象里头的那个属性的时候, 这个动作会被自动触发, 然后它会返回一个经过自定义处理之后的读取结果。在这个过程中, 会接收到两个核心的参数, 其中一个参数是宿主实例对象, 另外一个参数则是宿主类本身。正因为有了这两个参数, 所以它可以同时支持两种不同的使用场景, 分别是进行类的访问和进行实例的访问。第二点, 方法是负责属性赋值的那个操作。一旦给宿主对象里面的那个属性值进行了赋值行为, 它就会被自动地启动和触发起来。通过这种方式, 能够去实现数据校验功能, 能够对类型做出限制措施, 可以实现把原来的数值进行改写动作, 还能够起到截留住赋值这个行为的核心能力。第三, 是方法这一部分, 该部分的工作职责专门针对属性的删除操作进行负责, 具体情形是在执行名为 del 的语句来进行属性删除这个动作的过程中被触发所执行的, 其主要目的或者用于实施清理缓存数据、释放那些不再需要的资源以及清空映射数据等相关事宜。.6 新增了该新的方法, 在宿主类进行定义的时候会自动触发这个操作, 可以自动获取当前描述符所绑定的属性名称, 彻底解决了传统描述符需要手动传入属性名的冗杂问题的现象, 这成为了现代 ORM 中字段实现的核心依赖内容。二、描述符主要可以分成两个核心类别, 一个被称为数据描述符, 另一个则是非数据描述符。这是描述符最核心、也最容易让人搞混的一个底层规则。这个规则直接决定了属性的优先级以及最终的执行逻辑。同时, 它也是理解 ORM 字段行为的一个关键所在。2.1 数据描述符同时实现了相应的访问器方法的描述符, 被称为是数据描述符, 这个玩意儿的特点就是优先级特别高, 它会完全地把宿主实例里面同名的那个属性给覆盖掉, 不管说该实例到底有没有自己去定义过什么属性也好, 还是怎么样也好, 只要涉及读写操作的话, 最终都会去走描述符里面的逻辑。所有的ORM字段, 还有那些带有赋值校验功能的自定义属性, 全部都采用了数据描述符的方式来进行实现, 这样做的目的是为了能够保证在对这些字段进行读和写操作时候的逻辑可以被绝对地控制住。2.关于第二点, 也就是那个非数据描述符。仅仅实现了某些方法, 却没有实现其它相关方法的这类描述符, 就被称为非数据描述符。因为它的优先级比较低, 所以, 当宿主实例里面存在着同名的属性的时候, 这个同名属性会直接覆盖掉这个描述符的原有逻辑, 从而不再触发对应的机制了。日常所使用的、没有赋值能力的, 都是非数据描述符。这就是为什么可以动态地给实例绑定同名的方法, 以此来覆盖原有的类方法的底层原因。三、关于描述符优先级的底层规则, 这可是必须要牢记在最核心的位置的内容。属性查找它并不是说简单的那个情况, 就是优先考虑那个实例, 然后其次才考虑类, 而是它严格地去遵循那个固定的优先级, 你要是把那套规则全部都吃透的化, 那你就能彻底去解决所有描述符的那些诡异的问题了。从高到低依次排名的顺序是这样的: 第一个是数据描述符, 第二个是实例字典属性, 第三个是非数据描述符, 第四个是类普通属性, 第五个是父类属性。通俗来解读一下这个情况: 任何属于数据描述符性质的内容, 都要优先去执行它们自身的逻辑操作, 在这种情况下, 如果在实例里面定义了同名的属性, 试图通过这种方式来进行重写是无效的, 根本派不上用场而对于非数据描述符来讲, 如果与实例属性发生冲突, 那么会被实例属性轻松覆盖掉与此同时, 普通的类属性在整个优先级序列中处于最底下的位置, 权重最低。这个规定说的是ORM字段的一个核心特点, 你哪怕直接给模型实例的某一个个案赋值, 也还是能够引起字段类型的校验工作还有数据的映射操作发生的原因, 这是由于这种 ORM 字段它本身是标准类型的数据描述符。四、所谓的描述符所具备的核心能力, 其实就是从那些基础的语法知识开始, 一直到它最后所展现出来的在工程方面的实际价值。说句实在话, 描述符这个东西, 它的核心价值, 就是把那些分散在无数个业务类里面的属性校验规则, 还有类型限制、默认值、懒加载功能, 以及数据映射的逻辑, 全都给抽离出来, 集中处理。这么做的好处就是, 你只需要定义一次, 然后在整个项目里其他地方都能直接拿来用, 这就是所谓的统一复用属性逻辑。4.1 统一数据校验在传统开发场景里, 给每个类的属性进行赋值操作时, 都得单独写出 if 判断语句来检查类型还有设置范围限制规则, 导致代码看起来冗余而且风格不统一。要是通过描述符手段把通用的校验逻辑封装起来, 所有绑定上该描述符的属性就会 自动拥有校验能力, 一旦在赋值过程中出现违规情况就直接进行拦截并报错提醒。4.第2步, 来实现属性进行懒加载的这个功能。对于那些需要耗费时间进行计算的内容、数据库查询操作或者是文件读取之类的属性, 大家能够通过描述符在相关的地方去实现一个叫做懒加载的技术方案。简单来说呢, 就是第一次去访问这些内容的时候, 系统才会去执行具体的计算任务, 并且把结果给缓存起来。而到了第二次以及以后的每一次访问操作时, 就只需要直接去读取之前已经缓存好的数据就可以了。这样一来, 整体的性能就能够获得非常大幅度的提升。4.第三步涉及到对数据的无感劫持以及随后的改写操作。完全不用去修改那个宿主类里面的任何业务逻辑, 描述符这东西可以全权接管对属性的读取和写入操作, 然后自动去把数据格式化处理一下, 做一下脱敏, 搞一下加密, 再转换一下单位, 这样一来就能实现业务逻辑和数据处理逻辑两者之间达到完全解耦的效果。4.4 支撑框架级抽象所有的这些高级框架, 它们所使用的声明式语法, 在底层的部分, 全部都是由描述符来进行依赖的, 比如像 ORM 当中的字段声明, 还有表单校验这种操作, 以及配置映射这一块内容, 包括模型参数的约束这些情况, 都算是描述符在工程方面的具体落地体现。五、关于描述符与关系之间的具体联系, 我们应当对原有的认知误区进行破除。很多开发者会错误地产生一种想法, 认为这个符号是属于一种独立的语法的类型, 可是实际上呢, 它其实是被内置在里面的一个标准的那种描述符的对象或者说实体。它实际上是一个已经被封装好的数据描述符, 在内部实现了具体的逻辑功能, 只是从语法层面使用了装饰器进行了简化处理, 目的是为了帮助开发者能够更加方便、快速地去定义那些只读属性或者可读写属性。但是, 它存在明显的短板, 这种短板具体来说就是它仅能服务于单个属性, 因而无法做到批量的复用。与之形成对比的是, 自定义描述符具备一次定义、绑定无限个类属性的能力。正因为上述原因, ORM 最终选择了放弃原有方案, 转而采用原生描述符, 这被视作其核心的决策依据。六、在核心的工程实践方面, 我们所做的具体内容, 就是基于描述符这个东西来手工编写一个极简主义的ORM工具。这个ORM的核心本质, 其实就是基于数据描述符来实现的, 它做的事情是把数据库里的字段和对象里面的属性建立起双向映射关系。不管是什么样的ORM系统的字段系统, 它们在底层的逻辑全都是完全一样的。我们通过纯逻辑进行拆解, 把 ORM 最核心的底层原理给还原出来, 这样就能够彻底地把描述符和框架实战之间的关联给打通。6.1在关于ORM字段方面, 其核心设计思路。在 ORM 模型类里面进行定义的那些情况, 其本质原因, 实际上都是为了构成自定义的数据描述符的一个实例。字段描述符通过一种自动捕获的方式来处理数据库里的字段名, 接着又借助另外一种手段去对进到数据库里的数据进行检查以及调整格式的操作, 同时还利用第三种方法从放在实例里面的缓存当中把数据读出来, 最终达成这样一个结果, 也就是开发者在去操作对象的属性的时候, 其效果和去直接操作数据库里的字段是一样的。6.关于 2 这个极简 orm 完整的逻辑, 接下来我们要进行非常详细的拆解。先做一步, 也就是定制那个通用的字段描述符。你要去建一个字段类。在这个类里面, 要让它做到自动地把字段名给绑上。还要让校验和赋值数据类型那块的功能实现出来, 并且要把那些不合法的值给拦截掉。同时呢, 也要把从实例里读存储的字段的这个数据逻辑给实现了。因为这个字段类是标准的数据描述符, 所以它能保证你说的赋值、读取的逻辑是永远起作用的。这样以后就不会又被那个啥实例属性给重新覆盖了。第二步的工作是, 定义出一个模型基类出来。所有的那些数据模型都要去继承这个基类。这个基类的任务是, 统一封装好关于数据存储、数据查询以及数据保存这些通用的逻辑环节。它的目的是, 把所有的字段数据都统一地进行托管, 从而达到避免代码重复写这么多遍的目的。执行第三步, 去对业务模型类作出声明操作, 具体的做法是在这份业务模型当中, 把自定义字段的描述符以类属性的形式进行绑定处理, 这一过程完全不必要编写任何用于读写数据的逻辑代码, 而是可以直接自动拥有校验数据类型的功能, 以及建立属性之间映射关系的这项能力。第四步, 要实现双向映射。在实例化模型对象以后, 对对象的属性进行赋值、读取以及删除操作, 这些操作全部由描述符来接管。最终的内容能够同步落地到数据库里面去, 从而完成 ORM 核心映射的闭环环节。6.关于三, 也就是工业级别的对象关系映射框架中的扩展逻辑。主流且已经成熟的对象关系映射框架, 其实只是在极简的描述符模型上面做了能力拓展。这些拓展具体来说是增加了空值校验、长度限制、默认值、主键自增、外键关联、查询缓存、事务绑定、SQL 自动生成等功能。但是呢, 它的底层核心依然是属性描述符协议。这就是为什么在 ORM 中, 模型字段必须被定义为类属性, 而不能被定义为实例属性。这是因为唯有通过类属性这一方式, 才可以触发描述符协议, 而如果在实例层面进行属性定义, 那么对应的描述符是无法对其进行接管以及完成映射操作的。七、描述符高频坑点与工程避坑指南7.将混淆数据与非数据描述符的优先级进行区分, 也就是把这两者放在一起比高低。在新手编程时, 最常见的一个错误做法是自定义了一个非数据描述符, 然后给实例赋予一个同名属性, 这样的操作会导致之前的描述符完全失效。在工程开发的过程里, 如果是那种需要控制读写操作的场合, 特别是要用来做ORM字段的场景, 就必须把所有东西统一起来, 专门使用实现完整方法的数据描述符。7.第二部分是关于混淆类属性与实例属性的这个问题。描述符一定要作为宿主类的类属性来绑定。如果只是把它绑定为实例属性的话, 那么就会无法触发任何协议方法了。这就是为什么说 ORM 字段必须写在类层级里头的根本原因所在。7.3 多实例数据共享污染描述符实例作为一个类属性, 具有全局唯一的特性。如果直接在描述符的内部去存储数据, 就会导致所有的宿主实例之间的数据进行互通, 并且产生相互污染的情况。正确的做法是: 把数据存储在这宿主实例的属性字典里面, 让描述符仅仅去做代理逻辑, 而不要直接去存储业务数据。7.第 4 项为未使用状态, 其硬编码了字段名。在传统的描述符机制里, 开发者需要手动去传递字段的名称, 这样不仅代码显得非常冗余, 而且非常容易出错, 所以在现代的开发流程中, 我们必须依靠自动捕获属性名的方式, 这样的做法才能很好地适应在使用 ORM 框架进行批量定义为各种字段定义时的应用场景。八、关于总结这一部分的内容, 主要是去把描述符的底层价值以及相应的工程定位给说清楚。第一个方面, 属性描述符是高级面向对象编程模式底下的基础支撑结构, 静态方法、对象关系映射还有表单数据验证机制以及配置管理系统这些功能, 全部都是依托于这一种机制来构建完成的, 这对于使用者而言, 是从单纯掌握基础语法使用能力跨越到能够理解各种框架内部运行原理的必须经历的一道关口。第二个方面, 我们要去说描述器这个东西, 它的核心价值和它的重要程度在于逻辑的复用和解耦这两个点, 它是把那些零零散散的属性的读写操作、还有校验以及映射之类的逻辑给统一地进行封装起来的, 这样做的好处是可以大幅度地减少重复性的代码的数量, 同时也能够提升代码的规范性。第三点说法是, 关于 ORM 这个玩意儿, 它的本质内容其实是那种描述符东西在框架级别上进行的运用操作, 具体表现就是通过数据这种描述符工具去实现对象身上那些属性和数据库里面字段两者之间的那种没有任何感觉的双向映射关系动作, 这一点是属于所有 ORM 框架都有的统一并且作为底层的根本原理内容的。第四条规矩是关于工程项目怎么才能稳稳当当落地。要是涉及到可以读又能改的数据, 就得用那种叫做“数据描述符”的东西管着它。如果是那些只能读不能改的场景, 就用另一种叫作“非数据描述符”的玩意儿来标记。在碰到需要把这一个个字段跟属性限制条件都给对应好的时候, 也或者是需要进行约束的地方, 最好都优先选用自己写的这种自定义描述符。这么做就是为了把那像零散拼凑起来一样、一遍又一遍检查的 if 判断语句, 还有那些重复无聊的代码块给彻底换掉, 让它们不再是开发里的大麻烦。
返回列表