描述符
描述符(__get__,__set__,__delete__) # 这里着重描述了python的底层实现原理
1、 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。__get__():调用一个属性时,触发__set__():为一个属性赋值时,触发__delete__():采用del删除属性时,触发
1 class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
2 def __get__(self,instance,owner):3 print('get方法')4 def __set__(self, instance, value):5 print('set方法')6 def __delete__(self, instance):7 print('delete方法')
2、描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
classFoo:def __get__(self,instance,owner):print('===>get方法')def __set__(self, instance, value):print('===>set方法')def __delete__(self, instance):print('===>delete方法')#包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
f1=Foo()
f1.name='egon'
print(f1.name)delf1.name#疑问:何时,何地,会触发这三个方法的执行
3、描述符应用在什么时候,什么地方
classD:def __get__(self, instance, owner):print("-->get")def __set__(self, instance, value):print("-->set")def __delete__(self, instance):print("-->delete")classE:
e= D() #描述谁?
ee=E()
ee.y= 10 #此时描述的是e y则不会被描述
ee.e #访问e属性,则会触发__get__
ee.e = 2 #为e进行赋值操作,则会触发__set__
del ee.e #删除e的属性,则会触发__delete__#print(ee.__dict__)
4、描述符分为俩种形式。
a.数据描述符(至少实现了__get__()和__set__()两种方法)
classFoo:def __set__(self, instance, value):print('set')def __get__(self, instance, owner):print('get')
b.非数据描述符(没有实现__set__()方法)
1 classFoo:2 def __get__(self, instance, owner):3 print('get')
注意事项:一、描述符本身应该定义成新式类,被代理的类也应该是新式类二、必须把描述符定义成另外一个类触发的类属性,不能为定义到构造函数
5、严格遵循描述符的优先级别,由高到低
a.类属性—》b.数据数据描述符—》c.实例属性—》d.非数据描述符—》e.找不到的属性触发__getattr__()
1 classFoo:2 def __get__(self,instance,owner):3 print('===>get方法')4 def __set__(self, instance, value):5 print('===>set方法')6 def __delete__(self, instance):7 print('===>delete方法')8
9 classBar:10 x=Foo() #调用foo()属性,会触发get方法
11
12 print(Bar.x) #类属性比描述符有更高的优先级,会触发get方法
13 Bar.x=1 #自己定义了一个类属性,并赋值给x,跟描述符没有关系,所以不会触发描述符的方法
14 #print(Bar.__dict__)
15 print(Bar.x)16
17
18 ===>get方法19 None20 1
#有get,set就是数据描述符,数据描述符比实例属性有更高的优化级
classFoo:def __get__(self,instance,owner):print('===>get方法')def __set__(self, instance, value):print('===>set方法')def __delete__(self, instance):print('===>delete方法')classBar:
x= Foo() #调用foo()属性,会触发get方法
b1=Bar() #在自己的属性字典里面找,找不到就去类里面找,会触发__get__方法
b1.x #调用一个属性的时候触发get方法
b1.x=1 #为一个属性赋值的时候触发set方法
del b1.x #采用del删除属性时触发delete方法
===>get方法===>set方法===>delete方法
1 #类属性>数据描述符>实例属性
2
3 classFoo:4 def __get__(self,instance,owner):5 print('===>get方法')6 def __set__(self, instance, value):7 print('===>set方法')8 def __delete__(self, instance):9 print('===>delete方法')10
11 classBar:12 x = Foo() #调用foo()属性,会触发get方法
13
14 b1=Bar() #实例化
15 Bar.x=11111111111111111 #不会触发get方法
16 b1.x #会触发get方法
17
18 del Bar.x #已经给删除,所以调用不了!报错:AttributeError: 'Bar' object has no attribute 'x'
19 b1.x
#实例属性>非数据描述符
classFoo:def __get__(self,instance,owner):print('===>get方法')classBar:
x=Foo()
b1=Bar()
b1.x=1
print(b1.__dict__) #在自己的属性字典里面,{'x': 1}
{'x': 1}
1 #非数据描述符>找不到
2
3 classFoo:4 def __get__(self,instance,owner):5 print('===>get方法')6
7 classBar:8 x =Foo()9 def __getattr__(self, item):10 print('------------>')11
12 b1=Bar()13 b1.xxxxxxxxxxxxxxxxxxx #调用没有的xxxxxxx,就会触发__getattr__方法
14
15
16 ------------> #解发__getattr__方法
6、关于描述符的应用(类型检测的应用)
classTyped:def __get__(self, instance,owner):print('get方法')print('instance参数【%s】' %instance)print('owner参数【%s】' %owner) #owner是显示对象是属于谁拥有的
def __set__(self, instance, value):print('set方法')print('instance参数【%s】' %instance) #instance是被描述类的对象(实例)
print('value参数【%s】' %value) #value是被描述的值
def __delete__(self, instance):print('delete方法')print('instance参数【%s】'%instance)classPeople:
name=Typed()def __init__(self,name,age,salary):
self.name=name #触发的是代理
self.age=age
self.salary=salary
p1=People('alex',13,13.3)#'alex' #触发set方法
p1.name #触发get方法,没有返回值
p1.name='age' #触发set方法
print(p1.__dict__)#{'salary': 13.3, 'age': 13} # 因为name已经被描述,所以实例的属性字典并不存在name#当然也说明一点实例属性的权限并没有数据描述符的权限大
set方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
value参数【alex】
get方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
owner参数【】
set方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
value参数【age】
{'salary': 13.3, 'age': 13}
classFoo:def __init__(self,key,pd_type):
self.key=key
self.pd_type=pd_typedef __get__(self, instance, owner):print("get")return instance.__dict__[self.key] #返回值是 instace对象属性字典self.key所对应的值
def __set__(self, instance, value):print(value) #输出value所对应的值
if not isinstance(value,self.pd_type): #判断被描述的值 是否 属于这个类的
raise TypeError("%s 传入的类型不是%s" %(value,self.pd_type)) #为否 则抛出类型异常
instance.__dict__[self.key] = value #True 对instance对象的属性字典进行赋值操作
def __delete__(self, instance):print("delete")
instance.__dict__.pop(self.key) #如果进行删除操作,也是对instance对象的属性字典进行删除操作
classSea:
name= Foo("name",str) #向描述符传入俩个值
history = Foo("history",int)def __init__(self,name,addr,history):
self.name=name
self.addr=addr
self.history=history
s1= Sea("北冰洋","北半球",10000)print(s1.__dict__)print(s1.name) #对被描述的属性进行访问,触发__get__
北冰洋10000{'addr': '北半球', 'history': 10000, 'name': '北冰洋'}
get
北冰洋
装饰器和描述符实现类型检测的终极版本
7、描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.
a.利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)
classRoom:def __init__(self,name,width,length):
self.name=name
self.width=width
self.length=length
@propertydefarea(self):return self.width *self.length
r1=Room(Tom',1,1)
print(r1.area)
#伪造的property
classWzproperty:def __init__(self,func):
self.func=funcdef __get__(self, instance, owner):"""如果类去调用 instance 为None"""
print("get")if instance isNone:returnself
setattr(instance,self.func.__name__,self.func(instance)) #给实例字典设置值,避免重复计算
returnself.func(instance)classSea:def __init__(self,name,history,speed):
self.name=name
self.history=history
self.speed=speed
@Wzproperty#test = Wzptoperty(test)
deftest(self):return self.history *self.speed
s1= Sea("大西洋",10,20)#print(Sea.__dict__)#print(Sea.test) # 如果类去调用 描述符的instance 此时是None
print(s1.test)print(s1.test) #这一次就不会触发描述符,因为实例属性字典就有
"""因为有了为实例的属性字典设置了结果。所以会率先从自己的属性字典找
其次触发非数据描述符,同时也声明了实例属性的权限大于非数据描述。
如果给描述符+__set__,描述符就变为数据描述符,根据权限实例再去用不会先去
自己的属性字典,而是触发描述符的操作"""
print(s1.__dict__)
控制台输出
get200
200{'test': 200, 'speed': 20, 'name': '大西洋', 'history': 10} #实例的属性字典
#伪造的classmethod
classWzclassmethod:def __init__(self,func):
self.func=funcdef __get__(self, instance, owner):print("get")defbar():return self.func(owner) #test(Sea)
returnbardef __set__(self, instance, value):print("set")classSea:
long= 10kuan= 20@Wzclassmethod#test = Wzclassmethod(test)
deftest(cls):print("长%s 宽%s" %(cls.long,cls.kuan))
Sea.test()
#伪造的staticmethod
importhashlib,timeclassWzstaticmethod:def __init__(self,func):
self.func=funcdef __set__(self, instance, value):print("set")def __get__(self, instance, owner):print("get")defbar():if instance isNone:returnself.func()returnbardef __delete__(self, instance):print("delete")classOnepiece:def __init__(self):pass@Wzstaticmethod#test = Wzstaticmethod(test)
def test(x=1):
hash=hashlib.md5()
hash.update(str(time.time()).encode("utf-8"))
filename=hash.hexdigest()print(filename)returnfilename#print(Onepiece.__dict__)
Onepiece.test()
类装饰器
1、基本框架
defdeco(func):print('===================')return func #fuc=test
@deco#test=deco(test)
deftest():print('test函数运行')
test()
defdeco(obj):print('============',obj)
obj.x=1 #增加属性
obj.y=2obj.z=3
returnobj
@deco#Foo=deco(Foo) #@deco语法糖的基本原理
classFoo:pass
print(Foo.__dict__) #加到类的属性字典中
输出============ {'__module__': '__main__', 'z': 3, 'x': 1, '__dict__': , '__doc__': None, '__weakref__': , 'y': 2}
def Typed(**kwargs):defdeco(obj):
obj.x=1obj.y=2obj.z=3
returnobjprint('====>',kwargs)returndeco
@Typed(x=2,y=3,z=4) #typed(x=2,y=3,z=4)--->deco 会覆盖原有值
classFoo:pass
def Typed(**kwargs):defdeco(obj):for key,val inkwargs.items():
setattr(obj,key,val)returnobjreturndeco
@Typed(x=1,y=2,z=3) #typed(x=1,y=2,z=3)--->deco
classFoo:pass
print(Foo.__dict__)
@Typed(name='egon')classBar:pass
print(Bar.name)
控制台输出
{'y': 2, '__dict__': , 'z': 3, '__weakref__': , '__module__': '__main__', 'x': 1, '__doc__': None}
egon
元类
元类(metaclass)
classFoo:passf1=Foo() #f1是通过Foo类实例化的对象
python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)
上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?
#type函数可以查看类型,也可以用来查看对象的类,二者是一样的
print(type(f1)) #输出: 表示,obj 对象由Foo类创建
print(type(Foo)) #输出:
1、辣么,什么是元类?
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样
元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象
创建类有俩种方式
classFoo:deffunc(self):print('from func')
deffunc(self):print('from func')
x=1Foo=type('Foo',(object,),{'func':func,'x':1})
type要接收三个参数1、将要创建的类名2、继承的类3、类的属性字典
#方式1
classFoo:pass
#方式2
Bar = type("Bar",(object,),{})print(Foo.__dict__)print(Bar.__dict__)
控制台输出
{'__module__': '__main__', '__doc__': None, '__weakref__': , '__dict__': }
{'__module__': '__main__', '__doc__': None, '__weakref__': , '__dict__': }
2、一个类没有声明自己的元类,默认它的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)
classMytype(type):def __init__(self,a,b,c):print(self)print(a)print(b)print(c)def __call__(self, *args, **kwargs):print("call")class Slamdunk(metaclass=Mytype): #Mytype("Slamdunk",(object,),{}) 实际上就是这么做,但是传了4个参数
#声明Foo类由Mytype创建,声明自己的元类
def __init__(self,name):
self.name=name
s1= Slamdunk("樱木花道")#根据python一切皆对象,Slamdunk() 本质上就是在触发创建 Slamdunk类的 元类的__call__
控制台输出 #元类创建的实例(对象)
Slamdunk #实例名
() #继承的类,在python3中都默认继承object,即都为新式类
{'__qualname__': 'Slamdunk', '__init__': , '__module__': '__main__'} #实例类的属性字典
call #实例+() 触发了元类的__call__方法
classMytype(type):def __init__(self,a,b,c):print(self)def __call__(self, *args, **kwargs): #传的值是怎么传进去的,就去怎么接收
print("call")
obj= object.__new__(self) #生成一个实例
self.__init__(obj,*args,**kwargs) #这里的self是Mytype产生的实例,这一步触发 Slamdunk 的构造方法
return obj #__call__方法下的返回值是 self 产生的实例 赋值给s1
class Slamdunk(metaclass=Mytype):#Slamdunk = Mytype("Slamdunk",(object,),{}) 实际上就是这么做,但是传了4个参数
#声明Foo类由Mytype创建,声明自己的元类
#触发元类的__init__(元类的构造方法)
def __init__(self,name):
self.name=name
s1= Slamdunk("樱木花道")#根据python一切皆对象,Slamdunk() 本质上就是在触发创建 Slamdunk类的 元类的__call__
print(s1.__dict__) #可以访问到实例对象的属性字典
classMytype(type):def __init__(self,a,b,c):print(self)def __call__(self, *args, **kwargs):
obj= object.__new__(self)
self.__init__(obj,*args,**kwargs)returnobjclass Slamdunk(metaclass=Mytype):def __init__(self,name):
self.name=name
s1= Slamdunk("樱木花道")print(s1.__dict__)
控制台输出{'name': '樱木花道'}#可以加断点体验