初识双下方法
双下方法的作用
双下方法,也被称为魔法方法。为什么我喜欢叫它双下方法呢?因为本人在读"流程的Python"一书中,作者对其
__
开头__
结尾的方法是叫的双下方法(注意与只有开头双下划线的隐藏属性/方法进行区分),故我也跟着这样叫了。其实个人比较反感叫魔法方法,因为魔法这一词很玄乎,所以不太喜欢这种叫法。
那么双下方法的作用即在满足某一特定的条件下进行自动调用。比如当我们使用
len()
对某个实例对象进行统计长度时,就会自动触发其类中的__len__
方法。
使用双下方法的好处:可以高度定制化你的类。
print([1,2,3].__len__()) # 3 from collections import UserList class MyList(UserList): def __len__(self): # 由于 __len__ 需要返回统计的长度,故我们将其调用父类方法并进行返回即可。 print("执行了...") return super(MyList,self).__len__() ml1 = MyList([1,2,3,4]) print(len(ml1)) # ==== 执行结果 ==== """ 3 执行了... 4 """
常用双下方法
__getattribute__
触发条件
以任何形式对对象属性或方法进行访问时都会去调用实例化出自己类中的
__getattribute__
方法。在查找属性/方法不存在时,抛出
AttributeError
的异常。如属性或方法存在则返回该属性或方法。
实例对象调用实例化出自己类里面的
__getattribute__
,类对象则去调用元类中的__getattribute__
。
print([1,2,3].__len__()) # 3 from collections import UserList class MyList(UserList): def __len__(self): # 由于 __len__ 需要返回统计的长度,故我们将其调用父类方法并进行返回即可。 print("执行了...") return super(MyList,self).__len__() ml1 = MyList([1,2,3,4]) print(len(ml1)) # ==== 执行结果 ==== """ 3 执行了... 4 """
__del__
触发条件
在对对象进行手动的
del
操作时触发,或者在程序运行结束后自动触发。实例对象调用实例化出自己类里面的
__del__
,类对象则去调用元类中的__del__
。
注意
对于程序运行完后自动触发这一条,类对象的
__del__
触发一定在实例对象之前。
作用
可以用于关闭一些占据系统资源的操作,如打开文件等等。
# ==== __del__ 功能演示 ==== class MetaClass(type): # 元类必须继承type name = "元类" def __del__(self): print("执行元类中的__del__") class MyClass(object,metaclass=MetaClass): author = "云崖" def __init__(self,name): self.name = name print("执行了 __init__ ") def __del__(self): print("执行del...") m1 = MyClass("Yunya") del m1.name # ==== 执行结果 ==== """ 执行了 __init__ 执行元类中的__del__ 执行del... """
# ==== __del__ 应用场景 ==== #需求: 定制一个文件读写的类,带有自动close功能。 class FileHandle(object): def __init__(self, filename, mode="rt", encoding="utf-8"): self.file = open(file=filename, mode=mode, encoding=encoding) def __del__(self): print("发起系统调用,清理open()打开的系统内存资源...") self.file.close() # 防止忘记关闭 f = FileHandle("1.txt") content = f.file.read() print(content) print("======>") # ==== 执行结果 ==== """ apple 10 3 tesla 100000 1 mac 3000 2 lenovo 30000 3 chicken 10 3 ======> 发起系统调用,清理open()打开的系统内存资源... """
__getattr__ __setattr__ __delattr__
触发条件
__getattr__
:在对对象进行.
操作试图获取对象下的某一对象属性或方法而该属性或方法不存在时自动触发。
__setattr__
:在对对象进行.
操作试图修改对象下的某一对象属性或方法时自动触发。
__delattr__
:在对对象进行.
操作视图删除对象下的某一对象属性或方法时自动触发。
对于实例对象来说,使用
.
进行增删改查时都会去实例化出自己的类中寻找这三个方法。(__getattr__
为不存在时才会触发)而对于类对象来说,使用
.
进行增删改查时都会去自己的元类中寻找这三个方法。(__getattr__
为不存在时才会触发)
注意事项
1.对自定义的元类进行
__setattr__
与__delattr__
的定制,无法直接操纵底层__dict__
。需要调用父类或type
元类中的这2种方法2.若
__getattribute__
与__getattr__
同时出现,则只会调用__getattribute__
。
# ==== __getattr__ __setattr__ __delattr__功能演示 ==== class MetaClass(type): # 元类必须继承type name = "元类" def __getattr__(self, item): print("执行元类中的__getattr__") def __setattr__(self, key, value): print("执行元类中的__setattr__") return super(MetaClass, self).__setattr__(key, value) def __delattr__(self, item): print("执行元类中的__delattr__") return super(MetaClass, self).__delattr__(item) class MyClass(object, metaclass=MetaClass): author = "云崖" def __init__(self, name): self.name = name print("执行了 __init__ ") def __getattr__(self, item): print("执行__getattr__") def __setattr__(self, key, value): print("执行__setattr__") # self.key=value #这就无限递归了,你好好想想 self.__dict__[key] = value def __delattr__(self, item): print("执行__delattr__") # del self.item #无限递归了 del self.__dict__[item] # ==== 对类对象的attr操作 ==== 对自定义的元类进行__getattr__与__delattr__的方法的定制时,无法直接操纵底层__dict__。需要调用父类或type元类中的这2种方法 print(MyClass.name) print(MyClass.sex) # 执行元类中的 __getattr__ 属性不存在 MyClass.name = "metaclass" # 执行元类中的 __setattr__ 设置属性 del MyClass.name # 执行元类中的 __delattr__ 删除属性 # ==== 对实例对象的attr操作 ==== m1 = MyClass("Yunya") # 新增,对应类中的 __setattr__ 设置属性 print(m1.name) print(m1.sex) # 执行类中的 __getattr__ 属性不存在 m1.name = "小帅哥" # 执行 类中的 __setattr__ 设置属性 del m1.name # 执行类中的 __delattr__ 删除属性 """ 元类 执行元类中的__getattr__ None 执行元类中的__setattr__ 执行元类中的__delattr__ 执行__setattr__ 执行了 __init__ Yunya 执行__getattr__ None 执行__setattr__ 执行__delattr__ """
# ==== 注意事项:__getattribute__ 与 __getattr__ 共存 ==== class Foo: def __getattr__(self, item): # 如果存在__getattr__,则会捕获到AttributeError的异常。 print('只有在__getattribute__抛出AttributeError后才会执行__getattr__') # return self.__dict__[item] def __getattribute__(self, item): print('不管属性是否存在都会执行__getattribute__') # 有则返回,无则抛出异常 raise AttributeError('你查找的属性不存在') # 当查找属性不存在时,抛出 AttributeError后才会执行__getattr__,否则只会执行AttributeError.. f1=Foo() print(f1.xxxxxx) #当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError # ==== 执行结果 ==== """ 不管属性是否存在都会执行__getattribute__ 只有在__getattribute__抛出AttributeError后才会执行__getattr__ None """
# ==== __getattr__ 应用场景 ==== # 需求: 定制一个文件读写的类,要求写的每一行都加上时间显示的功能 import time class FileHandle(object): def __init__(self,filename,mode="rt",encoding="utf-8"): self.__file = open(file=filename,mode=mode,encoding=encoding) def write(self,content): data = time.strftime("%Y-%m-%d : %X") self.__file.write("{0}:{1} ".format(data,content)) def __getattr__(self, item): return getattr(self.__file,item) # 相当于拿到文件句柄中的某一个方法并返回 def __del__(self): self.__file.close() # 防止忘记关闭 f = FileHandle("1.txt") print(f.read()) # ==== 执行结果 ==== """ apple 10 3 tesla 100000 1 mac 3000 2 lenovo 30000 3 chicken 10 3 """
__getitem__ __setitem__ __delitem__
触发条件
__getitem__
:在对对象进行[]
操作试图获取对象下的某一对象属性或方法时自动触发。(注意区分与__getattr__
的区别,__getattr__
是用.
访问属性并且不存在时才会触发。)
__setitem__
:在对对象进行[]
操作试图修改对象下的某一对象属性或方法时自动触发。
__delitem__
:在对对象进行[]
操作试图删除对象下的某一对象属性或方法时自动触发。
对于实例对象来说,使用
[]
进行增删改查时都会去实例化出自己的类中寻找这三个方法。而对于类对象来说,使用
[]
进行增删改查时都会去自己的元类中寻找这三个方法。
注意事项
1.对自定义元类进行
__setitem__
与__delitem__
的定制,无法直接操纵底层__dict__
。可以使用反射机制来达成目的(其实就是使用attr
系列代替操作,因为父类或type
元类中没有__getitem__
或__setitem__
)。2.
__getitem__
是一个非常强大的功能。即使的你的定制类中没有实现__iter__
方法,使用__getitem__
依然可以为你创建出专属的迭代器。
# ==== __getitem__ __setitem__ __delitem__功能演示 ==== class MetaClass(type): # 元类必须继承type name = "元类" def __getitem__(self, item): print("执行元类中的__getitem__") if hasattr(MetaClass, item): return getattr(MetaClass, item) return self.__dict__[item] def __setitem__(self, key, value): print("执行元类中的__setitem__") setattr(self, key, value) def __delitem__(self, item): print("执行元类中的__delitem__") delattr(self, item) class MyClass(object, metaclass=MetaClass): author = "云崖" def __init__(self, name): self.name = name print("执行了 __init__ ") def __getitem__(self, item): print("执行__getitem__") return self.__dict__[item] def __setitem__(self, key, value): print("执行__setitem__") self.__dict__[key] = value def __delitem__(self, item): print("执行__delitem__") del self.__dict__[item] # ==== 对类对象的item操作 ==== print(MyClass["name"]) # 执行元类中的__getitem__ 获取属性 MyClass["name"] = "metaclass" # 执行元类中的 __setitem__ 设置属性 del MyClass["name"] # 执行元类中的 __delitem__ 删除属性 # ==== 对实例对象的item操作 ==== m1 = MyClass("Yunya") # 新增,对应类中的 __setitem__ 设置属性 print(m1["name"]) # 执行__getitem__ 获取属性 m1["name"] = "小帅哥" # 执行 类中的 __setitem__ 设置属性 del m1["name"] # 执行类中的 __delitem__ 删除属性 """ 执行元类中的__getitem__ 元类 执行元类中的__setitem__ 执行元类中的__delitem__ 执行了 __init__ 执行__getitem__ Yunya 执行__setitem__ 执行__delitem__ """
# ==== 注意事项:__getitem__ 代替 __iter__ ==== class Foo: def __getitem__(self, item): # __getitem__可以隐式的创建迭代器 pass # 如果不实现该方法或者 __iter__ 方法。会抛出异常: TypeError: 'Foo' object is not iterable f1=Foo() print(iter(f1)) # 可以看见,没有实现__iter__方法也能创建迭代器 # ==== 执行结果 ==== """ <iterator object at 0x000001DF109802E0> """
# ==== 扩展:__getitem__如何区分切片与取值 ==== """ 通过实现双下getitem方法后我们的类其实就能进行迭代。 当然这是残缺不全的,要想真正的让我们的类变的和列表一样具有切片和取值功能。 我们需要对双下getitem进行更加详细的重写 """ from numbers import Integral # 取值操作全部来源于该类 class My_list(object): def __init__(self,args): self.args = args def __getitem__(self, item): cls = type(self) # <class '__main__.My_list'> if isinstance(item,Integral): #如果是取值操作 return cls(self.args[item])#返回新的My_list实例,注意必须是可迭代形式 elif isinstance(item,slice): #若是切片操作 return cls(self.args[item]) # 返回新的My_list实例 def __repr__(self): return str(self.args) l = My_list([1,2,3]) print(l[1]) print(l[0:2]) # ==== 执行结果 ==== """ 2 [1, 2] """
__str__ __repr__ __format__
触发条件
__str__
:对象执行print()
操作后自动触发该方法。
__repr__
: 在交互式环境下打印对象名后会自动触发该方法。
__format__
:对象执行format()操作后自动触发该方法。
注意事项
1.当执行
print(对象)
的操作后,会先去找__str__
方法,如果没找到则用__repr__
方法代替,如果都没找到则打印原生的信息,即对象的内存地址编号相关。2.
__str__
与__repr__
以及__format__
必须返回str
类型。
# ==== __str__ 功能演示 ==== class MyClass(object): def __init__(self,name): self.name = name def __str__(self): # 注意:__str__必须返回str类型 return str(self.name) m1 = MyClass("云崖") print(m1) # 云崖
# ==== __repr__ 功能演示 ==== # ==== 交互式环境下 ==== >>> class MyClass(object): ... def __init__(self,name): ... self.name = name ... def __repr__(self): # 注意:__repr__必须返回str类型 ... return str(self.name) ... >>> m1 = MyClass("云崖") >>> m1 云崖 >>>
# ==== __format__ 功能演示 ==== date_dic = { 'ymd': '{0.year}:{0.month}:{0.day}', 'dmy': '{0.day}/{0.month}/{0.year}', 'mdy': '{0.month}-{0.day}-{0.year}', } class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day def __format__(self, format_spec): if not format_spec or format_spec not in date_dic: format_spec = 'ymd' fmt = date_dic[format_spec] return fmt.format(self) # self.year:self.month:self.day d1 = Date(2016, 12, 29) print(format(d1)) print('{0:mdy}'.format(d1)) # ==== 执行结果 ==== """ 2016:12:29 12-29-2016 """
__enter__ __exit__
触发条件
使用
with
语句自动触发__enter__
,with
语句代码块执行完毕后自动触发__exit__
。Ps:这种结构也被称为上下文管理协议。
作用
1.使用
with
语句的目的就是把代码块放入with
中执行,with
结束后,自动完成清理工作,无须手动干预2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在
__exit__
中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
注意事项
1.
__exit__()
中的三个参数分别代表异常类型,异常值和追溯信息,with
语句中代码块出现异常,则with
后的代码都无法执行。(注意!with
语句代码块中的所有异常都会被__exit__
捕捉!)2.如果
__exit__()
返回值为True
,那么异常会被清空,就好像啥都没发生一样,with
后的语句正常执行。
# ==== __enter__ __exit__ 功能演示 ==== class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') # return self def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') with Open('a.txt') as f: print('=====>执行代码块') # print(f,f.name) # ==== 执行结果 ==== """ 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 =====>执行代码块 with中代码块执行完毕时执行我啊 """
# ==== with代码块中的异常由__exit__接管 ==== class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') print(exc_type) print(exc_val) print(exc_tb) # 如果返回False代表不处理这些异常 with Open('a.txt') as f: print('=====>执行代码块') raise AttributeError('***着火啦,救火啊***') print('0'*100) #------------------------------->不会执行 # ==== 执行结果 ==== """ 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 =====>执行代码块 with中代码块执行完毕时执行我啊 <class 'AttributeError'> ***着火啦,救火啊*** <traceback object at 0x000001F597778400> Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/learn/反射与自省.py", line 20, in <module> raise AttributeError('***着火啦,救火啊***') AttributeError: ***着火啦,救火啊*** """ __exit__中的异常
# ==== __exit__返回True代表捕捉异常 ==== class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') print(exc_type) print(exc_val) print(exc_tb) return True # 返回True代表捕捉异常 with Open('a.txt') as f: print('=====>执行代码块') raise AttributeError('***着火啦,救火啊***') print('0'*100) #------------------------------->会执行 # ==== 执行结果 ==== """ 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 =====>执行代码块 with中代码块执行完毕时执行我啊 <class 'AttributeError'> ***着火啦,救火啊*** <traceback object at 0x0000020973EAF280> 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 """
双下方法大全一览
双下方法大全
注意:以下内容均来自互联网。可能有一定的描述不准确性,仅供参考! __ new__(cls[, …]) : 是在一个对象实例化的时候所调用的第一个方法,它的第一个参数是这个类,其他的参数是用来直接传递给 __ init__ 方法。决定是否要使用该 __ init__ 方法,因为 __ new__ 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果 __ new__ 没有返回实例对象,则 __ init__ 不会被调用。 __ new__ 主要是用于继承一个不可变的类型比如一个 tuple 或者 string。 __ init__(self[, …]): 构造器,当一个实例被创建的时候调用的初始化方法 __ del__(self): 析构器,当一个实例被销毁的时候调用的方法 __ call__(self[, args…]): 允许一个类的实例像函数一样被调用:x(a, b) 调用 x.__ call__(a, b) __ len__(self): 定义当被 len() 调用时的行为 __ repr__(self): 定义当被 repr() 调用时的行为 __ str__(self): 定义当被 str() 调用时的行为 __ bytes__(self): 定义当被 bytes() 调用时的行为 __ hash__(self): 定义当被 hash() 调用时的行为 __ bool__(self): 定义当被 bool() 调用时的行为,应该返回 True 或 False __ format__(self, format_spec): 定义当被 format() 调用时的行为 有关属性 __ getattr__(self, name): 定义当用户试图获取一个不存在的属性时的行为 __ getattribute__(self, name): 定义当该类的属性被访问时的行为 __ setattr__(self, name, value): 定义当一个属性被设置时的行为 __ delattr__(self, name): 定义当一个属性被删除时的行为 __ dir__(self): 定义当 dir() 被调用时的行为 __ get__(self, instance, owner): 定义当描述符的值被取得时的行为 __ set__(self, instance, value): 定义当描述符的值被改变时的行为 __ delete__(self, instance): 定义当描述符的值被删除时的行为 比较操作符 __ lt__(self, other): 定义小于号的行为:x < y 调用 x.lt(y) __ le__(self, other): 定义小于等于号的行为:x <= y 调用 x.le(y) __ eq__(self, other) : 定义等于号的行为:x == y 调用 x.eq(y) __ ne__(self, other): 定义不等号的行为:x != y 调用 x.ne(y) __ gt__(self, other): 定义大于号的行为:x > y 调用 x.gt(y) __ ge__(self, other) : 定义大于等于号的行为:x >= y 调用 x.ge(y) 算数运算符 __ add__(self, other): 定义加法的行为:+ __ sub__(self, other): 定义减法的行为:- __ mul__(self, other): 定义乘法的行为:* __ truediv__(self, other): 定义真除法的行为:/ __ floordiv__(self, other): 定义整数除法的行为:// __ mod__(self, other): 定义取模算法的行为:% __ divmod__(self, other): 定义当被 divmod() 调用时的行为 __ pow__(self, other[, modulo]): 定义当被 power() 调用或 ** 运算时的行为 __ lshift__(self, other): 定义按位左移位的行为:<< __ rshift__(self, other): 定义按位右移位的行为:>> __ and__(self, other): 定义按位与操作的行为:& __ xor__(self, other): 定义按位异或操作的行为:^ __ or__(self, other): 定义按位或操作的行为:| 反运算 __ radd__(self, other): (与上方相同,当左操作数不支持相应的操作时被调用) __ rsub__(self, other) : (与上方相同,当左操作数不支持相应的操作时被调用) __ rmul__(self, other) : (与上方相同,当左操作数不支持相应的操作时被调用) __ rtruediv__(self, other): (与上方相同,当左操作数不支持相应的操作时被调用) __ rfloordiv__(self, other): (与上方相同,当左操作数不支加粗样式持相应的操作时被调用) __ rmod__(self, other): (与上方相同,当左操作数不支持相应的操作时被调用) __ rdivmod__(self, other): (与上方相同,当左操作数不支持相应的操作时被调用) __ rpow__(self, other): (与上方相同,当左操作数不支持相应的操作时被调用) __ rlshift__(self, other): (与上方相同,当左操作数不支持相应的操作时被调用) __ rrshift__(self, other): (与上方相同,当左操作数不支持相应的操作时被调用) __ rand__(self, other): (与上方相同,当左操作数不支持相应的操作时被调用) __ rxor__(self, other): (与上方相同,当左操作数不支持相应的操作时被调用) __ ror__(self, other): (与上方相同,当左操作数不支持相应的操作时被调用) 增量赋值运算 __ iadd__(self, other): 定义赋值加法的行为:+= __ isub__(self, other): 定义赋值减法的行为:-= __ imul__(self, other): 定义赋值乘法的行为:*= __ itruediv__(self, other): 定义赋值真除法的行为:/= __ ifloordiv__(self, other): 定义赋值整数除法的行为://= __ imod__(self, other): 定义赋值取模算法的行为:%= __ ipow__(self, other[, modulo]): 定义赋值幂运算的行为:**= __ ilshift__(self, other): 定义赋值按位左移位的行为:<<= __ irshift__(self, other): 定义赋值按位右移位的行为:>>= __ iand__(self, other): 定义赋值按位与操作的行为:&= __ ixor__(self, other): 定义赋值按位异或操作的行为:^= __ ior__(self, other): 定义赋值按位或操作的行为:|= 一元操作符 __ pos__(self): 定义正号的行为:+x __ neg__(self): 定义负号的行为:-x __ abs__(self): 定义当被 abs() 调用时的行为 __ invert__(self): 定义按位求反的行为:~x 类型转换 __ complex__(self): 定义当被 complex() 调用时的行为(需要返回恰当的值) __ int__(self): 定义当被 int() 调用时的行为(需要返回恰当的值) __ float__(self): 定义当被 float() 调用时的行为(需要返回恰当的值) __ round__(self[, n]): 定义当被 round() 调用时的行为(需要返回恰当的值) __ index__(self): 当对象是被应用在切片表达式中时,实现整形强制转换,若定义了一个可能在切片时用到的定制的数值型,应该定义 __ index__,若 __ index__ 被定义,则 __ int__ 也需要被定义,且返回相同的值 上下文管理(with 语句) __ enter__(self): 定义当使用 with 语句时的初始化行为,返回值被 with 语句的目标或者 as 后的名字绑定 __ exit__(self, exc_type, exc_value, traceback): 定义当一个代码块被执行或者终止后上下文管理器应该做什么,一般被用来处理异常,清除工作或者做一些代码块执行完毕之后的日常工作 容器类型 __ len__(self): 定义当被 len() 调用时的行为(返回容器中元素的个数) __ getitem__(self, key): 定义获取容器中指定元素的行为,相当于 self[key] __ setitem__(self, key, value): 定义设置容器中指定元素的行为,相当于 self[key] = value __ delitem__(self, key): 定义删除容器中指定元素的行为,相当于 del self[key] __ iter__(self): 定义当迭代容器中的元素的行为 __ reversed__(self): 定义当被 reversed() 调用时的行为 __ contains__(self, item): 定义当使用成员测试运算符(in 或 not in)时的行为
优秀博客:
https://www.cnblogs.com/zhouyixian/p/11129347.html
https://www.cnblogs.com/linhaifeng/articles/6204014.html
扩展:反射自省的内部原理
在学习了双下 __setattr__
与 __delattr__
以及 __getattribute__
后我们应该发现了。反射自省的常用4个方法其实都是内部调用的这三个双下方法实现的。
hasattr() ---> __getattribute__ 找到返回True,没找到返回False getattr() ---> __getattribute__ 找到返回属性或方法的内存地址,没找到抛出Attribute的异常(前提是类中不存在__getattr__方法) setattr() ---> __setattr__ delattr() ---> __delattr__