Python基础:18类和实例之二

来源:互联网 发布:java 是开源的吗 编辑:程序博客网 时间:2024/05/16 23:55

        1:绑定和非绑定

        当存在一个实例时,方法才被认为是绑定到那个实例了。没有实例时方法就是未绑定的。在很多情况下,调用的都是一个绑定的方法。

 

        调用非绑定方法并不经常用到,其中一个主要的场景是:派生一个子类,而且要覆盖父类的方法,这时需要调用那个父类中被覆盖掉的构造方法:

class  EmplAddrBookEntry(AddrBookEntry):

        'Employee Address Book Entry class'

        def  __init__(self, nm,  ph,  em):

                AddrBookEntry.__init__(self,  nm,  ph)

                self.empid  =  id

                self.email  =  em

 

        EmplAddrBookEntry是AddrBookEntry 的子类,重载了__init__()。这是调用非绑定方法的最佳地方了。

        当一个EmplAddrBookEntry被实例化,并且调用 __init__() 时,其与AddrBookEntry的实例只有很少的差别,主要是因为我们还没有机会来自定义我们的EmplAddrBookEntry 实例,以使它与AddrBookEntry 不同。所以,可以将EmplAddrBookEntry实例传递给AddrBookEntry的__init__

        子类中 __init__() 的第一行就是对父类__init__()的调用。通过父类名来调用它,并且传递给它 self 和其他所需要的参数。一旦调用返回,我们就能定义那些与父类不同的仅存在我们的(子)类中的(实例)定制。

 

        2:静态方法和类方法

        静态方法和类方法在Python2.2中引入。经典类及新式(new-style)类中都可以使用它。

 

        Python中的静态方法和C++或者Java这些语言中的是一样的。它们仅是类中的函数(不需要实例)。

        对于类方法而言,需要类而不是实例作为第一个参数,它是由解释器传给方法。类不需要特别地命名,类似self,不过很多人使用cls作为变量名字。

 

        下面是在经典类中创建静态方法和类方法的一些例子(也可以把它们用在新式类中):

class  TestStaticMethod:

        def  foo():

                print  'calling  static  method foo()'

        foo  =  staticmethod(foo)

 

class  TestClassMethod:

        def  foo(cls):

                print  'calling  class  method foo()'

                print  'foo()  is  part of  class:',  cls.__name__

        foo  =  classmethod(foo)

        内建函数staticmethod和classmethod将它们转换成相应的类型,并且重新赋值给了相同的变量名。如果没有调用这两个函数,二者都会在Python 编译器中产生错误,显示需要带self 的常规方法声明。 可以通过类或者实例调用这些函数

>>> tsm  =  TestStaticMethod()

>>>TestStaticMethod.foo()

calling  static  method foo()

>>>tsm.foo()

calling  static  method  foo()

 

>>> tcm  =  TestClassMethod()

>>>TestClassMethod.foo()

calling  class  method foo()

foo()  is  part of  class: TestClassMethod

>>>tcm.foo()

calling  class  method foo()

foo()  is  part of  class:  TestClassMethod

 

        通过使用修饰符,可以避免像上面那样的重新赋值:

class  TestStaticMethod:

        @staticmethod

        def  foo():

                print  'calling  static  method foo()'

class  TestClassMethod:

        @classmethod

        def  foo(cls):

                print  'calling  class  method foo()'

                print  'foo()  is  part of  class:',  cls.__name__

 

        3:有两种方法可以在代码中利用类。第一种是组合(composition)。就是让不同的类混合并加入到其它类中,来增加功能和代码重用性。另一种方法是通过派生

 

        组合的例子如下:

class  NewAddrBookEntry(object):   

        'new  address  book  entry class'

        def  __init__(self, nm,  ph):  

                self.name  =  Name(nm)         #创建Name实例

                self.phone  =  Phone(ph)         #创建Phone实例

                print  'Created  instance  for:',  self.name

 

        NewAddrBookEntry类由其它类组合而成。这就在一个类和其它组成类之间定义了一种“有一个”的关系。比如,我们的NewAddrBookEntry 类“有一个” Name 类实例和一个Phone实例。

 

        4:子类和派生

        OOP的更强大方面之一是能够使用一个已经定义好的类,扩展它或者对其进行修改,而不会影响系统中使用现存类的其它代码片段。允许类特征在子孙类或子类中进行继承。

        新式类创建子类的语法:

class  SubClassName (ParentClass1[, ParentClass2,...]):

        'optional  class  documentation  string'

        class_suite

        如果你的类没有从任何祖先类派生,可以使用object 作为父类的名字。

 

        经典类的声明唯一不同之处在于其没有从祖先类派生--此时,没有圆括号:

class  ClassicClassWithoutSuperclasses:

        pass

 

        下面还有一个简单的例子:

class  Parent(object):          

        def  parentMethod(self):

                print  'calling  parent  method'

 

class  Child(Parent):            

        def  childMethod(self):

                print  'calling  child  method'

>>> p  =  Parent()                    

>>> p.parentMethod()

calling  parent  method

>>> 

>>> c  =  Child()

>>> c.childMethod()                     

calling  child  method

>>>c.parentMethod()   

calling  parent  method

 

        5:继承

        一个子类可以继承它的基类的任何属性,不管是数据属性还是方法。举个例子如下。P 是一个没有属性的简单类。C 从P 继承而来,也没有属性:

class  P(object):

        pass

class  C(P):

        pass

>>> c  =  C()

>>> c.__class__

<class  '__main__.C'>

>>>C.__bases__

(<class  '__main__.P'>,)

 

        下面给 P 添加一些属性:

class  P:

         'P  class'

        def  __init__(self):

                print  'created  an  instance of',  self.__class__.__name__

class  C(P):

        pass

 

        现在P 有文档字符串__doc__和__init__:

>>> p  =  P()

created  an  instance of  P

>>> p.__class__

<class  '__main__.P'>

>>> P.__bases__

(<type  'object'>,)

>>> P.__doc__

'P  class'

 

>>> c  =  C()

created  an  instance of  C

>>> c.__class__

<class  '__main__.C'>

>>> C.__bases__

(<class  '__main__.P'>,)

>>>C.__doc__

>>> 

        C没有声明__init__()方法,然而在类C 的实例c 被创建时,还是会有输出信息。原因在于C 继承了P 的__init__()。

需要注意的是,文档字符串对类,函数/方法,还有模块来说都是唯一的,所以特殊属性__doc__不会从基类中继承过来。

 

        对任何(子)类,__bases__类属性是一个包含其父类(parent)的集合的元组。

        那些没有父类的类,它们的__bases__属性为空。下面我们看一下如何使用__bases__的。

 

>>> class  A(object):  pass

...

>>> class  B(A):  pass

...

>>> class  C(B):  pass

...

>>> class  D(A, B):  pass

...

>>> A.__bases__

(<type  'object'>, )

>>> B.__bases__

(<class  '__main__.A'>,)

>>> C.__bases__

(<class  '__main__.B'>,)

>>> D.__bases__

(<class  '__main__.B'>,  <class  '__main__.A'>)

 

        在上面的例子中,尽管C 是A 和B 的子类(通过B 传递继承关系),但C的父类是B,所以,只有B 会在C.__bases__中显示出来。

 

        在父类 P 中再写一个函数,然后在其子类中对它进行覆盖:

class  P(object):

        def  foo(self):

                print  'Hi,  I  am  P-foo()'

>>> p  =  P()

>>> p.foo()

Hi,  I  am  P-foo()

 

class  C(P):

        def  foo(self):

                print  'Hi,  I  am  C-foo()'

>>> c  =  C()

>>> c.foo()

Hi,  I  am  C-foo()

        尽管C继承了P 的foo()方法,但因为C 定义了它自已的foo()方法,所以 P 中的foo() 方法被覆盖。

        尽管父类中的foo被覆盖了,但是还是可以调用那个被覆盖的基类方法。这时就需要去调用一个未绑定的基类方法,需要明确给出子类的实例,例如下边:

>>> P.foo(c)

Hi,  I am  P-foo()

 

        典型情况下,你不会以这种方式调用父类方法,你会在子类的重写方法里显式地调用基类方法。

class  C(P):

        def  foo(self):

                P.foo(self)

                print  'Hi, I am C-foo()'

 

        在这个(未绑定)方法调用中我们显式地传递了self. 一个更好的办法是使用super()内建方法:

class  C(P):

        def  foo(self):

                super(C,  self).foo()

                print  'Hi,  I am C-foo()'

 

        super()不但能找到基类方法,而且还为我们传进self

