面向对象之魔术方法

一、什么是魔术方法

在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、

 

posted @ 2019-05-18 00:10  天涯——咫尺  阅读(183)  评论(0编辑  收藏  举报