type
status
date
slug
summary
tags
category
icon
password
Property
访问限制
在
Class
内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样就隐藏了内部的复杂逻辑。但是从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的
name
、score
属性:如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线
__
,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问:这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
但是如果外部代码要获取
name
和score
怎么办?可以给Student
类增加get_name
和get_score
这样的方法:如果又要允许外部代码修改
score
怎么办?可以再给Student
类增加set_score
方法:原先那种直接通过
bart.score = 99
也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:注:在
Python
中,变量名类似__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private
变量,所以,不能用__name__
、__score__
这样的变量名。以一个下划线开头的实例变量名,比如
_name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,这样的变量意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问
__name
是因为Python
解释器对外把__name
变量改成了_Student__name
,所以可以通过_Student__name
来访问__name
变量:但是强烈建议不要这么干,因为不同版本的
Python
解释器可能会把__name
改成不同的变量名。总的来说就是,Python
本身没有任何机制阻止你干坏事,一切全靠自觉。注意下面的这种错误写法:
表面上看,外部代码“成功”地设置了
__name
变量,但实际上这个__name
变量和class
内部的__name
变量不是一个变量!内部的__name
变量已经被Python
解释器自动改成了_Student__name
,而外部代码给bart
新增了一个__name
变量:继承
在
OOP
程序设计中,当定义一个class的时候,可以从某个现有的class
继承,新的class
称为子类(Subclass),而被继承的class
称为基类、父类或超类(Base class、Super class)。比如已经编写了一个名为
Animal
的class
,有一个run()
方法可以直接打印:当需要编写
Dog
和Cat
类时,就可以直接从Animal
类继承:对于
Dog
来说,Animal
就是它的父类,对于Animal
来说,Dog
就是它的子类。Cat
和Dog
类似。继承最大的好处是子类获得了父类的全部功能。由于
Animial
实现了run()
方法,因此Dog
和Cat
作为它的子类,就自动拥有了run()
方法:当然,也可以对子类增加一些方法,比如
Dog
类:继承的第二个好处需要对代码做一点改进。无论是
Dog
还是Cat
,它们run()
的时候,显示的都是Animal is running...
,符合逻辑的做法是分别显示Dog is running...
和Cat is running...
,因此,对Dog
和Cat
类改进如下:当子类和父类都存在相同的
run()
方法时,子类的run()
覆盖了父类的run()
,在代码运行的时候,总是会调用子类的run()
。这样,就获得了继承的另一个好处:多态。多态
当定义一个class的时候,实际上就定义了一种数据类型。定义的数据类型和
Python
自带的数据类型,比如str
、list
没什么两样:要理解多态的好处,还需要再编写一个函数,这个函数接受一个
Animal
类型的变量:新增一个
Animal
的子类,不必对run_twice()
做任何修改,实际上,任何依赖Animal
作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。多态的好处就是,当需要传入
Dog
、Cat
、Tortoise
……时,只需要接收Animal
类型就可以了,因为Dog
、Cat
、Tortoise
……都是Animal
类型,然后,按照Animal
类型进行操作即可。由于Animal
类型有run()
方法,因此,传入的任意类型,只要是Animal
类或者子类,就会自动调用实际类型的run()
方法,这就是多态的意思:对于一个变量,我们只需要知道它是
Animal
类型,无需确切地知道它的子类型,就可以放心地调用run()
方法,而具体调用的run()
方法是作用在Animal
、Dog
、Cat
还是Tortoise
对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当新增一种Animal
的子类时,只要确保run()
方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:对扩展开放:允许新增
Animal
子类;对修改封闭:不需要修改依赖
Animal
类型的run_twice()
等函数。继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:
静态语言 vs 动态语言
对于静态语言(例如Java)来说,如果需要传入
Animal
类型,则传入的对象必须是Animal
类型或者它的子类,否则,将无法调用run()
方法。对于
Python
这样的动态语言来说,不一定需要传入Animal
类型,只需要保证传入的对象有一个run()
方法就可以了:这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
Python
的"file-like object"
就是一种鸭子类型。对真正的文件对象,它有一个read()
方法,返回其内容。但是,许多对象,只要有read()
方法,都被视为"file-like object"
。许多函数接收的参数就是"file-like object"
,不一定要传入真正的文件对象,完全可以传入任何实现了read()
方法的对象。多重继承
继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能
回忆一下
Animal
类层次的设计,假设要实现以下4种动物:- Dog - 狗狗;
- Bat - 蝙蝠;
- Parrot - 鹦鹉;
- Ostrich - 鸵鸟。
如果要再增加“宠物类”和“非宠物类”,这么搞下去,类的数量会呈指数增长,很明显这样设计是不行的。
正确的做法是采用多重继承。首先,主要的类层次仍按照哺乳类和鸟类设计:
通过多重继承,一个子类就可以同时获得多个父类的所有功能
MixIn
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,
Ostrich
继承自Bird
。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich
除了继承自Bird
外,再同时继承Runnable
。这种设计通常称之为MixIn。为了更好地看出继承关系,我们把
Runnable
和Flyable
改为RunnableMixIn
和FlyableMixIn
。类似的,还可以定义出肉食动物CarnivorousMixIn
和植食动物HerbivoresMixIn
,让某个动物同时拥有好几个MixIn:MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
Python自带的很多库也使用了MixIn。举个例子,Python自带了
TCPServer
和UDPServer
这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn
和ThreadingMixIn
提供。通过组合就可以创造出合适的服务来。比如,编写一个多进程模式的TCP服务,定义如下:
编写一个多线程模式的UDP服务,定义如下:
如果你打算搞一个更先进的协程模型,可以编写一个
CoroutineMixIn
:这样一来,不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。