武国民 發表於 2025-12-31 11:24:00

Python描述器(Descriptor)深度解析:OOP底层核心机制实操指南

<div data-page-id="E2rxfZQ9xdejlccaxaVcwCpsnef" data-lark-html-role="root" data-docx-has-block-data="false">
<div class="ace-line ace-line old-record-id-WPFDfsWb9dZQSPcPuMscfpwGnXf">前言:在Python面向对象(OOP)编程中,描述器是支撑诸多高级特性的底层核心机制——property、classmethod、staticmethod、甚至ORM框架的字段定义(如Django ORM的models.CharField),本质都是描述器的应用。但多数Python学习者停留在“使用封装好的特性”层面,对描述器本身的原理和实操认知模糊。本文从“原理极简拆解+多组实战代码”出发,带你吃透描述器,理解Python OOP的底层逻辑。</div>
<h2 class="heading-2 ace-line old-record-id-Pz7tfIRdmdf30jco1b2ciU8CnMe">一、核心定义:什么是描述器?</h2>
<div class="ace-line ace-line old-record-id-MJBNf1nHvdhE9xcTrnQcodyPnXb">描述器是实现了 <code>__get__</code>、<code>__set__</code>、<code>__delete__</code> 任意一个或多个方法的Python类(这三个方法被称为“描述器协议”)。</div>
<div class="ace-line ace-line old-record-id-FkrVfUdo5dtNMXcpMLOc28e1noQ">核心作用:<strong>控制属性的访问、赋值、删除行为</strong>,实现属性的精细化管控(如类型校验、值范围限制、懒加载等)。</div>
<div class="ace-line ace-line old-record-id-DXfAfSAaXdstKTcLLYCc3MfBn5b">分类:</div>
<ul class="list-bullet1">
<li class="ace-line ace-line old-record-id-QmETf7cWNdFJMecL2fMc2r79nDb" data-list="bullet">数据描述器(Data Descriptor):同时实现 <code>__get__</code> 和 <code>__set__</code></li>
<li class="ace-line ace-line old-record-id-ISnTfTu5HdCa21cQIUacxnQUnPf" data-list="bullet">非数据描述器(Non-Data Descriptor):仅实现 <code>__get__</code></li>
</ul>
<div class="ace-line ace-line old-record-id-KcFdfQsfBdAXGacgDGxcfRT6nce">优先级:数据描述器 &gt; 实例属性 &gt; 非数据描述器 &gt; 类属性(关键!后续代码验证)</div>
<div class="ace-line ace-line old-record-id-KcFdfQsfBdAXGacgDGxcfRT6nce">
<div data-page-id="E2rxfZQ9xdejlccaxaVcwCpsnef" data-lark-html-role="root" data-docx-has-block-data="false">
<h2 class="heading-2 ace-line old-record-id-FimUfuIa8dSzq0cB132c3afBnWf">二、核心原理:描述器协议三方法详解</h2>
<div class="ace-line ace-line old-record-id-JRK4fV6l2dE6k4c44Kicr0OTnDe">三个方法的通用签名(参数含义直接看注释,无废话):</div>
<div class="ace-line ace-line old-record-id-JRK4fV6l2dE6k4c44Kicr0OTnDe">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Descriptor:
    </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__get__</span><span style="color: rgba(0, 0, 0, 1)">(self, instance, owner):
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)">
      访问属性时触发
      :param instance: 拥有该描述器属性的实例对象(如obj),若通过类访问则为None
      :param owner: 拥有该描述器属性的类(如Cls)
      :return: 要返回的属性值
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span>
      <span style="color: rgba(0, 0, 255, 1)">pass</span>
   
    <span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__set__</span><span style="color: rgba(0, 0, 0, 1)">(self, instance, value):
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)">
      给属性赋值时触发
      :param instance: 实例对象(必传,不能通过类赋值触发)
      :param value: 要赋值的值
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span>
      <span style="color: rgba(0, 0, 255, 1)">pass</span>
   
    <span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__delete__</span><span style="color: rgba(0, 0, 0, 1)">(self, instance):
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)">
      删除属性时触发(del obj.attr)
      :param instance: 实例对象
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span>
      <span style="color: rgba(0, 0, 255, 1)">pass</span></pre>
