Python学习:__setitem__,__getitem,__delitem__

__setitem__,__getitem,__delitem__

在Python中,如果我们想实现创建类似于序列和映射的类,可以通过重写魔法方法__getitem____setitem____delitem____len__方法去模拟。
以字典的方式进行设置,访问和删除

魔术方法的作用:

__getitem__(self,key):返回键对应的值。

当你执行self[key]的时候,调用的就是该方法。该方法在可变容器和不可变容器中也都必须实现。
调用的时候,如果key的类型错误,该方法应该抛出TypeError
如果没法返回key对应的数值时,该方法应该抛出ValueError

__setitem__(self,key,value):设置给定键的值

__delitem__(self,key):删除给定键对应的元素。

__len__():返回元素的数量

class AritheneticSequence(object):
    def __init__(self, start=0, step=1):
        print('Call function __init__')
        self.start = start
        self.step = step
        self.myData = {}

    # 定义获取值的方法
    def __getitem__(self, key):
        print('Call function __getitem__')
        try:
            return self.myData[key]
        except KeyError:
            return self.start + key * self.step

    #定义赋值方法
    def __setitem__(self, key, value):
        print('Call function __setitem__')
        self.myData[key]=value

    #定义获取的长度
    def __len__(self):
        print('Call function __len__')
        ## 这里为了可以看出__len__的作用, 我们故意把length增加1
        print(len(self.myData)+1)

    # 定义删除元素的方法
    def __delitem__(self, key):
        print('Call function __delitem__')
        del self.myData[key]


s=AritheneticSequence(1,2)
print(s[3])
#这里应该执行self.start+key*self.step,因为没有3这个key
'''
Call function __init__
Call function __getitem__
7
'''
s[3]=100    # 进行赋值
print(s[3]) # 前面进行了赋值,那么直接输出赋的值100
'''
Call function __setitem__
Call function __getitem__
100
'''

print(len(s))   # 我们故意多加了1,应该返回2
'''
Call function __len__
2
'''
del s[3] # 删除3这个key
class Foo:
    def __init__(self,name):
        self.name=name

    def __getitem__(self, item):
        #print(self.__dict__[item])
        return self.__dic__[item]

    def __setitem__(self, key, value):
        self.__dict__[key]=value
    def __delitem__(self, key):
        print('del obj[key]时,我执行')
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print('del obj.key时,我执行')
        self.__dict__.pop(item)

f1=Foo('sb')
#f1.age=18
f1['age']=18
#f1.age1=19
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='alex'
print(f1.__dict__)

结果:

Call function __init__
Call function __getitem__
7
Call function __setitem__
Call function __getitem__
100
Call function __len__
2
Call function __delitem__
Call function __getitem__
7
  • 动态导入模块

__setattr__,__delattr__,__getattr__

  • _ _setattr_ _设置属性时触发
  • _ _delattr_ _删除属性时触发
  • _ _getattr_ _只有在属性不存在时 触发
class Foo:
    x=1
    def __init__(self,y):
        self.y=y

    def __getattr__(self, item):
        print('----> from getattr:你找的属性不存在')


    def __setattr__(self, key, value):
        print('----> from setattr')
        # self.key=value #这就无限递归了,你好好想想
        # self.__dict__[key]=value #应该使用它

    def __delattr__(self, item):
        print('----> from delattr')
        # del self.item #无限递归了
        self.__dict__.pop(item)

#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
#输出:----> from setattr

print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
#输出:{}

f1.z=3
# 输出:----> from setattr

print(f1.__dict__)
#输出:{}

#__delattr__删除属性的时候会触发
f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作

del f1.a   #输出:----> from delattr
del f1.x   #输出:----> from delattr

print(f1.__dict__)    #输出:{}

#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx
#输出:----> from getattr:你找的属性不存在

Case 1 通过__setattr__设置属性的类型 __delattr__控制属性的删除

class Foo:
    def __init__(self, name):
        self.name = name

    def __getattr__(self, item):
        print('你找的属性【%s】不存在' % item)

    def __setattr__(self, k, v):
        print('执行setattr', k, v)
        if type(v) is str:
            print('开始设置')
            # self.k=v #触发__setattr__
            self.__dict__[k] = v.upper()
        else:
            print('必须是字符串类型')

    def __delattr__(self, item):
        print('不允许删除属性【%s】' % item)
        # print('执行delattr',item)
                # self.__dict__.pop(item)


