coffee bene会员卡:作为面向对象语言的Python

来源:百度文库 编辑:中财网 时间:2024/04/28 12:51:55

为啥俺推荐Python[3]:作为面向对象语言的Python


  本系列已经中断了很长时间 :( 直到最近一个读者来信问俺,为啥不继续写,俺才突然想起这个被遗忘的系列。前一个帖子介绍了作为动态语言的Python,今天来聊一聊Python在面向对象编程(OOP)方面的特色。
  本文主要针对那些熟悉OOP,但还不熟悉Python的同学。为了让大伙儿有一个直观的认识,俺会拿C++/Java来进行语法上的对比。(这俩语言的名气够大,且号称支持OO,有某些可比性)
  强调一下:本文虽然拿了某些语言来作对比,但丝毫没有贬低这些语言的意思,请这些语言的粉丝们,不要对号入座 :)

  ★抽象(Abstraction)
  但凡介绍OOP,自然会提到抽象。因为抽象,是OO的第一要素,也是其它要素的基础。而提到抽象,又不免提到对象(Object)。所以,俺首先来聊一下,Python语言是如何体现对象的。

  ◇Python的对象
  如果要问俺,什么是Python中的对象,还真不好下一个严密又通俗易懂的定义。为了敷衍大伙儿,俺只好用一句话来概括,那就是Python语言中,一切皆对象。这句话该如何理解捏?简单来说,就是你在Python语言中涉及到的各种东东,都是“对象”。比如,函数是对象、各种数值(比如整数值、浮点数值、布尔值)是对象、模块(类似于Java的package)是对象、None(类似于Java的空引用null、C++的空指针NULL)也是对象、......
  对比一下C++和Java的语法:只有类的实例才能算得上是对象。连基本类型(比如int、char、float、等)都算不上对象,至于函数,就更算不上了。
  既然是一切皆对象,俺有必要稍微总结一下,Python对象的共性,否则初学Python的同学还是会一头雾水。

  ◇对象的属性
  首先,所有的Python的对象,都具有若干个属性。你可以通过内置的dir()函数进行反射,从而了解到某个对象分别都包含哪些属性。熟悉Java的同学,应该明白啥是"反射"。光懂C/C++的同学,如果理解上有困难,可以参见“这里”。
  另外,Python还提供了若干内置的函数,用于在运行时操作指定对象的属性。具体如下:
hasattr(obj, name)  #判断obj对象是否具有名为name的属性
setattr(obj, name, value)  #设置obj对象的名为name的属性值为value
getattr(obj, name)  #获取obj对象的名为name的属性值
delattr(obj, name)  #删除obj对象的名为name的属性

  ◇对象的类型
  所有的Python对象,都可以通过内置的type()函数获取该对象的类型。这实际上就是Python的RTTI机制的体现。懂C++的同学,可以回顾一下C++的typeid关键字;懂Java的同学,可以想一想instanceof关键字。

  ◇对象的标示
  所有的Python对象,都可以通过内置的id()函数获取该对象的唯一标示。而且当一个对象创建之后,这个唯一标示就会始终保持不变。对于学过C/C++的同学,不妨把这个唯一标示想象成该对象在内存的地址。这或许有助于你的理解 :)

  Python对象还有其它一些共性,考虑到本文的扫盲性质,就不再费口水了。有兴趣的同学,可以自己找些入门书研读一番。

  ◇“一切皆对象”的好处?
  可能有同学会问,“一切皆对象”有啥好处捏?俺窃以为:当一切皆为对象,就可以把很多概念、操作、惯用手法统一起来,在语法层面体现出美感。
  下面俺举几个例子,并拿Java来对比一下。
  在Java里面,由于基本类型不是继承自Object类,引出不少麻烦。当初Java它爹刚开始设计容器类(比如Vector、ArrayList、...)的时候,颇费了一番功夫。因为容器里面放置的东东必须是Object,为了让容器能适应基本类型,只好给每一种基本类型分别对应一个派生自Object的包装类(Integer类对应int、Float类对应float、...);后来又平添了自动装箱/拆箱的概念。而Python就没有这方面的困扰。
  再拿刚才提及的“反射”来说事儿。虽然Java语言支持对象的反射,但是Java的package不是Object,所以也就无法对package进行反射。反观Python,任何一个module(相当于Java的package)import之后,都可以直接通过前面提到的dir()函数进行反射,得知该module包含了哪些东东。仅仅需要2行代码:
