Python中__init__的通俗解释是什么? - 知乎

文章推薦指數: 80 %
投票人數:10人

__init__属于魔法函数的一种,让我们来看一下它的前世今生吧。

后面介绍了其他的魔法函数:__ str__()、__ new__()、__ unicode__()、__ call__()、__ ... Python编程机器学习计算机科学Python入门Python中__init__的通俗解释是什么?题主Python小白一枚,而且没有编程背景,正在自学机器学习。

目前努力尝试把各种机器学习算法用python来实现。

但是这两天一直被__init__的…显示全部​关注者1,201被浏览939,466关注问题​写回答​邀请回答​好问题97​3条评论​分享​71个回答默认排序初识CV​西安电子科技大学电子科学与技术硕士​关注892人赞同了该回答__init__属于魔法函数的一种,让我们来看一下它的前世今生吧。

后面介绍了其他的魔法函数:__str__()、__new__()、__unicode__()、__call__()、__len__()、__repr__()等等1.前言1.1什么是魔法函数?所谓魔法函数(MagicMethods),是Python的一种高级语法,允许你在类中自定义函数(函数名格式一般为__xx__),并绑定到类的特殊方法中。

比如在类A中自定义__str__()函数,则在调用str(A())时,会自动调用__str__()函数,并返回相应的结果。

在我们平时的使用中,可能经常使用__init__函数(构造函数)和__del__函数(析构函数),其实这也是魔法函数的一种。

Python中以双下划线(__xx__)开始和结束的函数(不可自己定义)为魔法函数。

调用类实例化的对象的方法时自动调用魔法函数。

在自己定义的类中,可以实现之前的内置函数。

1.2魔法函数有什么作用?魔法函数可以为你写的类增加一些额外功能,方便使用者理解。

举个简单的例子,我们定义一个“人”的类People,当中有属性姓名name、年龄age。

让你需要利用sorted函数对一个People的数组进行排序,排序规则是按照name和age同时排序,即name不同时比较name,相同时比较age。

由于People类本身不具有比较功能,所以需要自定义,你可以这么定义People类:classPeople(object): def__init__(self,name,age): self.name=name self.age=age return def__str__(self): returnself.name+":"+str(self.age) def__lt__(self,other): returnself.name('abc',){} __init__():<__main__.democlassobjectat0x0000025650facf28>abc __new__():('xyz',){} __init__():<__main__.democlassobjectat0x000002565ffc4cf8>xyz 02 12__new__()通常会返回该类的一个实例,但有时也可能会返回其他类的实例,如果发生了这种情况,则会跳过对__init__()方法的调用。

而在某些情况下(比如需要修改不可变类实例(Python的某些内置类型)的创建行为),利用这一点会事半功倍。

比如:classnonZero(int): def__new__(cls,value): returnsuper().__new__(cls,value)ifvalue!=0elseNone def__init__(self,skipped_value): #此例中会跳过此方法 print("__init__()") super().__init__() print(type(nonZero(-12))) print(type(nonZero(0)))输出结果:__init__() 那么,什么情况下使用__new__()呢?答案很简单,在__init__()不够用的时候。

例如,前面例子中对Python不可变的内置类型(如int、str、float等)进行了子类化,这是因为一旦创建了这样不可变的对象实例,就无法在__init__()方法中对其进行修改。

有些读者可能会认为,__new__()对执行重要的对象初始化很有用,如果用户忘记使用super(),可能会漏掉这一初始化。

虽然这听上去很合理,但有一个主要的缺点,即如果使用这样的方法,那么即便初始化过程已经是预期的行为,程序员明确跳过初始化步骤也会变得更加困难。

不仅如此,它还破坏了“__init__()中执行所有初始化工作”的潜规则。

注意,由于__new__()不限于返回同一个类的实例,所以很容易被滥用,不负责任地使用这种方法可能会对代码有害,所以要谨慎使用。

一般来说,对于特定问题,最好搜索其他可用的解决方案,最好不要影响对象的创建过程,使其违背程序员的预期。

