python基础之面向对象(描述符、类装饰器及元类)
描述符
描述符(__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、描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
class Foo: 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) del f1.name #疑问:何时,何地,会触发这三个方法的执行
3、描述符应用在什么时候,什么地方
class D: def __get__(self, instance, owner): print("-->get") def __set__(self, instance, value): print("-->set") def __delete__(self, instance): print("-->delete") class E: 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__()两种方法)
class Foo: def __set__(self, instance, value): print('set') def __get__(self, instance, owner): print('get')
b.非数据描述符(没有实现__set__()方法)
1 class Foo: 2 def __get__(self, instance, owner): 3 print('get')
注意事项:
一、描述符本身应该定义成新式类,被代理的类也应该是新式类
二、必须把描述符定义成另外一个类触发的类属性,不能为定义到构造函数
5、严格遵循描述符的优先级别,由高到低
a.类属性—》b.数据数据描述符—》c.实例属性—》d.非数据描述符—》e.找不到的属性触发__getattr__()
1 class 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方法') 8 9 class Bar: 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 None 20 1
#有get,set就是数据描述符,数据描述符比实例属性有更高的优化级 class Foo: def __get__(self,instance,owner): print('===>get方法') def __set__(self, instance, value): print('===>set方法') def __delete__(self, instance): print('===>delete方法') class Bar: 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 class Foo: 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 class Bar: 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
#实例属性>非数据描述符 class Foo: def __get__(self,instance,owner): print('===>get方法') class Bar: x = Foo() b1=Bar() b1.x=1 print(b1.__dict__) #在自己的属性字典里面,{'x': 1} {'x': 1}
1 #非数据描述符>找不到 2 3 class Foo: 4 def __get__(self,instance,owner): 5 print('===>get方法') 6 7 class Bar: 8 x = Foo() 9 def __getattr__(self, item): 10 print('------------>') 11 12 b1=Bar() 13 b1.xxxxxxxxxxxxxxxxxxx #调用没有的xxxxxxx,就会触发__getattr__方法 14 15 16 ------------> #解发__getattr__方法
6、关于描述符的应用(类型检测的应用)
class Typed: 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) class People: 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参数【<class '__main__.People'>】 set方法 instance参数【<__main__.People object at 0x000001CECBFF0080>】 value参数【age】 {'salary': 13.3, 'age': 13}
class Foo: def __init__(self,key,pd_type): self.key = key self.pd_type = pd_type def __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对象的属性字典进行删除操作 class Sea: 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 北冰洋
# 描述符和类装饰器的结合使用 class Foo: """ 描述符 """ def __init__(self,key,pd_type): self.key = key self.pd_type = pd_type def __get__(self, instance, owner): print("get") return instance.__dict__[self.key] def __set__(self,instance,value): """ instance是对象(实例) """ print("set") if not isinstance(value,self.pd_type): # 如果判断类型为False raise TypeError("%s 传入的类型不是%s" %(value,self.pd_type)) # 抛出异常 instance.__dict__[self.key] = value #操作对象(实例)的属性字典设置值 def __delete__(self, instance): print("delete") instance.__dict__.pop(self.key) def Typed(**kwargs): def func(obj): for key,value in kwargs.items(): # 遍历字典的键值对 setattr(obj,key,Foo(key,value)) # 为Sea的属性字典设置值。 并执行Foo 给Foo传入俩个值 # print("--->" ,obj) return obj # func的返回值是Sea return func @Typed(name=str,addr=str,history=int) # Typed函数运行结束实际上就是 @func --> sea=func(Sea) # Typed运行,并将参数全部传给kwargs class Sea: def __init__(self,name,addr,history): self.name = name self.addr = addr self.history = history s1 = Sea("大西洋","地球",10000) print(s1.__dict__) print(Sea.__dict__) # 此时的name,addr,history均被Foo所描述
7、描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.
a.利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)
class Room: def __init__(self,name,width,length): self.name=name self.width=width self.length=length @property def area(self): return self.width * self.length r1=Room(Tom',1,1) print(r1.area)
# 伪造的property class Wzproperty: def __init__(self,func): self.func = func def __get__(self, instance, owner): """ 如果类去调用 instance 为None""" print("get") if instance is None: return self setattr(instance,self.func.__name__,self.func(instance)) # 给实例字典设置值,避免重复计算 return self.func(instance) class Sea: def __init__(self,name,history,speed): self.name = name self.history = history self.speed = speed @Wzproperty # test = Wzptoperty(test) def test(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__) 控制台输出 get 200 200 {'test': 200, 'speed': 20, 'name': '大西洋', 'history': 10} # 实例的属性字典
# 伪造的classmethod class Wzclassmethod: def __init__(self,func): self.func = func def __get__(self, instance, owner): print("get") def bar(): return self.func(owner) # test(Sea) return bar def __set__(self, instance, value): print("set") class Sea: long = 10 kuan = 20 @Wzclassmethod # test = Wzclassmethod(test) def test(cls): print("长%s 宽%s" %(cls.long,cls.kuan)) Sea.test()
# 伪造的staticmethod import hashlib,time class Wzstaticmethod: def __init__(self,func): self.func = func def __set__(self, instance, value): print("set") def __get__(self, instance, owner): print("get") def bar(): if instance is None: return self.func() return bar def __delete__(self, instance): print("delete") class Onepiece: 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) return filename # print(Onepiece.__dict__) Onepiece.test()
类装饰器
1、基本框架
def deco(func): print('===================') return func #fuc=test @deco #test=deco(test) def test(): print('test函数运行') test()
def deco(obj): print('============',obj) obj.x=1 #增加属性 obj.y=2 obj.z=3 return obj @deco #Foo=deco(Foo) #@deco语法糖的基本原理 class Foo: pass print(Foo.__dict__) #加到类的属性字典中 输出 ============ <class '__main__.Foo'> {'__module__': '__main__', 'z': 3, 'x': 1, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, 'y': 2}
def Typed(**kwargs): def deco(obj): obj.x=1 obj.y=2 obj.z=3 return obj print('====>',kwargs) return deco @Typed(x=2,y=3,z=4) #typed(x=2,y=3,z=4)--->deco 会覆盖原有值 class Foo: pass
def Typed(**kwargs): def deco(obj): for key,val in kwargs.items(): setattr(obj,key,val) return obj return deco @Typed(x=1,y=2,z=3) #typed(x=1,y=2,z=3)--->deco class Foo: pass print(Foo.__dict__) @Typed(name='egon') class Bar: pass print(Bar.name) 控制台输出 {'y': 2, '__dict__': <attribute '__dict__' of 'Foo' objects>, 'z': 3, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__module__': '__main__', 'x': 1, '__doc__': None} egon
元类
元类(metaclass)
class Foo: pass f1=Foo() #f1是通过Foo类实例化的对象
python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)
上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?
#type函数可以查看类型,也可以用来查看对象的类,二者是一样的 print(type(f1)) # 输出:<class '__main__.Foo'> 表示,obj 对象由Foo类创建 print(type(Foo)) # 输出:<type 'type'>
1、辣么,什么是元类?
- 元类是类的类,是类的模板
- 元类是用来控制如何创建类的,正如类是创建对象的模板一样
- 元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
- type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象
创建类有俩种方式
class Foo: def func(self): print('from func')
def func(self): print('from func') x=1 Foo=type('Foo',(object,),{'func':func,'x':1}) type要接收三个参数 1、将要创建的类名 2、继承的类 3、类的属性字典
# 方式1 class Foo: pass # 方式2 Bar = type("Bar",(object,),{}) print(Foo.__dict__) print(Bar.__dict__) 控制台输出 {'__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__dict__': <attribute '__dict__' of 'Foo' objects>} {'__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__dict__': <attribute '__dict__' of 'Bar' objects>}
2、一个类没有声明自己的元类,默认它的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)
class Mytype(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__ 控制台输出 <class '__main__.Slamdunk'> # 元类创建的实例(对象) Slamdunk # 实例名 () # 继承的类,在python3中都默认继承object,即都为新式类 {'__qualname__': 'Slamdunk', '__init__': <function Slamdunk.__init__ at 0x000002106AFBF840>, '__module__': '__main__'} # 实例类的属性字典 call # 实例+() 触发了元类的__call__方法
class Mytype(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__) # 可以访问到实例对象的属性字典
class Mytype(type): def __init__(self,a,b,c): print(self) def __call__(self, *args, **kwargs): obj = object.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Slamdunk(metaclass=Mytype): def __init__(self,name): self.name = name s1 = Slamdunk("樱木花道") print(s1.__dict__) 控制台输出 <class '__main__.Slamdunk'> {'name': '樱木花道'} # 可以加断点体验