【python】 描述符:描述符协议、数据描述符、非数据描述符
描述符-文字介绍
Python为开发者提供了一个非常强大的功能——描述符。那什么是描述符呢?通过查看Python的官方文档,我们知道把实现了__get__()、__set__()和__delete__()
中的其中任意一种方法的类称之为描述符,描述符的本质是新式类,并且被代理的类(即应用描述符的类)也是新式类。描述符的作用是用来代理一个类的属性
,需要注意的是描述符不能定义在类的构造函数中,只能定义为类的属性,它只属于类的,不属于实例,我们通过查看实例和类的字典即可知晓。
描述符是可以实现大部分Python类特性中最底层的数据结构的实现手段,我们常使用的@classmethod、@staticmethd、@property
、甚至是 __slots__ ()
等属性都是通过描述符来实现的。它是很多高级库和框架的重要工具之一,是使用到装饰器或者元类的大型框架中的一个非常重要组件。在一般的开发中我们可能用不到描述符,但是我们如果想要开发一个大型的框架或者大型的系统,那使用描述符会起到如虎添翼的作用。它的加盟将会使得系统更加完美。
描述符协议:
实现了 get()、set()、delete() 其中至少一个方法的类,就是一个描述符。
__get__:
用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。__ set__:
在属性分配操作中调用。不会返回任何内容。__ delete__:
制删除操作。不会返回内容。
描述符的特点
-
是一个类,定义了访问另一个类属性的方式;
-
把至少实现了内置属性
__set__()和__get__()
方法的描述符称为数据描述符; -
把实现了除
__set__()
以外的方法的描述符称为非数据描述符; -
访问对象属性的时候:obj.attr,一般会先调用
__getattribute__()
方法,查找顺序如下:
(1)数据描述符 (2)__dict__
属性 (3)非数据描述符 -
描述符的优先级的高低如下:
类属性 > 数据描述符 > 实例属性 > 非数据描述符 > 找不到的属性触发_getattr__()
数据描述符
举例来看:
class RevealAccess:
def __init__(self,initval = None,name='var'):
self.val = initval
self.name = name
def __get__(self, instance, owner):
print("获取..",self.name)
return self.val
def __set__(self, instance, value):
print("设置值:",self.name)
self.val = value
class MyClass:
x = RevealAccess(10,"var 'x'")
y = 5
def __init__(self):
self.x = 'self x'
# pass
m = MyClass()
print(m.x)
print("-----------------")
m1 = MyClass()
m1.x = 20
print(m1.x)
output:
设置值: var 'x'
获取.. var 'x'
self x
-----------------
设置值: var 'x'
设置值: var 'x'
获取.. var 'x'
20
分析一下内存:

非数据描述符
class InitOnAccess:
def __init__(self, klass, *args, **kwargs):
self.klass = klass
self.args = args
self.kwargs = kwargs
self._initialized = None
def __get__(self, instance, owner):
if self._initialized is None:
print("_initialized is None")
self._initialized = self.klass(*self.args, **self.kwargs)
else :
print("cached")
return self._initialized
class TestClass:
lazy_initialized =InitOnAccess(list, "arguments")
t = TestClass()
t.lazy_initialized
t.lazy_initialized
output:
_initialized is None
cached
Python 元类 metaclass:
"""
"Metaclasses" section example of metaclass that reveal order of all its
methods called.
"""
print(" >>> defining RevealingMeta(type)")
class RevealingMeta(type):
def __new__(mcs, name, bases, namespace, **kwargs):
print(mcs, "__new__ called")
return super().__new__(mcs, name, bases, namespace)
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
print(mcs, "__prepare__ called")
return super().__prepare__(name, bases, **kwargs)
def __init__(cls, name, bases, namespace, **kwargs):
print(cls, "__init__ called")
super().__init__(name, bases, namespace)
def __call__(cls, *args, **kwargs):
print(cls, "__call__ called")
return super().__call__(*args, **kwargs)
print(" >>> defining RevealingClass(metaclass=RevealingMeta)")
class RevealingClass(metaclass=RevealingMeta):
def __new__(cls):
print(cls, "__new__ called")
return super().__new__(cls)
def __init__(self):
print(self, "__init__ called")
super().__init__()
if __name__ == "__main__":
print(" >>> Creating RevealingClass()")
instance = RevealingClass()
输出:
'''
>>> defining RevealingMeta(type)
定义meta类时不调用 meta 类的函数
>>> defining RevealingClass(metaclass=RevealingMeta)
<class '__main__.RevealingMeta'> __prepare__ called
<class '__main__.RevealingMeta'> __new__ called
<class '__main__.RevealingClass'> __init__ called
定义具体类时先调用 meta 类的 prepare 函数和 new 函数,再调用具体类的 init 方法
prepare 函数和 new 函数,参数是 mcs,即meta类RevealingMeta
init 函数和 call 函数,参数是 cls,即"具体类"RevealingClass
>>> Creating RevealingClass()
<class '__main__.RevealingClass'> __call__ called --> 调用meta 类的call函数,但参数是 cls,即"具体类"RevealingClass
<class '__main__.RevealingClass'> __new__ called --> 参数是 cls
<__main__.RevealingClass object at 0x10bea0bd0> __init__ called --> 参数是 self
'''