写在之前
我们在之前说过,「封装」「继承」 和「多态」是 OOP 的重要特征,前面我们已经讲过了继承,今天我们来学习的是 「封装」和「多态」。这里更多的是针对初学 Python 的读者谈谈如何去理解 这俩兄弟。
「封装」是对具体对象的一种抽象,简单来说就是将某些部分隐藏起来,在程序外部看不到,这个看不到不是说人用眼睛看不到那个代码,其含义是其它的程序无法调用。
「多态」就如同它的名字一样,在理解上也是「多态」的,算是各有千秋。建议各位在看完本篇文章以后,也要多 Google 一下相关的内容再加深理解。
封装
想要了解封装,就免不了要提到「私有化」。私有化就是将类或者函数中的某些属性限制在某个区域内,从而让外部无法调用。
Python 中私有化的方法相对来说也比较简单,就是在准备私有化的属性或方法名字前面加上双下滑线。我们来看下面的例子:
class Sample:
def __init__(self):
self.my_name = 'rocky'
self.__name = 'snow'
def __python(self):
print('i love python')
def use_code(self):
print('which is your love?')
self.__python()
if __name__ == "__main__":
s = Sample()
print(s.my_name)
print(s.__name)
复制代码
然后我们来运行一下,看一下结果:
rocky
Traceback (most recent call last):
File "test.py", line 16, in <module>
print(s.__name)
AttributeError: Sample instance has no attribute '__name'
复制代码
竟然报错了,我们查看一下报错的信息,显示的是我们没有 __name 属性。果然前面加上双下划线以后就被隐藏了,在类的外面无法被调用,我们再来试试那个函数是否可以使用,修改一下:
class Sample:
def __init__(self):
self.my_name = 'rocky'
self.__name = 'snow'
def __python(self):
print('i love python')
def use_code(self):
print('which is your love?')
self.__python()
if __name__ == "__main__":
s = Sample()
s.use_code()
s.__python()
复制代码
s.use_code() 的结果是要打印两句话: “which is your love?” 和 “i love python”,use_code() 方法和 __python() 方法是在同一个类中,可以调用。后面的 s.__python() 试图调用被私有化的方法,我们运行一下来看结果:
which is your love?
i love python
Traceback (most recent call last):
File "test2.py", line 16, in <module>
s.__python()
AttributeError: Sample instance has no attribute '__python'
复制代码
还是报错,告诉我们没有找到 __python 方法。你看,我们是如愿以偿,该调用的调用,该隐藏的隐藏了。虽然用上面的方法确实的做到了「封装」,但是如果我们想要调用那些私有属性的时候该怎么办?Python 当然给我们想了办法,使用 property 装饰器。我们来看下面的例子:
class Sample:
def __init__(self):
self.my_name = 'rocky'
self.__name = 'snow'
@property
def name(self):
return self.__name
if __name__ == "__main__":
s = Sample()
print(s.name)
复制代码
运行的结果如下:
snow
复制代码
从上面的结果可以看出,用了 @property 这个装饰器以后,在调用那个方法的时候,用的是 s.name 的形式,这就好像是在调用一个属性一样,跟前面例子中的 s.my_name 的格式一样。
看来,封装的确不是让人 “看不见” 啊。
多态
首先让我们来看一个例子:
>>> 'my name is rocky'.count('m')
2
>>> [1,2,3,4,5,3].count(3)
2
复制代码
上述例子中 count() 的作用是计数,数数某个元素在对象中出现的次数。在上面的例子里我们并没有限定参数的类型,类似的例子还有:
>>> f = lambda x,y:x+y
复制代码
还记得上面的上面的 lambda 函数吗?不记得的请看Python拓展之特殊函数。
>>> f('ro','cky')
'rocky'
>>> f(2,3)
5
>>> f(['c','c++'],['java','python'])
['c', 'c++', 'java', 'python']
复制代码
在上面的例子中我们同样也没有限定参数的类型,当然了也一定不能限制,如果限制的话,那么 Python 就失去了它最舒服的简洁的特性。在使用的时候可以给参数任意适合的类型,总能得到不错的结果。
其实以上就体现了「多态」,即同一种行为具有不同的表现形式和形态的能力,也可以说就是对象多种表现形式的体现。
当然,也有人对此提出了反对意见,因为本质上在参数传入之前,Python 并没有确定参数的类型,所以只能让数据进入函数之后再处理,能处理则最好,不能处理只能罢工报错了。就像下面一样:
>>> f('roc',2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <lambda>
TypeError: cannot concatenate 'str' and 'int' objects
复制代码
对于这种概念之争就比如先有的鸡还是鸡蛋之争,所以我们不来判断哪种对或者哪种错,这个也不是我们本篇文章的重点,在这仅仅是把相关的信息告诉给大家。
「多态」在一些地方也被称为「多型」。我们来看一下权威的《维基百科》中对此的详细解释:
多型(Polymorphism),是指对象导向程式执行时,相同的信息可能会送给多个不同的类别对象,而系统可依据物件所属类别,引发对应类别的方法,而有不同的行为。简单来说,所谓多型意指相同的信息给予不同的对象会引发不同的动作。
简单的说法就是有多种形式,就算不知道变量或参数所引用的对象类型,也一样可以进行操作,可以说是来着不拒了。就像上面的例子那样。
再比如,我们之前说过的 repr() 函数,它可以根据输入的任意对象返回一个字符串,这个就是 “多态” 的代表之一。
>>> repr([1,2,3,4,5])
'[1, 2, 3, 4, 5]'
>>> repr(123)
'123'
>>> repr({'name':'rocky'})
"{'name': 'rocky'}"
复制代码
使用它来实现一个小函数,依然还是多态的代表:
>>> length('what is your name')
the length of 'what is your name' is 17
>>> length([1,2,3])
the length of '[1, 2, 3]' is 3
复制代码
当然了,多态不是万能的,比如像下面这样做:
>>> length(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in length
TypeError: object of type 'int' has no len()
复制代码
上面的例子出错了,出错的愿意根据错误提示,明确的告诉我们 “type 'int' has no len()”。
上述的种种多态表现,全是因为 Python 是一种不需要预编译的语言,只在运行的时候才确定状态(虽然最终还是编译了)。所以可以这么来说,Python 就被认为天生是一种多态的语言。当然了也有人持有相反的观点,认为 Python 不支持多态,理由也是上面的说法。就比如长跑最后的一公里,有的人想就只剩一公里了,也有的人想竟然还有一公里,是一个道理。
鸭子类型
我们在前面所说的,Python 不检查传入的对象的类型,这种方式通俗的被称为「鸭子类型」,比较高端的方式是叫做「隐式类型」或者「结构式类型」。鸭子类型这个命名源于一句名言:
如果它像鸭子一样走路,像鸭子一样叫,那么它就是一只鸭子。
鸭子类型就意味着可以向任何对象发送任何的消息,语言只关心这个对象能不能接收该消息,不会去强求该对象是否为某一种特定的类型 —— 该对象的多态表现。
多态的介绍大概就是这些,对于Python 多态之间的争论我们看看就好,不要太过于在意,我们只要明白了我们该明白的就好。
写在最后
更多内容,欢迎关注公众号「Python空间」,期待和你的交流。