7.1 对象的魔力
- 多态:对不同类的对象使用同样的操作
- 封装:对外部世界隐藏对象的工作细节
- 继承:以普通的类为基础建立专门的类对象
7.1.1 多态
- 多态意味着不知道变量引用的对象类型是什么,还是能对它进行操作,而且会根据对象类型的不同表现出不同的行为。对于一个商品,可以用元组的形式考虑它,比如(‘SPAM’, 2.50),假设商品的价格随时变化,就不能用元组的方式固定商品的价格,此时可以用一个函数来获取
def getPrice(object)
if isinstance(object, tuple):
return object[1]
else:
return magic_network_method(object)
- 有时候会用十六进制数的字符串来表示价格,然后存储在字典的键”price”下面,更新函数
def getPrice(object)
if isinstance(object, tuple):
return object[1]
elif isinstance(object, dict):
return int(object['price'])
else:
return magic_network_method(object)
- 但是我们不可能考虑所有的可能性,每次实现价格对象的不同功能时,都需要更新函数,这是不切实际的。可以让对象自己操作,每个新的对象类型都可以检索和计算自己的价格并且返回结果,这时可以用多态。
1. 多态和方法
程序得到了一个对象,只需要直接询问价格,如下:
>>>object.getPrice()
2.5
- 绑定在对象特性上面的函数称为方法。例如
>>>'abc'.count('a')
1
>>>[1, 2, 'a'].count('a')
1
- 对于对象来说,不需要知道它是字符串还是列表,就可以调用它的count方法。
>>>from random import choice
>>>x = choice(['Hello, world!', [1, 2, 'e', 'e', 4]])
>>>x.count('e')
2
2. 多态的多钟形式
- 当不知道对象的类型,又想对对象进行操作的时候,可以使用多态。不仅限于方法,很多内建运算符和函数都有多态的性质。
>>>1+2
3
>>>'Fish' + 'license'
'Fishlicense'
- 如果需要编写打印对象长度消息的函数,只需要对象具有长度即可。repr函数是多态特性的代表之一
def length_message(x)
print 'The length of ', repr(x), 'is', len(x)
- 唯一能够毁掉多态的就是使用函数显示的检查类型,比如type,isinstance以及issubclass函数等,如果可能的话,应该尽力避免使用这些毁掉多态的方式。真正重要的是如何让对象按照你所希望的方式工作,不管它是否是正确的类型。
7.1.2 封装
- 封装是对全局作用域中其他区域隐藏多余信息的原则,和多态类似,适用对象不需要知道其内部细节,它们都是抽象的原则,都会帮忙处理程序组件而不用过多关心多余细节。
- 多态可以让用户对于不知道是什么类的对象进行方法调用,而封装是可以不用关心对象是如何构建的而直接进行使用。
>>>o = OpenObject()
>>>o.setName('AAA')
>>>o.getName()
'AAA'
如果o将它的名字绑定到全局变量globalName中,
>>>globalName
'AAA'
这就意味着在使用OpenObject类的实例的时候,不得不关注globalName的内容,如果创建了多个OpenObject的实例,就会出现混淆
>>>o1 = OpenObject()
>>>o2 = OpenObject()
>>>o1.setName('BBB')
>>>o2.getName()
'BBB'
因此需要将对象看做抽象,调用方法的时候不需要关心其他东西,即“名字”被封装在对象内,作为对象的特性存储。正如方法一样,特性是对象内部的变量,方法是绑定到函数的特性。
7.1.3 继承
- 有时候为了避免代码的重复,而仅仅是想扩充一个原有类的方法,可以使用继承。
7.2 类和类型
7.2.1 类是什么
- 类是一种对象,所有的对象都属于同一个类,称为类的实例。当一个对象所属的类是另外一个对象所属类的自己时,前者就被称为后者的子类。相反后者称为超类。类的定义取决于它所支持的方法,类的所有实例都会包含这些方法,所以所有的子类的所有实例都有这些方法,定义子类只是定义更多方法的过程。
7.2.2 创建自己的类
__metaclass__ = type # 确定使用新式类
class Person:
def sefName(self, name):
self.name = name
def getName(self):
return self.name
def greet(self):
print "Hello, world! I'm %s." %self.name
7.2.3 特性、函数和方法
- self参数正是方法和函数的区别,方法(绑定方法)将它们的第一个参数绑定到所属实例上,因此这个参数可以不必提供,所以可以将特性绑定到一个普通函数上,这样就不会有特殊的self参数了。
>>> class Class:
def method(self):
print 'I have a self!'
>>>def function()
print "I don't..."
>>>instance = Class()
>>>instance.method()
'I have a self!'
>>>instance.method = function
>>>instance.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
尽管最后一个方法调用看起来与函数调用十分类似,但是变量birdosng引用绑定方法bird.sing上,也就意味着这还是对self参数的访问。
再论私有化
- 默认情况下,程序可以从外部访问一个对象的特性
>>>c.name
‘AAA'
>>>c.name = 'BBB'
>>>c.getName()
'BBB'
有些人觉得这样破坏了封装的原则,他们认为对象的状态对于外部应该是完全隐藏的。Python并不直接支持私有方式,而要靠程序员自己把握在外部进行特性修改的时机。为了让方法或者特性变为私有,只要在它的名字前加上双下划线即可。
class Secretive:
def __inaccessible(self):
print "Bet you can't see me..."
def accessible(self):
print "The secret message is:"
self. __inaccessible()
- 现在__inaccessible从外界是无法访问的,而在类内部还能访问。类的内部定义中,所有以双下划线开始的名字都被“翻译”成前面加上单下划线和类名的形式。
>>>Secretive._Secretive_inaccessible
<unbound method Secretive.__inaccessible>
所以在类外部还是可以访问方法,尽管不应该这么做。简而言之,确保别人不会访问对象的方法和特性是不可能的,但是这类“名称变化术“就是他们不应该访问这些函数或者特性的强有力信号。如果不需要使用这种方法但是又想让其他对象不要访问内部数据,可以使用单下划线。前面有下划线的名字都不会被带星号的imports语句导入。
7.2.4 类的命名空间
- 下面两个语句几乎等价
def foo(x): return x*x
foo = lamda x: x*x
两者都创建了返回参数平方的函数,而且都将变量foo绑定到函数上,变量foo可以在全局范围定义,也可处于局部的函数或方法内。定义类时,同样的事情也会发生,所有位于class语句中的代码都在特殊的命名空间中执行——类命名空间。这个命名空间可由类内所有成员访问,并不是所有所有的Python程序都知道类的定义其实就是执行代码块。
class MemberCounter:
members = 0
def init(self):
MemberCounter.members += 1
>>>m1 = MemberCounter()
>>>m1.init()
>>>MemberCounter.members
1
>>>m2 = MemberCounter()
>>>m2.init()
>>>MemberCounter.members
2
就像方法一样,类作用域内的变量也可以被所有实例访问
>>>m1.members
2
>>>m2.members
2
重新绑定member的特性
>>>m1.members = 'Two'
>>>m1.members
'Two'
>>>m2.members
2
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):
self.blocked = ['SPAM']
Filter是过滤序列的通用类,事实上它不能过滤任何东西。
>>>f = Filter()
>>>f.init()
>>>f.filter([1,2,3])
[1,2,3]
Filter类的用处在于它可以用作其他类的基类,比如SPAMFilter类,可以将序列中的”SPAM”过滤出去。
>>>s = SPAMFilter()
>>>s.init()
>>>s.filter(['SPAM', 'SPAM', 'SPAM', 'SPAM', 'eggs', 'bacon', 'SPAM'])
>>>['eggs', 'bacon']
7.2.6 调查继承
- 如果想要查看一个类是不是另一个类的子类,可以使用内建的issubclass函数
>>>issubclass(SPAMFilter, Filter)
True
>>>issubclass(Filter, SPAMFilter)
False
- 如果想要知道已知类的基类,可以直接使用它的特殊特性:
>>>SPAMFilter.__class__
(<class __main__.Filter at Ox171e40>,)
>>>Filter.__bases__
()
同样还可以使用isinstance方法检查一个对象是否是一个类的实例。
>>>s = SPAMFilter()
>>>isinstance(s, SPAMFilter)
True
>>>isinstance(s, Filter)
True
>>>isinstance(s, str)
False
7.2.7 多个超类
- 子类的基类可以有多个,它自己可以不做任何事,从自己的超类继承所有的行为。
class Calculator:
def calculate(self, expression):
self.value = eval(expression)
class Talker:
def talk(self):
print 'Hi, my value is', self.value
class TalkingCalculator(Calculator, Talker):
pass
>>>tc = TalkingCalculator()
>>>tc.calculate('1+2+3')
>>>tc.talk()
Hi, my value is 7
- 当使用多重继承的时候,如果一个方法从多个超类继承,那么必须要注意一下超类的顺序:先继承的类中方法会重写后继承的类中的方法
7.2.8 接口和内省
- 接口的概念与多态有关,处理多态对象时,只关心它的接口即可,也就是公开的方法和特性。在Python中,不用显式地指定对象必须包含哪些方法才能作为参数接收。例如,不用显式地编写接口,可以在使用对象的时候假定它可以实现你所要求的行为,如果它不能实现的话,程序就会失败。
- 一般来说只需要让对象符合当前的接口,但是还可以更灵活一些,除了调用方法然后期待一切顺利之外,还可检查所需方法是否已经存在。
>>>hasatter(tc, 'talk')
True
>>>hasattr(tc, 'fnord')
False
7.3 一些关于面向对象设计的思考
- 将属于一类的对象放在一起,如果一个函数操纵一个全局变量,那么两者做好都在类内作为特性和方法出现。
- 不要让对象过于亲密。方法应该只关心自己实例的特性,让其他实例管理自己的状态。
- 要小心继承,尤其是多重继承。继承机制有时很有用,但也会在某些情况让事情变得过于复杂,多继承难以正确使用,更难以调试。
- 简单就好,让你的方法小巧。一般来说,多数方法都应能在30s内被读完(以及理解),尽量将代码行数控制在一页或者一屏之内。
当考虑需要什么类以及类要有什么方法时,应该尝试下面的方法。 - 写下问题的描述(需要做什么),把所有名词,动词和形容词下面加上划线
- 对于所有名词,用作可能的类
- 对于所有的动词,用作可能的方法
- 对于所有的形容词,用作可能的特性
- 把所有方法和特性分配到类
7.4 小结
- 对象:对象包括特性和方法,特性只是作为对象的一部分的变量,方法则是存储在对象内的函数。(绑定)方法和其他函数的区别在于方法总是将对象作为自己的第一个参数,这个参数一般叫做self
- 类:类代表对象的集合,每个对象都有一个类,类的主要任务是定义它的实例会用到的方法
- 多态:多态是实现将不同类型和类的对象进行同样对待的特性
- 封装:对象可以将它们的内部状态隐藏起来
- 继承:一个类可以是一个或者多个类的子类,子类从父类继承所有方法,可以使用多个超类。
- 接口和内省
- 面向对象设计