X Tutup
>爱是恒久忍耐,又有恩慈;爱是不嫉妒,爱是不自夸,不张狂,不作害羞的事,不求自己的益处,不轻易发怒,不计算人的恶,不喜欢不义,只喜欢真理;凡事包容,凡事相信,凡事盼望,凡事忍耐。(1 CORINTHIANS 13:4-7) #多态和封装 “多态”和“封装”是OOP的重要特征——前面说的“继承”也是。但是,对于Python而言,对这两个的理解也有很多不同。建议读者“吃百家宴”,到网上搜一搜有关话题,不少人写了文章来讨论。 ##多态 这里我仅仅针对初学者,按照自己的理解,谈谈零基础学Python的读者可以怎样理解“多态”,因为“多态”就如同其名字一样,在理解上也是“多态”的。 先来看这样的例子: >>> "This is a book".count("s") 2 >>> [1,2,4,3,5,3].count(3) 2 上面的`count()`的作用是数一数某个元素在对象中出现的次数。从例子中可以看出,我们并没有限定`count()`的参数类型。类似的例子还有: >>> f = lambda x,y:x+y 还记得这个`lambda`函数吗?如果忘记了,请复习[函数(5)](./237.md)中对此的解释。 >>> f(2,3) 5 >>> f("qiw","sir") 'qiwsir' >>> f(["python","java"],["c++","lisp"]) ['python', 'java', 'c++', 'lisp'] 这里我们没有限制参数的类型,也一定不能限制,因为如果限制了,就不是pythonic了。在使用的时候,可以给参数任意类型,都能得到不报错的结果。 以上,就体现了“多态”——同一种行为具有不同表现形式和形态的能力,换一种说法,就是对象多种表现形式的体现。 当然,也有人就此提出了反对意见,因为本质上是在参数传入值之前,Python并没有确定参数的类型,只能让数据进入函数之后再处理,能处理则罢,不能处理就报错。例如: >>> f("qiw", 2) Traceback (most recent call last): File "", line 1, in File "", line 1, in TypeError: cannot concatenate 'str' and 'int' objects 本书由于不属于这种概念争论范畴,所以不进行这方面的深入探索,仅仅是告诉各位读者相关信息。并且,也是按照“人云亦云”的原则,既然大多数程序员都在讨论多态,那么我们就按照大多数人说的去介绍(尽管有时候真理掌握在少数人手中)。 “多态”,英文是:Polymorphism,在台湾被称作“多型”。维基百科中对此有详细解释说明。 >多型(英语:Polymorphism),是指物件導向程式執行時,相同的訊息可能會送給多個不同的類別之物件,而系統可依據物件所屬類別,引發對應類別的方法,而有不同的行為。簡單來說,所謂多型意指相同的訊息給予不同的物件會引發不同的動作稱之。 再简化的说法就是“有多种形式”,就算不知道变量(参数)所引用的对象类型,也一样能进行操作,来者不拒。比如上面显示的例子。在Python中,更为pythonic的做法是根本就不进行类型检验。 例如著名的`repr()`函数,它能够针对输入的任何对象返回一个字符串。这就是多态的代表之一。 >>> repr([1,2,3]) '[1, 2, 3]' >>> repr(1) '1' >>> repr({"lang":"python"}) "{'lang': 'python'}" 使用它写一个小函数,还是作为多态代表的。 >>> def length(x): ... print "The length of", repr(x), "is", len(x) ... >>> length("how are you") The length of 'how are you' is 11 >>> length([1,2,3]) The length of [1, 2, 3] is 3 >>> length({"lang":"python","book":"itdiffer.com"}) The length of {'lang': 'python', 'book': 'itdiffer.com'} is 2 不过,多态也不是万能的,如果这样做: >>> length(7) The length of 7 is Traceback (most recent call last): File "", line 1, in File "", line 2, in length TypeError: object of type 'int' has no len() 报错了。看错误提示,明确告诉了我们`object of type 'int' has no len()`。 上述的种种多态表现,皆因为Python是一种解释型的语言,不需要进行预编译,只在运行时才确定状态。所以,Python就被认为天生是一种多态的语言。也有人持相反观点,认为Python不支持多态,在理由中,也用了上述内容。看来,看着半杯水,的确能够有不同的结论——“还有半杯水呢!”和“还剩半杯水了!”。 争论,让给思想者。我们,围观。 在诸多介绍多态的文章中,都会有这样关于猫和狗的例子。这里也将代码贴出来,读者去体会所谓多态体现。其实,如果你进入了Python的语境,有时候是不经意就已经在应用多态特性呢。 #!/usr/bin/env python # coding=utf-8 "the code is from: http://zetcode.com/lang/python/oop/" class Animal(object): #Python 3: class Animal: def __init__(self, name=""): self.name = name def talk(self): pass class Cat(Animal): def talk(self): print "Meow!" #Python 3: print('Meow!'),下同,从略 class Dog(Animal): def talk(self): print "Woof!" a = Animal() a.talk() c = Cat("Missy") c.talk() d = Dog("Rocky") d.talk() 保存后运行之: $ python 21101.py Meow! Woof! 代码中有`Cat`和`Dog`两个类,都继承了类`Animal`,它们都有`talk()`方法,输入不同的动物名称,会得出相应的结果。 根据前面已经学习过的类和继承的知识,我们知道,对于实例`c`是Cat类型的对象,`d`是Dog类型的对象,但它们也都是Animal类型的对象——类,也是一种对象类型,这样,不论是谁,也都有`Animal`类所规定的方法和属性——多态就体现在这里——继承了`Animal`的子类,实例化之后都可以具有`Animal`的表现形式。 对“多态”的理解,还可以在实践中慢慢增加。 关于多态,有一个被称作“鸭子类型”(duck typeing)的东西,其含义在维基百科中被表述为: >在程序设计中,鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试(见下面的“历史”章节),“鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。” 对于鸭子类型,也是有争议的。这方面的详细信息,读者可以去看有关维基百科的介绍。 对于多态问题,最后还要告诫读者,类型检查是毁掉多态的利器,比如type、isinstance以及isubclass函数,所以,一定要慎用这些类型检查函数。 ##封装和私有化 “封装”,是不是把代码写到某个东西里面,“人”在编辑器中打开,就看不到了呢? 除非是你的显示器坏了。 在程序设计中,封装(Encapsulation)是对具体对象的一种抽象,即将某些部分隐藏起来,在程序外部看不到,即无法调用(不是人用眼睛看不到那个代码,除非用某种加密或者混淆方法,造成现实上的困难,但这不是封装)。 要了解封装,离不开“私有化”,就是将类或者函数中的某些属性限制在某个区域之内,外部无法调用。 Python中私有化的方法也比较简单,就是在准备私有化的属性(包括方法、数据)名字前面加双下划线。例如: #!/usr/bin/env python # coding=utf-8 class ProtectMe(object): #Python 3: class ProtectMe: def __init__(self): self.me = "qiwsir" self.__name = "kivi" def __python(self): print "I love Python." #Python 3: print("I love Python."),下同,从略 def code(self): print "Which language do you like?" self.__python() if __name__ == "__main__": p = ProtectMe() print p.me print p.__name 运行一下,看看效果: $ python 21102.py qiwsir Traceback (most recent call last): File "21102.py", line 21, in print p.__name AttributeError: 'ProtectMe' object has no attribute '__name' 查看报错信息,告诉我们没有`__name`那个属性。果然隐藏了,在类的外面无法调用。再试试那个函数,可否? if __name__ == "__main__": p = ProtectMe() p.code() p.__python() 修改这部分即可。其中`p.code()`的意图是要打印出两句话:`"Which language do you like?"`和`"I love Python."`,`code()`方法和`__python()`方法在同一个类中,可以调用之。后面的那个`p.__python()`试图调用那个私有方法。看看效果: $ python 21102.py Which language do you like? I love Python. Traceback (most recent call last): File "21102.py", line 23, in p.__python() AttributeError: 'ProtectMe' object has no attribute '__python' 如愿以偿。该调用的调用了,该隐藏的隐藏了。 用上面的方法,的确做到了封装。但是,我如果要调用那些私有属性,怎么办? 可以使用`property`函数。 #!/usr/bin/env python # coding=utf-8 class ProtectMe(object): #Python 3: class ProtectMe: def __init__(self): self.me = "qiwsir" self.__name = "kivi" @property def name(self): return self.__name if __name__ == "__main__": p = ProtectMe() print p.name #Python 3: print(p.name) 运行结果: $ python 21102.py kivi 从上面可以看出,用了`@property`之后,在调用那个方法的时候,用的是`p.name`的形式,就好像在调用一个属性一样,跟前面`p.me`的格式相同。 看来,封装的确不是让“人看不见”。 ------ [总目录](./index.md)   |   [上节:类(5)](./209.md)   |   [下节:定制类](./239.md) 如果你认为有必要打赏我,请通过支付宝:**qiwsir@126.com**,不胜感激。
X Tutup