python进阶之内置函数和语法糖触发魔法方法
2020-02-12

前言

前面已经总结了关键字、运算符与魔法方法的对应关系,下面总结python内置函数对应的魔法方法。

魔法方法

数学计算

abs(args):返回绝对值,调用__abs__;round(args):返回四舍五入的值,调用__round__;math.floor():向下取整,调用__floor__;math.ceil():向上取整,调用__ceil__;math.trunc():求一个值距离0最近的整数,调用__trunc__;divmod(a,b):返回商和余,调用__divmod__;pow(a,b):返回幂,调用__pow__;sum():返回和,调用__sum__;float():转换小数,调用__float__;int():转换整数,调用__int__;str():转换字符串,调用__str__;sys.getsizeof():对象占内存的大小,调用__sizeof__;bin(*args, **kwargs):调用参数的__bin__方法,返回整数的二进制表示形式,只支持一个参数,只支持int类型hash():调用__hash__方法,获取一个对象的散列值,相等的两个数哈希值相等,反过来不一定成立hex(*args, **kwargs):调用__hex__方法,求整数的十六进制表示形式,只支持Int类型oct(*args, **kwargs):调用__oct__方法,求整数的八进制表示形式,只支持Int类型

访问控制

__getattr__(self, name):getattr方法触发,仅对对象未定义的属性有效,即如果视图获取一个没有的属性时会调用该方法,前提是该对象未定义__getattribute__(self, name)方法;__getattribute__(self, name):getattr方法触发,如果对象定义了该方法,一定触发,__getattr__方法将不会被调用;它也可以被self.name语法糖触发;__setattr__(self, name, value):setattr方法触发,设置一个对象的属性;也可以被self.name = ""语法糖触发。__delattr__(self, name):delattr方法触发,删除一个对象的属性,或由del self.name 形式触发;

容器类型

在Python中实现自定义容器类型需要用到一些协议。不可变容器类型有如下协议:

    不可变容器,需要定义 _len_ 和 _getitem_ ;可变容器,需要定义 _len_ 、_getitem_、_setitem_、_delitem_ ;容器可迭代,需要定义 _iter_ ;迭代器,必须遵守迭代器协议,需要定义 _iter_ 和 _next_ 方法。
索引语法糖与魔法方法

__len__(self):返回容器的长度;__getitem__(self, key):使用self[key]形式语法糖获取元素会触发;__setitem__(self, key):使用self[key] = "xxx"形式复制会触发;__delitem__(self, key):使用del self[key]语法糖触发__reversed__(self):reversed(self)触发,反转容器;__missing__(self, key):字典结构使用self[key]形式获取元素,如果元素不存在触发;分片语法糖与魔法方法

切片在底层的原理,py2和py3有很大的不同,py2中使用_getslice_、_setslice_、__delslice__三个魔法方法控制,py3中将索引和切片统一由_getitem_、_setitem_、__delitem__控制。

# py2中ls = [1,2,3,4]print(ls[1:3]) # py2中该语法糖调用__getslice__方法,py3中废弃del ls[1:3] # py2中该语法糖调用__delslice__方法,py3中废弃ls[1:3] = [1,2,2] # py2中该语法糖调用__setslice__方法,py3中废弃

# py3中class Person(object): def __getitem__(self, item): print(item) return "getitem" def __setitem__(self, key, value): print(key, value) return "setitem" def __delitem__(self, key): print(key) return "delitem"if __name__ == "__main__": person = Person() print(person[0]) # person[0] ==> person.__getitem__(0) print(person[0:2]) # person[0:2] ==> person.__getitem__(slice(0,2,None)) person[0:2] = "test" # ==> person.__setitem__(slice(0,2,None), "test") del person[0:2] # ==> person.__delitem__(slice(0,2,None))# 结果0getitemslice(0, 2, None)getitemslice(0, 2, None) testslice(0, 2, None)

