|
| 1 | +>爱是恒久忍耐,又有恩慈;爱是不嫉妒,爱是不自夸,不张狂,不作害羞的事,不求自己的益处,不轻易发怒,不计算人的恶,不喜欢不义,只喜欢真理;凡事包容,凡事相信,凡事盼望,凡事忍耐。(1 CORINTHIANS 13:4-7 |
| 2 | +
|
| 3 | +#多态和封装 |
| 4 | + |
| 5 | +前面讲过的“继承”,是类的一个重要特征,在编程中用途很多。这里要说两个在理解和实践上有争议的话题:多态和封装。所谓争议,多来自于对同一个现象不同角度的理解,特别是有不少经验丰富的程序员,还从其它语言的角度来诠释python的多态等。 |
| 6 | + |
| 7 | +##多态 |
| 8 | + |
| 9 | +在网上搜索一下,发现对python的多态问题,的确是仁者见仁智者见智。 |
| 10 | + |
| 11 | +作为一个初学者,不一定要也没有必要、或者还没有能力参与这种讨论。但是,应该理解python中关于多态的基本体现,也要对多态有一个基本的理解。 |
| 12 | + |
| 13 | + >>> "This is a book".count("s") |
| 14 | + 2 |
| 15 | + >>> [1,2,4,3,5,3].count(3) |
| 16 | + 2 |
| 17 | + |
| 18 | +上面的`count()`的作用是数一数某个元素在对象中出现的次数。从例子中可以看出,我们并没有限定count的参数。类似的例子还有: |
| 19 | + |
| 20 | + >>> f = lambda x,y:x+y |
| 21 | + |
| 22 | +还记得这个lambda函数吗?如果忘记了,请复习[函数(4)](https://github.com/qiwsir/StarterLearningPython/blob/master/204.md)中对此的解释。 |
| 23 | + |
| 24 | + >>> f(2,3) |
| 25 | + 5 |
| 26 | + >>> f("qiw","sir") |
| 27 | + 'qiwsir' |
| 28 | + >>> f(["python","java"],["c++","lisp"]) |
| 29 | + ['python', 'java', 'c++', 'lisp'] |
| 30 | + |
| 31 | +在那个lambda函数中,我们没有限制参数的类型,也一定不能限制,因为如果限制了,就不是pythonic了。在使用的时候,可以给参数任意类型,都能到的不报错的结果。当然,这样做之所以合法,更多的是来自于`+`的功能强悍。 |
| 32 | + |
| 33 | +以上,就体现了“多态”。当然,也有人就此提出了反对意见,因为本质上是在参数传入值之前,python并没有确定参数的类型,只能让数据进入函数之后再处理,能处理则罢,不能处理就报错。例如: |
| 34 | + |
| 35 | + >>> f("qiw", 2) |
| 36 | + Traceback (most recent call last): |
| 37 | + File "<stdin>", line 1, in <module> |
| 38 | + File "<stdin>", line 1, in <lambda> |
| 39 | + TypeError: cannot concatenate 'str' and 'int' objects |
| 40 | + |
| 41 | +本教程由于不属于这种概念争论范畴,所以不进行这方面的深入探索,仅仅是告诉各位读者相关信息。并且,本教程也是按照“人云亦云”的原则,既然大多数程序员都在讨论多态,那么我们就按照大多数人说的去介绍(尽管有时候真理掌握在少数人手中)。 |
| 42 | + |
| 43 | +“多态”,英文是:Polymorphism,在台湾被称作“多型”。维基百科中对此有详细解释说明。 |
| 44 | + |
| 45 | +>多型(英语:Polymorphism),是指物件導向程式執行時,相同的訊息可能會送給多個不同的類別之物件,而系統可依據物件所屬類別,引發對應類別的方法,而有不同的行為。簡單來說,所謂多型意指相同的訊息給予不同的物件會引發不同的動作稱之。 |
| 46 | +
|
| 47 | +再简化的说法就是“有多种形式”,就算不知道变量(参数)所引用的对象类型,也一样能进行操作,来者不拒。比如上面显示的例子。在python中,更为pythonic的做法是根本就不进行类型检验。 |
| 48 | + |
| 49 | +例如著名的`repr()`函数,它能够针对输入的任何对象返回一个字符串。这就是多态的代表之一。 |
| 50 | + |
| 51 | + >>> repr([1,2,3]) |
| 52 | + '[1, 2, 3]' |
| 53 | + >>> repr(1) |
| 54 | + '1' |
| 55 | + >>> repr({"lang":"python"}) |
| 56 | + "{'lang': 'python'}" |
| 57 | + |
| 58 | +使用它写一个小函数,还是作为多态代表的。 |
| 59 | + |
| 60 | + >>> def length(x): |
| 61 | + ... print "The length of", repr(x), "is", len(x) |
| 62 | + ... |
| 63 | + |
| 64 | + >>> length("how are you") |
| 65 | + The length of 'how are you' is 11 |
| 66 | + >>> length([1,2,3]) |
| 67 | + The length of [1, 2, 3] is 3 |
| 68 | + >>> length({"lang":"python","book":"itdiffer.com"}) |
| 69 | + The length of {'lang': 'python', 'book': 'itdiffer.com'} is 2 |
| 70 | + |
| 71 | +不过,多态也不是万能的,如果这样做: |
| 72 | + |
| 73 | + >>> length(7) |
| 74 | + The length of 7 is |
| 75 | + Traceback (most recent call last): |
| 76 | + File "<stdin>", line 1, in <module> |
| 77 | + File "<stdin>", line 2, in length |
| 78 | + TypeError: object of type 'int' has no len() |
| 79 | + |
| 80 | +报错了。看错误提示,明确告诉了我们`object of type 'int' has no len()`。 |
| 81 | + |
| 82 | +在诸多介绍多态的文章中,都会有这样关于猫和狗的例子。这里也将代码贴出来,读者去体会所谓多态体现。其实,如果你进入了python的语境,有时候是不经意就已经在应用多态特性呢。 |
| 83 | + |
| 84 | + #!/usr/bin/env python |
| 85 | + # coding=utf-8 |
| 86 | + |
| 87 | + "the code is from: http://zetcode.com/lang/python/oop/" |
| 88 | + |
| 89 | + __metaclass__ = type |
| 90 | + |
| 91 | + class Animal: |
| 92 | + def __init__(self, name=""): |
| 93 | + self.name = name |
| 94 | + |
| 95 | + def talk(self): |
| 96 | + pass |
| 97 | + |
| 98 | + class Cat(Animal): |
| 99 | + def talk(self): |
| 100 | + print "Meow!" |
| 101 | + |
| 102 | + class Dog(Animal): |
| 103 | + def talk(self): |
| 104 | + print "Woof!" |
| 105 | + |
| 106 | + a = Animal() |
| 107 | + a.talk() |
| 108 | + |
| 109 | + c = Cat("Missy") |
| 110 | + c.talk() |
| 111 | + |
| 112 | + d = Dog("Rocky") |
| 113 | + d.talk() |
| 114 | + |
| 115 | +保存后运行之: |
| 116 | + |
| 117 | + $ python 21101.py |
| 118 | + Meow! |
| 119 | + Woof! |
| 120 | + |
| 121 | +代码中有Cat和Dog两个类,都继承了类Animal,它们都有`talk()`方法,输入不同的动物名称,会得出相应的结果。 |
| 122 | + |
| 123 | +关于多态,有一个被称作“鸭子类型”(duck typeing)的东西,其含义在维基百科中被表述为: |
| 124 | + |
| 125 | +>在程序设计中,鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试(见下面的“历史”章节),“鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。” |
| 126 | +
|
| 127 | +对于鸭子类型,也是有争议的。这方面的详细信息,读者可以去看有关维基百科的介绍。 |
| 128 | + |
| 129 | +对于多态问题,最后还要告诫读者,类型检查是毁掉多态的利器,比如type、isinstance以及isubclass函数,所以,一定要慎用这些类型检查函数。 |
| 130 | + |
| 131 | +##封装和私有化 |
| 132 | + |
| 133 | +在正式介绍封装之前,先扯个笑话。 |
| 134 | + |
| 135 | +>某软件公司老板,号称自己懂技术。一次有一个项目要交付给客户,但是他有不想让客户知道实现某些功能的代码,但是交付的时候要给人家代码的。于是该老板就告诉程序员,“你们把那部分核心代码封装一下”。程序员听了之后,迷茫了。 |
| 136 | +
|
| 137 | +不知道你有没有笑。 |
| 138 | + |
| 139 | +“封装”,是不是把代码写到某个东西里面,“人”在编辑器中打开,就看不到了呢?除非是你的显示器坏了。 |
| 140 | + |
| 141 | +在程序设计中,封装(Encapsulation)是对object的一种抽象,即将某些部分隐藏起来,在程序外部看不到,即无法调用(不是人用眼睛看不到那个代码,除非用某种加密或者混淆方法,造成现实上的困难,但这不是封装)。 |
| 142 | + |
| 143 | +要了解封装,离不开“私有化”,就是将类或者函数中的某些属性限制在某个区域之内,外部无法调用。 |
| 144 | + |
| 145 | +python中私有化的方法也比较简单,就是在准备私有化的属性(包括方法、数据)名字前面加双下划线。例如: |
| 146 | + |
| 147 | + #!/usr/bin/env python |
| 148 | + # coding=utf-8 |
| 149 | + |
| 150 | + __metaclass__ = type |
| 151 | + |
| 152 | + class ProtectMe: |
| 153 | + def __init__(self): |
| 154 | + self.me = "qiwsir" |
| 155 | + self.__name = "kivi" |
| 156 | + |
| 157 | + def __python(self): |
| 158 | + print "I love Python." |
| 159 | + |
| 160 | + def code(self): |
| 161 | + print "Which language do you like?" |
| 162 | + self.__python() |
| 163 | + |
| 164 | + if __name__ == "__main__": |
| 165 | + p = ProtectMe() |
| 166 | + print p.me |
| 167 | + print p.__name |
| 168 | + |
| 169 | +运行一下,看看效果: |
| 170 | + |
| 171 | + $ python 21102.py |
| 172 | + qiwsir |
| 173 | + Traceback (most recent call last): |
| 174 | + File "21102.py", line 21, in <module> |
| 175 | + print p.__name |
| 176 | + AttributeError: 'ProtectMe' object has no attribute '__name' |
| 177 | + |
| 178 | +查看报错信息,告诉我们没有`__name`那个属性。果然隐藏了,在类的外面无法调用。再试试那个函数,可否? |
| 179 | + |
| 180 | + if __name__ == "__main__": |
| 181 | + p = ProtectMe() |
| 182 | + p.code() |
| 183 | + p.__python() |
| 184 | + |
| 185 | +修改这部分即可。其中`p.code()`的意图是要打印出两句话:`"Which language do you like?"`和`"I love Python."`,`code()`方法和`__python()`方法在同一个类中,可以调用之。后面的那个`p.__python()`试图调用那个私有方法。看看效果: |
| 186 | + |
| 187 | + $ python 21102.py |
| 188 | + Which language do you like? |
| 189 | + I love Python. |
| 190 | + Traceback (most recent call last): |
| 191 | + File "21102.py", line 23, in <module> |
| 192 | + p.__python() |
| 193 | + AttributeError: 'ProtectMe' object has no attribute '__python' |
| 194 | + |
| 195 | +如愿以偿。该调用的调用了,该隐藏的隐藏了。 |
| 196 | + |
| 197 | +用上面的方法,的确做到了封装。但是,我如果要调用那些私有属性,怎么办? |
| 198 | + |
| 199 | +可以使用`property`函数。 |
| 200 | + |
| 201 | + #!/usr/bin/env python |
| 202 | + # coding=utf-8 |
| 203 | + |
| 204 | + __metaclass__ = type |
| 205 | + |
| 206 | + class ProtectMe: |
| 207 | + def __init__(self): |
| 208 | + self.me = "qiwsir" |
| 209 | + self.__name = "kivi" |
| 210 | + |
| 211 | + @property |
| 212 | + def name(self): |
| 213 | + return self.__name |
| 214 | + |
| 215 | + if __name__ == "__main__": |
| 216 | + p = ProtectMe() |
| 217 | + print p.name |
| 218 | + |
| 219 | +运行结果: |
| 220 | + |
| 221 | + $ python 21102.py |
| 222 | + kivi |
| 223 | + |
| 224 | +从上面可以看出,用了`@property`之后,在调用那个方法的时候,用的是`p.name`的形式,就好像在调用一个属性一样,跟前面`p.me`的格式相同。 |
| 225 | + |
| 226 | +看来,封装的确不是让“人看不见”。 |
| 227 | + |
| 228 | +------ |
| 229 | + |
| 230 | +[总目录](./index.md) | [上节:类(5)](./210.md) | [下节:更多类属性](./212.md) |
0 commit comments