第7章 更加抽象
7.1 对象的魔力
□多态:意味着可以对不同类的对象使用同样的操作。
□封装:对外部世界隐藏对象的工作细节。
□继承:以普通的类为基础建立专门的类对象。
7.1.1 多态
多态意味着,就算不知道变量所引用的对象类型是什么,还是能对它进行操作,而它也会根据对象(或类)类型的不同而表现出不同的行为。
1. 多态和方法
>>> object.getPrice()
2.5
绑定到对象特性上面的函数,称为方法(method)。
7.1.2 封装
封装是对全局作用域中其他区域隐藏多余信息的原则。
特性(attribute,属性)是对象内部的变量;
方法更像是绑定到函数的属性。
7.1.3 继承
如果已经有了一个类,而又想建立一个非常类似的呢?新的类可能只是添加几个方法。在编写新的类时,又不想把旧类的代码全都复制过去。
7.2 类和类型
7.2.1 类到底是什么
7.2.2 创建自己的类
__metaclass__ = type
class Person:
def setName(self, name):
self.name = name
def getName(self):
return self.name
def greet(self):
print "hello, world! i'm %s." % self.name
注意 新式类的语法中,需要在模块或者脚本开始的地方放置赋值语句__metaclass__ = type
self是对于对象自身的引用。
>>> foo = Person()
>>> foo.setName('henry')
>>> foo
<__main__.Person object at 0x7f4f7e17db50>
>>> foo.greet()
hello, world! i'm henry.
>>> Person.greet(foo)
hello, world! i'm henry.
提示 foo是Person的实例,foo.greet()等价于Person.greet(foo)。
7.2.3 特性、函数和方法
self参数正是方法和函数的区别。方法(更专业一点可以称为绑定方法)将它们的第一个参数绑定到所属的实例上,因此这个参数可以不提供。
>>> p = Person()
>>> p.setName('henry')
>>> p.name
'henry'
>>> p.getName()
'henry'
>>> p.name = 'yan'
>>> p.getName()
'yan'
每个对象管理自己的特性还不够吗?为什么还要对外部世界隐藏呢?毕竟,如果能直接使用ClosedObject的name特性的话,就不用使用setName和getName方法了。
关键在于,其他程序员可能不知道(可能也不应该知道)你的对象内部的具体操作。例如,ClosedObject可能会在其他对象更改自己的名字的时候,还有其他的一些动作(例如,给一些管理员发送邮件信息)。这应该是setName方法的一部分。但是,如果直接用p.name设定名字,会发生什么?什么也没发生,Email也没发送出去。为了避免这类事情发生,应该使用私有(private)特性,这是外部对象无法访问,但是getName和setName等访问器(accessor)能够访问的特性。
Python并不直接支持私有方式,而要靠程序员自己把握在外部进行特性修改的时机。
为了让方法或者特性变为私有(从外部无法访问),只要在它的名字前面加上双下划线即可:
__metaclass__ = type
class Secretive():
def __inaccessible(self):
print "you can't see me."
def accessible(self):
print "the secret message is:"
self.__inaccessible()
现在__inaccessible
从外界是无法访问的,而在类内部还能使用(比如从accessible)访问:
>>> s = Secretive()
>>> s.__inaccessible()
Traceback (most recent call last):
File "<pyshell#14>", line 1, in <module>
s.__inaccessible()
AttributeError: 'Secretive' object has no attribute '__inaccessible'
>>> s.accessible()
the secret message is:
you can't see me.
类的内部定义中,所有以双下划线开始的名字都被“翻译”成前面加上单下划线和类名的形式。
>>> Secretive._Secretive__inaccessible
<unbound method Secretive.__inaccessible>
在了解了这些幕后的事情后,实际上还是能在类外访问这些私有方法,尽管不应该这么做:
>>> s._Secretive__inaccessible()
you can't see me.
简而言之,确保其他人不会访问对象的方法和特性是不可能的。
如果不需要使用这种方法但是又想让其他对象不要访问内部数据,那么可以使用单下划线。例如,前面有下划线的名字都不会被带有星号的imports语句(from module import *)导入。
7.2.4 类的命名空间
定义类时,所有位于class语句中的代码都在特殊的命名空间中执行——类命名空间(class namespace)。这个命名空间可由类内所有成员访问。
并不是所有Python程序员都知道类的定义其实就是执行代码块,这一点非常有用,比如,在类的定义区并不只限使用def语句:
>>> class C:
print 'Class C being defined...'
Class C being defined...
7.2.5 指定超类
7.2.6 调查继承
内建的issubclass函数可以查看一个类是否是另一个的子类。
>>> issubclass(SPAMFilter, Filter)
__bases__
可以查看已知类的基类:
>>> class A(object):
... pass
...
>>> A.__bases__
(<type 'object'>,)
isinstance方法可以检查一个对象是否是一个类的实例:
>>> a = A()
>>> isinstance(a, A)
True
注意 使用isinstance并不是个好习惯,使用多态会好一些。
__class__
可以知道一个对象属于哪个类。
>>> a.__class__
<class '__main__.A'>
注意 如果使用__metaclas__ = type
或从object继承的方式来定义新式类,那么,可以使用type(s)查看实例的类。
7.2.7 多个超类
详见书上。
除非非常熟悉多重继承,否则应该尽量避免使用,因为有时候会出现不可预见的麻烦。
7.2.8 接口和内省
“接口”的概念与多态有关。
在处理多态对象时,只要关心它的接口(或称“协议”)即可——也就是公开的方法和特性。
hasattr()可以检查方法是否已经存在:
>>> hasattr(tc, 'talk')
True
对象tc有一个叫做talk的特性(包含一个方法)。
callable能检查talk特性是否可调用:
>>> callable(getattr(tc, 'talk', None))
True
注意 callable函数在Python3.0中已经不再可用。可以使用hasattr(x, __call__
)代替callable(x)。
如果要查看对象内所有存储的值,那么可以使用__dict__
特性。
>>> Person.__dict__
dict_proxy({'__module__': '__main__', 'setName': <function setName at 0x7f7b2abb5140>, 'getName': <function getName at 0x7f7b2abb51b8>, 'greet': <function greet at 0x7f7b2abb5230>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None})
如果想要找到对象是由什么组成的,可以看看inspect模块。
7.4 小结
对象。对象包括属性和方法。属性只是作为对象的一部分的变量,方法则是存储在对象内的函数。(绑定)方法和其他函数的区别在于方法总是将对象作为自己的第一个参数,这个参数一般称为self。
类。
多态。
封装。对象可以将它们内部状态隐藏(或封装)起来。在Python中,所有的特性都是公开可用的。
继承。
接口和内省。
面向对象设计。
参考文献:
1.《Python基础教程(第2版·修订版)》。