zoukankan      html  css  js  c++  java
  • 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}
    # 下次调用时可节省计算的内存

    就是这些啦,明天计划多做些习题,前面的东西感觉只有映像了,要写估计还得想好一会呢,所以明天更新的内容就是相关习题了,还有我的选课系统。

  • 相关阅读:
    数据加载存储和文件格式
    基本概率分布图的绘制
    pandas处理各类表格数据
    python时间处理
    matplotlib实现数据可视化
    sql学习笔记:表的运算
    sql杂记:一些坑和数据库恢复
    exists关键词和case表达式
    后台工具screen
    SQL函数小记
  • 原文地址:https://www.cnblogs.com/xiaoyaotx/p/12543574.html
Copyright © 2011-2022 走看看