Python 更加抽象

更加抽象

前几章介绍了Python主要内建对象类型(数字、字符串、列表、元组和字典),以及内建函数和标准库的用法,还有定义函数的方法。现在看来还差一点——创建自己的对象。为什么要自定义对象呢?创建自己的对象类型可能很酷,但是做什么用呢?使用字典、序列、数字和字符串来创建函数,完成这项工作还不够吗?这样做当然可以,但是创建自己的对象(尤其是类型或者被称为类的对象)是Python的核心概念——非常核心,事实上,Python被称为面向对象的语言(和SmallTalk、C++、Java以及其他语言一样)。本章将会介绍如何创建对象,以及多态、封装、方法、特性、超类以及继承的概念——新知识很多。

7.1 对象的魔力

在面向对象程序设计中,术语对象基本上可以看作数据(特征)以及一系列可以存取,操作这些数据的方法所组成的集合。使用对象代替全局变量和函数的原因可能有很多。其中对象最重要的有点包括以下几个方面。
多态:意味着可以对不同类的对象使用同样的操作,他们会像被“施了魔法一般”工作。
封装:对外部世界隐藏对象的工作细节。
继承:以通用的类为基础建立专门的类对象。
在许多关于面向对象程序设计的介绍中,这几个概念的顺序是不同的。封装和继承会首先被介绍,因为他们被用作现实世界中的对象的模型。这种方法不错,但在我看来,面向对象程序设计最有趣的特征是多态。他也是让大多数人犯晕的特性。所以本章会以多态开始,而且这一个概念就足以让你喜欢面向对象程序设计了。

7.1.1 多态

术语多态来自希腊语,意思是“有多种形态”。多态意味着就算不知道变量所引用的对象类型是什么,还是能对他进行操作,而它也会根据对象(或类)类型的不同而表现出不同的行为。例如,假设一个食品销售的商业网站建立了一个在线支付系统。程序会从系统的其他部分(或者以后可能会设计的其他类似系统)获得一“购物车”中的商品,接下来要做的是算出总价后使用信用卡支付。
当你的程序获得商品时,首先想到的可能是如何具体地表示它们。比如需要将他们作为元组接受,像下面这样:
('SPAM',2.50)
如果需要描述性标签和价格,这样就够了。但是这个程序还不够灵活。我们假设网站支持拍卖服务,价格在货物卖出之前会逐渐降低。如果用户能够把对象放入购物车,然后处理结账,等价格到了满意的程度后按下“支付”按钮就够了。
但是这样一来简单的元组就不能满足需要了。为了实现这个功能,代码每次询问价格的时候,对象都需要检查当前的价格,价格不能固定在元组中。
1、多态和方法
程序接收到一个对象,完全不了解该对象的内部实现方式——他可能有多种“形状”。
绑定到对象特征上面的函数称为方法。
2、多态的多种形式
任何不知道对象到底是什么类型,但是又要对对象做点什么的时候,都会用到多态。
很多函数和运算符都是多态的——你写的绝大多数程序可能都是,即便你并非有意这样。只要使用多态函数和运算符,就会与“多态”发生联系。事实上,唯一能够毁掉多态的就是使用函数显示地检查类型,比如type、isinstance、以及issubclass函数等。如果可能的话,应该尽力避免使用这些毁掉多态的方式,真正重要的就是如何让对象按照你所希望的方式工作,不管他是否是正确的类型(或者类)

7.1.2 封装

封装是指向程序中的其他部分隐藏对象的具体实现细节的原则。听起来有些像多态,也是使用对象而不用其内部细节,两者概念有些相似,因为他们都是抽象原则,他们都会帮助处理程序组件而不用过多关心多余的细节,就像函数做的一样。
但是封装并不同于多态。多态可以让用户对于不知道是什么类的对象进行方法调用,而封装是可以不关心对象是如何构建的而直接进行使用。

7.1.3 继承

