Python之旅的第28天(描述符、类的装饰器)
周末真好,时间充裕,都能按照要求自己练习,感觉就是好
一、描述符
上次针对描述符的内容,理解的非常不到位,所以我就特地找了新的内容好好看了看,总结一下就是下面这些
# 前天我大概知道类描述符的一些特性,以及什么是数据描述符和非数据描述符 # 今天白天没事琢磨了一下,顺便又看了看各种案例,貌似理解更深入了一些 # 数据描述符:就是这个类属性是由一个实现了__get__(),__set__(),__delete__()中方法的新式类搞定了 # 非数据描述符则是,没有__set__方法的 # 当时没有闹明白主要是因为没说作用,今天看了一下使用实例,感觉超级牛逼 # 主要就是让对类实例化过程中的参数进行一个限制,保证不能乱传 # 主要Python软件是一个弱类性的编程语言,想起之前看了一点点java,设置一个变量前都是要制定他的数据类型的 # 但是Python完全不用,java中都是 int a = 1 然而Python中直接就 a = 1 # 开始演示,还是用一个定制人员职工的类进行操作吧 class Type: def __get__(self, instance, owner): #获取值的时候会触发 print('get method is conming') print(instance) print(owner) def __set__(self, instance, value): # 实例化和设置值的时候都会被触发 print('set method is coming') print(instance) print(value) def __delete__(self, instance): #删除的时候会被触发 pass # 就暂时先拿一个参数name来试手,主要看运行的开始时间,以及__get__(),__set__(), # __delete__()这些方法中instance、owner、value具体指的是什么 class People: name = Type() # 在前面增加上的就是数据描述符 def __init__(self,name): self.name = name # 此时我们开始实例化一个People类的对象,触发set方法 p1 = People('alex') # 输出结果如下 # set method is coming # <__main__.People object at 0x0000025843ACEF60> # alex # 可以获取的信息:instance是People类的实例化对象 # value就是我们传入的name参数 # 此时通过参数调用来触发一下get方法 p1.name # <__main__.People object at 0x0000021E0016EF60> # <class '__main__.People'> # instance是同一个东西 # owner指的是instance的类名 # 这些搞清楚了,我们就可以开始进行实际的参数验证操作了
那么描述符究竟有哪些作用呢?下面是一个不错的案例
class Type: def __init__(self,key,expect_type): self.key = key self.expect_type = expect_type def __get__(self, instance, owner): return instance.__dict__[self.key] def __set__(self, instance, value): if not isinstance(value,self.expect_type): raise TypeError('%s 必须是 %s 类型的数据'%(self.key,self.expect_type)) else: instance.__dict__[self.key] = value def __delete__(self, instance): del instance.__dict__[self.key] class People: name = Type('name',str) # 第一个参数是实例化过程中的k值 # 第二个参数是你希望k值的数据类型是什么 # 此时的参数传递再也不是简单的等号传递,而是通过Type这个数据描述符进行实现 age = Type('age',int) xinchou = Type('xinchou',float) def __init__(self, name, age, xinchou): self.name = name self.age = age self.xinchou = xinchou # 先测试一下name如果传入的是整数类型,看会不会报错,以及报错内容 # p1 =People(18,18,13.1) # 成功实现功能:TypeError: name 必须是 <class 'str'> 类型的数据 # 按照要求进行传递参数 p1 = People('alex',18,18.3) print(p1.__dict__) # {'age': 18, 'xinchou': 18.3, 'name': 'alex'} # 基本实现了对实例化过程中参数类型的检测
二、类的装饰器
Python的世界里面真的是万物皆对象啊,今天确实刷新了三观,修饰器可以修饰函数也可以修饰类,修饰器本身也可以是类,惊呆了
1.类装饰器的引入
# 之前是有提到过关于函数的修饰器,现在又是类的修饰器 # 其实反映问题的本质就是Python中一切皆对象的本质 # 先来印证一下一切皆对象的说法 # 设置一个有参数的修饰器 # def dec0(**kwargs): # def wrapper(obj): # obj.x = 1 # obj.y = 2 # obj.z = 3 # return obj # return wrapper # @dec0() # def test(): # pass # print(test.__dict__) # {'x': 1, 'z': 3, 'y': 2} # 你会惊讶的发现这个函数还有字典属性了 # def deco(obj): # print('obj is coming') # return obj # @deco # def test_1(): # print('test_1 is coming') # test_1() # obj is coming # test_1 is coming # 这个是最早的函数装饰器 # 下面我们就试着给类也引进一个装饰器吧 # 要求可以给类传递属性 def deco(**kwargs): def wrapper(obj): for k ,v in kwargs.items(): setattr(obj, k , v) return obj return wrapper @deco(x=1,y=2,z=3) class People: pass print(People.__dict__) # {'__weakref__': <attribute '__weakref__' of 'People' objects>, '__module__': '__main__', # '__dict__': <attribute '__dict__' of 'People' objects>, 'z': 3, 'x': 1, 'y': 2, '__doc__': None} # 这样就把对应的属性加入到了类里面
2.类装饰器的运用
class Type: def __init__(self,key,expect_type): self.key = key self.expect_type = expect_type def __get__(self, instance, owner): return instance.__dict__[self.key] def __set__(self, instance, value): if not isinstance(value,self.expect_type): raise TypeError('%s 必须是 %s 类型的数据'%(self.key,self.expect_type)) else: instance.__dict__[self.key] = value def __delete__(self, instance): del instance.__dict__[self.key] # class People: # name = Type('name',str) # 第一个参数是实例化过程中的k值 # # 第二个参数是你希望k值的数据类型是什么 # # 此时的参数传递再也不是简单的等号传递,而是通过Type这个数据描述符进行实现 # age = Type('age',int) # xinchou = Type('xinchou',float) # def __init__(self, name, age, xinchou): # self.name = name # self.age = age # self.xinchou = xinchou # 上面是刚才关于类的数据描述符,里面存在一个缺陷,就是如果我们这个类的初始化参数少, # 我们可以这么挨个写Type('name',str),但是如果多,是不是就很不方便 # 所以就着刚才刚学会还热乎的类的装饰器,抓紧搞定 # 原有的Type()方法还在 def deco(**kwargs): def wrapper(obj): for k , v in kwargs.items(): setattr(obj,k ,Type(k,v)) return obj return wrapper @deco(name = str, age = int , xinchou = float) class People: def __init__(self,name, age, xinchou): self.name = name self.age = age self.xinchou = xinchou def test(self): pass print(People.__dict__) # {'__init__': <function People.__init__ at 0x0000025AB0FF4730>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'People' objects>, # '__module__': '__main__', '__dict__': <attribute '__dict__' of 'People' objects>, 'xinchou': <__main__.Type object at 0x0000025AB0FF89E8>, # 'test': <function People.test at 0x0000025AB0FF47B8>, 'age': <__main__.Type object at 0x0000025AB0FF8A20>,# 'name': <__main__.Type object at 0x0000025AB0FF8A58>} # 'age': <__main__.Type object at 0x0000025AB0FF8A20> # 此时在字典发现这三个参数就有了对应的属性 # 而且对应的value值是一个类属性 # 此时我们就可以直接进行输入,这样程序的可读性就大大增强,同时也节省了一点代码 # p1 = People('alex', 18.3, 18.3) # TypeError: age 必须是 <class 'int'> 类型的数据 p1 = People('alex',18,18.3)
3.自制@property的过程,用了今天看到的数据描述符、类的装饰器功能
# 之前在class属性的时候引入过@propetry来实现了在实例化对象调用时,不用括号就可以直接运行 # 这里注意的是,propetry修饰的功能属性是不能传递参数的,只有self一个参数 # 先回忆一下之前的情况吧 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 p1 = Room('alex',2,2) print(p1.area) # 如果没有@property就需要p1.area()才能触发运行 print(Room.area) # <property object at 0x000001F0A829A2C8> # 现在我们就使用数据修饰符和修饰器的原理来自己实现一个@property的功能 # 这里还有一个颠覆三观的概念 # 类也是可以当做修饰符的 # class LazyProperty: # def __init__(self,func): # self.func = func # # def __get__(self, instance, owner): # if instance is None: # return self # else: # res = self.func(instance) # return res # class Room_1: # def __init__(self,name,width,length): # self.name = name # self.width = width # self.length =length # @LazyProperty # 这里发生的情况是 area = LazyProperty(area) # # 此时直管的看这里的结构就可以看出,实际上就是将area用了一个数据描述符来代替 # # 既然是数据描述符,我们就可以通过相同的方式对他的__get__方法进行部分修改就可以实现对应内容 # # 同时为了保证类本身可以调用,我们需要在方法中增加一个为空的判断 # # 类调用的时候instance的值是空的 # def area(self): # return self.width * self.length # p1 = Room_1('ALEX',2,2) # print(p1.area) # print(Room_1.area) # <property object at 0x000001F0A829A2C8> # 当时用@property的时候用类本身去掉用这个area方法,会返回上面的东西 # 我们可以看到是一个property的对象 # 自然我们自己写的LazyProperty如果是类的调用也会返回这样一个对象 # <__main__.LazyProperty object at 0x000001F0A8288A90> # 然后,上面这个其实还有改进的地方,这里的area是一个很好计算的过程 # 假如是一个非常复杂的运算,这样每次都进行调用,会增加内存负担 # 所以我们可以做如下修改 class LazyProperty: def __init__(self,func): self.func = func def __get__(self, instance, owner): if instance is None: return self else: res = self.func(instance) setattr(instance,self.func.__name__,res) # 这一步的作用:我们把运算结果放入了实例对象的属性字典,那么下一次p1.area的时候就直接从属性字典中调出数值,避免再次计算 # 节省了内存空间 # 但是还有一个需要注意的地方 # 这里的Lazyproperty是一个非数据描述符,优先级低于实例对象,如果这个方法有了set方法,升级为数据描述符 # 那么加入字典也是没有用的,数据描述符的优先级高于实例对象 # 所以,即使字典里面已经存在了,p1.area也会再进行一次计算 # 就是这些了 return res class Room_1: def __init__(self,name,width,length): self.name = name self.width = width self.length =length @LazyProperty def area(self): return self.width * self.length p1 = Room_1('ALEX',2,2) print(p1.__dict__) # 此时的实例对象属性字典{'name': 'ALEX', 'length': 2, 'width': 2} print(p1.area) print(p1.__dict__) # 随后将面积增加如实例对象属性字典中{'name': 'ALEX', 'width': 2, 'area': 4, 'length': 2} # 下次调用时可节省计算的内存
就是这些啦,明天计划多做些习题,前面的东西感觉只有映像了,要写估计还得想好一会呢,所以明天更新的内容就是相关习题了,还有我的选课系统。