比如说,前面提到的覆写不可变类型初始化的例子,完全可以用工厂方法(一种设计模式)来替代。

2.4__unicode__()__unicode__()方法是在一个对象上调用unicode()时被调用的。

因为Django的数据库后端会返回Unicode字符串给model属性,所以我们通常会给自己的model写一个__unicode__()方法。

如果定义了__unicode__()方法但是没有定义__str__()方法,Django会自动提供一个__str__()方法调用__unicode__()方法,然后把结果转换为UTF-8编码的字符串对象,所以在一般情况下,只定义__unicode__()方法,让Django来处理字符串对象的转换,看一个小栗子:classDemo(object): def__init__(self): self.a=1 def__unicode__(self): returnf"thevalueis{self.a}" print(unicode(Demo()))输出结果:thevalueis1在django中,虽然没有定义__str__,但是django会将__unicode__转为了str,当然你调用unicode更加是没有问题的。

2.5__call__()该方法的功能类似于在类中重载()运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。

classCLanguage: #定义__call__方法 def__call__(self,name,add): print("调用__call__()方法",name,add) clangs=CLanguage() clangs("C语言中文网","http://c.biancheng.net")程序执行结果为:调用__call__()方法C语言中文网http://c.biancheng.net可以看到,通过在CLanguage类中实现__call__()方法,使的clangs实例对象变为了可调用对象。

Python中,凡是可以将()直接应用到自身并执行,都称为可调用对象。

可调用对象包括自定义的函数、Python内置函数以及本节所讲的类实例对象。

对于可调用对象,实际上“名称()”可以理解为是“名称.__call__()”的简写。

仍以上面程序中定义的clangs实例对象为例,其最后一行代码还可以改写为如下形式:clangs.__call__("C语言中文网","http://c.biancheng.net")运行程序会发现,其运行结果和之前完全相同。

用__call__()弥补hasattr()函数的短板:hasattr()函数的用法,该函数的功能是查找类的实例对象中是否包含指定名称的属性或者方法,但该函数有一个缺陷,即它无法判断该指定的名称,到底是类属性还是类方法。

要解决这个问题,我们可以借助可调用对象的概念。

要知道,类实例对象包含的方法,其实也属于可调用对象,但类属性却不是。

举个例子:classCLanguage: def__init__(self): self.name="C语言中文网" self.add="http://c.biancheng.net" defsay(self): print("我正在学Python") clangs=CLanguage() ifhasattr(clangs,"name"): print(hasattr(clangs.name,"__call__")) print("**********") ifhasattr(clangs,"say"): print(hasattr(clangs.say,"__call__"))程序执行结果为:False ********** True可以看到,由于name是类属性,它没有以__call__为名的__call__()方法;而say是类方法,它是可调用对象,因此它有__call__()方法。

2.6__len__()在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法。

classStudents(): def__init__(self,*args): self.names=args def__len__(self): returnlen(self.names) ss=Students('Bob','Alice','Tim') print(len(ss))输出结果:32.7__repr__()函数str()用于将值转化为适于人阅读的形式,而repr()转化为供解释器读取的形式,某对象没有适于人阅读的解释形式的话,str()会返回与repr(),所以print展示的都是str的格式。

我们经常会直接输出类的实例化对象,例如:classCLanguage: pass clangs=CLanguage() print(clangs)程序运行结果为:<__main__.clanguageobjectat0x000001a7275221d0>通常情况下,直接输出某个实例化对象,本意往往是想了解该对象的基本信息,例如该对象有哪些属性,它们的值各是多少等等。

但默认情况下,我们得到的信息只会是“类名+objectat+内存地址”,对我们了解该实例化对象帮助不大。

那么,有没有可能自定义输出实例化对象时的信息呢?答案是肯定,通过重写类的__repr__()方法即可。

事实上,当我们输出某个实例化对象时,其调用的就是该对象的__repr__()方法,输出的是该方法的返回值。

