第三十五篇 类的内置属性(attr属性),包装和授权,__getattr__
双下划线开头的attr方法,都是类内置的方法。
一. 如果没有在类里定义这三个方法,调用的时候就调用类内置的默认的方法
class Too: pass # 类没有定义这三个属性,就用系统默认的方法 t1 = Too() print(t1.x) # 只有在属性不存在时, 会自动触发__getattr__ # AttributeError: 'Too' object has no attribute 'x' del t1.x # 删除属性时会触发 __delattr__ # AttributeError: 'Too' object has no attribute 'x' t1.y = 10 # 设置属性的时候会触发 __setattr__
二. 如果你在类里定义了这三个属性,当触发的时候,就会用你自己定义的方法,而不会再去调用Python内置的三个属性了。
# 实例代码 class Foo: x = 1 def __init__(self,y): self.y = y # __getattr__用处是最大的 def __getattr__(self, item): print("执行__getattr__") def __delattr__(self, item): print("删除操作__delattr__") # del self.item # 无限递归了 self.__dict__.pop(item)
# 1. 所以删除需要直接使用它,操作底层字典。
# 2. 如果不允许删除,就需要注释掉这句话,就能控制你的所有属性都不被删除 def __setattr__(self, key, value): print("__setattr__开始执行") # self.key = value # 这就无限递归了,不能这么用的哦 # 设置属性最根本的就是在操作最底层的字典属性,所以可以直接改__dict__, 所以应该用下面的方法 self.__dict__[key] = value # 实例化 f1 = Foo(10)
__getattr__:是在调用的属性不存在的时候才会执行
# 调用 print(f1.y) # 10 print(getattr(f1, 'y')) # 10 # 调用不存在的属性 f1.ssssss # 执行__getattr__ ''' __getattr__:是在调用的属性不存在的时候才会执行 '''
__delattr__:执行删除属性操作的时候,会触发__delattr__方法
del f1.y # 删除操作__delattr__ del f1.x # 删除操作__delattr__
__setattr__:添加或者修改属性,就会触发__setattr__方法
# 实例化 f1 = Foo(10) # 实例化的时候,就是在设置字典属性,所以此时会调用一次__setattr__ f1.z = 3 # 再次设置一个z,还会调用__setattr__。 print(f1.__dict__) # {'y': 10, 'z': 3}
# 因为你重写了__setattr__,凡是赋值操作都会触发它的执行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值。
二次加工标准类型(包装)
__delattr__和 __setattr__作用不大,最有用的是__getattr__,看看它的牛逼之处。
包装:Python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景,这些内置方法远远无法满足你的需求,所以我们都需要基于标准数据类型来定制我们自己的数据类型,新增、改写方法,这就用到了继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工).
包装是通过继承加派生的方式实现的。
class List(list): # 继承了list类 # 定制了List自己的append方法,只有字符串类型的才可以append到列表里 def append(self, p_object): if type(p_object) is str: # self.append(p_object) # 无限递归了 super().append(p_object) # 等价于 list.append(p_object),前面讲过可以用super(). else: print('只能添加字符串类型') # 取中间值,自己定义了一个方法,派生 def show_midlle(self): mid_index=int(len(self)/2) return self[mid_index]
l1=List('helloworld') print(type(l1)) # <class '__main__.List'> print(l1.show_midlle()) # w l1.append(1111111111111111111111) # 只能添加字符串类型 l1.append('SB') print(l1) # ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd', 'SB']
授权:授权是包装的一个特性,包装一个类型通常是对已经存在的类型的一些定制,这种做法可以新建,修改,或者删除原有产品的功能。其他的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,单已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法
(1)该示例相当于实现了包装的继承和派生
class FileHandle: def __init__(self,filename,mode='r',encoding='utf-8'): self.file=open(filename, mode, encoding=encoding) # 调用的是系统提供open打开文件方法 self.mode=mode self.encoding=encoding # def write(self,line): # print('------------>',line) # t=time.strftime('%Y-%m-%d %X') # self.file.write('%s %s' %(t,line)) def __getattr__(self, item): # 这个方式,相当于是文件里有什么方法,我就提供什么 return getattr(self.file,item) f1=FileHandle('a.txt','w+') print(f1.file) # <_io.TextIOWrapper name='a.txt' mode='w+' encoding='utf-8'> print(f1.__dict__) # {'file': <_io.TextIOWrapper name='a.txt' mode='w+' encoding='utf-8'>, 'mode': 'w+', 'encoding': 'utf-8'} # <_io.TextIOWrapper name='a.txt' mode='w+' encoding='utf-8'>--->这是系统提供的 # 先在f1里找是否有read,然后再类里找,也没有read,最后触发了__getattr__, # 就把f1传给self, read 传给item,最后用getattr的方式调用self.file,self.file就是f1实例调用自己的file属性 # f1自己的file属性就是上面字典里file,这个file里有,就是系统提供的的open方法 print('==>',f1.read) #触发__getattr__ # ==> <built-in method read of _io.TextIOWrapper object at 0x0134BA30> # 先在自己f1的字典里找没有write方法,然后类FileHandle里也没有write方法,最后触发了__getattr__方法 # 就把f1传给self,把write传给了item,就相当于是get的而是self.write下面的那个write方法,所以就能找到了。 print(f1.write) # 通过这种方式,f1的write,read, seek,truck等方法都可以操作了 # 牛逼的地方在于,f1是通过FileHandle类实现的,而FildHandle内部是通过__getattr__帮你做了一次中转。 # 通过这种方式,相当于文件的所有属性都传递过来了。 # 这相当于刚才包装的继承和派生。 # 文件操作里有什么,我就给你提供什么 f1.write('1111111111342311\n') f1.seek(0) print('--->',f1.read())
(2)上面的示例还没有实现修改的功能
import time class FileHandle: def __init__(self,filename,mode='r',encoding='utf-8'): self.file=open(filename, mode, encoding=encoding) # 调用的是系统提供open打开文件方法 self.mode=mode self.encoding=encoding # 写的查找过程是:f1调用write方法,如果找到了就直接执行修改操作,不会再去管__getattr__了 def write(self,line): # f1传给self, 写入的内容11111等传给line去接 # print('------------>',line) # 需求是:给写到文件里的内容,都要加上时间 t=time.strftime('%Y-%m-%d %X') self.file.write('%s %s' %(t,line)) def __getattr__(self, item): # 这个方式,相当于是文件里有什么方法,我就提供什么 return getattr(self.file,item) f1=FileHandle('a.txt','w+') f1.write('1111111111342311\n') f1.write('cpu负载过高\n') f1.write('内存剩余不足\n') f1.write('硬盘剩余不足\n') # 上面写入了,就可以读出来了 f1.seek(0) print('--->',f1.read())
这个过程,为什么叫授权?哪一步实现了授权?
import time class FileHandle: def __init__(self,filename,mode='r',encoding='utf-8'): # 1.self.file=open()类似于组合的方式,赋予了一个文件描述符 self.file=open(filename, mode, encoding=encoding) self.mode=mode self.encoding=encoding # 3. 如果自己定制了方法,就可以重写原来的方法,实现项目所需要的需求。 # 比如:写的时候剔除敏感词等 # 原来的方法什么内容都可以写入,而现在去能够限制你不写入敏感词,这就类似实现了权限管理 # 这就是授权(不要纠结于叫什么,理解意思) def write(self,line): t=time.strftime('%Y-%m-%d %X') self.file.write('%s %s' %(t,line)) # 2.利用__getattr__的属性去找想要的属性; # 如果不定制,意味着所有的属性、方法都放开了。 def __getattr__(self, item): return getattr(self.file,item)