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
北冰洋
装饰器和描述符实现类型检测的终极版本
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': '樱木花道'}
# 可以加断点体验