对类的二次包装-----通过已有的类定制特定的类
有时候,我们需要对已有的类进行二次包装,使之成为我们需要的特制的类
在讲如何定制所需的类时,我们需要先学 __delattr__ __setattr__ getattr__ 三个类里面内置的方法 这三个方法是只有类的实例也就是类的对象才可以使用的(这里这个不是一切皆对象的对象了,是真正的类的实例对象)
//__getattr__()方法
#__getattr__()方法,两个参数,都是在找不到所调用的属性时才执行__getattr__()方法,如果找得到就不执行该方法
class Foo: def __init__(self,y): self.y = y def __getattr__(self, item): print("执行的是__getarrt__()方法!") print('属性【%s】不存在!' % item) f = Foo(10) print(f.y) //找得到 print(getattr(f,'y'))//找得到 print(getattr(f,'s'))//找不到,所以执行__getattr__()方法,且方法__getattr__()返回None f.sssss //也是找不到,执行__getattr__()方法
f.sssss()//这样的话会报错,因为你直接调用了sssss()方法,但是这个方法找不到且不会调用__getattr__()方法,可以这么理解,如果后面加了(),python就认为你是确定这个方法的存在了,所以不会再去调用
//__getattr__()方法,但如果找不到这个方法,就会报错!!! 结果 10 10 执行的是__getarrt__()方法! 属性【s】不存在! None 执行的是__getarrt__()方法! 属性【sssss】不存在!
//__getattribute__()方法,大概了解一下
//__getattribute__()方法,这几乎是一个类里面最叼的方法了,不是说它的作用功能叼,而是它的地位很叼,因为无论你对类的实例也就是对象进行任何操作,python都会无条件的首先调用__getattribute__()
方法,如果你自己的类里面没有自己重写__getattribute__()方法,就调用python内置的,这个内置的__getattribute__()方法里面我估计是写了很多很多代码的,因为它要考虑很多很多情况,比如你要调用一个
数据属性,在__getattribute__()方法里它会去看对象的属性字典,类的属性字典,父类的属性字典(如果有),查看这些属性字典看一下有没有你调用的这个属性,如果有就返回,没有就抛出异常(注意,
__getattribute__()方法抛出的AtrributeError是可以被__getattr__()方法给接收的,这里关键看你自己的类里面有没有自己重写__getattr__()方法,如果有重写就执行你重写的__getattr__()方法,
如果没有重写就调用python自己内置的__getattr__()方法,但是python内置的__getattr__()估计是它没有写好或者在这个里面又写了抛出异常,所以最终结果是程序直接终止而不像我我们自己重写的__getattr
__()方法一样,可以不终止), 再比如说__setattr__()方法,也是无条件执行__getattribute__()然后在__getattribute__()里面有判断的逻辑,看到你是赋值的语句,就去调用__setattr__()方法(这里
就甭管有没有重写,反正就是会调用__setattr__()方法) 其实__getattr__()方法也是,甭管有没有重写,反正就是会调用。
反正就是一句话,无论你对类的实例也就是对象进行任何操作,python都会无条件的首先调用__getattribute__()方法,如果再根据__getattribute__()方法里的逻辑代码去做其他相关的操作!!!!
class Fun: def __init__(self,x): self.x = x def __getattr__(self, item): print("getattr 执行!") def __getattribute__(self, item): print("getattribute 执行!") f = Fun(10) print(f.x) // 虽然f对象有x属性,但是由于在类里面写好了自己的__getattribute__()方法,不再是用python内置自己写好的__getattribute__()方法,所以无条件的调用自己写的__getattribute__()
//方法,输出getattribute 执行!同时这个属性的调用f.x返回None,因为你自己写的__getattribute__()方法太水了,没有相关的逻辑代码去查属性字典,只有一个打印操作,所以返回None #结果 getattribute 执行! None
class Fun:
def __init__(self,x):
self.x = x
def __getattr__(self, item):
print("getattr 执行!")
def __getattribute__(self, item):
print("getattribute 执行!")
raise AttributeError("错了")
f = Fun(10)
print(f.xx)
结果
getattribute 执行!
getattr 执行!
None
//__setattr__()方法
#__setarrt__()方法,只要是对对象属性进行赋值操作的都会调用这个方法,也仅仅是对对象赋值操作时才调用
class Foo: x = 1 def __init__(self,y): self.s = y //对对象属性进行操作,会调用__setattr__()方法,传进去的参数是self对象本身的内存地址,将变量s以字符串的形式也就是“s”传给key,y这个值10传给value def __setattr__(self,key,value): print("执行的是__setatttr__()方法!") # self.key = value //在这里不可以用这样的方法赋值,不然的话会无限调用 __setattr__()方法,死循环 self.__dict__[key] = value //只能直接对属性字典操作 //如果在这个函数里面不进行任何赋值操作,最后其实是没有对对象进行赋值操作的!因为只要是在这个函数外对对象进行赋值操作的都只会调用这个函数,而真正决定有没有赋值的,是在这个函数里决定的! f = Foo(10) print(f.__dict__) f.z = 100 //这个也会调用__setattr__()方法 print(f.__dict__) 结果 执行的是__setatttr__()方法! {'s': 10} 执行的是__setatttr__()方法! {'s': 10, 'z': 100}
#####################################################
用__setattr__()方法实现对对象添加的值必须字符串才添加
class Foo:
def __init__(self,x):
self.x = x
def __setattr__(self, key, value):
if type(value) is str:
print("满足要求可以添加")
self.__dict__[key] = value
else:
print("输入的不是字符串,不满足要求,不添加!")
f = Foo('djh')
print(f.__dict__)
f.age = 18
print(f.__dict__)
结果
满足要求可以添加
{'x': 'djh'}
输入的不是字符串,不满足要求,不添加!
{'x': 'djh'}
//__delattr__()方法
__delattr__()方法,和__setattr__()方法性质一样,任何对对象属性的删除操作都是调用这个方法,也仅仅是对对象属性进行删除操作时才调用
class Foo: x = 1 def __init__(self,y): self.y = y def __delattr__(self, item): print("执行的是__delatttr__()方法!")
#del self.item //会无限循环下去,不可以这样删除,只可以像下面那样直接对属性字典进行操作 self.__dict__.pop(item) f = Foo(10) print(f.__dict__) del f.y //执行__delattr__()方法,把对象 f 传给self,把变量的名字一字符串的形式也就是“y”传给item print(f.__dict__) # del f.x //这个是错误的,不可以通过对象删除类的属性 结果 {'y': 10} 执行的是__delatttr__()方法! {}
对已有类的二次包装,定制特定的类
一定要注意区分这两种方法,原理都差不多,但还是有一些原理上的区别,第二种方法可能没那好理解 但是!!!只要你看完我的注释,你肯定就懂了
第一种:继承已有类,对已有类的方法进行同名“覆盖”!
# 对已有类的重新包装,实现自己需要的功能,比如这里,只有字符串类型的才加入列表,正常来说list无这个功能 class List(list): //list其实也是一个类,可以继承 def append(self, item): //同名“覆盖”列表的append方法 if type(item) is str: print("满足要求,可以添加!") super().append(item) else: print("不满足要求,不予添加!") l1 = List('djh大帅哥') //这里的l1说白了还是 list 类的对象,因为调用构造函数时,在自定义的List里没有构造函数,用的是继承父类list来的构造函数 print(l1) //因为l1是List类的对象,也就是列表,所以可以直接输出 l1.append(13523) print(l1) l1.append('帅到不行') print(l1) 结果 ['d', 'j', 'h', '大', '帅', '哥'] 不满足要求,不予添加! ['d', 'j', 'h', '大', '帅', '哥'] 满足要求,可以添加! ['d', 'j', 'h', '大', '帅', '哥', '帅到不行']
第二种:不用继承,用授权,将重新定制的功能单独放在一个类里面,借助 __getattr__()方法完成授权
#授权的方式进行定制类 -- 根本原理是给自己定制的类对象里添加一个属性,这个对象属性是所要限制权限的类的对象 class List: def __init__(self,*args): //这里的所有操作都是先在对象里添加一个所要限制权限的类的对象(在这里是list类的对象),也就是 self.obj 是所要限制权限的类的对象 if len(args) == 1 and type(args[0]) is str: self.obj = list(*args) else: self.obj = list() for each in args: if type(each) is str: self.obj.append(each) def append(self,item): if type(item) is str: print("满足要求,可以添加!") self.obj.append(item) else: print("不满足要求,不予添加!") def __getattr__(self, item): return getattr(self.obj,item) //这里必须用getattr()函数,因为item参数本质上接收的值是字符串,想要通过字符串来调用属性,只能用getattr()函数!直接返回如果调用的是属性则返回值
//如果调用的是方法,则返回方法的内存地址 def show_list(self): return self.obj l1 = List(1111,'asd')//这里和上面第一种方法不一样,这里的l1是调用List类的构造函数,所以最后是List类的对象,也就是说l1不是列表 print(l1)//输出一个内存地址 print(l1.show_list()) l1.append(11) print(l1.show_list()) l1.append('帅到不行') print(l1.show_list()) l1.pop() //执行顺序是先执行 l1.pop 因为对象l1的属性字典中没有pop属性,所以会调用__getattr__()方法,将pop变量以字符串的形式传给参数item,然后执行return getattr(self.obj,item),也就是
//return getattr(self.obj,‘pop’),这里self.obj就是一个list内置类的对象,然后当然是找得到啦,返回pop()方法得内存地址给 l1.pop 然后再执行 l1.pop(),也就是执行了list这个内置类
//pop()方法,所以就完成了授权呀,就是相当于你想要基于已有得内置得类定制一个自己的类,可以不用第一种方法继承,而是用这种方法授权,要修改的方法在类里面重新同名“覆盖”,不修改的方法在
//__getattr__()方法里用getattr()函数进行进行授权(“也就是返回数据属性或者方法的内存地址”)
print(l1.show_list()) 结果 <__main__.List object at 0x000001CDA3FE4C50> ['asd'] 不满足要求,不予添加! ['asd'] 满足要求,可以添加! ['asd', '帅到不行'] ['asd']