继承是另外一个懒惰的行为。程序员不想把一段代码输入好几次。之前使用的函数避免了这种情况,但是现在又有几个微妙的问题。如果已经有一个类,而又相继那里一个非常类似的呢?新的类可能只是添加几个方法。在编写的同时又不想把旧代码全部复制过去。
比如说有个shape类,可以用来在屏幕上画出指定的形状。现在又需要创建一个叫做Rectangle的类,他不但可以在屏幕上显示出指定的形状,而且还能计算出形状的面积。但又不想把Shape里面已经写好的draw方法再写一次。那么该怎么办?可以让Rectangle从Shape类继承方法。在Rectangle对象上调用draw的方法时,程序会自动从Shape类调用该方法。

7.2 类和类型

现在读者可能对什么是类有了大体感觉。

7.2.1 类到底是什么

前面的部分中,类这个词已经多次出现,可以将它或多或少的视为种类或者类型的同义词。从很多方面来说,这就是类——一种对象。所有的对象都属于某一个类,称为类的实例。
例如,鸟就是鸟类的实例。鸟类是一个非常通用(抽象)的类,具有很多子类:鸟可能属于子类百灵鸟。可以将“鸟类”想象成所有鸟的集合,而“百灵鸟类”是其中的一个子集。当一个对象所属的类是另一个对象所属类的子集时,前者就被称为后者的子类,所以百灵鸟类是鸟类的子类。相反,鸟类是百灵鸟的超类。
在日常交谈中,可能经常用复数来描述对象的类,比如birds或者larkes。Python中,习惯上都使用单数名词,并且首字母大写,比如Birds和Lark。
这样比喻,子类和超类就容易理解多了。但是在面向对象程序设计中,子类的关系是隐式的,因为一个类的定义取决于它所支持的方法。类的所有实例都会包含这些方法,所以所有子类的所有实例都有这些方法。定义子类只是个定义更多(也有可能重载已经存在的)的方法的过程。
例如,鸟类Bird可能支持fly方法,而企鹅类Penguin(Bird的子类)可能会增加个eatFish的方法。创建Penguin类时,可能会想要重写超类的fly方法,对于Penguin的实例来说,这个方法要么什么也不做,要么就产生异常,因为penguin不会fly。

7.2.2 创建自己的类

# _*_ coding:utf8 _*_
__mateclass__=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
这个例子包含三个方法定义,除了它们是写在class语句里面外,一切都像是函数定义。Person当然是类的名字。class语句会在函数定义的地方创建自己的命名空间。一切看起来都挺好,但是那个self参数看起来有点奇怪。它是对于对象自身的引用。那么他是什么对象?创建一些实例看看:

foo=Person()
bar=Person()
foo.setName('Luke Skywalker')
bar.setName('Anakin Skywalker')
print foo.greet()
print bar.greet()
输出:

Hello,world! I'm Luke Skywalker.
None
Hello,world! I'm Anakin Skywalker.
None

好了,例子一目了然,应该能说明self的用处了。在调用foo的setName和greet函数时,foo自动将自己作为第一个参数传入函数中——因此形象地命名为self。对于这个变量,每个人可能都会有自己的叫法,但是因为它总是对象自身,所以习惯上总是叫做self。

显然这就是self的用处和存在的必要性。成员方法就没法访问他们要对其特性进行操作的对象本身了。

和之前一样,特性是可以在外部访问的:

print foo.name
bar.name='Yoda'
print bar.greet()
输出:
Luke Skywalker
Hello,world! I'm Yoda.
None
如果直到foo是Person的实例的话,那么还可以把foo.greet()看作Person.greet(foo)方便的简写。

7.2.3 特性、函数和方法

(在前面提到的)self参数事实上正是方法和函数的区别。方法(更专业一点可以称为绑定方法)将他们的第一个参数绑定到所属的实例上,因此你无需显示提供该参数。当然也可以将特性绑定到一个普通函数上,这样就不会有特殊的self参数了:

# _*_ coding:utf8 _*_
__mateclass__=type#确定使用新式类
class Class:
    def method(self):
        return 'I have a self!'
def function():
    return "I don't..."
instance=Class()
print instance.method()
instance.method=function
print instance.method()
输出:

I have a self!
I don't...

注意,self参数并不依赖于调用方法的方式,前面我们使用的是instance.method的形式,可以随意使用其他变量引用同一个方法:

# _*_ coding:utf8 _*_
__mateclass__=type#确定使用新式类
class Bird:
    song='Squaawk'
    def sing(self):
        return self.song
