面向对象之魔术方法
一、什么是魔术方法
在Python中,像__init__这类双下划线开头,双下划线结尾的方法,统称为魔术方法,魔术方法会在特定的时候自动调用;
注意:
魔术方法都是Python内部定义的,我们创建类的时候,不要定义双下划线开头和双下划线结尾这样的方法;
二、魔术方法
1、__new__方法
class MyClass(object): def __init__(self,name): self.name = name def __new__(cls, *args, **kwargs): print("这是自定义的new方法") # return super().__new__(cls) return object.__new__(cls) mc = MyClass("YEWEIYIN") 执行结果为: 这是自定义的new方法
__new__方法是创建对象时调用的第一个魔术方法,__new__方法在类的父类object类里面,它被@staticmethod装饰器装饰成了静态方法,
如果自己定义的类里面要重写__new__方法,则新定义的__new__方法一定要有retrun返回值,不然不能创建实例对象;
为了创建的实例对象能够调用类里面的方法,__new__方法返回的值要为类对象创建的实例对象,如果返回的是其他对象,则不能调用__init__方法初始化,
__new__方法返回实例对象有两种写入方法:
return object.__new__(cls);
return super().__new__(cls);
__new__方法的应用场景:设计单例模式
****单例模式:
类第一次实例化之后创建了一个新的实例对象,往后类每次实例化创建的对象都是第一次创建的实例对象,也就是说,
类不管实例化多少次,它创建的实例对象都只有一个,而且是第一次创建的那一个;
简单的单例模式:
class MyClass(object): __instance = None # __开头表示是私有属性,定义之后不可进行更改 def __new__(cls, *args, **kwargs): print("自定义的new方法实现单例模式") if cls.__instance is None: cls.__instance = object.__new__(cls) return cls.__instance mc = MyClass() mc.name = "yeweiyin" print(id(mc)) print(mc.name) mc2 = MyClass() print(mc2.name) print(id(mc2)) 执行结果为: 自定义的new方法实现单例模式 20514352 yeweiyin 自定义的new方法实现单例模式 yeweiyin 20514352
通过执行结果,可以发现,我们创建了mc和mc2两个实例对象,通过mc实例对象又创建了一个name属性,在执行结果中,
mc和mc2两个实例对象的id是同一个,mc2实例对象能够调用mc实例创建的name属性,并得到相同的name属性值;
通过装饰器实现单例模式:
def single(cls): instance = {} # 字典的键为类,值为类的实例对象 def func(*args,**kwargs): if cls in instance: # 如果类被创建过实例对象,那么忽略后面创建实例需求,直接返回已创建的实例对象 return instance[cls] else: instance[cls] = cls(*args,**kwargs) # 如果类没有被创建过实例对象,那么创建类的实例对象,并保存在字典中,然后返回实例对象 return instance[cls] return func @single class MySingle(object): def myname(self): print("yeweiyin") @single class MySingleT(object): def __init__(self,name): self.name = name def updatename(self): print(self.name) ms3 = MySingle() ms3.age = 19 ms4 = MySingle() print(ms4.age)
如上:只要类被Sigle这个装饰器装饰了,都是一个单例模式的类;
2、__str__方法和__repr__方法
>>> a = "abc" >>> print(a) abc >>> a 'abc' >>>
在交互环境下测试,如上:定义一个变量a = “abc”,通过print打印a的值,和直接输入a给出的值不一样,这是为什么呢?
原来,在使用print打印时,调用了__str__这个魔术方法,而直接输入变量a,调用的则是__repr__这个魔术方法;
触发__str__方法有三种情况:
通过print打印时;
调用内置函数str()方法时;
调用format()方法时;
触发__repr__方法有两种情况:
在交互环境下时;
调用repr()方法时;
具体调用如下:
class MyMethod(object): def __init__(self,name): self.name = name def __str__(self): print("触发str方法") return ("str方法返回的值:" + self.name) def __repr__(self): print("触发repr方法") return ("repr方法返回的值:" + self.name) mm = MyMethod("yeweiyin") print(mm) str(mm) format(mm) repr(mm) print(MyMethod(repr(MyMethod("MM")))) 执行结果: 触发str方法 str方法返回的值:yeweiyin 触发str方法 触发str方法 触发repr方法 触发repr方法 触发str方法 str方法返回的值:repr方法返回的值:MM
****注意:
****重写__str__方法和__repr__方法时,必须要加return,而且return返回的必须为字符串对象
在触发__str__方法的那三种情况下,会优先触发__str__方法,如果没有定义__str__方法,会去找__repr__方法触发,如果这两个方法都没有定义,
那么就会去找父类中的__str__反方法触发;
在触发__repr__方法的那两种情况下,会先找自身的__repr__方法去触发,如果自身没有定义__repr__方法,那么就会去找父类中的__repr__方法去触发;
深入解析__str__和__repr__的返回值:
__str__方法的返回值通俗点的意思是给用户看的,很直观的看到数据值,如上所示的,在交互环境下通过print方法触发了__str__方法,
返回a的值为:abc,我们就只看到a的值是abc,而不知道abc是什么类型,代表什么;
__repr__方法的返回值是给程序员看的,追踪到数据的根源(如:那个类创建出来的),如上所示的,在交互环境下直接输入a,
触发了__repr__方法,返回a的值为:'abc',这样我们就知道了a的值是一个值为'abc'的字符串;
3、__call__方法
我们知道函数可以:函数名(),这样调用,而类或者其他对象不能通过:对象名(),这样调用呢,其实是__call__方法在起作用;
如果我们想要类创建的对象可以像函数一样调用,应该怎么做呢?
我们可以在类里面定义一个__call__方法即可,如:
class MyObject(object): def __init__(self,name): self.name = name def __call__(self, *args, **kwargs): print("call方法被调用") mo = MyObject("yeweiyin") mo() 执行结果: call方法被调用
**对象在像函数一样被调用时触发__call__方法
由于__call__方法的特性,我们可以通过在类里面定义__call__方法来实现类作为装饰器
class MyCall(object): def __init__(self,func): self.func = func def __call__(self, *args, **kwargs): print("这是类装饰器的功能") self.func() print("调用原功能函数之后的装饰器功能") @MyCall def muen(): print("实现新的功能") muen() 执行结果: 这是类装饰器的功能 实现新的功能 调用原功能函数之后的装饰器功能
4、上下文管理器的__enter__和__exit__的魔术方法
上下文管理器最常用的场景是操作文件,一般用with as 语法连用;如:with open(filename,"r",encoding="utf-8") as f:
通常我们使用open打开文件读取或写入操作后要close关闭文件,但是使用with as 的上下文管理器之后,就不要需要再关闭文件了,
其实际是with关键字触发了__enter__方法和__exit__方法;
自定义一个上下文管理器:
class MyOpen(object): def __init__(self,filename,method,encoding="utf8"): self.filename = filename self.method = method self.encoding = encoding def __enter__(self): self.f = open(self.filename,self.method,encoding=self.encoding) return self.f def __exit__(self, exc_type, exc_val, exc_tb): self.f.close()
再简单一点:
class MyOpen(object):
def __init__(self,filename,method,encoding="utf8"):
self.f = open(filename,method,encoding=encoding)
def __enter__(self):
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
self.f.close()
with MyOpen("test.txt","r") as f:
# print(f.write("鹅鹅鹅,曲项向天歌"))
print(f.read())
通过上面自定义的上下文管理器,我们可以知道,首先MyOpen这个类创建了一个实例对象,在引用with这个关键字方法时,触发了其内部的__enter__()魔术方法,打开文件,并将文件的句柄返回出来,再由as关键字传给f,完成读取文件内容,或者往文件内写入内容后,再触发__exit__()这个魔术方法,将文件关闭;
其中__exit__()方法的参数exc_type表示异常类型,exc_val表示异常值,exc_tb表示异常回溯追踪
自定义一个读取数据库数据的上下文管理器:
import mysql.connector class DB: def __init__(self,config): self.cnn = mysql.connector.connect(**config) self.cursor = self.cnn.cursor() def __enter__(self): return self.cursor def __exit__(self, exc_type, exc_val, exc_tb): self.cursor.close() self.cnn.close() config = dict( host = "localhost", user = "root", password = "mysql", database = "test", port = 3306, charset = "utf-8") with DB(config) as f: f.execute("select * from username;") print(f.fetchone())
5、算术运算的魔术方法__add__,__sub__等
以__add__方法为例,其他方法类似:
class MyAdd(object): def __init__(self,data): self.data = data
def __add__(self, other): print(self.data) print(other.data) # print(self+other) 切记不能这样写,这样写会变成一个死循环,因为self和other都是实例对象,如果外部再写一个加法运算:d+f,那么会无限执行上面打印的那两行代码 return self.data+other.data d = MyAdd("D") f = MyAdd("F") print(d + f) 执行结果为: D F DF
重写__add__魔术方法要注意:
self和other这两个参数都为实例对象,不能在__add__方法内部写self+other;
如上面的例子:d+f,是d触发的__add__这个方法,相当于d.__add__(f)
6、