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}
# 下次调用时可节省计算的内存

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

原文地址:https://www.cnblogs.com/xiaoyaotx/p/12543574.html