当前位置: 首页 > 科技观察

__init__,__new__,__call__方法简析

时间:2023-03-19 17:26:15 科技观察

题目:摄影:JeremyPerkinsonUnsplash万物都有一个从创造、使用到消亡的过程。在编程语言的面向对象编程模型中,对象是相似的命运:创建、初始化、使用、垃圾回收,不同的阶段由不同的方法(角色)执行。定义类时,__init__方法用得最多,__new__和__call__用得少。本文试图帮助您解释这三种方法的正确用法和应用场景。Python的新式类和旧式类在本文中不会过多讨论,因为旧式类是Python2中的概念,现在几乎没有人会再使用旧式类了。New-style类必须显式继承object,而在Python3中,只有new-style类默认继承object,不需要显式指定。本文的代码都是基于Python3来讨论的。__init__方法#classA(object):python2必须显式继承objectclassA:def__init__(self):print("__init__")super(A,self).__init__()def__new__(cls):print("__new__")returnsuper(A,cls).__new__(cls)def__call__(self):#可以定义任意参数print('__call__')A()outputs__new____init__从输出结果看,__new__方法先被调用,返回一个实例对象,然后__init__被调用。尚未调用__call__方法。这个在***里说吧。先说前两个方法,稍微改写为:def__init__(self):print("__init__")print(self)super(A,self).__init__()def__new__(cls):print("__new__")self=super(A,cls).__new__(cls)print(self)returnselfoutput:__new__<__main__.Aobjectat0x1007a95f8>__init__<__main__.Aobjectat0x1007a95f8>outputfrom因此,__new__方法的返回值是类的实例对象,并将这个实例对象传递给定义在__init__方法中的self参数,这样就可以正确初始化实例对象。如果__new__方法没有返回值(或返回None),那么__init__将不会被调用。这是有道理的,因为没有创建实例对象,调用init是没有意义的。另外,Python还规定__init__只能返回None值,否则会报错,这个留给大家试试。__init__方法可以用来做一些初始化工作,比如初始化实例对象的状态:def__init__(self,a,b):self.a=aself.b=bsuper(A,self).__init__()__new__方法是通用的我们不会覆盖这个方法,除非你确切地知道如何去做,当你会关心它时,它被用作创建对象的构造函数,它是一个专门用于生产实例对象的工厂函数。著名的设计模式之一,单例模式,就可以通过这种方式来实现。当你自己写框架级别的代码时,你可能会用到它,我们也可以从开源代码中找到它的应用场景,比如微网框架Bootle。classBaseController(object):_singleton=Nonedef__new__(cls,*a,**k):ifnotcls._singleton:cls._singleton=object.__new__(cls,*a,**k)returncls._singleton这段代码来自https://github.com/bottlepy/bottle/blob/release-0.6/bottle.py这是一种通过__new__方法实现单例模式的方法。如果实例对象存在,直接返回实例即可。如果还是Ifnot,那就先创建实例再返回。当然,单例模式的实现方式不止一种。Python之禅说:应该有一种——最好只有一种——显而易见的方法来做到这一点。__call__方法关于__call__方法,不得不先提一个概念,那就是可调用对象(callable)。我们的自定义函数、内置函数和类都是可调用对象,但是任何一对括号()应用于对象都可以称为可调用对象,通过函数callable来判断对象是否是一个可调用对象。如果类中实现了__call__方法,则实例对象也将成为可调用对象。我们回到原来的例子:a=A()print(callable(a))#True同时是实例对象和可调用对象,所以我可以像函数一样调用它。尝试:a()#__call__太棒了。不,实例对象也可以像函数一样用作可调用对象。那么这个特性在什么场景下有用呢?这个要结合类的特点。类可以记录数据(属性),但是函数不可以(闭包某种意义上也是可行的)。使用此功能,您可以实现基于类的装饰器并在类中记录状态。例如下面的例子用来记录函数被调用的次数:classCounter:def__init__(self,func):self.func=funcself.count=0def__call__(self,*args,**kwargs):self.count+=1returnsself.func(*args,**kwargs)@Counterdeffoo():passforiinrange(10):foo()print(foo.count)#10Bottle中也有call方法的用例。另外,stackoverflow也有一些关于call的实用例子。建议看一看。如果你的项目需要更多的抽象和框架代码,那么这些高级特性往往可以发挥它们的作用。https://stackoverflow.com/questions/5824881/python-call-special-method-practical-example【本文为专栏作家“刘志军”原创文章,作者微信♂:Python之禅(VTtalk)]点这里,阅读该作者更多好文