python在处理索引语法糖的时候,将索引当做参数传入相关getitem、setitem、delitem的魔法方法;在处理切片语法糖的时候先调用slice方法得到slice实例对象,将其作为参数调用相关的魔法方法。

拷贝

__copy__(self):如果对象定义了该方法,copy.copy()就会调用该方法返回拷贝对象;__deepcopy__(self, x):如果对象定义了该方法,copy.deepcopy()就会调用该方法返回拷贝对象;

序列化

序列化我们可以简单理解成对任何数据的一种描述方法,如果多种平台遵循了相同的序列化协议,数据之间的传递就会变得方便。python默认的序列化模块为pickle。

序列化的简单例子

class Person(object): def __init__(self): self.name = "cai"if __name__ == "__main__": import pickle person = Person() with open("./person.txt", "wb") as f: # 序列化后存储 pickle.dump(person,f) with open("./person.txt", "rb") as f: # 反序列化 per = pickle.load(f) print(per.name)# 我们可以把一个类保存起来,后续读取它直接使用。相关的魔法方法

__getinitargs__(self):该魔法方法在py3中似乎被废弃,原本的功能是在序列化时获取实例化参数,应该返回一个元组;__getnewargs__(self):对新式类,通过这个方法改变类在反pickle时传递给__new__ 的参数;应该返回一个参数元组。__getstate__(self):定义对象被序列化时的状态,而不使用对象的 __dict__ 属性,必须返回一个字典,他会去替代 __dict__ 属性,在序列化时被调用;__setstate__(self,state):当一个对象被反pickle时,如果定义了 __setstate__ ,对象的状态会传递给这个魔法方法,而不是直接应用到对象的 __dict__ 属性, state参数是序列化前的__dict__属性。

class Person(object): def __init__(self,name): print("init") self.name = name def __getinitargs__(self): print("initargs") return "zhao", def __getnewargs__(self): print("newargs") return "wang", def __getstate__(self): print("getstate") return {"name":"xiao"} def __setstate__(self, state): print("setstate") print(state) self.__dict__ = stateif __name__ == "__main__": import pickle person = Person("cai") with open("./person.txt", "wb") as f: # 序列化后存储 pickle.dump(person,f) with open("./person.txt", "rb") as f: # 反序列化 per = pickle.load(f) print(per.name)# 结果__new__initnewargsgetstate__new__setstate{"name": "xiao"}xiao

说明:

    pickle序列化对象之前,先执行__getnewargs__或__new__方法的参数;

    然后执行__getstate__方法,返回的值替代对象的__dict__属性值;

    反序列化时调用new方法,以getnewargs返回的值作为参数创建实例;

    最后调用__setstate__方法,将getstate方法的返回值作为state参数;

    所以由于反序列化时不会调用init方法初始化,getinitargs和getnewargs方法的作用都变得不大;

其他

__instancecheck__(self, instance):instance触发,判断对象的类型__subclasscheck__(self, subclass):issubclass触发,判断一个对象是另一个对象的子类;__call__:callable触发,判断一个对象是否可调用;__dir__(self):dir()触发,获取对象的所有属性、方法的名字组成的列表;__str__和__repr__

调用str触发_str_,调用repr()触发_repr_,但是print()也可以触发__str__和__repr__,如果对象定义了_str_,则print()一般触发_str_,否则触发_repr_;但列表以及字典等容器总是会使用_repr_ 方法.

__str__和__repr__的区别

    一般来说,_str_ 的返回结果在于强可读性,而 _repr_ 的返回结果在于准确性;

    默认情况下,在需要却找不到 __str__方法的时候,会自动调用 _repr_ 方法。

总结

熟悉了python语法糖、内置函数与魔法方法之间的关系后,显然对于如何写好一个优雅易用的类有很大的帮助。

参考

https://blog.csdn.net/u012332571/article/details/70141438

https://baijiahao.baidu.com/s?id=1596817611604972751&wfr=spider&for=pc

https://docs.python.org/3/