魔术方法
Python的魔术方法(也称为特殊方法)是对象的一些特殊方法,它们都是以双下划线开头并以双下划线结尾,例如__init__。
魔术方法所体现的设计思想是构成python风格的重要组成部分。
它们的目的是让对象能够响应内置的运算符或函数,比如len(),+运算符等。例如,当我们使用len(obj)函数调用对象obj的长度时,实际上会调用对象的__len__方法。
1、 __new__
魔术方法
触发时机:实例化类生成对象的时候触发(触发时机在__init__之前) 功能:控制对象的创建过程 参数:至少一个cls接受当前的类,其他根据情况决定。cls是系统自动传递的。类名字随意 返回值:通常返回对象 或 None
创建对象时可以选择给或者不给
(1) 基本使用
class MyClass2(): a = 100 obj2 = MyClass2() #print(obj2) 一般情况下,通过类创建对象,默认返回类对象。是借助其默认继承的父类object创建的。如果我们想自己控制对象的创建,要通过__new__
自己控制创建对象,使用__new__
魔术方法。但要写返回值,不然创建的对象为None
class MyClass1(): def __new__(cls): # print(cls) # 1.返回本类对象 """类.成员方法(类)""" # return object.__new__(cls) # 2.返回其他类的对象 # return obj2 # 3.不返回对象,None return None obj = MyClass1() #print(obj.a) print(obj) __new__传参,传类本身
#通过父类调用方法返回,这是默认情况下,借助父类创建的对象
返回其它类对象,可以直接调用其它类成员
#不返回对象,None
可以严格控制创建对象的过程
(2) __new__
触发时机要快于 __init__
__new__
创建对象
__init__
初始化对象
先创建,再初始化
class MyClass(): def __new__(cls): print(1) return object.__new__(cls) def __init__(self): print(2) obj = MyClass() #__new__触发时机快于__init__,如下,虽然__init__定义早于__new__。但创建对象的时候,执行顺序还是先执行__new__ 定义谁在前谁在后,没影响,关键在调用处,谁先谁后。系统默认调用的。由此可见__new__优先调用,触发时机快于__init__
(3) __new__
的参数要和__init__
参数一一对应。参数个数一致就行
class Boat(): def __new__(cls,name): return object.__new__(cls) def __init__(self,name): self.name = name obj = Boat("万里阳光号") print(obj.name)
参数个数不一致会报错
其实传给__new__
的参数也没用上,要是船的参数比较多,这样一一对应比较麻烦。我们可以使用收集参数进行改造
这样,不管初始化多少参数,__new__
都可以接收
#使用收集参数进行改造
class Boat(): # *args,**kwargs 可以收集多余的所有参数 def __new__(cls,*args,**kwargs): return object.__new__(cls) def __init__(self,name,type): self.name = name self.type = type obj = Boat("万里阳光号","破木头做的") print(obj.name , obj.type)
(4) __new__
和__init__
之间的注意点
如果__new__ 没有返回对象,或者返回的是其他类的对象,不会调用构造方法. 只有在返回自己本类对象的时候,才会调用构造方法. class Children(): def __new__(cls,*args,**kwargs): return obj2 # pass def __init__(self,name,skin): print("构造方法被触发 ... ") # self.name = name # self.skin = skin obj = Children("灭霸","紫薯") # print(obj.name) error # print(obj.skin) error 如果__new__什么也不返回,那返回的是None。此时该类的构造方法也不会被调用。构造不会被触发。对象都没被创建,无法初始化
如果__new__
方法返回其他对象,则构造函数不会被调用。其实此时对象找的是被返回对象类里面的该成员
如果被返回的对象,类里面有name成员,那么将被正常打印。虽然是通过此类创建的对象,但由于__new__返回的是其他类的对象,实际上创建的是其他类的对象
2、 单态模式 : 同一个类,无论实例化多少次,都有且只有一个对象
每创建一个对象,就会在内存中多占用一份空间
为了节省空间,提升执行效率,使用单态模式
场景:只是单纯调用类中的成员,而不会额外为当前对象添加成员;
一般__new__
方法大多数出现在单态模式
单态适用于对数据库操作,用一个对象解决不同的问题。节省空间
class Singleton(): __obj = None def __new__(cls): if cls.__obj is None: cls.__obj = object.__new__(cls) return cls.__obj
给类设置私有属性,公有属性都可以。但一般设置为私有,因为设置公有的话,可以通过类来获取到,但获取到的为None。所以设置为私有,只能通过实例化之后调用
通过__new__方法创建对象之前,先判断一下,如果对象存在就将原对象返回,不存在再创建
调用属性,要么通过 类.属性 要么通过 对象.属性 不能直接调属性,否则报错 如下 cls.obj是通过类调 cls是__new__方法传参的传的类
如下所示,使用单态模式,两次创建的对象是同一个内存地址。而正常的类创建两个对象,内存地址不同,开辟了两个内存空间
由此可见,单态模式节省内存空间
“”"
第一次,在实例化对象时触发__new__魔术方法
if cls.__obj is None 条件成立 cls.__obj = object.__new__(cls) 创建一个对象给私有成员属性__obj
return cls.__obj 用obj1接收到了对象
第二次,在实例化对象时触发__new__魔术方法 if cls.__obj is None不满足,因为已经在__obj属性中存放了一个对象
return cls.__obj
第三次,在实例化对象时触发__new__魔术方法 if cls.__obj is None不满足,因为已经在__obj属性中存放了一个对象
return cls.__obj
“”"
obj1 = Singleton() obj2 = Singleton() obj3 = Singleton() print(obj1,obj2,obj3)
这样无论实例化多少次对象,有且只有第一次创建的一个对象
class Singleton(): __obj = None def __new__(cls,*args,**kwargs): if cls.__obj is None: cls.__obj = object.__new__(cls) return cls.__obj def __init__(self,name): self.name = name obj1 = Singleton("康玉康") obj2 = Singleton("张保张") print(obj1,obj2) print(obj1.name) print(obj2.name)
“”"
康玉康 康玉康
康玉康 张保张
张保张 张保张
第一次实例化对象时,
触发__new__ if cls.__obj is None: 创建一个新的对象进行返回
然后触发__init__ self.name = 康玉康
第二次实例化对象时
触发__new__ if cls.__obj is None: 条件不满足,返回的是第一次实例化的对象,是同一个
然后触发__init__ self.name = 张保张
“”"
name = "康裕康" name = "张保障" print(name)
名义上创建不同对象,实际上返回的是第一个对象,但是每次创建时都可以调用构造方法,根据传参不同,得到不同的执行结果
但要是多次实例化对象后再打印,对象是第一次实例化的,虽然后面返回的是第一次实例化的对象,但构造函数每次都执行,再打印成员属性时,是最后一次执行构造函数的结果。
3、 __del__
魔术方法(析构方法)
触发时机:当对象被内存回收的时候自动触发[1.页面执行完毕回收所有变量 2.所有对象被del的时候] 功能:对象使用完毕后资源回收 参数:一个self接收对象 返回值:无
(1) 基本语法
class Lion(): def __init__(self,name): self.name = name def __del__(self): print("析构方法被触发 ... ") # 触发方式一: 页面执行完毕回收所有变量 obj1 = Lion("辛巴")
#触发方式二: 所有对象被del的时候 obj2 = obj1 obj3 = obj1 print(obj2 , obj1 ,obj3) print("<====start===>") del obj1 del obj2 del obj3 print("<====end===>")
所有对象被删除时触发,而不是页面走完才触发
如果不是所有对象被删除,则到页面执行结束时触发
本质上都是对象全部释放的时候触发
析构方法综合运用
(2) 模拟文件操作
import os class ReadFile(): # 根据文件是否存在,创建对象 def __new__(cls,filename): if os.path.exists(filename): return object.__new__(cls) else: print("抱歉,没有这个文件") # 打开文件 def __init__(self,filename): self.fp = open(filename,mode="r",encoding="utf-8") # 关闭文件 def __del__(self): self.fp.close() # 读取文件 def readcontent(self): return self.fp.read() obj = ReadFile("0.py") print(obj.readcontent())
4、 __str__
魔术方法
触发时机: 使用print(对象)或者str(对象)的时候触发 功能: 查看对象 参数: 一个self接受当前对象 返回值: 必须返回字符串类型
如果定义了__str__
方法,但没有返回字符串,或者无返回值,则会报错。必须返回字符串
class Cat(): gift = "抓老鼠" def __init__(self,name): self.name = name def cat_gift(self): return "小猫叫{},小猫会{}".format(self.name,self.gift) def __str__(self): return self.cat_gift() __repr__ = __str__ tom = Cat("汤姆") # 触发时机1 : print(对象) # print(tom) 定义了__str__方法。当打印对象时,会将__str__方法返回的内容打印出来
#触发时机2 : str(对象)
res = str(tom) print(res)
print("<==================>") res = repr(tom) print(res , type(res)) print("<==================>")
5、__repr__
魔术方法
触发时机: 使用repr(对象)的时候触发 功能: 查看对象,与魔术方法__str__相似 参数: 一个self接受当前对象 返回值: 必须返回字符串类型
class Mouse(): gift = "偷油吃" def __init__(self,name): self.name = name def mouse_gift(self): return "老鼠叫{},老鼠会{}".format(self.name,self.gift) def __repr__(self): return self.mouse_gift() # 系统底层默认把__repr__方法赋值给__str__方法,所以通过print或者str强转可以触发; # __str__ = __repr__ jerry = Mouse("杰瑞") # res = repr(jerry) # print(res) # 可以触发 # print(jerry) res = str(jerry) print(res)
如果写了__str__
。想实现__repr__
方法,必须将__str__
赋值给__repr__
。因为这个方向,系统默认没写
手动将__str__
赋值给__repr__
就可以了
Python常见的魔术方法和魔术属性(二):https://developer.aliyun.com/article/1495785