以本节开头的程序为例,执行print(clangs)等同于执行print(clangs.__repr__()),程序的输出结果是一样的(输出的内存地址可能不同)。

和__init__(self)的性质一样,Python中的每个类都包含__repr__()方法,因为object类包含__reper__()方法,而Python中所有的类都直接或间接继承自object类。

默认情况下,__repr__()会返回和调用者有关的“类名+objectat+内存地址”信息。

当然,我们还可以通过在类中重写这个方法,从而实现当输出实例化对象时,输出我们想要的信息。

举个例子:classCLanguage: def__init__(self): self.name="C语言中文网" self.add="http://c.biancheng.net" def__repr__(self): return"CLanguage[name="+self.name+",add="+self.add+"]" clangs=CLanguage() print(clangs)程序运行结果为:CLanguage[name=C语言中文网,add=http://c.biancheng.net]由此可见,__repr__()方法是类的实例化对象用来做“自我介绍”的方法,默认情况下,它会返回当前对象的“类名+objectat+内存地址”,而如果对该方法进行重写,可以为其制作自定义的自我描述信息。

2.8__setattr__()在类中对属性进行赋值操作时,python会自动调用__setattr__()函数,来实现对属性的赋值。

但是重写__setattr__()函数时要注意防止无限递归的情况出现,一般解决办法有两种,一是用通过super()调用__setatrr__()函数,二是利用字典操作对相应键直接赋值。

简单的说,__setattr__()在属性赋值时被调用,并且将值存储到实例字典中,这个字典应该是self的__dict__属性。

即:在类实例的每个属性进行赋值时,都会首先调用__setattr__()方法,并在__setattr__()方法中将属性名和属性值添加到类实例的__dict__属性中。

实例属性管理__dict__:下面的测试代码中定义了三个实例属性,每个实例属性注册后都print()此时的__dict__,代码如下:classAnotherFun: def__init__(self): self.name="Liu" print(self.__dict__) self.age=12 print(self.__dict__) self.male=True print(self.__dict__) another_fun=AnotherFun()得到的结果显示出,每次实例属性赋值时,都会将属性名和对应值存储到__dict__字典中:{'name':'Liu'} {'name':'Liu','age':12} {'name':'Liu','age':12,'male':True}__setattr__()与__dict__:由于每次类实例进行属性赋值时都会调用__setattr__(),所以可以重载__setattr__()方法,来动态的观察每次实例属性赋值时__dict__()的变化。

下面的Fun类重载了__setattr__()方法,并且将实例的属性和属性值作为__dict__的键-值对:classFun: def__init__(self): self.name="Liu" self.age=12 self.male=True def__setattr__(self,key,value): print("*"*50) print("setting:{},with:{}".format(key[],value)) print("current__dict__:{}".format(self.__dict__)) #属性注册 self.__dict__[key]=value fun=Fun()通过在__setattr__()中将属性名作为key,并将属性值作为value,添加到了__dict__中,得到的结果如下:************************************************** setting:name,with:Liu current__dict__:{} ************************************************** setting:age,with:12 current__dict__:{'name':'Liu'} ************************************************** setting:male,with:True current__dict__:{'name':'Liu','age':12}可以看出,__init__()中三个属性赋值时,每次都会调用一次__setattr__()函数。

重载__setattr__()必须谨慎:由于__setattr__()负责在__dict__中对属性进行注册,所以自己在重载时必须进行属性注册过程,下面是__setattr__()不进行属性注册的例子:classNotFun: def__init__(self): self.name="Liu" self.age=12 self.male=True def__setattr__(self,key,value): pass not_fun=NotFun() print(not_fun.name)由于__setattr__中并没有将属性注册到__dict__中,所以not_fun对象并没有name属性,因此最后的print(not_fun.name)会报出属性不存在的错误:AttributeErrorTraceback(mostrecentcalllast) in() 8pass 9not_fun=NotFun() --->10print(not_fun.name) AttributeError:'NotFun'objecthasnoattribute'name'所以,重载__setattr__时必须要考虑是否在__dict__中进行属性注册。

总结:Python的实例属性的定义、获取和管理可以通过__setattr__()和__dict__配合进行,当然还有对应的__getattr__()方法,本文暂时不做分析。

__setattr__()方法在类的属性赋值时被调用,并通常需要把属性名和属性值存储到self的__dict__字典中。

2.9__getattr__()当我们访问一个不存在的属性的时候,会抛出异常,提示我们不存在这个属性。

而这个异常就是__getattr__方法抛出的,其原因在于他是访问一个不存在的属性的最后落脚点,作为异常抛出的地方提示出错再适合不过了。

看例子,我们找一个存在的属性和不存在的属性:classA(object): def__init__(self,value): self.value=value def__getattr__(self,item): print("into__getattr__") return"cannotfind" a=A(10) print(a.value) #10 print(a.name) #into__getattr__ #cannotfind输出结果:10 into__getattr__ cannotfind2.10__getattribute__()首先理解__getattribute__的用法,先看代码:classTree(object): def__init__(self,name): self.name=name self.cate="plant" def__getattribute__(self,obj): print("哈哈") returnobject.__getattribute__(self,obj) aa=Tree("大树") print(aa.name)执行结果是:哈哈 大树为什么会这个结果呢?__getattribute__是属性访问拦截器,就是当这个类的属性被访问时,会自动调用类的__getattribute__方法。

即在上面代码中,当我调用实例对象aa的name属性时,不会直接打印,而是把name的值作为实参传进__getattribute__方法中(参数obj是我随便定义的,可任意起名),经过一系列操作后,再把name的值返回。

Python中只要定义了继承object的类,就默认存在属性拦截器,只不过是拦截后没有进行任何操作,而是直接返回。

所以我们可以自己改写__getattribute__方法来实现相关功能,比如查看权限、打印log日志等。

如下代码,简单理解即可:classTree(object): def__init__(self,name): self.name=name self.cate="plant" def__getattribute__(self,*args,**kwargs): ifargs[0]=="大树" print("log大树") return"我爱大树" else: returnobject.__getattribute__(self,*args,**kwargs) aa=Tree("大树") print(aa.name) print(aa.cate)结果是:log大树 我爱大树 plant另外,注意注意:初学者用__getattribute__方法时,容易栽进这个坑,什么坑呢,直接看代码:classTree(object): def__init__(self,name): self.name=name self.cate="plant" def__getattribute__(self,obj): ifobj.endswith("e"): returnobject.__getattribute__(self,obj) else: returnself.call_wind() defcall_wind(self): return"树大招风" aa=Tree("大树") print(aa.name)#因为name是以e结尾,所以返回的还是name,所以打印出"大树" print(aa.wind)#这个代码中因为wind不是以e结尾,#所以返回self.call_wind的结果,打印的是"树大招风"上面的解释正确吗?先说结果,关于print(aa.name)的解释是正确的,但关于print(aa.wind)的解释不对,为什么呢?我们来分析一下,执行aa.wind时,先调用__getattribute__方法,经过判断后,它返回的是self.call_wind(),即self.call_wind的执行结果,但当去调用aa这个对象的call_wind属性时,前提是又要去调用__getattribute__方法,反反复复,没完没了,形成了递归调用且没有退出机制,最终程序就挂了!2.11__delattr__()本函数的作用是删除属性,实现了该函数的类可以用del命令来删除属性。

classMyClass: def__init__(self,work,score): self.work=work self.score=score def__delattr__(self,name): print("你正在删除一个属性") returnsuper().__delattr__(name) defmain(): test=MyClass(work="math",score=100) #删除work属性 deltest.work #work属性删除,score属性还在 print(test.score) try: print(test.work) exceptAttributeErrorasreason: print(reason) if__name__=='__main__': main()输出结果:你正在删除一个属性 100 'MyClass'objecthasnoattribute'work'2.12__setitem__()__setitem__(self,key,value):该方法应该按一定的方式存储和key相关的value。

在设置类实例属性时自动调用的。

#-*-coding:utf-8-*- classA: def__init__(self): self['B']='BB' self['D']='DD' def__setitem__(self,name,value): print"__setitem__:Set%sValue%s"%(name,value) if__name__=='__main__': X=A() 输出结果为:__setitem__:SetBValueBB __setitem__:SetDValueDD2.13__getitem__()Python的特殊方法__getitem_()主要作用是可以让对象实现迭代功能。

我们通过一个实例来说明。

定义一个Sentence类,通过索引提取单词。

importre RE_WORD=re.compile(r'\w+') classSentence: def__init__(self,text): self.text=text self.words=RE_WORD.findall(text)#re.findall函数返回一个字符串列表,里面的元素是正则表达式的全部非重叠匹配 def__getitem__(self,index): returnself.words[index]测试:>>>s=Sentence('Thetimehascome') >>>forwordins: print(word) The time has come >>>s[0] 'The' >>>s[1] 'time'通过测试发现,示例s可以正常迭代。

但是没有定义getitem()测试则会报错,TypeError:'***'objectisnotiterable。

序列可以迭代:我们都知道序列是可以迭代,下面具体说明原因。

解释器需要迭代对象x时,会自动调用iter(x)方法。

内置的iter(x)方法有以下作用:检查对象是否实现了__iter__方法,如果实现了就调用它(也就是我们偶尔用到的特殊方法重载),获取一个迭代器。

如果没有实现iter()方法,但是实现了__getitem__方法,Python会创建一个迭代器,尝试按顺序(从索引0开始,可以看到我们刚才是通过s[0]取值)获取元素。

如果尝试失败,Python抛出TypeError异常,通常会提示TypeError:'***'objectisnotiterable。

任何Python序列都可迭代的原因是,他们都实现了__getitem__方法。

其实,标准的序列也都实现了__iter__方法。

注意:从python3.4开始,检查对象x能否迭代,最准确的方法是:调用iter(x)方法,如果不可迭代,在处理TypeError异常。

这比使用isinstance(x,abc.Iterable)更准确,因为iter()方法会考虑到遗留的__getitem__()方法,而abc.Iterable类则不考虑。

2.14__delitem__()__delitem__(self,key):这个方法在对对象的组成部分使用__del__语句的时候被调用,应删除与key相关联的值。

同样,仅当对象可变的时候,才需要实现这个方法。

classTag: def__init__(self): self.change={'python':'Thisispython', 'php':'PHPisagoodlanguage'} def__getitem__(self,item): print('调用getitem') returnself.change[item] def__setitem__(self,key,value): print('调用setitem') self.change[key]=value def__delitem__(self,key): print('调用delitem') delself.change[key] a=Tag() print(a['php']) dela['php'] print(a.change)输出结果:调用getitem PHPisagoodlanguage 调用delitem {'python':'Thisispython'}2.15__iter__()迭代器就是重复地做一些事情,可以简单的理解为循环,在python中实现了__iter__方法的对象是可迭代的,实现了next()方法的对象是迭代器,这样说起来有点拗口,实际上要想让一个迭代器工作,至少要实现__iter__方法和next方法。

很多时候使用迭代器完成的工作使用列表也可以完成,但是如果有很多值列表就会占用太多的内存,而且使用迭代器也让我们的程序更加通用、优雅、pythonic。

如果一个类想被用于for...in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

容器(container):容器是用来储存元素的一种数据结构,容器将所有数据保存在内存中,Python中典型的容器有:list,set,dict,str等等。

classtest(): def__init__(self,data=1): self.data=data def__iter__(self): returnself def__next__(self): ifself.data>5: raiseStopIteration else: self.data+=1 returnself.data foritemintest(3): print(item)输出结果:4 5 6for…in…这个语句其实做了两件事。

第一件事是获得一个可迭代器,即调用了__iter__()函数。

第二件事是循环的过程,循环调用__next__()函数。

对于test这个类来说,它定义了__iter__和__next__函数,所以是一个可迭代的类,也可以说是一个可迭代的对象(Python中一切皆对象)。

迭代器:含有__next__()函数的对象都是一个迭代器,所以test也可以说是一个迭代器。

如果去掉__itet__()函数,test这个类也不会报错。

如下代码所示:classtest(): def__init__(self,data=1): self.data=data def__next__(self): ifself.data>5: raiseStopIteration else: self.data+=1 returnself.data t=test(3) foriinrange(3): print(t.__next__())输出结果:4 5 6生成器:生成器是一种特殊的迭代器。

当调用fib()函数时,生成器实例化并返回,这时并不会执行任何代码,生成器处于空闲状态,注意这里prev,curr=0,1并未执行。

然后这个生成器被包含在list()中,list会根据传进来的参数生成一个列表,所以它对fib()对象(一切皆对象,函数也是对象)调用__next()__方法。

deffib(end=1000): prev,curr=0,1 whilecurr,'__dict__':,'__weakref__':,'__doc__':None} {'name':'C语言中文网','add':'http://c.biancheng.net'}不仅如此,对于具有继承关系的父类和子类来说,父类有自己的__dict__,同样子类也有自己的__dict__,它不会包含父类的__dict__。

