[Python] 详解从属性描述符到实现ORM模型

1. 什么是描述符?

描述符是对多个属性运用相同存取逻辑的一种方式。例如,Django ORM 和 SQLAlchemy 等 ORM 中的字段类型是描述符,把数据库记录中字段里的数据与 Python 对象的属性对应起来。 描述符是实现了特定协议的类,这个协议包括 __get____set____delete__ 方 法。property 类实现了完整的描述符协议。通常,可以只实现部分协议。其实,我们在真实的代码中见到的大多数描述符只实现了 __get____set__ 方法,还有很多只实现了其中的一个。 描述符是 Python 的独有特征,不仅在应用层中使用,在语言的基础设施中也有用到。除了特性之外,使用描述符的 Python 功能还有方法及 classmethod 和 staticmethod 装饰器。理解描述符是精通 Python 的关键。
——《流畅的Python》

简单来说,实现了 __get____set____delete__ 方法的类是描述符。描述符的用法是,创建一个实例,作为另一个类的类属性。

class Grade(object):
    def __get__(self, instance, owner):
        pass    # 暂时忽略这部分实现的代码

    def __set__(self, instance, value):
        pass    # 暂时忽略这部分实现的代码

class Exam(object):
    math_grade = Grade()		# 描述符将 Grade 作为类属性
    writing_grade = Grade()
    science_grade = Grade()

Python 访问描述符属性时,会对这种访问操作进行转译。这点很重要,我们先看看这种转译:

为属性赋值时:

exam = Exam()
exam.writing_grade = 40

Python 会将代码转译为:

Exam.__dict__['writing_grade'].__set__(exam, 40)

获取属性时:

exam.writing_grade

Python 会将代码转译为:

Exam.__dict__['writing_grade'].__get__(exam, Exam)

之所以会有这样的转译,关键在于 object 类的 __getattribute__ 方法。简单来说,如果 Exam 实例没有名为 writing_grade 的属性,那么 Python 就会转向 Exam 类,并在该类中查找同名的类属性。这个类属性,如果是实现了 __get____set__ 方法的对象,那么 Python 就认为此对象遵从描述符协议。

2. 理解类属性和实例属性访问的不同

我们说过实例属性的访问顺序是这样的:

Python 实例属性访问链:__getattribute__()-> property.get() -> __getattr__

  1. __getattribute__():访问属性必被调用。
  2. property.get() :当属性不在实例、基类和祖先类的__dict__中时,被调用。
  3. __getattr__ :当属性不在实例、基类和祖先类的__dict__中、__getattribute__()property.get() 抛出异常时,被调用。
class Student:
    @property
    def name(self):
        print('Student.property.get')
        raise AttributeError	  # 抛出异常会调用__getattr__

    def __getattr__(self, item):
        print('Student.__getattr__')
        if item == 'name':
            return 'John'

    def __getattribute__(self, item):
        print('Student.__getattribute__')
        return super().__getattribute__(item)

john = Student()
print(john.name)
Student.__getattribute__
Student.property.get
Student.__getattr__
John

而类属性的访问顺序是:

__getattribute__()-> 描述符类的 __get__()

假如我们创建一个 Apple 类,在 Student 中创建一个 Apple 实例作为类属性:

class Apple(object):
    def __init__(self):
        self.name = 'RedApple'

    def __get__(self, instance, owner):
        print('Apple.__get__')
        return self

    def __getattribute__(self, item):
        print('Apple.__getattribute__')
        return super().__getattribute__(item)

class Student:
    apple = Apple()     # Apple 实例作为类属性,这是重点!!!

    @property
    def name(self):
        print('Student.property.get')
        raise AttributeError

    def __getattr__(self, item):
        print('Student.__getattr__')
        if item == 'name':
            return 'John'

    def __getattribute__(self, item):
        print('Student.__getattribute__')
        return super().__getattribute__(item)

john = Student()
print(john.name)
print('-----------')
print(john.apple)
print('-----------')
print(john.<
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值