import xxx
dir(xxx)

  ★封装(Encapsulation)
  为了避免歧义,首先要明确一下:什么是“封装”?为了叙述方便,俺把OOP的封装,分为狭义和广义两种。(关于封装的深入讨论,可以参见“这里”)

  ◇广义封装
  OOP很强调以数据为中心。所以OOP的广义封装,就是把数据和操作数据的行为,打包到一起。比如C++/Java里的class,可以同时包含数据成员和函数成员,就算是满足广义的封装了。对于Python而言,其class关键字类似于C++和Java,也已经具有广义的封装性了。

  ◇狭义封装
  而OOP的狭义封装,则更进一步,增加了信息隐藏(Information Hiding)。比如C++和Java的public、protected、private关键字,就是通过访问控制来达到信息隐藏的效果。Python虽然没有针对访问控制的关键字来修饰类成员,但是Python采用了另外一套机制——根据命名来约定。在Python的对象中,如果某个属性以双下划线开头来命名(比如 __name),就能起到类似于private的效果。

  ◇对访问控制的偏见
  俺曾经在某技术论坛看到有人质疑Python的访问控制机制,说Python的私有属性,可以通过反射机制绕过,因此形同虚设。在此,俺想举C++和Java来进行反驳。
  在Java中,同样可以通过反射机制,来访问类的私有成员。至于C++,得益于指针的强大,只要能访问某个对象(的this指针),通过计算该对象成员变量在内存中的偏移,即可轻易对其进行读写。虽然这么干挺变态滴,但理论上是可行滴。

  ★继承(Inheritance)
  紧接着,咱再来说一下继承的话题。

  ◇Python的继承
  Python没有像Java那样,区分出类继承(OO的术语中也叫“实现继承”)、接口继承;也没有像C++那样,区分出公有继承、私有继承、保护继承这么花哨的玩意儿。Python就只有一种继承方式。

  ◇继承的语法
  Python的继承语法,相比C++/Java而言,更加简洁。比如子类Child需要继承父类Parent,代码只需如下:
class Child(Parent) :

  如果是多继承,代码大同小异:
class Child(Parent1, Parent2, Parent3) :

  如果你想知道某个类有哪些父类(基类),只需要通过 Child.__bases__ 便可知晓。

  ◇继承的动态性
  其实上一个帖子已经介绍了动态改变继承关系的例子。然而上一个帖子年代久远(距今快1年),想必很多同学没看过或者看过又忘了。俺不妨再啰嗦一下。作为一种动态语言,Python可以在运行时修改类的继承关系。这个特性比较酷,是C++/Java所望尘莫及滴。请看下面的例子:
class Parent1 :
    def dump(self) :
        print("parent1")

class Parent2 :
    def dump(self) :
        print("parent2")

class Child :
    def dump(self) :
        print("child")

print(Child.__bases__)
Child.__bases__ += (Parent1, Parent2) # 动态追加了2个父类
print(Child.__bases__) # 打印出的父类信息中,已经包含Parent1、Parent2

  ★多态(Polymorphism)
  至于Python的多态,和传统的OO语言差不多,似乎没有太多值得说道的地方。俺简单举个代码作例子。为了省打字,直接复用上述的3个类,然后再另外增加一个test()函数如下:
def test(obj) :
    obj.dump()

  然后对test()函数分别传入不同的类型的对象,后面俺就无需多说了吧?
c = Child()
test(c) # 打印出 child
p1 = Parent1()
test(p1) # 打印出 parent1

  ★结尾
  今天的话题,主要是让不熟悉Python的网友,对Python在面向对象方面的特性,有一个粗浅、感性的认识。聊完了OOP,下一个帖子会聊一下关于关于FP(函数式编程)的话题。


版权声明
本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者编程随想和本文原始地址:
http://program-think.blogspot.com/2010/08/why-choose-python-3-oop.html