马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
x
描述符及property的实现原理
一、描述符:前面学习property()函数、类方法和静态方法,它们背后实现的技术都是依赖于“描述符”。
(一)描述符协议
从定义上来讲,只要实现了__get__(self,instance,owner=None),__set__(self,instance,value),__delete__(self,instance),三种中任何一个或多个方法的类,那么这个类就叫描述符。
这三个方法的功能分别用于拦截对象属性的读取、写入和删除的操作,不过与我们之前接触的魔法方法有所不同,它们管的不是自家的属性,而是别人家的属性。举列子:
Python 3.12.1 (tags/v3.12.1:2305ca5, Dec 7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license()" for more information.
class D: (类D就是描述符了)
def __get__(self,instance,owner):
print(f"get~\nself -> {self}\ninstance ->{instance}\nowner -> {owner}")
def __set__(self,instance,value):
print(f"set~\nself -> {self}\ninstance ->{instance}\nvalue -> {value}")
def __delete__(self,instance): (注意:不要与__del__魔法方法搞混淆了)
print(f"delete~\nself -> {self}\ninstance ->{instance}")
这个类D就是描述符了,那么要怎样启用它呢?我们只需要在另一个类里面将它实例化赋值给想要管理的属性就可以了
class C:
x = D() (另一个类C,它有一个类属性叫x,把上面这个描述符(类D)的实例对象赋值给这个想要被管理的属性x)
c = C()
c.x = 250 (它打印了set~,就是这个__set__()魔法方法被调用了)
set~
self -> <__main__.D object at 0x000002811C3CBEC0> (这里的箭头->理解为等于)
instance -><__main__.C object at 0x000002811C3CBC20>
value -> 250
c.x
get~
self -> <__main__.D object at 0x000002811C3CBEC0>
instance -><__main__.C object at 0x000002811C3CBC20>
owner -> <class '__main__.C'>
del c.x (当我们试图删除它的时候,也会被__delete__()方法所拦截)
delete~
self -> <__main__.D object at 0x000002811C3CBEC0>
instance -><__main__.C object at 0x000002811C3CBC20>
可以看出,self参数对应的是描述符这个类D()实例对象,也是相当于x属性的值;instance参数对应的是被描述符拦截的属性所在的类的实例对象“小c”;owner参数对应的是被描述符拦截的属性所在的类C;
课堂小挑战
这里有上上节课时(即第66课时)property()函数引用的代码如下:
class C:
def __init__(self):
self._x = 250
def getx(self):
return self._x
def setx(self,value):
self._x = value
def delx(self):
del self._x
x = property(getx,setx,delx)
c = C()
c.x
250
c.__dict__
{'_x': 250}
c.x = 520
c.__dict__
{'_x': 520}
del c.x
c.__dict__
{}
小挑战:将上面的代码修改为描述符的实现方式:
class D: (要先写一个描述符类)
def __get__(self,instance,owner): (预先知道,待会要访问私有变量_x)
return instance._x (instance被描述符拦截的属性所在的类的实例对象)
def __set__(self,instance,value):
instance._x = value
def __delete__(self,instance):
del instance._x
class C:
def __init__(self,x=250):(我们是通过一个属性x间接来管理私有变量_x,x默认给到250)
self._x = x
x = D()
c = C()
c.x
250
c.x = 520
c.__dict__
{'_x': 520}
del c.x
c.__dict__
{}
跟使用了property的效果是一模一样的,但相比之下,使用了property()函数更简洁易懂。
这个代码虽然能实现,但不是健康的代码,辗转曲折不说,在类D中你竟然去访问类C的的内部私有属性,显然这不是一个合理的作法,那么描述符这么菜,那么我们为什么还要讲它呢?注意是先有描述符,再有property属性的,所以本节内容是知其所以然的环节。
如何利用描述符自己来造出一个我们自己的property函数。
尝试自己利用描述符来创造一个Property()函数,它是一个内置类。
class MyProperty():
def __init__(self,fget=None,fset=None,fdel=None): (看第66课时开头,property函数它是一个内置类,它有几个重要参数,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):
self.fset(instance,value)
def __delete__(self,instance):
self.fdel(instance)
三个定制的方法(参考本课时开头),通过参数给传进来,传进来之后,当我们__get__()、__set__(),__delete__()拦截发生的时候,我们再去调用传进来的方法,有点像通过中间人,然后再通过中间人做一件事一样。
class C:
def __init__(self):
self._x = 250
def getx(self):
return self._x
def setx(self,value):
self._x = value
def delx(self):
del self._x
x = MyProperty(getx,setx,delx) (描述符,实例化一个property对象,把三个方法名给传进去,把它生成的结果赋值给一个新的属性名x,我们就可以通过这个x来管理这个私有的_x,访问x的时候会调用MyProperty的__get__()方法,__get__()方法就会调用实例对象的getx方法,其它同理。
c = C()
c.x
250
c.x = 520
c.__dict__
{'_x': 520}
del c.x
c.__dict__
{}
终极挑战:尝试自己实现getter(),setter()和deleter()三个方法
在演示property函数的时候,它可以当装饰器来使用(第66课时),然后还有getter(),setter()和deleter()三个方法(第66课时),那么我们自己实现应该怎么做?
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):
self.fset(instance,value)
def __delete__(self,instance):
self.fdel(instance)
def getter(self,func):
self.fget = func
return self
def setter(self,func):
self.fset = func
return self
def deleter(self,func):
self.fdel = func
return self
class D:
def __init__(self):
self._x = 250
@MyProperty
def x(self):
return self._x
@x.setter
def x(self,value):
self._x = value
@x.deleter
def x(self):
del self._x
d = D()
d.x
250
d.x = 520
d.__dict__
{'_x': 520}
del d.x
d.__dict__
{}
|