例如:classCLanguage: a=1 b=2 def__init__(self): self.name="C语言中文网" self.add="http://c.biancheng.net" classCL(CLanguage): c=1 d=2 def__init__(self): self.na="Python教程" self.ad="http://c.biancheng.net/python" #父类名调用__dict__ print(CLanguage.__dict__) #子类名调用__dict__ print(CL.__dict__) #父类实例对象调用__dict__ clangs=CLanguage() print(clangs.__dict__) #子类实例对象调用__dict__ cl=CL() print(cl.__dict__)运行结果为:{'__module__':'__main__','a':1,'b':2,'__init__':,'__dict__':,'__weakref__':,'__doc__':None} {'__module__':'__main__','c':1,'d':2,'__init__':,'__doc__':None} {'name':'C语言中文网','add':'http://c.biancheng.net'} {'na':'Python教程','ad':'http://c.biancheng.net/python'}显然,通过子类直接调用的__dict__中,并没有包含父类中的a和b类属性;同样,通过子类对象调用的__dict__,也没有包含父类对象拥有的name和add实例属性。

除此之外,借助由类实例对象调用__dict__属性获取的字典,可以使用字典的方式对其中实例属性的值进行修改,例如:classCLanguage: a="aaa" b=2 def__init__(self): self.name="C语言中文网" self.add="http://c.biancheng.net" #通过类实例对象调用__dict__ clangs=CLanguage() print(clangs.__dict__) clangs.__dict__['name']="Python教程" print(clangs.name)程序运行结果为:{'name':'C语言中文网','add':'http://c.biancheng.net'} Python教程注意,无法通过类似的方式修改类变量的值。

