📄 node11.html
字号:
</dl> <p> 其中,基础类别的名字 <tt class="class">BaseClassName</tt> 这个字必须是在子类别所处的scope里面有定义的。除了直接使用基础类别的名字之外,你也可以使用一个expression。这在当你的基础类别是定义在别的module里的时候特别有用: </p> <p> </p> <dl> <dd><pre class="verbatim">class DerivedClassName(modname.BaseClassName):<br></pre> </dd> </dl> <p> 子类别定义的执行过程与基础类别定义的执行过程是一样的。当一个类别物件被创造出来时,基础类别也同样会存在记忆体中。这是为了要确保能够找到正确的attribute的所在,如果你的子类别没有定义某个attribute的话,就会自动去找基础类别的定义。如果这个基础类别也是某个类别的子类别的话,这个法则是一直延伸上去的。 </p> <p> 子类别的特例化(instantiation)也没有什么特别之处,使用 <code>DerivedClassName()</code> 就会创造出子类别的一个新的instance。子类别的method 则是由以下的过程来寻找:会先找该类别的attribute,然后如果需要的话会沿着继承的路线去找基础类别,如果找到任何的函式物件的话,这个method的参考(reference)就是有效的。 </p> <p> 子类别可以override基础类别里的method。因为method在呼叫自己物件的其他method的时候没有特别的权限,当一个基础类别的method呼叫原属于该基础类别的method的时候,有可能真正呼叫到的是一个在子类别里面定义的override的method。(给C++的程式设计师们:所有在Python里面的method都是 <tt class="keyword">virtual</tt> 的。) </p> <p> 一个在子类别里面override的method也许会需要延伸而非取代基础类别里面同名的method,这时候你就需要呼叫在基础类别里面的method:你只需要呼叫"<tt class="samp">BaseClassName.methodname(self, arguments)</tt>" 就可以了。这对于类别的使用者来说,有时候也是有用的。(注意的是,如果你要这样做,你需要将基础类别定义在globalscope或是import进来global scope 里面。) </p> <p> </p> <h2> <br> 9.5.1 多重继承 </h2> <p> Python也支援部分的多重继承形式。一个类别如果要继承多个基础类别的话,其形式如下: </p> <p> </p> <dl> <dd><pre class="verbatim">class DerivedClassName(Base1, Base2, Base3):<br> <statement-1><br> .<br> .<br> .<br> <statement-N><br></pre> </dd> </dl> <p> 唯一需要解释的规则是,当你寻找一个attribute的定义时你要如何寻找。其规则是先深,而后由左至右(depth-first,left-to-right)。所以当你要找一个在子类别 <tt class="class">DerivedClassName</tt> 里面的attribute却找不到时,会先找 <tt class="class">Base1</tt> ,然后沿着 <tt class="class">Base1</tt> 的所有基础类别寻找,如果找完还没有找到的话再找 <tt class="class">Base2</tt> 及其基础类别,依此类推。 </p> <p> (也许有些人认为先左至右然后在深才对,应该是先找 <tt class="class">Base2</tt> 及 <tt class="class">Base3</tt> ,然后才找 <tt class="class">Base1</tt> 的基础类别。如果你这样想的话,你可以再想一想,当你找 <tt class="class">Base1</tt> 的时候,你需要先知道这个attribute到底是定义在 <tt class="class">Base1</tt> 本身或是其基础类别里面,如此才不会与 <tt class="class">Base2</tt> 里面的attribute有同名的困扰。如果你使用先深,而后由左至右的规则的话,就不会有这个困扰。) </p> <p> 大家都知道如果不小心使用的话,多重继承可能变成在维护程式时的一个恶梦。Python仰赖程式设计师们的约定俗成的习惯来避免可能的名称冲突。例如一个众所周知多重继承的问题,如果一个类别继承了两个基础类别,这两个基础类别又分别继承了同一个基础类别。也许你很容易就了解在这样的情况下到底会是什么状况,(这个instance将会只有一个单一共用基础类别的``instancevariables''或是data attributes),但是很难了解这到底有什么用处。 </p> <p> </p> <h1> <br> 9.6 Private变数 </h1> <p> 在Python里面只有有限度的支援类别中的private指称 (class-privateidentifiers,译:指变数及函式)。任何的identifier,在之前是以 <code>__spam</code> 这个形式存在的(最前面至少要有两个底线,最后面最多只能有一个底线)现在都要以 <code>_classname__spam</code> 这个形式来取代之。在这里的 <code>classname</code>指的是所在的类别名称,拿掉所有前面的底线。这个名称的变化不受限于这个identifier其语法上所在的位置,所以可以套用在定义类别的privateinstance,类别变数,method,global名称,甚至用来储存 <i>其他</i> 的类别instance里,对目前这个类别来说是private的instance变数。当这个变化过的名称超过255个字元时,有可能超过的部分是会被截掉的。在类别之外,或者是当类别的名称只包含底线的时候,就没有任何名称的变化产生。 </p> <p> 这个名称的变化主要是用来给类别有一个简单的方法来定义``private''的instance变数及methods,而不需要担心其他子类别里面所定义的instance变数,或者与其他的在类别之外的程式码里的instance变数有所混淆。注意的是这个变化名称的规则主要是用来避免意外的,如果你存心要使用或修改一个private的变数的话,这还是可行的。某方面来说这也是有用的,比如说用在除错器(debugger)上面,这也是为什么这个漏洞没有被补起来的一个原因。(如何制造bug:如果一个类别继承自某个基础类别时用了相同的名字,这会使得你可以从子类别里面使用基础类别里的private的变数。) </p> <p> 值得注意的是,被传到 <code>exec</code>, <code>eval()</code> 或 <code>evalfile()</code> 的程式码并不用考虑引发这个动作的类别是目前的类别,这是相类似于 <code>global</code> 叙述的效果,但是这个效果只限于这个程式码是一起被编译器编译成bytecode的时候的。同样的限制也存在于 <code>getattr()</code>, <code>setattr()</code> 以及 <code>delattr()</code>,或是当直接使用 <code>__dict__</code> 的时候。 </p> <p> 底下这个例子是一个类别里面定义了自己的 <tt class="method">__getattr__()</tt> 以及 <tt class="method">__setattr__()</tt> 两个方法,并且把所有的attributes都储存在private的变数里面。这个例子适用于所有的Python版本,甚至是包括在这个特性加入之前的版本都可以: </p> <p> </p> <dl> <dd><pre class="verbatim">class VirtualAttributes:<br> __vdict = None<br> __vdict_name = locals().keys()[0]<br> <br> def __init__(self):<br> self.__dict__[self.__vdict_name] = {}<br> <br> def __getattr__(self, name):<br> return self.__vdict[name]<br> <br> def __setattr__(self, name, value):<br> self.__vdict[name] = value<br></pre> </dd> </dl> <p> </p> <h1> <br> 9.7 其它 </h1> <p> 有的时候如果有一个像是Pascal的``record'',或者是C的``struct''这类的资料型态是很方便的,这类的资料型态可以把一些的资料成员都放在一起。这种资料型态可以用空白的类别来实作出来,例如: </p> <p> </p> <dl> <dd><pre class="verbatim">class Employee:<br> pass<br><br>john = Employee() # Create an empty employee record<br><br># Fill the fields of the record<br>john.name = 'John Doe'<br>john.dept = 'computer lab'<br>john.salary = 1000<br></pre> </dd> </dl> <p> 如果有一段的Python程式码需要一个特别的抽象资料型态的时候,通常你可以传给这段程式码一个类似功能的类别来代替。例如,如果你有一个函式是用来格式化一些来自于file物件的资料,你可以定义一个类别,类别里面有类似 <tt class="method">read()</tt> 以及 <tt class="method">readline()</tt> 之类method可以从一个字串缓冲区(stringbuffer)读出资料,然后再把这个类别传入函式当作参数。 </p> <p> Instance的method物件也可以有attributes: <code>m.im_self</code> 就是其method为instance的一个物件, <code>m.im_func</code> 就是这个method相对应的函式物件。 </p> <p> </p> <h2> <br> 9.7.1 例外(Exceptions)也可以是类别 </h2> <p> 使用者自订的exception不用只是被限定于只是字串物件而已,它们现在也可以用类别来定义了。使用这个机制的话,就可以创造出一个可延伸的屋exception的阶层了。 </p> <p> 有两个新的有效的(语意上的)形式现在可以用来当作引发exception的叙述: </p> <p> </p> <dl> <dd><pre class="verbatim">raise Class, instance<br><br>raise instance<br></pre> </dd> </dl> <p> 在第一个形式里面, <code>instance</code> 必须是 <tt class="class">Class</tt> 这个类别或其子类别的一个instance。第二种形式其实是底下这种形式的一个简化: </p> <p> </p> <dl> <dd><pre class="verbatim">raise instance.__class__, instance<br></pre> </dd> </dl> <p> 所以现在在except的语句里面就可以使用字串物件或是类别都可以了。一个在exception子句里的类别可以接受一个是该类别的exception,或者是该类别之子类别的exception。(相反就不可以了,一个except子句里如果用的是子类别,就不能接受一个基础类别的exception。)例如,下面的程式码就会依序的印出B,C, D来: </p> <p> </p> <dl> <dd><pre class="verbatim">class B:<br> pass<br>class C(B):<br> pass<br>class D(C):<br> pass<br><br>for c in [B, C, D]:<br> try:<br> raise c()<br> except D:<br> print "D"<br> except C:<br> print "C"<br> except B:<br> print "B"<br></pre> </dd> </dl> <p> 值得注意的是,如果上面的例子里的except子句次序都掉转的话(也就是 "<tt class="samp">except B</tt>" 是第一个),这样子印出来的就是B, B, B,也就是只有第一个可以接受的except子句被执行而已。 </p> <p> 当一个没有被处理到的exception是一个类别时,所印出来的错误信息会包含其类别的名称,然后是(:),然后是这个instance用内建的 <tt class="function">str()</tt> 函式转换成的字串。 </p> <p> <br> </p> <hr> <h4>Footnotes</h4> <dl> <dt><a name="foot1309">... 一个namespace里面。</a><a name="foot1309" href="node11.html#tex2html5"><sup>9.1</sup></a> </dt> <dd> 除了一件事之外。module物件有一个秘密的attribute叫做 <tt class="member">__dict__</tt> ,这个attribute会传回这个module的namespace所对应的dictionary。 <tt class="member">__dict__</tt> 这个名字就是一个attribute,但却不是一个global的名称。很明显的,使用这个attribite将会破坏这个namespace命名的抽象性(abstraction),所以应该只限制于像是检验尸体一样的除错器使用。 </dd> </dl> <div class="navigation"> <table align="Center" width="100%" cellpadding="0" cellspacing="2"> <tbody> <tr> <td><a href="node10.html"><img src="../icons/previous.gif" border="0" height="32" alt="Previous Page" width="32"></a></td> <td><a href="tut.html"><img src="../icons/up.gif" border="0" height="32" alt="Up One Level" width="32"></a></td> <td><a href="node12.html"><img src="../icons/next.gif" border="0" height="32" alt="Next Page" width="32"></a></td> <td align="Center" width="100%">Python 教学文件</td> <td><a href="node2.html"><img src="../icons/contents.gif" border="0" height="32" alt="Contents" width="32"></a></td> <td><img src="../icons/blank.gif" border="0" height="32" alt="" width="32"></td> <td><img src="../icons/blank.gif" border="0" height="32" alt="" width="32"></td> </tr> </tbody> </table> <b class="navlabel">Previous:</b> <a class="sectref" href="node10.html">8. 程式错误与例外(Exceptions)情形</a> <b class="navlabel">Up:</b> <a class="sectref" href="tut.html">Python 教学文件</a> <b class="navlabel">Next:</b> <a class="sectref" href="node12.html">10. 现在呢? </a> <br> <hr></div> <!--End of Navigation Panel--> <address> </address> <hr>请看<i><a href="about.html">关于此文件…</a></i> 里面有关如何给我们建议的说明。 </body> </html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -