【Python046--魔法方法:描述符】

一、描述符的定义:

描述符就是将特殊类型的类的实例指派给另外一个类的属性

1、举例:

特殊类型的类要实现以下三个方法中的其中一个或者全部实现

* __get__(self,instance,owner)

 --用于访问属性,它返回属性的值

* __set__(self,instance,value)

  --用于设置属性,不返回任何内容

* __delete__(self,instance)

  --控制删除操作,不返回任何内容

class MyDecriptor:
    def __get__(self,instance,owner):
        #打印查看描述符的get,set,delete方法中各个参数的含义
        print("getting...",self,instance,owner)

    def __set__(self,instance,value):
        print("setting...",self,instance,value)

    def __delete__(self,instance):
        print("deleteing...",self,instance)

class Test:
    #把特殊类MyDecriptor的实例指派给Test类的x属性
    x = MyDecriptor()

执行结果:
>>> test = Test()
>>> test.x
getting... <__main__.MyDecriptor object at 0x10a2186d8> <__main__.Test object at 0x10a208f60> <class '__main__.Test'>
>>> test
<__main__.Test object at 0x10a208f60>
>>> Test
<class '__main__.Test'>

'''
|--由打印出来的getting...,可以看出来,参数self是MyDecriptor的对象(<__main__.MyDecriptor object at 0x10a2186d8>)
|--参数instance是Test的对象(<__main__.Test object at 0x10a208f60>)
|--参数owner是Test的本类自己(<class '__main__.Test'>)
|--具体可由对象test打印出来的内容,和Test类打印出来的内容得到验证
|--set和delete同理
'''
>>> test.x = "X-man"
setting... <__main__.MyDecriptor object at 0x10a2186d8> <__main__.Test object at 0x10a208f60> X-man
>>> del test.x
deleteing... <__main__.MyDecriptor object at 0x10a2186d8> <__main__.Test object at 0x10a208f60>
>>> 

2、为了能让描述符正常使用,必须定义在类的层次上,如果不是在类的层次上,Python无法会为你自动调用__get__(),__set__()方法

>>> class MyDes:
        def __init__(self, value = None):
                self.val = value
        def __get__(self, instance, owner):
                return self.val ** 2

        
>>> class Test:
        def __init__(self):
                self.x = MyDes(3)  #用在了使用类的方法上,则不会调用描述符的__get__()和__set__()方法

                
>>> test = Test()
>>> test.x
<__main__.MyDes object at 0x109715588>

二、动手题

1、按要求编写描述符MyDes:当类的属性被访问,修改或设置的时候,分别作出提示

#当类的属性被访问,修改或设置时,分别给出提示
class MyDes:
    def __init__(self,initval=None,name=None):
        self.val = initval
        self.name = name

    def __get__(self,instance,owner):
        print("正在获取变量:",self.name)
        return self.val

    def __set__(self,instance,value):
        print("正在设置变量:",self.name)
        self.var = value

    def __delete__(self,instance):
        print("正在删除变量:",self.name)
        print("不能删除变量!")

class Test:
    x = MyDes(10,'x')
    

执行结果:

>>> test = Test()
>>> test.x
正在获取变量: x
10
>>> test.x = 'X-man'
正在设置变量: x
>>> del test.x
正在删除变量: x
不能删除变量!
>>> test.x
正在获取变量: x
10

2、按要求编写描述符MyDes:记录指定变量的读取和写入操作,并将记录以及触发时间保存到文件:record.txt

考察的内容:字符串的读取,文件的操作,time模块的用法,描述符

import time as t

class MyDes:
    def __init__(self,initval=None,name=None):
        self.var = initval
        self.name = name
        self.filename = "record.txt"

    def __get__(self,instance,owner):
        with open(self.filename,'a',encoding='utf-8') as f:
            f.write("%s变量于北京时间%s被读取,%s = %s
" % (self.name,t.ctime(),self.name,str(self.var)))

        print("var==", self.var)
        return self.var

    def __set__(self, instance, value):
        filename = "%s_record.txt" % self.name
        with open(self.filename,'a',encoding='utf-8') as f:
            f.write("%s变量于北京时间%s被修改,%s = %s
" %(self.name,t.ctime(),self.name,str(value)))

        print("value==",value)
        return value

class Test:
    x=MyDes(10,'x')
    y=MyDes(8.8,'y')

if __name__ == '__main__':
    for i in range(10):
        test = Test()
        print("get_testX++",test.x,"
get_testY--",test.y)
        test.x = 123
        test.x = 1.23
        test.y = "I LOVE AI"
        print("
==================")

执行结果:
var== 10
var== 8.8
get_testX++ 10 
get_testY-- 8.8
value== 123
value== 1.23
value== I LOVE AI

==================
var== 10
var== 8.8
get_testX++ 10 
get_testY-- 8.8
value== 123
value== 1.23
value== I LOVE AI

x变量于北京时间Wed Nov 14 23:32:54 2018被读取,x = 10
y变量于北京时间Wed Nov 14 23:32:54 2018被读取,y = 8.8
x变量于北京时间Wed Nov 14 23:32:54 2018被修改,x = 123
x变量于北京时间Wed Nov 14 23:32:54 2018被修改,x = 1.23
y变量于北京时间Wed Nov 14 23:32:54 2018被修改,y = I LOVE AI
x变量于北京时间Wed Nov 14 23:32:54 2018被读取,x = 10
y变量于北京时间Wed Nov 14 23:32:54 2018被读取,y = 8.8
x变量于北京时间Wed Nov 14 23:32:54 2018被修改,x = 123
x变量于北京时间Wed Nov 14 23:32:54 2018被修改,x = 1.23
y变量于北京时间Wed Nov 14 23:32:54 2018被修改,y = I LOVE AI

3、编写描述符MyDes,使用文件来存储属性,属性的值会被存放到对应的pickle文件内(腌菜),如果属性被删除了,文件也会同时被删除,属性的名字也会被注销

# coding=utf-8
import os
import pickle

class MyDes:
    saved = []

    def __init__(self,name= None):
        self.name = name
        self.filename = self.name + '.pkl'


    def __get__(self, instance, owner):
        if self.name not in MyDes.saved:
            raise AttributeError("%s 的属性没有被赋值!"%self.name)

        with open(self.filename,'rb') as f:
            value = pickle.load(f)
            print("value++ ",value)

        return value

    def __set__(self, instance, value):
        with open(self.filename,'wb') as f:
            pickle.dump(value,f)
            MyDes.saved.append(self.name)
            print("f_name ",MyDes.saved)

        with open(self.filename,'rb') as f:
            f_value = pickle.load(f)
            print("f_value-- ",f_value)

    def __delete__(self, instance):
        os.remove(self.filename)
        MyDes.saved.remove(self.name)

class Test:
    x = MyDes('x')
    y = MyDes('y')

if __name__ == '__main__':
    test = Test()
    test.x = '123'
    test.y = 'I LOVE AI'#del test.y

执行结果:
f_name  ['x']
f_value--  123
f_name  ['x', 'y']
f_value--  I LOVE AI
value++  123
value++  I LOVE AI

并且生成两个pkl文件:x.pkl y,pkl

 扩展知识:

1、用于序列化的两个模块

json:用于字符串和Python数据类型间进行转换

pickle:用于Python特有的类型和Python的数据类型间进行转换

json提供四个功能:dumps,dump,loads,load

pickle提供四个功能:dumps,dump,loads,load

2、pickle可以存储什么类型的数据呢?

1)、所有python支持的原生类型:布尔型,整形,浮点型,复数,字符串,字节,None

2)、由任何原生类型组成的列表,数组,字典

3)、函数,类和类的实例

3、pickle中常用的方法有:

1)、pickle.dump(obj,file,protocol=none)

必填参数obj表示要封装的对象

必填参数file表示obj要写入的文件对象,file必须以二进制可写模式打开,即:wb

可选参数protocol表示告知pickler使用的协议,支持的协议有0,1,2,3 默认的协议是添加在Python3中的协议3

        with open(self.filename,'wb') as f:
            pickle.dump(value,f)
            MyDes.saved.append(self.name)
            print("f_name ",MyDes.saved)

#pickle.dump(obj,file)的使用必须结合open(file,'wb')

2)、 pickle.load(file,*,fix_imports=True, encoding="ASCII", errors="strict")

必填参数file必须已二进制可读模式打开,即:wb,其他都为可选参数

        with open(self.filename,'rb') as f:
            f_value = pickle.load(f)
            print("f_value-- ",f_value)

#pickle.load(file)的使用必须结合open(file,'rb')

3)、pickle.dumps(obj):已字节对象形式返回封装的对象,不需要写入文件中

4)、pickle.loads(bytes_object):从字节对象中读取被封装的对象,并返回

4、pickle模块可能出现三种异常:

1). PickleError:封装和拆封时出现的异常类,继承自Exception

2). PicklingError: 遇到不可封装的对象时出现的异常,继承自PickleError

3). UnPicklingError: 拆封对象过程中出现的异常,继承自PickleError

原文地址:https://www.cnblogs.com/frankruby/p/9951484.html