>>> c  =  C()

>>> c.foo()

Hi,  I  am  P-foo()

Hi,  I  am  C-foo()

 

        类似于上面的覆盖非特殊方法,当从一个带__init()__的类派生,如果不覆盖__init__(),它将会被继承并自动调用。但如果在子类中覆盖了__init__(),子类被实例化时基类的__init__()就不会被自动调用。

class  P(object):

        def  __init__(self):

                print  "calling  P's  constructor"

class  C(P):

        def  __init__(self):

                print  "calling  C's  constructor"

>>> c  =  C()

calling  C's  constructor

        如果还想调用基类的 __init__(),需要明确指出,使用一个子类的实例去调用基类(未绑定)方法。相应地更新类C,会出现下面预期的执行结果:

class  C(P):

        def  __init__(self):

                P.__init__(self)

                print  "calling C's  constructor"

>>> c  =  C()

calling  P's  constructor

calling  C's  constructor

        上边的例子中,子类的__init__()方法首先调用了基类的的__init__()方法。这是相当普遍(不是强制)的做法,用来设置初始化基类,然后可以执行子类内部的设置。super()内建函数引入到Python中,可以这样写:

class  C(P):

        def  __init__(self):

                super(C,  self).__init__()

                print  "calling  C's  constructor"

        使用super()的漂亮之处在于,不需要明确提供父类。这意味着如果改变了类继承关系,只需要改一行代码(class 语句本身)而不必在大量代码中去查找所有被修改的那个类的名字。

 

        如果一个类的构造方法被重写,那么就需要调用超类(你所继承的类)的构造方法,否则对象可能不会被正确地初始化。比如下面的例子:

>>> class  Bird:

...     def  __init__(self):

...        self.hungry=True

...     def  eat(self):

...        if  self.hungry:

...            print  'aaah...'

...            self.hungry=False

...        else:

...            print  'no thanks'

>>> b=Bird()

>>> b.eat()

aaah...

>>> b.eat()

no  thanks

 

        现在考虑子类SongBird,它添加了唱歌的行为:

>>> class  SongBird(Bird):

...     def  __init__(self):

...        self.sound='Squawk'

...     def  sing(self):

...        print  self.sound

...

>>> s=SongBird()

>>> s.sing()

Squawk

        因为SongBird是Bird的一个子类,它继承了eat方法,但如果调用eat方法,就会产生一个问题:

>>> s.eat()

Traceback (most recent calllast):

File"<input>", line 1, in <module>

File"<input>", line 5, in eat

AttributeError:SongBird instance has no attribute 'hungry'


        原因是:在SongBird中,构造方法被重写,但新的构造方法没有任何关于初始化hungry特性的代码。为了达到预期的效果,SongBird的构造方法必须调用其超类Bird的构造方法来确保进行基本的初始化。


        6:继承标准类型

        经典类中的一个问题是,不能对标准类型进行子类化。幸运的是,随着类型(types)和类(class)的统一和新式类的引入。这一点已经被修正。下面,介绍两个子类化Python 类型的相关例子。

        一个处理浮点数的子类,它带两位小数位:

class  RoundFloat(float):

        def  __new__(cls,  val):

                return  float.__new__(cls,  round(val,  2))

 

        覆盖__new__()特殊方法来定制对象,使之和标准Python 浮点数(float)有一些区别。最好是使用super()内建函数去捕获对应的父类以调用它的__new()__方法,下面,对它进行这方面的修改:

class  RoundFloat(float):

        def  __new__(cls,  val):

                return  super(RoundFloat,  cls).__new__(cls,  round(val,  2))

 

下面是一些样例输出:

>>>RoundFloat(1.5955)

1.6

>>>RoundFloat(1.5945)

1.59

>>>RoundFloat(-1.9955)

-2.0

        object.__new__(cls, ...),它会创建cls的实例,__new__是静态方法(因其特殊性,不需要显示声明)。而且它的第一个参数是要创建实例的类型。他返回一个cls的实例。

        一般的用法中,创建子类的实例,可以调用父类的new:

super(currentcls, cls).__new__(cls, ...)。比如:

float.__new__(cls, 3.456),创建的就是cls的实例。

 

        7:多重继承

        Python允许子类继承多个基类。也就是多重继承。这里的难点在于:如何正确找到没有在当前(子)类定义的属性。也就是所谓的:方法解释顺序(MRO)。

        在Python 2.2 以前的版本中,算法非常简单:深度优先,从左至右进行搜索,多重继承则取找到的第一个名字。

        由于类,类型和内建类型的子类,都经过全新改造, 有了新的结构,这种算法不再可行。这样一种新的MRO 算法被开发出来,新的查询方法是采用广度优先,而不是深度优先。

        下面的示例,展示经典类和新式类中,方法解释顺序有什么不同。

class  P1:   #(object)

        def  foo(self):

                print  'called  P1-foo()'

class  P2:   #(object)

        def  foo(self):

                print  'called  P2-foo()'

        def  bar(self):

                print  'called P2-bar()'

class  C1(P1,  P2):          pass

class  C2(P1,  P2):

        def  bar(self):

                print  'called  C2-bar()'

class  GC(C1,  C2):       pass

 

    上面这些类的关系如下图所示:

 

        首先来使用经典类,可以验证经典类使用的解释顺序,深度优先,从左至右:

>>> gc  =  GC()

>>> gc.foo()                            # GC ==> C1 ==> P1

called  P1-foo()

>>> gc.bar()                            # GC ==> C1 ==> P1 ==> P2

called  P2-bar()

 

        当调用foo()时,它首先在当前类(GC)中查找。如果没找到,就向上查找最亲的父类,C1。查找未遂,就继续沿树上访到父类P1,foo()被找到。

        同样,对bar()来说,它通过搜索GC,C1,P1 然后在P2 中找到。因为使用这种解释顺序的缘故,C2.bar()根本就不会被搜索了。

        如果需要调用C2 的bar()方法,则必须调用它的合法的全名,采用典型的非绑定方式去调用,并且提供一个合法的实例:

>>> C2.bar(gc)

called  C2-bar()

 

        对于新式类,取消类P1 和类P2 声明中的对(object)的注释,重新执行一下。新式方法的查询有一些不同:

>>> gc  =  GC()

>>> gc.foo()                            # GC==> C1 ==> C2 ==> P1

called  P1-foo()

>>> gc.bar()                            # GC ==> C1 ==> C2

called  C2-bar()

        与沿着继承树一步一步上溯不同,它首先查找同胞兄弟,采用一种广度优先的方式。当查找foo(),它检查GC,然后是C1 和C2,然后在P1 中找到。如果P1 中没有,查找将会到达P2。

        然而,bar()的结果是不同的。它搜索GC 和C1,紧接着在C2 中找到了。这样,就不会再继续搜索到祖父P1 和P2。

 

        新式类有一个__mro__属性,告诉你查找顺序是怎样的。它是类属性,实例没有该属性。它是新式类的属性,经典类没有:

>>> GC.__mro__

(<class '__main__.GC'>, <class'__main__.C1'>, <class '__main__.C2'>, <class '__main__.P1'>,  <class '__main__.P2'>, <type'object'>)

 

        为什么经典类MRO 会失败?在版本2.2 中,类型与类的统一,带来了一个新的“问题”,波及所有从object派生出来的(根)类,一个简单的继承结构变成了一个菱形。

        比如,有经典类B 和C,C 覆盖了构造器,B 没有,D 从B 和C 继承而来:

class  B:

        pass

class  C:

        def  __init__(self):

                print  "the  default  constructor"

class  D(B,  C):

        pass

当我们实例化D,得到:

>>> d  = D()

the  default  constructor


        上图为B,C 和D 的类继承结构,现在把代码改为采用新式类的方式,问题也就产生了:

class  B(object):

        pass

class  C(object):

        def  __init__(self):

                print  "the  default  constructor"

 

        由于在新式类中,需要出现基类,这样就在继承结构中,形成了一个菱形。D 的实例上溯时,不应当错过C, 但不能两次上溯到A(因为B 和C 都从A 派生)。

        继承结构已变成了一个菱形,如果使用经典类的MRO,当实例化D 时,不再得到C.__init__()的结果,而是得到object.__init__()!这就是为什么MRO 需要修改的真正原因。

        尽管我们看到了,在上面的例子中,类GC的属性查找路径被改变了,但你不需要担心会有大量的代码崩溃。经典类将沿用老式MRO,而新式类将使用它自己的MRO。

0 0