python编程魔术方法
一、介绍
首先,你一定用过魔术方法,也一定见过魔术方法。以下划线开头的方法,比如:__new____init____len____add__
这些被统称为魔术方法。
二、举例详解
给整数和字符串做加法:print(1 + 2) print("hello" + "world") # 输出:3 # 输出:helloworld
我们写个表示城市的类,它有两个属性:城市名和人口。
然后我们给两个城市做加法,发现不能相加:class City: def __init__(self, city_name, pop): self.city_name = city_name self.pop = pop city1 = City("深圳市", 2000) city2 = City("长沙市", 4000) city3 = city1 + city2 print(city3) # 输出:TypeError: unsupported operand type(s) for +: "City" and "City"
报错是说City不支持"+"号,如何让它支持"+"呢?需要给类加上魔术方法__add__就可以相加了。
我们给City添加一个__add__的方法,城市相加,人口相加,创建一个新的城市:class City: def __init__(self, city_name, pop): self.city_name = city_name self.pop = pop def __add__(self, city): new_city_name = self.city_name + city.city_name new_pop = self.pop + city.pop return City(new_city_name, new_pop) city1 = City("深圳市", 2000) city2 = City("长沙市", 4000) city3 = city1 + city2 print(city3.city_name) print(city3.pop) print(city3) # 输出:深圳市长沙市 # 输出: 6000 # 输出: <__main__.City object at 0x0000020937A5B7C8>
这说明__add__有一定的魔力,当我们用到加号"+"时,python就回去寻找这个方法,如果这个对象没有这个方法就会报错。
python中,所有的运算符都是通过魔术方法来实现的。
如果我们在City类有以下方法,就可以做加减乘除了:__add__(self, other) 定义加法的行为:+ __sub__(self, other) 定义减法的行为:-__mul__(self, other) 定义乘法的行为:*__truep__(self, other) 定义真除法的行为:/
我们再来打印int和str查看他们的方法,int有加减乘除,str只有__add__ __mul__,它只能做加法和乘法:print(dir(int)) # 输出:["__abs__", "__add__", "__and__", "__bool__", "__ceil__", "__class__", "__delattr__", "__dir__", "__pmod__", "__doc__", "__eq__", "__float__", "__floor__", "__floorp__", "__format__", "__ge__", "__getattribute__", "__getnewargs__", "__gt__", "__hash__", "__index__", "__init__", "__init_subclass__", "__int__", "__invert__", "__le__", "__lshift__", "__lt__", "__mod__", "__mul__", "__ne__", "__neg__", "__new__", "__or__", "__pos__", "__pow__", "__radd__", "__rand__", "__rpmod__", "__reduce__", "__reduce_ex__", "__repr__", "__rfloorp__", "__rlshift__", "__rmod__", "__rmul__", "__ror__", "__round__", "__rpow__", "__rrshift__", "__rshift__", "__rsub__", "__rtruep__", "__rxor__", "__setattr__", "__sizeof__", "__str__", "__sub__", "__subclasshook__", "__truep__", "__trunc__", "__xor__", "bit_length", "conjugate", "denominator", "from_bytes", "imag", "numerator", "real", "to_bytes"] print(dir(str)) # 输出:["__add__", "__class__", "__contains__", "__delattr__", "__dir__", "__doc__", "__eq__", "__format__", "__ge__", "__getattribute__", "__getitem__", "__getnewargs__", "__gt__", "__hash__", "__init__", "__init_subclass__", "__iter__", "__le__", "__len__", "__lt__", "__mod__", "__mul__", "__ne__", "__new__", "__reduce__", "__reduce_ex__", "__repr__", "__rmod__", "__rmul__", "__setattr__", "__sizeof__", "__str__", "__subclasshook__", "capitalize", "casefold", "center", "count", "encode", "endswith", "expandtabs", "find", "format", "format_map", "index", "isalnum", "isalpha", "isascii", "isdecimal", "isdigit", "isidentifier", "islower", "isnumeric", "isprintable", "isspace", "istitle", "isupper", "join", "ljust", "lower", "lstrip", "maketrans", "partition", "replace", "rfind", "rindex", "rjust", "rpartition", "rsplit", "rstrip", "split", "splitlines", "startswith", "strip", "swapcase", "title", "translate", "upper", "zfill"]
三、魔术方法的定义魔术方法对python是至关重要的,python是运行在魔术方法的轮子之上的,没有魔术方法就没有python。形如__xxx__的方法自己随意定义的__xxx__方法是没有用的python运算符都对应着一个魔术方法,所以魔术方法都是内定的,具有特殊的含义for循环,被循环的对象之所以被循环,是因为他们有__iter__和__next__len(),del都有对应的魔术方法列表获取元素也有对应的魔术方法
列表为什么能获取元素, __getitem__,可以再任何一个类里加上这个方法,然后也就可以用[]方括号来获取元素:cities = ["珠海市", "厦门市", "青岛市"] print(cities[0]) # 输出:珠海市 print(dir(list)) # 输出:["__add__", "__class__", "__contains__", "__delattr__", "__delitem__", "__dir__", "__doc__", "__eq__", "__format__", "__ge__", "__getattribute__", "__getitem__", "__gt__", "__hash__", "__iadd__", "__imul__", "__init__", "__init_subclass__", "__iter__", "__le__", "__len__", "__lt__", "__mul__", "__ne__", "__new__", "__reduce__", "__reduce_ex__", "__repr__", "__reversed__", "__rmul__", "__setattr__", "__setitem__", "__sizeof__", "__str__", "__subclasshook__", "append", "clear", "copy", "count", "extend", "index", "insert", "pop", "remove", "reverse", "sort"]
我们使用最多的方法一定是__new__和__init__, 在新建方法的时候,都会调用到这两个方法:city1 = City("深圳市", 2000) city2 = City("长沙市", 4000)
四、魔术方法的好处python的一个点,可以非常灵活地实现运算符通过实现魔术方法可以让自定义的类支持各种操作
五、魔术属性
不止有魔术方法,还有魔术属性,形如"__yyy__",通常是python自动设置的属性,我们可以使用这些属性,比如:print(__file__) # 打印出当前文件所在地目录和文件名 print(__name__) # 判断当前模块是被导入进来的,还是被直接调用的 # 输出:C:/Users//Desktop/magic_method.py # 输出:__main__ (或者输出文件名)
六、查看所有魔术方法和魔术属性import gc # gc管理内存的 print(" ".join(sorted({attrname for item in gc.get_objects() for attrname in dir(item) if attrname.startswith("__")}))) # 从内存中打印所有对象,并打印出每个对象中的魔术方法 # 输出: __about__ __abs__ __abstractmethods__ __add__ __aenter__ __aexit__ __aiter__ __all__ __and__ __anext__ __annotations__ __author__ __await__ __base__ __bases__ __basicsize__ __bool__ __breakpointhook__ __build_class__ __builtins__ __cached__ __call__ __callback__ __cause__ __class__ __closure__ __code__ __complex__ __concat__ __contains__ __context__ __copy__ __create_codec __debug__ __defaults__ __del__ __delattr__ __delete__ __delitem__ __dict__ __dictoffset__ __dir__ __displayhook__ __doc__ __enter__ __eq__ __excepthook__ __exit__ __file__ __flags__ __float__ __floorp__ __format__ __fspath__ __func__ __ge__ __get__ __getattr__ __getattribute__ __getitem__ __getnewargs__ __getstate__ __globals__ __gt__ __hash__ __iadd__ __iand__ __iconcat__ __ifloorp__ __ilshift__ __imatmul__ __imod__ __import__ __imul__ __index__ __init__ __init_subclass__ __instancecheck__ __int__ __interactivehook__ __inv__ __invert__ __ior__ __ipow__ __irshift__ __isabstractmethod__ __isub__ __itemsize__ __iter__ __itruep__ __ixor__ __kwdefaults__ __le__ __len__ __loader__ __lshift__ __lt__ __map_gb18030ext __map_gb2312 __map_gbcommon __map_gbkext __matmul__ __missing__ __mod__ __module__ __mro__ __mul__ __name__ __ne__ __neg__ __new__ __next__ __not__ __objclass__ __or__ __package__ __path__ __pos__ __pow__ __prepare__ __qualname__ __radd__ __rand__ __reduce__ __reduce_ex__ __repr__ __reversed__ __rmod__ __rmul__ __ror__ __rshift__ __rsub__ __rxor__ __self__ __set__ __setattr__ __setitem__ __setstate__ __sizeof__ __slots__ __spec__ __stderr__ __stdin__ __stdout__ __str__ __sub__ __subclasscheck__ __subclasses__ __subclasshook__ __suppress_context__ __text_signature__ __traceback__ __truep__ __weakref__ __weakrefoffset__ __wrapped__ __xor__
七、常见的魔术方法初始化和构造构造方法 __new__(cls,other) 对象实例化时自动触发(在__init__之前触发)初始化方法 __init__(self,other) 对象实例化之后立即触发析构方法 __del__(self) 当该类对象被销毁时
一元运算符__pos__(self) 定义正号的行为:+x __neg__(self) 定义负号的行为:-x __abs__(self) 定义当被 abs() 调用时的行为 __invert__(self) 定义按位求反的行为:~x __round__(self) 调用round()方法时被调用 __floor__(self) 调用math.floor()方法时被调用 __ceil__(self) 调用math.ceil()方法时被调用 __trunc__(self) 调用math.trunc()方法时被调用
增量赋值__iadd__(self, other) 定义赋值加法的行为:+= __isub__(self, other) 定义赋值减法的行为:-= __imul__(self, other) 定义赋值乘法的行为:*= __itruep__(self, other) 定义赋值真除法的行为:/= __ifloorp__(self, other) 定义赋值整数除法的行为://= __imod__(self, other) 定义赋值取模算法的行为:%= __ipow__(self, other[, modulo]) 定义赋值幂运算的行为:**= __ilshift__(self, other) 定义赋值按位左移位的行为:<<= __irshift__(self, other) 定义赋值按位右移位的行为:>>= __iand__(self, other) 定义赋值按位与操作的行为:&= __ixor__(self, other) 定义赋值按位异或操作的行为:^= __ior__(self, other) 定义赋值按位或操作的行为:|=
类型转换__int__(self) 定义当被 int() 调用时的行为(需要返回恰当的值) __float__(self) 定义当被 float() 调用时的行为(需要返回恰当的值) __complex__(self) 定义当被 complex() 调用时的行为(需要返回恰当的值) __oct__(self) 定义当被 oct() 调用时的行为(需要返回恰当的值) __hex__(self) 定义当被 hex() 调用时的行为(需要返回恰当的值) __trunc__(self) 定义当被 trunc() 调用时的行为(需要返回恰当的值) __index(self)__ 当对象是被应用在切片表达式中时,实现整形强制转换
字符串str(self) 调用str()方法时被调用 repr 调用repr()方法时被调用 unicode 调用unicode()方法时被调用 format 调用string.format()方法时被调用 hash 调用hash()方法时被调用 nonzero 调用nonzero ()方法时被调用 dir 调用 dir()方法时被调用 sizeof 调用sys.get sizeof()方法时被调用
属性getattr(self,name) 定义当用户试图获取一个不存在的属性时的行为setattr(self,name,value) 定义对属性的赋值行为,无论属性是否存在delattr(self,name) 定义删除属性的行为
操作符__add__(self, other) 定义加法的行为:+ __sub__(self, other) 定义减法的行为:- __mul__(self, other) 定义乘法的行为:* __truep__(self, other) 定义真除法的行为:/ __floorp__(self, other) 定义整数除法的行为:// __mod__(self, other) 定义取模算法的行为:% __pow__(self, other[, modulo]) 定义当被 power() 调用或 ** 运算时的行为 __ 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) __lshift__(self, other) 定义按位左移位的行为:<< __rshift__(self, other) 定义按位右移位的行为:>> __and__(self, other) 定义按位与操作的行为:& __xor__(self, other) 定义按位异或操作的行为:^ __or__(self, other) 定义按位或操作的行为:|
八、魔术方法的操作实例__new__常用操作# 2 new调用其他类 class OtherClass1: print("其他类") a = "其他类的值" other_obj = OtherClass1() # 3 new 实现单例模式 class OtherClass2: def __init__(self): print(" 触发了OtherClass2的初始化 ") other_obj1 = OtherClass2() other_obj2 = OtherClass2() print("other_obj1的id :", id(other_obj1)) print("other_obj2的id :", id(other_obj2)) # 装饰器单例模式 def class_one_case(cls): # 空字典储存 类 和 类实例(key:value) instace = {} def inner(*args, **kwargs): # 如果类不在字典中实例化对象储存,否者用字典中的对象 if cls not in instace: instace[cls] = cls(*args, **kwargs) return instace[cls] else: return instace[cls] return inner @class_one_case class City(object): instance = None def __init__(self, city_name): print("触发了初始化方法") self.city_name = city_name # def __new__(cls, *args, **kwargs): # print("触发了构造方法") # return super().__new__(cls) # 1 重写__new__方法,调用父类的__new__方法创建对象,并进行返回 # return object.__new__(cls) # return other_obj # 2 返回其他类的对象 # 3 如果 instance 为None 实例化对象,否则用第一次实例的对象 # if not cls.instance: # print("instance为: ", cls.instance) # cls.instance = object.__new__(cls) # return cls.instance # else: # print("instance为: ", cls.instance) # return cls.instance def run_one_case(self): print("单例装饰器 run_one_case : ", self.city_name) # city1 = City("深圳市") # print(city1.city_name) # city2 = City() # print(city2.a) city3 = City("深圳市") city4 = City("深圳市") print("city3的id :", id(city3)) print("city3的id :", id(city4)) # 所以city3创建的属性,city4一样有 city3.pop = 1234 print("city4的pop为:", city4.pop) # 单例装饰器 city4.run_one_case()
__str__和__repr__常用操作使用 format函数、str函数、print打印对象时会优先触发str方法 没定义str方法的情况下,会再去找repr方法,如果都没有,那么就会去找父类的str方法。如果父类没有str方法,会找父类的repr方法。 如果继承object的话,object里面肯定有str方法的。除非是继承一个自己定义的类。
repr在交互环境下和内置函数处理对象时,它只会触发repr方法,如果repr方法不存在,会找父类的repr方法。 repr方法是str方法的备胎。但是repr方法不能拿str方法做备胎。父类是第二轮备胎。 print打印是由str方法决定的,交互环境下是由repr方法决定的。
什么使用str方法,什么时候用repr方法?str和repr方法都是返回字符串。在str方法里面返回的是字符串,直观的描述信息。str方法返回内容是给用户看的,返回是直接的描述信息。repr返回的东西是给程序员看的。程序员能看到是哪个类出来的对象,知道最开始的样子。
如果我们想让print打印出来好看,可以定义__str__的方法:class City: def __init__(self, city_name, pop): self.city_name = city_name self.pop = pop def __add__(self, city): new_city_name = self.city_name + city.city_name new_pop = self.pop + city.pop return City(new_city_name, new_pop) # 打印就会调用这个方法,如果str方法不存在,会去触发repr方法,打印出repr方法的内容: def __str__(self): print("触发了 __str__ 方法") return f"{self.city_name} : {self.pop}" def __repr__(self): print("__repr__被触发") # 报错:TypeError: __repr__ returned non-string (type NoneType) # repr返回的不是个字符串,是个None。必须返回字符串。 return "