欢迎关注博主 Mindtechnist 或加入【Linux C/C++/Python社区】一起学习和分享Linux、C、C++、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。
包装与授权
- 包装
- 授权
- \_\_setattr__,\_\_delattr\_\_,\_\_getattr\_\_
- \_\_getattribute__
专栏:《python从入门到实战》
所有便准数据类型都可以通过两种方式产生,一种是直接定义,一种是使用相应的函数,比如要产生字符串,可以s=’hello’或s=str(hello)。像str这种函数都是工厂函数,实际上工厂函数都是类,我们可以通过继承这些类来定制自己的数据类型。(包装)
包装
python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)。
二次加工标准类型(基于继承实现)
class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid
def append(self, p_object):
' 派生自己的append:加上类型检查'
if not isinstance(p_object,int): #if type(p_object) is int:
raise TypeError('must be int')
#self.append(p_object) #死循环
#list.append(self, p_object) #调用父类的方法
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)
clear加权限限制
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()
授权
授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
授权是采用已存在的功能达到最大限度的代码重用。在包装中我们可以新建、修改或删除已有的功能,授权的过程就是将更新的功能交由新类来处理,已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法 (包装是通过继承实现)。在代码里包含一个对getattr()内建函数的调用,调用getattr()得到默认对象的属性(数据属性或者方法)并返回它,以便于访问或者调用。
当引用一个属性时,解释器首先会在局部名称空间中查找那个名字,比如一个自定义的方法或局部实例属性。如果没有在局部字典中找到,则搜索类名称空间,以防一个类属性被访问。最后,如果两类搜索都失败了,搜索则对原对象开始授权请求,此时__getattr__()会被调用。
示例1
import time
class FileHandle:
def __init__(self,filename,mode='r',encoding='utf-8'):
self.file=open(filename,mode,encoding=encoding)
def write(self,line): #定制自己的write方法 – 对写入的内容加上时间
t=time.strftime('%Y-%m-%d %T')
self.file.write('%s %s' %(t,line))
def __getattr__(self, item):
return getattr(self.file,item)# self.file是通过系统默认的open获取的,它包含了默认open的所有方法,item是一个字符串,getattr以字符串形式调用方法
f1=FileHandle('b.txt','w+')
f1.write('你好啊')
f1.seek(0)
print(f1.read())
f1.close()
在实例和类FileHandle中都没有找到该属性,所以触发__getattr__先找f1的属性字典,没有则找类FileHandle的属性字典,没有则触发__getattr__,__getattr__中调用的是系统open返回的文件描述符的方法,通过自己的类实例化,调用系统的方法。
示例2
#_*_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()
示例3
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)
示例4
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)
__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 #这就无限递归了,因为设置属性会触发__setattr__
# self.__dict__[key]=value #应该使用它,直接修改底层字典
def __delattr__(self, item):
print('----> from delattr')
# del self.item #无限递归了
self.__dict__.pop(item)# 直接在底层字典删除,就不会触发了
#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3
print(f1.__dict__)
#__delattr__删除属性的时候会触发
f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)
#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx
getattr(obj)相当于obj.__getattr__()
setattr(obj)相当于obj.__setattr__()
delattr(obj)相当于obj.__delattr__()
- 类的内置attr属性,你不定义的话会用默认的,如果定义了则用自己定义的。
- class.at 调用时触发:如果类class中属性at不存在,则触发__getattr__
- del class.at删除时触发:删除类class的属性at时,触发__delattr__
- class.at=1设置时触发:设置类class的属性at时,触发__setattr__
自己定义这三个函数,在相应操作时,就可以触发自己想要的逻辑。
class MyClass:
def __init__(self, name)
self.name = name #触发__setattr__
def __getattr__(self, item) #默认的__getattr__是属性不存在则报错,我们修改后不报错,仅打印提示
print(“attr [%s] not found” %item)
def __setattr__(self, k, v) #默认的setattr会把设置的属性加在属性字典,但是我们自定义的setattr什么也没做,属性字典不会增加属性
print(‘set attr’, k, v)
if tyoe(v) is str: #限制value只能以字符串的形式设置
print(‘set’)
#self.k = v #再次触发__setattr__,进入死循环
self.__dict__[k] = v.upper()
#真正的设置 – 直接操作底层数据结构,在属性字典中添加
else:
print(‘not str’)
def __delattr__(self, item)
""" #自己控制-所有属性不可删除
print(‘can not delete [%s]’ %item)
pass
"""
print(‘delete attr[%s]’ %item)
#del self.item #触发__delattr__ 进入死循环
self.__dict__.pop(item) #真正的删除
mc = MyClass(‘hello’) #实例化的时候会触发__init__
print(mc.name) #hello
print(mc.hhh) #触发__getattr__ mcself hhhitem(字符串的形式)
mc.age = 18
mc.age = ‘18’ #触发__setattr__ mcself agek ‘18’v
del mc.name #触发__delattr__ mcself name item(字符串的形式)
__getattribute__
#__getattr__
class Foo:
def __init__(self,x):
self.x=x
def __getattr__(self, item):
print('执行的是我')
# return self.__dict__[item]
f1=Foo(10)
print(f1.x)
f1.xxxxxx #不存在的属性访问,触发__getattr__
#__getattribute__ - 不管属性是否存在,都会触发
class Foo:
def __init__(self,x):
self.x=x
def __getattribute__(self, item):
print('不管是否存在,我都会执行')
f1=Foo(10)
f1.x
f1.xxxxxx
如果二者同时出现
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
class Foo:
def __init__(self,x):
self.x=x
def __getattr__(self, item):
print('执行的是我')
# return self.__dict__[item]
def __getattribute__(self, item):
print('不管是否存在,我都会执行')
raise AttributeError('哈哈') #抛出AttributeError异常–跳转到__getattr__
f1=Foo(10)
f1.x
f1.xxxxxx
当__getattribute__与__getattr__同时存在,只会执行__getattribute__,除非__getattribute__在执行过程中抛出异常AttributeError,系统默认的__getattribute__,如果查找的属性不存在,当抛出异常AttributeError的时候,会跳转到__getattr__,也就是说,只有抛出异常AttributeError的时候,才会执行__getattr__,其他情况下只会执行__getattribute__。系统提供的默认__getattribute__,当要查找的属性存在时,会在当前属性字典中返回属性,如果要找的属性不存在会抛出异常AttributeError,转到__getattr__去执行,__getattr__接收异常来避免程序崩溃,并执行定义好的逻辑。