bird=Bird()
print bird.sing()
birdsong=bird.sing
print birdsong()
输出:
Squaawk
Squaawk

尽管最后一个方法调用看起来与函数调用十分相似,但是变量birdsong引用绑定方法bird.sing上,也就意味着这还是会对self参数进行访问。

再论私有化

默认情况下,程序可以从外部访问一个对象的特征。

有些程序员觉得这样做是可以的,但是有些人觉得这样就破坏了封装的原则。他们认为对象的状态对于外部应该是完全隐藏的。有人可能会奇怪为什么他们会站在如此极端的立场上。每个对象管理自己的特征不够吗?为什么还要对外部世界隐藏呢?毕竟如果能直接使用CloseObject和name特征的话就不用使用setName和getName方法了。

关键在于其他程序员可能不知道你的对象的具体操作。例如,CloseObject可能会在其他对象更改自己的名字的时候,给一些管理员发送邮件消息。这应该是setName方法的一部分。但是如果直接用c.name设定名字会发生什么?什么都没发生,Email也没发出去。为了避免这类事情的发生,应该使用是有特征,这是外部对象无法访问,但getName和setName等访问器能够访问的特征。

Python并不直接支持私有方式,而要靠程序员自己把握在外部进行特征修改的时机。毕竟在使用对象前应该如何使用。但是,可以用一些小技巧达到私有特性的效果。

为了让方法或者特征变为私有,只要在它的名字面前加上双下划线即可:

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.accessible()
输出:

The secret message is:
Bet you can't see me...


__inaccessible从外界无法访问,而在类内部还能使用访问。

尽管双下划线有些奇怪,但是看起来像是其他语言中的标准私有方法。真正发生的事情才不是标准的。类的内部定义中,所有双下划线开始的名字都被“翻译”成前面家伙是那个单下划线和类名的形式。

所以,可以这样访问:

s._Secretive__inaccessible()

7.2.4 类的命名空间

定义类时,同样的事也会发生,所有位于class语句中的代码都在特殊命名空间执行——类命名空间。这个命名空间可由类内所有成员访问。

7.2.5 指定超类

子类可以扩展超类的定义。将其他类名写在class语句后的圆括号内可以指定超类:
#_*_ coding:utf8 _*_
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):#SPAMFilter是Filter的子类
    def init(self):#重写Filter超类中的init方法
        self.blocked = ['SPAM']

f = Filter()
f.init()
print f.filter([1,2,3])

s = SPAMFilter()
s.init()
print s.filter(['SPAM','SPAM','SPAM','SPAM','eggs','bacon','SPAM'])
输出:
[1, 2, 3]
['eggs', 'bacon']
这里用提供新定义的方式重写了Filter的init定义。
filter方法的定义是从Filter类中拿过来的,所以不用重写它的定义。
第二个要点就是揭示了继承的用处:我们可以写一大堆不同的过滤类,全都从Filter继承,每一个我都可以使用已经实现的filter方法。

7.2.6 检查继承

如果想要查看一个类是否是否是另一个的子类,可以使用内建的issubclass函数:
#_*_ coding:utf8 _*_
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):#SPAMFilter是Filter的子类
    def init(self):#重写Filter超类中的init方法
        self.blocked = ['SPAM']


print issubclass(SPAMFilter,Filter)
print issubclass(Filter,SPAMFilter)
输出:
True
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+897345')
tc.talk()
输出:
Hi, my value is 897352

7.2.8 接口和内省

“接口”的概念与多态有关。在处理多态对象时,只要关心他的接口(或称“协议”)即可,也就是公开的方法和特征。在Python中,不用显式地指定对象必须包含哪些方法才能作为参数接收。

7.3 一些关于面向对象设计的思考

将属于一类的对象放在一起。如果一个函数操纵一个全局变量,那么两者最好都在类内作为特征和方法出现。
不要让对象过于亲密。方法应该只关心自己实例的特征。让其他实例管理自己的状态。
要小心继承,尤其是多重继承。继承机制有时很有用,但也会在某些情况下让事情变得过于复杂。多继承难以正确使用,更难以调试。
简单就好。让你的方法小巧。一般来说,多数方法都应该在30秒内被读完(以及理解),尽量将代码行数控制在一页或者一屏之内。







评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值