f1 = Foo('alex')
f1.age = 18  # 触发__setattr__
print(f1.__dict__)

#输出
执行setattr name alex
开始设置
执行setattr age 18
必须是字符串类型
{'name': 'ALEX'}

二次加工标准类型(包装)

包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)

class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid
    def append(self, p_object):
        ' 派生自己的append:加上类型检查'
        if not isinstance(p_object,int):
            raise TypeError('must be int')
        super().append(p_object)

    @property
    def mid(self):
        '新增自己的属性'
        index=len(self)//2
        return self[index]

l=List([1,2,3,4])
print(l)
l.append(5)
print(l)
# l.append('1111111') #报错,必须为int类型

print(l.mid)

#其余的方法都继承list的
l.insert(0,-123)
print(l)
l.clear()
print(l)

二次加工标准类型(基于继承实现)
class List(list):
    def __init__(self,item,tag=False):
        super().__init__(item)
        self.tag=tag
    def append(self, p_object):
        if not isinstance(p_object,str):
            raise TypeError
        super().append(p_object)
    def clear(self):
        if not self.tag:
            raise PermissionError
        super().clear()

l=List([1,2,3],False)
print(l)
print(l.tag)

l.append('saf')
print(l)

# l.clear() #异常

l.tag=True
l.clear()

练习(clear加权限限制)

授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。

实现授权的关键点就是覆盖__getattr__方法

import time
class FileHandle:
    def __init__(self,filename,mode='r',encoding='utf-8'):
        self.file=open(filename,mode,encoding=encoding)
    def write(self,line):
        t=time.strftime('%Y-%m-%d %T')
        self.file.write('%s %s' %(t,line))

    def __getattr__(self, item):
        return getattr(self.file,item)

f1=FileHandle('b.txt','w+')
f1.write('你好啊')
f1.seek(0)
print(f1.read())
f1.close()

 授权示范一
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
#我们来加上b模式支持
import time
class FileHandle:
    def __init__(self,filename,mode='r',encoding='utf-8'):
        if 'b' in mode:
            self.file=open(filename,mode)
        else:
            self.file=open(filename,mode,encoding=encoding)
        self.filename=filename
        self.mode=mode
        self.encoding=encoding

    def write(self,line):
        if 'b' in self.mode:
            if not isinstance(line,bytes):
                raise TypeError('must be bytes')
        self.file.write(line)

    def __getattr__(self, item):
        return getattr(self.file,item)

    def __str__(self):
        if 'b' in self.mode:
            res="<_io.BufferedReader name='%s'>" %self.filename
        else:
            res="<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'>" %(self.filename,self.mode,self.encoding)
        return res
f1=FileHandle('b.txt','wb')
# f1.write('你好啊啊啊啊啊') #自定制的write,不用在进行encode转成二进制去写了,简单,大气
f1.write('你好啊'.encode('utf-8'))
print(f1)
f1.close()

授权示范二
#练习一
class List:
    def __init__(self,seq):
        self.seq=seq

    def append(self, p_object):
        ' 派生自己的append加上类型检查,覆盖原有的append'
        if not isinstance(p_object,int):
            raise TypeError('must be int')
        self.seq.append(p_object)

    @property
    def mid(self):
        '新增自己的方法'
        index=len(self.seq)//2
        return self.seq[index]

    def __getattr__(self, item):
        return getattr(self.seq,item)

    def __str__(self):
        return str(self.seq)

l=List([1,2,3])
print(l)
l.append(4)
print(l)
# l.append('3333333') #报错,必须为int类型

print(l.mid)

#基于授权,获得insert方法
l.insert(0,-123)
print(l)





#练习二
class List:
    def __init__(self,seq,permission=False):
        self.seq=seq
        self.permission=permission
    def clear(self):
        if not self.permission:
            raise PermissionError('not allow the operation')
        self.seq.clear()

    def __getattr__(self, item):
        return getattr(self.seq,item)

    def __str__(self):
        return str(self.seq)
l=List([1,2,3])
# l.clear() #此时没有权限,抛出异常


l.permission=True
print(l)
l.clear()
print(l)

#基于授权,获得insert方法
l.insert(0,-123)
print(l)

练习题(授权)

你可能感兴趣的:(python面向对象编程)