[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__
__getattribute__():访问属性必被调用。property.get():当属性不在实例、基类和祖先类的__dict__中时,被调用。__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.<

1217

被折叠的 条评论
为什么被折叠?



