Python对象模型

Python对象模型

对一门具体的编程语言来说,在应用设计模式时影响最大的莫过于它的对象模型了,这是因为大部分设计模式都源自于C++和Java这类面向对象编程语言。要想在Python中复用这些设计模式,首先需要对Python的对象模型有一个比较清晰的认识。

4.1 类

同其它面向对象编程语言一样,Python中的类也是一种用户自定义的数据类型,其基本的语法格式是:



        class <name>(superclass, ...):  # 定义类
              data = value    # 共享的类变量
              def method(self, ...):   # 类中的方法
                self.member = value  # 实例的数据
        

类定义从关键字class开始,并包含整个缩进代码块,类中定义的方法和属性构成了类的名字空间(name space)。一个类通常会有多个方法,它们都以关键字def开头,并且第一个参数通常都是self,Python中的变量self相当于C++中的关键字this,其作用是传递一个对象的引用。

Python中的类属性位于类的名字空间中,可以被所有的类实例所共享,这一点同C++和Java相同。访问类属性时不需要事先创建类的实例,直接使用类名就可以了。例如:



>>> class Friend:
    default_age = 20

>>> Friend.default_age
20

除了自定义的类属性外,Python中的每个类其实都具有一些特殊的类属性,它们都是由Python的对象模型所提供的。表1列出了这些类属性:

属性名 说明
__dict__ 类名字空间的字典变量
__doc__ 类的文档说明字符串
__name__ 类的名称
__module__ 类的模块名
__bases__ 该类所有父类组成的元组

>表1特殊的类属性 

4.2 实例

定义类的目的是为了创建它的实例,从面向对象的角度看,类是对数据及其相关操作的封装,而类实例则是对现实生活中某个实体的抽象。假设定义了如下一个类:



        class School:
              def __init__(self, name):
                 self.name = name
                 self.students = []
              def addStudent(self, student):
                 self.students.append(student)
        

要创建School类的一个实例,可以执行下面的语句:



bit = School("Beijing Institute of Technology")

在C++和Java中创建类实例时,与类具有相同名称的构造函数被调用,而在Python中创建一个类的实例时,将调用名为__init__的特殊方法。Python中的类实例继承了类的所有方法和属性,并且有自己独立的名字空间,使用下面的方法可以访问类实例的方法和属性:



bit.addStudent("gary")
bit.students

Python中的对象属性有一个非常有趣的地方,那就是使用它们之前不用像C++和Java那样,必须先在类中进行声明,因为这些都是可以动态创建的。作为一门动态类型语言,Python的这一特性的确非常灵活,但有时也难免产生问题。例如在许多针对接口的设计模式中,通常都需要知道对象所属的类,以便能够调用不同的实现方法,这些在C++和Java这些强类型语言的对象模型中不难实现,但对Python来讲可就不那么简单了,因为Python中的每个变量事实上都没有固定的类型。

为了解决这一问题,Python的__builtin__模块提供了两个非常实用的内建函数:isinstance()和issubclass()。其中函数isinstance()用于测试一个对象是否是某个类的实例,如果是的话则返回1,否则返回0。其基本的语法格式是:



isinstance (instance_object, class_object)

例如:



>>> class Test:
    pass

>>> inst = Test()
>>> isinstance(inst, Test)
1

而函数issubclass()则用于测试一个类是否是另一个类的子类,如果是的话则返回1,否则返回0。其基本的语法格式是:



issubclass(classobj1, classobj2)

例如:



>>> class TestA:
 pass

>>> class TestB(TestA):
 pass

>>> issubclass(TestA, TestB)
0
>>> issubclass(TestB, TestA)
1

和类一样,Python中的每个类实例也具有一些特殊的属性,它们都是由Python的对象模型所提供的。表2列出了这些属性:

属性名 说明
__dict__ 实例名字空间的字典变量
__class__ 生成该实例的类
__methods__ 实例所有方法的列表

表2 特殊的实例属性 

4.3继承

在面向对象的程序设计中,继承(Inheritance)允许子类从父类那里获得属性和方法,同时子类可以添加或者重载其父类中的任何方法。在Python中定义继承类的语法格式是:



class <name>(superclass, superclass, ...)
    suit

例如,对于下面这个类:



        class Employee:
              def __init__(self, name, salary = 0):
                 self.name = name
                 self.salary = salary
              def raisesalary(self, percent):
                 self.salary = self.salary  * (1 + percent)
              def work(self):
                  print self.name, "writes computer code"
        

