一、什么是魔术方法
在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、
来源:https://www.cnblogs.com/lzh501/p/10884317.html