</div>
<div data-page-id="E2rxfZQ9xdejlccaxaVcwCpsnef" data-lark-html-role="root" data-docx-has-block-data="false">
<h2 class="heading-2 ace-line old-record-id-IJTUfRvuYdSgBAcRyRLcny8rnEc">三、实操代码:从基础到进阶(全可运行)</h2>
<h3 class="heading-3 ace-line old-record-id-THIUfErL1duRXncFj20cYCQWnFv">3.1 基础案例:实现一个数据描述器(类型校验)</h3>
<div class="ace-line ace-line old-record-id-GifWfgDQjd64GWcdd6Tc2rWnngb">需求:定义一个IntField字段,要求属性值必须是整数,否则抛出异常。</div>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> IntField:
    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 实现__get__和__set__,成为数据描述器</span>
    <span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__get__</span><span style="color: rgba(0, 0, 0, 1)">(self, instance, owner):
      </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 这里用instance.__dict__存储实际值,避免触发__get__递归</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> instance.<span style="color: rgba(128, 0, 128, 1)">__dict__</span><span style="color: rgba(0, 0, 0, 1)">.get(self, None)
   
    </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__set__</span><span style="color: rgba(0, 0, 0, 1)">(self, instance, value):
      </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 类型校验(核心功能)</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(0, 0, 255, 1)">not</span><span style="color: rgba(0, 0, 0, 1)"> isinstance(value, int):
            </span><span style="color: rgba(0, 0, 255, 1)">raise</span> TypeError(f<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">属性值必须是整数,当前传入:{type(value)}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
      </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 把值存入实例的__dict__,key用self(描述器实例本身)</span>
      instance.<span style="color: rgba(128, 0, 128, 1)">__dict__</span> =<span style="color: rgba(0, 0, 0, 1)"> value

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 测试:使用描述器</span>
<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> User:
    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 给User类定义两个IntField属性</span>
    age =<span style="color: rgba(0, 0, 0, 1)"> IntField()
    score </span>=<span style="color: rgba(0, 0, 0, 1)"> IntField()

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 正常赋值</span>
u1 =<span style="color: rgba(0, 0, 0, 1)"> User()
u1.age </span>= 25<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 合法</span>
u1.score = 90<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 合法</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>(u1.age, u1.score)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出:25 90</span>

<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 异常赋值(触发类型校验)</span>
<span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">:
    u1.age </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">25</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 传入字符串</span>
<span style="color: rgba(0, 0, 255, 1)">except</span><span style="color: rgba(0, 0, 0, 1)"> TypeError as e:
    </span><span style="color: rgba(0, 0, 255, 1)">print</span>(e)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出:属性值必须是整数,当前传入:&lt;class 'str'&gt;</span>
    </pre>
</div>
<div data-page-id="E2rxfZQ9xdejlccaxaVcwCpsnef" data-lark-html-role="root" data-docx-has-block-data="false">
<div class="ace-line ace-line old-record-id-SMQxffzj2d49xPc60aecAQVcnVb">关键说明:用 <code>instance.__dict__ </code> 存储值,而非直接 <code>instance.attr = value</code>,避免赋值时再次触发 <code>__set__</code> 导致递归调用。</div>
<h3 class="heading-3 ace-line old-record-id-Zv6MfnohndwJmDcOtx8cyNvbnWd">3.2 验证描述器优先级(核心知识点)</h3>
<div class="ace-line ace-line old-record-id-GPt7f1xhOdtlYTcjkMIcQvBWnkb">用代码验证:数据描述器 &gt; 实例属性 &gt; 非数据描述器</div>
<div class="ace-line ace-line old-record-id-GPt7f1xhOdtlYTcjkMIcQvBWnkb">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 1. 定义数据描述器</span>
<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> DataDesc:
    </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__get__</span><span style="color: rgba(0, 0, 0, 1)">(self, instance, owner):
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">DataDesc的__get__被触发</span><span style="color: rgba(128, 0, 0, 1)">"</span>
    <span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__set__</span><span style="color: rgba(0, 0, 0, 1)">(self, instance, value):
      instance.</span><span style="color: rgba(128, 0, 128, 1)">__dict__</span> =<span style="color: rgba(0, 0, 0, 1)"> value

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 2. 定义非数据描述器</span>
<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> NonDataDesc:
    </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__get__</span><span style="color: rgba(0, 0, 0, 1)">(self, instance, owner):
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">NonDataDesc的__get__被触发</span><span style="color: rgba(128, 0, 0, 1)">"</span>

<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 3. 测试类</span>
<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Test:
    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 类属性:数据描述器、非数据描述器</span>
    data_desc =<span style="color: rgba(0, 0, 0, 1)"> DataDesc()
    non_data_desc </span>=<span style="color: rgba(0, 0, 0, 1)"> NonDataDesc()
    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 普通类属性</span>
    cls_attr = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">普通类属性</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">

t </span>=<span style="color: rgba(0, 0, 0, 1)"> Test()

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 验证1:数据描述器 &gt; 实例属性</span>
t.data_desc = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">我是实例属性</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 给实例赋值(本应存入__dict__)</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>(t.data_desc)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出:DataDesc的__get__被触发(数据描述器优先,忽略实例属性)</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>(t.<span style="color: rgba(128, 0, 128, 1)">__dict__</span>.get(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">data_desc</span><span style="color: rgba(128, 0, 0, 1)">"</span>))<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出:None(赋值被__set__拦截,未存入实例__dict__)</span>

<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 验证2:实例属性 &gt; 非数据描述器</span>
t.non_data_desc = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">我是实例属性</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 给实例赋值</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>(t.non_data_desc)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出:我是实例属性(实例属性优先,非数据描述器失效)</span>
<span style="color: rgba(0, 0, 255, 1)">del</span> t.non_data_desc<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 删除实例属性</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>(t.non_data_desc)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出:NonDataDesc的__get__被触发(实例属性删除后,非数据描述器生效)</span>

<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 验证3:非数据描述器 &gt; 普通类属性</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>(t.cls_attr)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出:普通类属性(无实例属性时,访问类属性)</span><span style="color: rgba(0, 128, 0, 1)">
#</span><span style="color: rgba(0, 128, 0, 1)"> 动态添加非数据描述器到类</span>
Test.cls_attr =<span style="color: rgba(0, 0, 0, 1)"> NonDataDesc()
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(t.cls_attr)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出:NonDataDesc的__get__被触发(非数据描述器优先)</span></pre>
</div>
<div data-page-id="E2rxfZQ9xdejlccaxaVcwCpsnef" data-lark-html-role="root" data-docx-has-block-data="false">
<h3 class="heading-3 ace-line old-record-id-HvMyfcdzRdjxcLcazAnc7XLhnse">3.3 进阶实战:用描述器实现懒加载(延迟初始化)</h3>
<div class="ace-line ace-line old-record-id-JNUXfGy0edHEIbcMuS5c3QJynxd">需求:某些属性(如数据库查询结果、大文件内容)初始化耗时,希望在<strong>第一次访问时才加载</strong>,而非实例创建时。</div>
<div class="ace-line ace-line old-record-id-JNUXfGy0edHEIbcMuS5c3QJynxd">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> time

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> LazyLoad:
    </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span><span style="color: rgba(0, 0, 0, 1)">(self, load_func):
      </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 接收一个加载函数(负责实际的耗时操作)</span>
      self.load_func =<span style="color: rgba(0, 0, 0, 1)"> load_func
   
    </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__get__</span><span style="color: rgba(0, 0, 0, 1)">(self, instance, owner):
      </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 第一次访问时,执行加载函数获取值</span>
      value =<span style="color: rgba(0, 0, 0, 1)"> self.load_func()
      </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 把加载后的值存入实例__dict__(用属性名作为key)</span>
      <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 这里通过instance.__dict__绑定,避免重复加载</span>
      instance.<span style="color: rgba(128, 0, 128, 1)">__dict__</span> =<span style="color: rgba(0, 0, 0, 1)"> value
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> value

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 模拟耗时操作(如数据库查询)</span>
<span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> load_user_info():
    </span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">开始加载用户信息(耗时操作)...</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
    time.sleep(</span>2)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 模拟耗时</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span> {<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">name</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">张三</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">id</span><span style="color: rgba(128, 0, 0, 1)">"</span>: 1001<span style="color: rgba(0, 0, 0, 1)">}

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 模拟耗时操作(如读取大文件)</span>
<span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> load_file_content():
    </span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">开始读取大文件(耗时操作)...</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
    time.sleep(</span>1<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">大文件内容...</span><span style="color: rgba(128, 0, 0, 1)">"</span>

<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 测试类</span>
<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> UserInfo:
    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 用LazyLoad描述器绑定耗时属性</span>
    user_info =<span style="color: rgba(0, 0, 0, 1)"> LazyLoad(load_user_info)
    file_content </span>=<span style="color: rgba(0, 0, 0, 1)"> LazyLoad(load_file_content)

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 实例化(此时不触发耗时操作)</span>
ui =<span style="color: rgba(0, 0, 0, 1)"> UserInfo()
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">实例创建完成,未触发加载</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 第一次访问user_info(触发加载)</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>(ui.user_info)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出:开始加载用户信息(耗时操作)...然后输出字典</span>

<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 第二次访问user_info(直接从实例__dict__获取,不触发加载)</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>(ui.user_info)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 直接输出字典,无耗时</span>

<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 访问file_content(触发加载)</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>(ui.file_content)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出:开始读取大文件(耗时操作)...然后输出内容</span>
    </pre>
</div>
<div data-page-id="E2rxfZQ9xdejlccaxaVcwCpsnef" data-lark-html-role="root" data-docx-has-block-data="false">
<div class="ace-line ace-line old-record-id-Yr95fGLQ9dbaEWcMiOTcKfqQnQe">优势:减少实例初始化时间,尤其适合有多个耗时属性的类(如ORM模型、大数据处理类)。</div>
<h3 class="heading-3 ace-line old-record-id-HcdyfDPhYdbsEZcBzKscVHkdnpc">3.4 源码级理解:property本质是数据描述器</h3>
<div class="ace-line ace-line old-record-id-JmWefjvxodjknbcGW7tcIbYjnMd">我们常用的 <code>@property</code> 装饰器,底层就是用描述器实现的。下面用描述器复刻一个简易版property:</div>
<div class="ace-line ace-line old-record-id-JmWefjvxodjknbcGW7tcIbYjnMd">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MyProperty:
    </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span>(self, fget=None, fset=None, fdel=<span style="color: rgba(0, 0, 0, 1)">None):
      </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 接收getter、setter、deleter函数</span>
      self.fget =<span style="color: rgba(0, 0, 0, 1)"> fget
      self.fset </span>=<span style="color: rgba(0, 0, 0, 1)"> fset
      self.fdel </span>=<span style="color: rgba(0, 0, 0, 1)"> fset
   
    </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__get__</span><span style="color: rgba(0, 0, 0, 1)">(self, instance, owner):
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> self.fget:
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> self.fget(instance)
   
    </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__set__</span><span style="color: rgba(0, 0, 0, 1)">(self, instance, value):
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> self.fset:
            self.fset(instance, value)
      </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">:
            </span><span style="color: rgba(0, 0, 255, 1)">raise</span> AttributeError(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">该属性不可赋值</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
   
    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 实现装饰器的setter方法(模仿@property.setter)</span>
    <span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> setter(self, func):
      self.fset </span>=<span style="color: rgba(0, 0, 0, 1)"> func
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> self

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 用MyProperty替代@property</span>
<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Person:
    </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span><span style="color: rgba(0, 0, 0, 1)">(self):
      self._name </span>= None<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 私有变量</span>
   
    <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 用MyProperty定义name属性</span>
<span style="color: rgba(0, 0, 0, 1)">    @MyProperty
    </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> name(self):
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> self._name
   
    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 用MyProperty.setter定义赋值逻辑</span>
<span style="color: rgba(0, 0, 0, 1)">    @name.setter
    </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> name(self, value):
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(0, 0, 255, 1)">not</span><span style="color: rgba(0, 0, 0, 1)"> isinstance(value, str):
            </span><span style="color: rgba(0, 0, 255, 1)">raise</span> TypeError(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">名字必须是字符串</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
      self._name </span>=<span style="color: rgba(0, 0, 0, 1)"> value

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 测试</span>
p =<span style="color: rgba(0, 0, 0, 1)"> Person()
p.name </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">李四</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 触发MyProperty.__set__</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>(p.name)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 触发MyProperty.__get__,输出:李四</span>

<span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">:
    p.name </span>= 123<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 非字符串,触发异常</span>
<span style="color: rgba(0, 0, 255, 1)">except</span><span style="color: rgba(0, 0, 0, 1)"> TypeError as e:
    </span><span style="color: rgba(0, 0, 255, 1)">print</span>(e)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出:名字必须是字符串</span>
    </pre>
</div>
<div data-page-id="E2rxfZQ9xdejlccaxaVcwCpsnef" data-lark-html-role="root" data-docx-has-block-data="false">
<div class="ace-line ace-line old-record-id-NCDrfkw9Vd0dMPcP6YOcGaNTnxb">结论:<code>@property</code> 本质是对描述器的封装,让我们无需手动实现 <code>__get__</code>/<code>__set__</code> 就能实现属性管控。</div>
<h2 class="heading-2 ace-line old-record-id-Ig8FfD2EidaKfscPdzLcr5twncc">四、实际应用场景(企业开发中常用)</h2>
<ol class="list-number1" start="1">
<li class="ace-line ace-line old-record-id-Zy3GfnNPjddlYCc9Nr1c42lunGb" data-list="number">ORM框架字段定义:如Django ORM的 <code>models.IntegerField</code>、<code>models.CharField</code>,底层用描述器实现字段类型校验、数据转换(数据库类型&lt;-&gt;Python类型)。</li>
<li class="ace-line ace-line old-record-id-XS4sfokWidkClOcB4DKcxOmcnZb" data-list="number">配置类属性管控:如项目配置类中,用描述器限制配置项的类型、值范围(如端口必须是0-65535的整数)。</li>
<li class="ace-line ace-line old-record-id-MYCzfFITQdaskMcDWpocecz4nTb" data-list="number">缓存/懒加载:如前面的案例,延迟加载耗时数据,提升程序启动速度。</li>
<li class="ace-line ace-line old-record-id-BgD4fRnpqdjluFcAuJJcRharnGf" data-list="number">权限控制:在属性访问时,通过描述器校验用户权限(如某些属性仅管理员可访问)。</li>
</ol>
<h2 class="heading-2 ace-line old-record-id-RpIWfLfQVdjm0dcB6mQcWKpWn2b">五、常见坑点(避坑指南)</h2>
<ul class="list-bullet1">
<li class="ace-line ace-line old-record-id-FuEFfpXrwd7vBuc3vArcJ3a8nkc" data-list="bullet">递归调用:在 <code>__get__</code>/<code>__set__</code> 中直接访问 <code>instance.attr</code> 会再次触发描述器,导致递归栈溢出,需用 <code>instance.__dict__</code> 直接操作。</li>
<li class="ace-line ace-line old-record-id-HVq1fmGiXdtQU3c65CVcDPQFnqA" data-list="bullet">类属性 vs 实例属性:描述器通常定义为类属性(如 <code>class User: age = IntField()</code>),若定义为实例属性则无法生效。</li>
<li class="ace-line ace-line old-record-id-Nnq0f1P3mdkzejc6eUmcZm7Tnzf" data-list="bullet">优先级混淆:数据描述器优先级最高,若想覆盖数据描述器的属性,需直接操作 <code>instance.__dict__</code>(不推荐)。</li>
</ul>
<h2 class="heading-2 ace-line old-record-id-SYanf4tKLd9OvYc2n5ccXQXdnng">六、总结</h2>
<div class="ace-line ace-line old-record-id-Kwdaf0fyodDUOycdAjlcfHOKndf">描述器是Python OOP的底层核心机制,虽然日常开发中不常直接写,但很多高级特性(property、ORM字段)都依赖它。掌握描述器的价值在于:</div>
<ul class="list-bullet1">
<li class="ace-line ace-line old-record-id-Q4ZIfKrmcd6Ufxc4krHcbNwSnLd" data-list="bullet">理解Python属性访问的底层逻辑,遇到相关问题能快速定位。</li>
<li class="ace-line ace-line old-record-id-DfQUfWzQZdxCNOclNWWcDSU1nLd" data-list="bullet">实现灵活的属性管控,应对复杂业务场景(如类型校验、懒加载)。</li>
<li class="ace-line ace-line old-record-id-AE5ofwQfTdC2SDcWZDAcT4hnnbe" data-list="bullet">读懂框架源码(如Django、Flask)中关于属性管控的实现。</li>
</ul>
<div class="ace-line ace-line old-record-id-Ee5efGaNidtz3ucgpLfcNimCnGg">建议:把本文的代码逐行运行一遍,修改参数、补充逻辑(如给LazyLoad添加缓存过期功能),加深理解。</div>
<div class="ace-line ace-line old-record-id-K8JCf0tyud5ZoLcco5AcwUrDnWh">参考资料:Python官方文档 - Descriptor HowTo Guide(https://docs.python.org/zh-cn/3/howto/descriptor.html)</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div>
</div>
</div>
</div>

</div>
<div id="MySignature" role="contentinfo">
    石家庄的.net程序员<br><br>
来源:https://www.cnblogs.com/yanshanshuo/p/19425706
頁: [1]
查看完整版本: Python描述器(Descriptor)深度解析:OOP底层核心机制实操指南