可以为其定义如下的子类:



        class Designer(Employee):
              def __init__(self, name):
                  Employee.__init__(self, name, 5000)
              def work(self):
                  print self.name, "writes design document"
        

在C++和Java的对象模型中,子类的构造函数会自动调用父类的构造函数,但在Python中却不是这样,你必须在子类中显示调用父类的构造函数,这就是为什么在Designer. __init__方法中必须调用Employee.__init__方法的原因。

人们对多重继承的看法一直褒贬不一,C++对象模型允许多重继承,而Java对象模型则是通过接口(Interface)来间接实现多重继承的。在对多重继承的处理上,Python采用了和C++类似的方法,即允许多重继承,例如:



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

4.4 多态

严格说来,像C++和Java这些强类型语言对象模型中的多态概念并不适用于Python,因为Python没有提供类型声明机制。但由于Python本身是一种动态类型语言,允许将任意值赋给任何一个变量,如果我们对多态的概念稍加扩充,将其理解为具有能同时处理多种数据类型的函数或方法,那么Python对象模型实际上也支持经过弱化后的多态。

Python直到代码运行之时才去决定一个变量所属的类型,这一特性称为运行时绑定(runtime binding)。Python解析器内部虽然也对变量进行类型分配,但却十分模糊,并且只有在真正使用它们时才隐式地分配类型。例如,如果程序调用abs(num),则除数字之外的任何类型对变量num都没有意义,此时变量num事实上就进行了非正式的类型分配。

能够处理不同抽象层次的对象,是面向对象编程最重要的特性之一,也是Python的一个非常重要的组成部分。下面的例子示范了如何让Python中的一个函数能够同时处理多种类型的数据,在C++的对象模型中,这种多态被称为方法重载。



            class Polymorph:
                  def deal_int(self, arg):
                      print '%d is an integer' % arg
                  def deal_str(self, arg):
                      print '%s is a string' % arg
                  def deal(self, arg):
                     if type(arg) == type(1):
                        self.deal_int(arg)
                     elif type(arg) == type(' '):
                        self.deal_str(arg)
                     else:
                        print '%s is not an integer or a string' % arg
            

这样,Polymorph类中的方法deal就可以同时处理数字和字符串了:



>>> p = Polymorph()
>>> p.deal(100)
100 is an integer
>>> p.deal("Hello World!")
Hello World! is a string

4.5 可见性

Python对象模型对可见性的处理与C++和Java完全不同。在C++和Java中,如果属性或者方法被声明为private,那就意味着它们只能在类中被访问,而如果被声明为protected,则只有该类或者其子类中的代码能够访问这些属性和方法。但在Python对象模型中,所有属性和方法都是public的,也就是说数据没有做相应的保护,你可以在任何地方对它们进行任意的修改。

能够对可见性进行约束是面向对象编程的一个重要特点,其目的是使对象具有优良的封装性:对象仅仅向外界提供访问接口,而内部实现细节则被很好地隐藏起来。奇怪的是作为一门面向对象脚本语言,Python并没有提供对可见性进行约束的机制,所有属性和方法对任何人都是可见的,任何人想知道对象的内部实现细节都是可能的。虽然这样做能够带来部分效率上的优化,但却无法阻止其它程序员对已经封装好的类进行破坏,从某种程度上这不得不说是Python带给我们的一丝的缺憾。

直到Python 1.5,Guido才引入了名字压缩(name mangling)的概念,使得类中的一些属性得以局部化。在进行定义类时,如果一个属性的名称是以两个下划线开始,同时又不是以下划线结束的,那么它在编译时将自动地被改写为类名加上属性名。例如:



class Greeting:
    __data = "Hello World!"
    def __init__(self, str):
        Greeting.__data = str

>>> g = Greeting("Hello Gary!")
>>> dir (g)
['_Greeting__data', '__doc__', '__init__', '__module__']

从上面的显示结果可以看出,Greeting类的属性__data变成了_Greeting__data。虽然这样仍然无法阻止外界对它的访问,但的确使得访问变得不再那么直接了,从而在一定程序上保护了类中的数据不被外界破坏。

posted on 2012-10-29 09:56  linzuxin  阅读(359)  评论(0编辑  收藏  举报

导航