2.19__exit__,__enter____exit__和__enter__函数是与with语句的组合应用的,用于上下文管理。

1.__enter(self)__:负责返回一个值,该返回值将赋值给as子句后面的var_name,通常返回对象自己,即“self”。

函数优先于with后面的“代码块”(statements1,statements2,……)被执行。

2.__exit__(self,exc_type,exc_val,exc_tb):withxxxasvar_name: #代码块开始 statements1 statements2 …… #代码块结束 #代码快后面的语句 statementsaftercodeblock执行完with后面的代码块后自动调用该函数。

with语句后面的“代码块”中有异常(不包括因调用某函数,由被调用函数内部抛出的异常),会把异常类型,异常值,异常跟踪信息分别赋值给函数参数exc_type,exc_val,exc_tb,没有异常的情况下,exc_type,exc_val,exc_tb值都为None。

另外,如果该函数返回True、1类值的Boolean真值,那么将忽略“代码块”中的异常,停止执行“代码块”中剩余语句,但是会继续执行“代码块”后面的语句;如果函数返回类似0,False类的Boolean假值、或者没返回值,将抛出“代码块”中的异常,那么在没有捕获异常的情况下,中断“代码块”及“代码块”之后语句的执行。

代码:classUser(object): def__init__(self,username,password): self._username=username self._password=password @property defusername(self): returnself._username @username.setter defusername(self,username): self._username=username @property defpassword(self): returnself._password @password.setter defpassword(self,password): self._password=password def__enter__(self): print('before:autodosomethingbeforestatementsbodyofwithexecuted') returnself def__exit__(self,exc_type,exc_val,exc_tb): print('after:autodosomethingafterstatementsbodyofwithexecuted') if__name__=='__main__': boy=User('faker','faker2021') print(boy.password) print("上下文管理器with语句:") withUser('faker','faker2021')asuser: print(user.password) print('---------end-----------')输出结果:faker2021 上下文管理器with语句: before:autodosomethingbeforestatementsbodyofwithexecuted faker2021 after:autodosomethingafterstatementsbodyofwithexecuted ---------end-----------更改上述部分代码如下,继续运行:classUser(object): def__init__(self,username,password): self._username=username self._password=password @property defusername(self): returnself._username @username.setter defusername(self,username): self._username=username @property defpassword(self): returnself._password @password.setter defpassword(self,password): self._password=password def__enter__(self): print('before:autodosomethingbeforestatementsbodyofwithexecuted') returnself def__exit__(self,exc_type,exc_val,exc_tb): print('autodosomethingafterstatementsbodyofwithexecuted') print('exc_type:',exc_type) print('exc_val:',exc_val) print('exc_tb:',exc_tb) returnFalse if__name__=='__main__': boy=User('faker','faker2021') print(boy.password) print("上下文管理器with语句:") withUser('faker','faker2021')asuser: print(user.password) 12/0 print('afterexecption') print('---------end-----------')输出结果:Traceback(mostrecentcalllast): faker2021 File"/code/0.py",line42,in 上下文管理器with语句: 12/0 before:autodosomethingbeforestatementsbodyofwithexecuted ZeroDivisionError:divisionbyzero faker2021 autodosomethingafterstatementsbodyofwithexecuted exc_type: exc_val:divisionbyzero exc_tb:在上述的基础上继续修改代码,将__exit__的返回值设置为True:def__exit__(self,exc_type,exc_val,exc_tb): print('autodosomethingafterstatementsbodyofwithexecuted') print('exc_type:',exc_type) print('exc_val:',exc_val) print('exc_tb:',exc_tb) returnTrue输出结果:faker2021 上下文管理器with语句: before:autodosomethingbeforestatementsbodyofwithexecuted faker2021 autodosomethingafterstatementsbodyofwithexecuted exc_type: exc_val:divisionbyzero exc_tb: ---------end-----------注意:1、抛异常后,代码块中剩余的语句没有再继续运行2、如果在上述的基础上,把代码中的12/0剪切后放到password(self)中,抛出异常的异常信息也会传递给__exit__函数的@property defpassword(self): 12/0 returnself._password if__name__=='__main__': print("上下文管理器with语句:") withUser('faker','faker2021')asuser: print(user.password) print('---------end-----------')输出结果:上下文管理器with语句: before:autodosomethingbeforestatementsbodyofwithexecuted autodosomethingafterstatementsbodyofwithexecuted exc_type: exc_val:divisionbyzero exc_tb: ---------end-----------[1][2][3][4]参考^Python类特殊成员(属性和方法) http://c.biancheng.net/view/2367.html^Python中内建属性__getattribute__的用法总结 https://blog.csdn.net/yitiaodashu/article/details/78974596^Python3基础__delattr__在一个属性被删除时的行为 https://www.cnblogs.com/xingchuxin/p/10425683.html^Python__exit__,__enter__函数with语句的组合应用 http://blog.sina.com.cn/s/blog_13cc013b50102wvp1.html编辑于2021-01-2022:39​赞同892​​22条评论​分享​收藏​喜欢收起​刘志军​关注781人赞同了该回答这篇文章希望把__init_,__call__,__new__3个魔术方法一起讲明白,以及他们的正确使用方式和应用场景。

