zoukankan      html  css  js  c++  java
  • 面向对象之魔术方法

    一、什么是魔术方法

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

  • 相关阅读:
    png-8 和 png-24的区别
    css控制标题长度超出部分显示省略号
    php正则表达式
    10分钟了解JSON Web令牌(JWT)
    Python制作微信小助手
    优质网站、赚钱、教程、分享、网赚
    从Github上将laravel项目拉到新开发环境
    gitlab添加公钥
    Git 配置用户名、密码
    虚拟机Oracle VM VirtualBox linux系统如何访问windows共享文件夹
  • 原文地址:https://www.cnblogs.com/lzh501/p/10884317.html
Copyright © 2011-2022 走看看