第7章 更加抽象
创建自己的对象(类型或者类的对象)是Python的核心概念——非常核心。事实上,Python被称为面向对象的语言。
7.1. 对象的魔力
面向对象程序设计中的属于对象(object)基本上可以看作数据(特性)以及由一系列可以存取、操作这些数据的方法所组成的集合。
多态:即使不知道变量所引用的对象类型是什么,还是能对她进行操作,而它会根据对象(或类)类型的不同而表现出不同的行为。
封装:对全局作用域中其他区域隐藏多余信息的原则。不用关心对象是如何构建的。
继承:子类可以从超类继承方法,在子类上调用方法时,程序会自动从超类调用该方法。
7.2. 类和类型
7.2.2. 创建自己的类
创建类的格式
>>> __metaclass__ = type #确定使用新式类
>>> class Person:
... def set_name(self, name):
... self.name = name
... def get_name(self):
... return self.name
... def greet(self):
... print "Hello, world! I'm %s." % self.name
...
关于类有以下说明:
1. 旧式类和新式类有区别。除非Python3.0之前默认附带的代码,没必要使用旧式类。在新式类中,要在模块或脚本开始的地方放置赋值语句metaclass = type。
2. class语句会在函数定义的地方创建自己的命名空间。
3. 方法参数的self表示对象自己。在方法被调用时,自动将对向自己传递给方法(不需要用户显式传递)。例如:
```
>>> foo=Person()
>>> foo.set_name('holly')
>>> foo.get_name()
'holly'
>>> foo.greet()
Hello, world! I'm holly.
```
4. 类的特性(例如例子中self.name)可以在外部访问。
```
>>> foo.name
'holly'
```
7.2.3. 特性、函数和方法
方法将它的第一个参数绑定到所属的实例上,调用方法时可以不必提供该参数。可以将特性绑定到普通函数上,就不会有特殊的self参数了。
>>> class Class: ... def method(self): ... print 'I have a self!' ... >>> def function(): ... print "I don't..." ... >>> foo = Class() >>> foo.method() I have a self! >>> foo.method = function >>> foo.method() I don't...
self参数不取决于调用方法的方式,可以随意使用引用同一个方法的其他变量。
>>> class Bird: ... song = 'Squaawk!' ... def sing(self): ... print self.song ... >>> bird = Bird() >>> bird.sing() Squaawk! >>> birdsong = bird.sing >>> birdsong() Squaawk!
为了让方法或特性变为私有,只要在它的名字前添加双下划线。
>>> class Secretive: ... def __inaccessible(self): #定义私有方法 ... print 'Bet you can\'t see me...' ... def accessible(self): ... print 'The secret message is:' ... self.__inaccessible() ... >>> s = Secretive() #外部无法访问私有方法 >>> s.__inaccessible() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Secretive' object has no attribute '__inaccessible' >>> s.accessible() The secret message is: Bet you can't see me...
★在类的内部定义中,所有双下划线开始的名字都被“翻译”称前面加上下划线和类名的形式。因此可以采用这种方式访问私有方法。
>>> Secretive._Secretive__inaccessible <unbound method Secretive.__inaccessible>
- 不想使用3中的方法但是又想让其他对象不要访问内部数据,可以使用单下划线。但下划线的名字不会被带星号的imports语句(from module import *)导入。
有些语言支持多种层次的成员变量(特性)私有性。比如Java就支持4中级别。尽管单双下划线在某种程度上给出两个级别的私有性,但Python并没有真正的私有化支持。
7.2.4. 类的命名空间
定义类时,所有位于class语句中的代码都在特殊的命名空间中执行——类命名空间(class namespace)。该空间可由类内所有成员访问。
★Python中,类的定义就是执行代码块,例如:
>>> class C:
... print 'Class C being defined...'
...
Class C being defined...
书中对命名空间的说明有点简略,可参考
http://blog.csdn.net/heli200482128/article/details/74990347
7.2.5. 指定超类
将其他类名写在class语句后的圆括号内可以指定超类。
>>> class Filter:
... def init(self):
... self.blocked = []
... def filter(self, sequence):
... return [x for x in sequence if x not in self.blocked]
...
>>> class SPAMFilter(Filter):
... def init(self): #重载init()
... self.blocked = ['SPAM']
...
>>> s = SPAMFilter()
>>> s.init()
#filter方法是从Filter类中继承的
>>> s.filter(['SPAM', 'SPAM','eggs','bacon','SPAM'])
['eggs', 'bacon']
7.2.6. 调查继承
★参照7.2.5,假定SPAMFilter是Filter的子类。
查看一个类是否是另一个的子类,可使用内建函数issubclass()。
>>> issubclass(SPAMFilter, Filter) True >>> issubclass(Filter, SPAMFIlter) False
若想知道已知类的基类,可以使用特性__bases__
>>> SPAMFilter.__bases__ (<class '__main__.Filter'>,)
检查对象是否是一个类的实例,可以使用函数isinstance()
>>> s = SPAMFilter() >>> isinstance(s, SPAMFilter) #s是SPAMFilter类的直接实例 True >>> isinstance(s, Filter) #s是SPAMFilter类的间接实例 True >>> isinstance(s, str) False
直接检查对象属于哪个类,可以使用__class__特性
>>> s.__class__ <class '__main__.SPAMFilter'>
7.2.7. 多个超类
多重继承(multiple inheritance)**:子类有多个基类
>>> class Calculator: ... def calculate(self, expression): ... self.value = eval(expression) ... >>> class Talker: ... def talk(self): ... print 'Hi, my value is', self.value ... >>> class TalkingCaculator(Calculator, Talker): ... pass ... >>> tc = TalkingCaculator() >>> tc.calculate('1+2*3*4/6') >>> tc.talk() Hi, my value is 5
2.多重继承需要注意多个超类的继承顺序:先继承的类中的方法会重写后继承的类的方法。 即上个例子中,若Calculator类中也有talk()方法,由于先继承Calculator后继承Talker,当外部调用talk()方法时,将调用Calculator中的talk()。
3. 若超类们共享了一个超类,在查找给定方法或者特性时,访问超类的顺序称为MRO(Method Resolution Order,方法判定顺序)。
7.2.8. 接口和内省
- 接口:即公开的方法和特性。这个概念于多态有关。
- Python中,不需要显式指定对象必须包含哪些方法才能作为参数接收。使用对象时,默认对象可以实现要求的方法,若不能实现,程序会失败。
调用对象时,可以使用函数hasattr()、callable()、hasattr(x, ‘__call__’)检查方法是否存在,避免上述问题。
7.3. 一些关于面向对象设计的思考
面向对象设计要点
♦将属于一类的对象放在一起。若一个函数操纵一个全局变量,两者最好作为类内的特性和方法。
♦不要让对象过于亲密。方法应只关心自己实例的特性。让其它实例管理自己的状态。
♦小心继承,尤其是多重继承。继承机制有时很有用,但某些情况下让事情变得过于复杂。多继承难以正确使用,更难以调试。
♦简单就好。让方法小巧。一般来说,多数方法都应能在30秒内被理解,尽量将代码行数控制在一页或一屏之内。
建立面向对象模型方法
♦写下问题的描述,把名词、动词、形容词加下划线。
♦对于所有名词,用作可能的类。
♦对于所有动词,用作可能的方法。
♦对于所有形容词,用作可能的特性。
♦把所有方法和特性分配到类。
精炼模型步骤
♦设计一系列使用实例——即程序应用时的场景,试着包括所有的功能。
♦逐个考虑每个场景,保证模型包含所有需要的东西。若有遗漏,则添加;若有错误,则修正。直到满意为止。