python中所有对象都有一个从创建,被使用,再到消亡的过程,不同的阶段由不同的方法负责执行。

定义一个类时,大家用得最多的就是__init__方法,而__new__和__call__使用得比较少本文代码都是基于Python3来讨论__init__方法__init__方法负责对象的初始化,系统执行该方法前,其实该对象已经存在了,要不然初始化什么东西呢?先看例子:#classA(object): classA: 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()输出__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) returnself输出:__new__ <__main__.aobjectat0x1007a95f8> __init__ <__main__.aobjectat0x1007a95f8>从输出结果来看,__new__方法的返回值就是类的实例对象,这个实例对象会传递给__init__方法中定义的self参数,以便实例对象可以被正确地初始化。

如果__new__方法不返回值(或者说返回None)那么__init__将不会得到调用,这个也说得通,因为实例对象都没创建出来,调用init也没什么意义,此外,Python还规定,__init__只能返回None值,否则报错,这个留给大家去试。

__init__方法可以用来做一些初始化工作,比如给实例对象的状态进行初始化:def__init__(self,a,b): self.a=a self.b=b super(A,self).__init__()另外,__init__方法中除了self之外定义的参数,都将与__new__方法中除cls参数之外的参数是必须保持一致或者等效。

classB: def__init__(self,*args,**kwargs): print("init",args,kwargs) def__new__(cls,*args,**kwargs): print("new",args,kwargs) returnsuper().__new__(cls) B(1,2,3) #输出 new(1,2,3){} init(1,2,3){}__new__方法一般我们不会去重写该方法,除非你确切知道怎么做,什么时候你会去关心它呢,它作为构造函数用于创建对象,是一个工厂函数,专用于生产实例对象。

