描述符:描述符就是讲某种特殊类型的类的实例指派给另一个类的属性。某种特殊类型,指的是至少实现以下方法其中一个:
get(self, instance, owner) ——用于访问属性,它返回属性的值;
set(self, instance, value) ——将在属性分配操作中调用,不返回任何内容;
delete(self, instance) ——控制删除操作,不返回任何内容;
描述符的本质是新式类,并且被代理的类(即应用描述符的类)也是新式类。描述符的作用是用来代理一个类的属性,需要注意的是描述符不能定义在类的构造函数中,只能定义为类的属性,它只属于类的,不属于实例。
class MyDecriptor:
def __get__(self, instance, owner):
print('触发get')
def __set__(self, instance, value):
print('触发set')
def __delete__(self, instance):
print('触发delete')
'''
self: 是描述符的对象,不是使用描述符类的对象
instance: 这才是使用描述符类的对象
owner: 是instance的类
value: 是instance的值
'''
f1=MyDecriptor()
f1.name='X-man'
f1.name
del f1.name
#包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
触发条件:定义成另外一个类的类属性
class Str:
def __get__(self, instance, owner):
print('Str is getting......')
def __set__(self, instance, value):
print('Str is setting......')
def __delete__(self, instance):
print('Str is deleting......')
class Int:
def __get__(self, instance, owner):
print('Int is getting......')
def __set__(self, instance, value):
print('Int is setting......')
def __delete__(self, instance):
print('Int is deleting......')
class Person:
name=Str()
age=Int()
def __init__(self,name,age): #name被Str类代理,age被Int类代理,
self.name=name
self.age=age
输出:
>>> p1 = Person('Tom',20)
Str is setting......
Int is setting......
>>> p1.name
Str is getting......
>>> print(p1.name)
Str is getting......
None
>>> p1.name = 'Tony'
Str is setting......
class MyDecriptor:
def __get__(self, instance, owner):
print("getting...", self, instance, owner)
def __set__(self, instance, value):
print("setting...", self, instance, value)
def __delete__(self, instance):
print("deleting...", self, instance)
class Test:
x = MyDecriptor() #MyDecriptor是x的描述符
test = Test()
print(test.x)
print("=================")
print(test)
print("=================")
test.x = 'X-man'
print(test.x)
print("=================")
del test.x
print(test.x)
输出结果:
getting... <__main__.MyDecriptor object at 0x0000000002F65240> <__main__.Test object at 0x0000000002F65438> <class '__main__.Test'>
None
=================
<__main__.Test object at 0x0000000002F65438>
=================
setting... <__main__.MyDecriptor object at 0x0000000002F65240> <__main__.Test object at 0x0000000002F65438> X-man
getting... <__main__.MyDecriptor object at 0x0000000002F65240> <__main__.Test object at 0x0000000002F65438> <class '__main__.Test'>
None
=================
deleting... <__main__.MyDecriptor object at 0x0000000002F65240> <__main__.Test object at 0x0000000002F65438>
getting... <__main__.MyDecriptor object at 0x0000000002F65240> <__main__.Test object at 0x0000000002F65438> <class '__main__.Test'>
None
描述符是干什么的?
描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)。
再看下面一个例子:
#定义一个MyProperty来实现Property的功能
class MyProperty:
def __init__(self, fget= None, fset= None, fdel = None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, instance, owner):
return self.fget(instance)
def __set__(self, instance, value):
return self.fset(instance, value)
def __delete(self, instance):
self.fdel(instance)
class C:
def __init__(self):
self._x = None
def getX(self):
return self._x
def setX(self, value):
self._x = value
def delX(self):
del self._x
x = MyProperty(getX, setX, delX)
c = C()
c.x = 'X-man'
print(c.x)
print(c._x)
输出结果为:
X-man
X-man
>>>
二、描述符的种类及优先级
描述符分为数据描述符和非数据描述符。把至少实现了内置属性__set__()和__get__()方法的描述符称为数据描述符;把实现了除__set__()以外的方法的描述符称为非数据描述符。之所以要区分描述符的种类,主要是因为它在代理类属性时有着严格的优先级限制。例如当使用数据描述符时,因为数据描述符大于实例属性,所以当我们实例化一个类并使用该实例属性时,该实例属性已被数据描述符代理,此时我们对该实例属性的操作是对描述符的操作。描述符的优先级的高低如下:
类属性 > 数据描述符 > 实例属性 > 非数据描述符 > 找不到的属性触发__getattr__()
1. 类属性 > 数据描述符
在以下实验中,使用 Light.name = “电灯泡” 语句没有触发set的执行说明类属性的优先级大于数据描述符的优先,此时相当于类属性覆盖了数据描述符,从而说明对类属性的一切操作都与描述符无关。
class Descriptors:
"""
数据描述符
"""
def __get__(self, instance, owner):
print("执行Descriptors的get")
def __set__(self, instance, value):
print("执行Descriptors的set")
def __delete__(self, instance):
print("执行Descriptors的delete")
class Light:
#使用描述符
name = Descriptors()
#测试
Light.name #执行描述符的get内置属性
print(Light.__dict__) #此时的name显示的是描述符的对象
Light.name = "电灯泡" #没有执行描述符的set内置属性
print(Light.name) #输出:电灯泡
del Light.name #没有执行描述符的delete内置属性
print(Light.name) #报错,因为Light类中的name被删了
- 数据描述符 > 实例属性
在以下实验中,数据描述符的优先级大于实例属性的优先级,此时实例属性name被数据描述符所覆盖,而price没有描述符代理,所以它任然是实例属性。
class Descriptors:
"""
数据描述符
"""
def __get__(self, instance, owner):
print("执行Descriptors的get")
def __set__(self, instance, value):
print("执行Descriptors的set")
def __delete__(self, instance):
print("执行Descriptors的delete")
class Light:
#使用描述符
name = Descriptors()
def __init__(self, name, price):
self.name = name
self.price = price
#使用类的实例对象来测试
light = Light("电灯泡",60) #执行描述符的set内置属性
light.name #执行描述符的get内置属性
print(light.__dict__) #查看实例的字典,不存在name
print(Light.__dict__) #查看类的字典,存在name(为描述符的对象)
del light.name #执行描述符的delete内置属性
- 实例属性 > 数据描述符
在以下实验中,如果我们的实例属性中使用了非数据描述符,就不能对其进行复制操作。可见非数据描述符应该应用于不需要设置值的属性或者函数中。上述的设计没有多大的意义,只是增加对描述符的理解。
class Descriptors:
"""
非数据描述符
"""
def __get__(self, instance, owner):
print("执行Descriptors的set")
def __delete__(self, instance):
print("执行Descriptors的delete")
class Light:
#使用描述符
name = Descriptors()
def __init__(self, name, price):
self.name = name
self.price = price
#测试
light = Light("电灯泡",60) #报错,描述符中没有__set__()方法
经以下实验证明在该类中并没有set方法,所以该类是一个非数据描述符,Python中一切皆对象,函数也是一个对象,既然是对象那也可以是类实例化所得到的结果。函数在类中本身也是一种属性(函数属性),描述符在应用的时候也是被实例化为一个属性。
class Descriptors:
"""
非数据描述符
"""
def func(self):
print("世界的变化真快!近日00后都已经开始在街头打小三了")
d = Descriptors()
d.func()
print(hasattr(Descriptors.func,"__set__")) #False
print(hasattr(Descriptors.func,"__get__")) #True
print(hasattr(Descriptors.func,"__delete__")) #False
d.func = "函数也是属性,也可以赋值,没毛病"
print(d.func)
del d.func
d.func
三、描述符模拟系统的内置属性
此利用描述符的原理,我们完全可以自定义模拟@classmethod、@staticmethd、@property、等属性。实现这种类似系统的属性,我们还需要装饰器作为修饰,结合装饰器做成一个描述符。下面将简单的介绍使用描述符模拟系统自带的装饰器。
- 模拟 @classmethod
class Imitate_classmethod:
"""
使用描述符模拟@classmethod
"""
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
#对传进函数进行加工,最后返回该函数
def machining_func(*args, **kwargs):
print("函数加工处理后,返回实例的类")
return self.func(owner, *args, **kwargs)
return machining_func
class Test:
book_name = "从你的世界路过"
#使用装饰器的结果等价于将函数变为属性:
# book = Imitate_classmethod( book )
@Imitate_classmethod
def book_1(cls):
print("这本书的名字是:%s"%cls.book_name)
@Imitate_classmethod
def book_2(cls, price):
print("这本书的名字是:%s;价格是 %s"%(cls.book_name, price))
#测试
Test.book_1()
Test.book_2(28.5)
"""
运行输出:
---------------------------------------------
函数加工处理后,返回实例的类
这本书的名字是:从你的世界路过
函数加工处理后,返回实例的类
这本书的名字是:从你的世界路过;价格是 28.5
---------------------------------------------
- 模拟 @staticmethod
staticmethod方法与classmethod方法的区别在于classmethod方法在使用需要传进一个类的引用作为参数。而staticmethod则不用。
class Imitate_staticmethod:
"""
使用描述符模拟@staticmethod
"""
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
#对传进函数进行加工,最后返回该函数
def machining_func(*args, **kwargs):
print("函数加工处理后,返回实例的类")
return self.func(*args, **kwargs)
return machining_func
class Test:
@Imitate_staticmethod
def static_func(*args):
print("您输入的是:",*args)
#测试
Test.static_func("柠檬","香蕉")
test = Test()
test.static_func(110, 112)
"""
运行输出:
-------------------------------------------------------------------
函数加工处理后,返回实例的类
您输入的是: 柠檬 香蕉
函数加工处理后,返回实例的类
您输入的是: 110 112
-------------------------------------------------------------------
"""
- 模拟 @property
在以下实验中,我们将描述符的回调结果存入对象字典中的好处是我们以后再执行函数时就不会每一次都触发描述的运行,从而提高程序的效率。这样,我们有再执行同样的函数时,解释器会先检索对象的字典,如果字典存在上次执行结果的值,那就不用触发描述符的运行了。在这个实验中必须强调的一点是描述符的优先级,我们想让程序的描述符不能覆盖实例属性就必须使用非数据描述符。所以因需求不同,描述符的优先级也不同。
class Imitate_property:
"""
使用描述符模拟property
"""
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if instance is None:
return self
#回调传入的函数,将运行结果保存在变量res中
res = self.func(instance)
#为函数名func.__name__设置一个值res后存入对象的字典中
setattr(instance, self.func.__name__, res)
return res
class Test:
def __init__(self, value):
self.value = value
@Imitate_property
def function(self):
return self.value**2
test = Test(2)
print(test.function) #输出:4
print(test.__dict__) #输出:{'value': 2, 'function': 4}
print(test.function) #输出:4
print(test.function) #输出:4
print(test.__dict__) #输出:{'value': 2, 'function': 4}
本文部分转载自:
作者: 下一站,我等你
出处: http://www.cnblogs.com/Lynnblog/>