上一篇文章中,我们详细介绍了 Python 中的几个最常用的魔术方法。 python 魔术方法(一) 自定义容器类与类属性控制
但上一篇文章中没有介绍 Python 创建对象的两个魔术方法 — __new__ 与 __init__,而这两者的区别却常常困扰着很多 Python 开发人员。 本文我们就来详细讲解他们的区别和用法。
__init__ 方法作为类似于其他面向对象语言中构造函数的存在,是十分常用的,我们通常在 __init__ 方法中放入对象的初始化工作。
def __init__(self)
与 __init__ 方法不同,__new__ 方法必须具有一个返回值,返回所创建对象的实例。
def __new__(cls, *args, **kwargs)
class TechlogTest:
def __init__(self):
print('Techlog __init__')
def __new__(cls, *args, **kwargs):
print('Techlog __new__')
return object.__new__(cls, *args, **kwargs)
if __name__ == '__main__':
testobj = TechlogTest()
运行结果如下:
Techlog __new__ Techlog __init__
可以看到,__new__ 方法在 __init__ 方法前被调用,加上 __new__ 方法必须返回当前类的实例,我们就可以知道他们的区别了。 __new__ 方法担负了对象的创建工作,而 __init__ 方法则在对象完成创建后对该对象进行必要的初始化工作。 同时,__new__ 方法的首个参数是 cls,实际上他是一个属于类的静态方法,这也是我们能够通过 object.__new__ 的方法来调用他的原因。
因为 __new__ 方法担负了所有类对象的创建,因此我们可以通过实现 __new__ 方法就可以控制类对象的创建流程。 单例模式就是一个很好的例子。
我们曾经介绍过单例模式: 单例模式 — Singleton java 实现单例的各种方式
他保证了一个类仅有一个实例,并且提供访问这个实例的全局访问方式。 很多情况下,保证一个类同时最多只有一个实例是非常必要的,例如项目中的线程池组件,之所以使用线程池,往往是为了降低反复创建、销毁线程的开销,如果项目中维护多个线程池将是很令人头疼的一件事。 有时,一个类也并没有必要存在多个实例,例如对于线程安全的类来说,一个实例可以处理并发环境下的所有请求,如果为每一个请求单独创建一个类实例,那么会造成很大程度上的资源浪费。
class SingleTon:
_instance = {}
def __new__(cls, *args, **kwargs):
if cls not in cls._instance:
cls._instance[cls] = super(SingleTon, cls).__new__(cls, *args, **kwargs)
return cls._instance[cls]
上面这个类通过一个类成员 _instance 保存各个类型的单例实例,我们通过继承 SingleTon 类就可以实现单例模式了。 继承到子类中的 __new__ 方法确保了无论如何创建,都保证只获取到一个对象,而 _instance 作为一个 dict 让我们可以同时创建多个单例模式类型。
class SingleTon:
_instance = {}
def __new__(cls, *args, **kwargs):
if cls not in cls._instance:
cls._instance[cls] = super(SingleTon, cls).__new__(cls, *args, **kwargs)
return cls._instance[cls]
class TechTest(SingleTon):
testfield = 12
if __name__ == '__main__':
tech01 = TechTest()
tech02 = TechTest()
print(tech01)
print(tech02)
打印出了:
<__main__.TechTest object at 0x000001E40736A710> <__main__.TechTest object at 0x000001E40736A710>
可以看到,两个对象的地址是一模一样的,他们实际上是同一个对象。