著名的设计模式之一,单例模式,就可以通过此方法来实现。

在自己写框架级的代码时,可能你会用到它,我们也可以从开源代码中找到它的应用场景,例如微型Web框架Bootle就用到了。

classBaseController(object): _singleton=None def__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__方法是实现单例模式的的一种方式,如果实例对象存在了就直接返回该实例即可,如果还没有,那么就先创建一个实例,再返回。

当然,实现单例模式的方法不只一种,Python之禅有说:Thereshouldbeone--andpreferablyonlyone--obviouswaytodoit.用一种方法,最好是只有一种方法来做一件事__call__方法关于__call__方法,不得不先提到一个概念,就是可调用对象(callable),我们平时自定义的函数、内置函数和类都属于可调用对象,但凡是可以把一对括号()应用到某个对象身上都可称之为可调用对象,判断对象是否为可调用对象可以用函数callable如果在类中实现了__call__方法,那么实例对象也将成为一个可调用对象,我们回到最开始的那个例子:a=A() print(callable(a))#Truea是实例对象,同时还是可调用对象,那么我就可以像函数一样调用它。

试试:a()#__call__很神奇不是,实例对象也可以像函数一样作为可调用对象来用,那么,这个特点在什么场景用得上呢?这个要结合类的特性来说,类可以记录数据(属性),而函数不行(闭包某种意义上也可行),利用这种特性可以实现基于类的装饰器,在类里面记录状态,比如,下面这个例子用于记录函数被调用的次数:classCounter: def__init__(self,func): self.func=func self.count=0 def__call__(self,*args,**kwargs): self.count+=1 returnself.func(*args,**kwargs) @Counter deffoo(): pass foriinrange(10): foo() print(foo.count)#10在Bottle中也有call方法的使用案例,另外,stackoverflow也有一些关于call的实践例子,推荐看看,如果你的项目中,需要更加抽象化、框架代码,那么这些高级特性往往能发挥出它作用。

欢迎关注我,编程路上多个朋友@刘志军发布于2021-04-1907:41​赞同781​​25条评论​分享​收藏​喜欢收起​



請為這篇文章評分?