zoukankan      html  css  js  c++  java
  • Python基础:18类和实例之二

            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。

  • 相关阅读:
    Spring Boot项目配置RabbitMQ集群
    rabbitMQ第二篇:java简单的实现RabbitMQ
    SpringBoot启动和停止脚步
    SpringBoot实战之SpringBoot自动配置原理
    maven profile 多环境
    Mybatis根据配置文件获取session(多数据源)
    Mybatis的mapper注册
    MyBatis SqlSessionFactory的几种常见创建方式
    Result映射成对象和List
    JAVA反射的使用之ResultSet的自动转换
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247197.html
Copyright © 2011-2022 走看看