码迷,mamicode.com
首页 > 编程语言 > 详细

python之抽象二

时间:2015-06-13 06:26:55      阅读:151      评论:0      收藏:0      [点我收藏+]

标签:python   抽象   

多态:意味着可以对不同类的对象使用同样的操作

封装:对外部世界隐藏对象的工作细节

继承:以普通的类为基础建立专门的类对象


def getPrice(object):

if isinstance(object,tuple):

return object[1]

else:

return magic_network_method(object)

这里用isinstance进行类型/类检查是为了说明一点-----类型检查一般说来并不是什么好方法,能不用则不用。


前面的代码中使用isinstance函数查看对象是否为元组。如果是的话,就返回它的第二个元素,否则会调用一些“有魔力的”网络方法。

假设网络功能部分已经存在,那么问题解决了------目前为止是这样。但程序还不是很灵活。如果某些聪明的程序员决定用十六进制数的字符串来表示价格,然后存储在字典中德键“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


对于变量x来说,不需要知道它是字符串还是列表,就可以调用它的count方法----不用管它是什么类型。

标准库random中包含choice函数,可以从序列中随机选出来元素。给变量赋值:

>>>from random import choice

>>>x = choice([‘hello,world!‘,[1,2,‘e‘,‘e‘,4]])

运行后,变量x可能会包含字符串‘hello,world!‘,也有可能包含列表[1,2,‘e‘,‘e‘,4]----不用关心到底是哪个类型。要关心的就是在变量x中字符e出现多少次,而不管x是字符串还是列表。可以使用刚才的count函数:

>>>x.count(‘e‘)

2

x有个叫做count的方法,带有一个字符作为参数,并且返回整数值就够了。如果其他人创建了对象类也有count方法,那也无所谓-----你只需要像用字符串和列表一样使用该对象就行了。


2.多态的多种形式

任何不知道对象到底是什么类型,但是又想对对象做点什么的时候,都会用到多态,这不仅限于方法----很多内建运算符和函数都有多态的性质:

>>>1+2

3

>>>‘Fish‘+‘license‘

‘Fishlicense‘

这里的加运算符对于数字和字符串都能起作用。为说明这一点,假设有个叫做add的函数,它可以将两个对象相加。那么可以直接将其定义成上面的形式。

def add(x,y):

return x+y


对于很多类型的参数都可以用:

>>>add(1,2)               只支持同类相加

3

>>>add(‘Fish‘,‘license‘)

‘Fishlicense‘


打印对象长度

def length_message(x):

print "The length of",repr(x),"is",len(x)

 repr函数是多态特性的代表之一-------可以对任何东西使用。

>>>length_message(‘Fnord‘)

The length of ‘Fnord‘ is 5

>>>length_message([1,2,3])

The length of [1,2,3] is 3


7.1.2 封装

封装是对外部隐藏不必要细节的原则。

多态可以让用户对于不知道是什么类(或对象类型)的对象进行方法调用,而封装是可以不用关心对象是如何构建的而直接进行使用。

>>>o = OpenObject()

>>>o.setName(‘Sir Lancelot‘)

>>>o.getName()

‘Sir Lancelot‘

创建了一个对象(通过像调用函数一样调用类)后,将变量o绑定到该对象上。可以使用setName和getName方法(假设已经由OpenObject类提供)。假设变量o将它的名字存储在全局变量globalName中:

>>>globalName = ‘Sir Gumby‘

>>>o.getName()

‘Sir Gumby‘

如果创建了多个OpenObject实例的话就会出现问题,因为变量相同,所以可能会混了:

>>>o1 = OpenObject()

>>>o2 = OpenObject()

>>>o1.setName(‘Robin Hood‘)

>>>o2.getName()

‘Robin Hood‘

可以看到,设定一个名字后,其他的名字也就自动设定了。

基本上,需要将对象看作抽象,调用方法的时候不用关心其他的东西,比如它是否干扰了全局变量。所以能将名字“封装”在对象内吗?。可以将其作为特性存储。

正如方法一样,特性是对象内部的变量;事实上方法更像是绑定到函数的特性。

如果不用全局变量而用特性重写类,并且重命名为ClosedObject,它会像下面这样工作:

>>>c = ClosedObject()

>>>c.setName(‘Sir Lancelot‘)

>>>c.getName()

‘Sir Lancelot‘


>>>c.getName()

‘Sir Lancelot‘

名字还在!这是因为对象有它自己的状态。对象的状态由它的特性来描述。对象的方法可以改变它的特性。所以就像是将一大堆函数捆在一起,并且给予它们访问变量的权利,它们可以在函数调用之间保持保存的值。


7.1.3 继承

 所有的对象都属于某一个类,称为类的实例。 例如 鸟  就是鸟类

当一个对象所属的类是另外一个对象所属类的子集时,前者就被称为后者的子类,所以百灵鸟类是鸟类的子集,鸟类是百灵鸟类的超类。


7.2.2 创建自己的类

__metaclass_ = type   #确定使用新式类

class Person:

def setName(self,name):

self.name = name

def getName(self):

return self.name

def greeting(self):

print "Hello,world! I‘m %s." % self.name

所谓的旧式类和新式类之间是有区别的。除非是Python3.0之前版本中默认附带的代码,否则在继续使用旧式类已无必要。新式类的语法中,需要在模块或者脚本开始的地方放置赋值语句_metaclass_ = type。除此之外也有其他的方法,例如继承新式类。后面马上就会介绍继承的知识。


这个例子包含3个方法定义,除了它们是写在class语句里面外,一切都像是函数定义。

>>>foo = Person()

>>>bar = Person()

>>foo.setName(‘Luke Skywalker‘)

>>>bar.setName(‘Anakin Skywalker‘)

>>>foo.greet()

hello,world! I‘m Luke Skywalker

>>>bar.greet()

hello,world! I‘m Anakin Skywalker.


在调用foo的setName和greet函数时,foo自动将自己作为第一个参数传入函数中----因此形象地命名为self。对于这个变量,每个人可能都会有自己的叫法,但是因为它总是对象自身,所以习惯上总是叫做self。显然这就是self的用处和存在的必要性。没有它的话,成员方法就没法访问她们要对其特性进行操作的对象本身了。

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

>>>foo.name

‘Luke Skywalker‘

>>>bar.name = ‘Yoda‘

>>>bar.greet()

Hello,world! I‘m Yoda



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()

Squaakw!


尽管最后一个方法调用看起来与函数调用十分相似,但是变量birdsong引用绑定方法bird.sing上,也就意味着这还是对self参数的访问(也就是说,它仍绑定到类的相同实例上)


再论私有化

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

>>>c.name

‘Sir Lancelot‘

>>>c.name = ‘Sir Gumby‘

>>>c.getName()

‘Sir Gumby‘


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

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

class Secretive:

def __inaccessible(self):

print "Bet you can‘t see me ..."

def accessible(self):

print "The secret message is:"

self.__inaccessible()

现在__inaccessible从外界是无法访问的/ ,而在类内部还能使用(比如从accessible)访问:

>>>s = Secretive()

>>>s.__inaccessible()

报错

>>>s.accessible()

The secret message is:

Bet you can‘t see me...


类的内部定义中,所有以双下划线开始的名字都被“翻译”成前面加上单下划线和类名的形式。

>>>Secretive._Secretive__inaccessible


其实还是能在类外访问这些私有方法,尽管不应该这么做:

>>>s._Secretive__inaccessible()

Bet you can‘t see me...


简而言之,确保其他人不会访问对象的方法和特性是不可能的,但是这类“名称变化术”就是他们不应该访问这些函数或者特性的强有力信号。

如果不需要使用这个种方法但是又想让其他对象不要访问内部数据,那么可以使用单下划线。这不过是个人习惯,例如,前面有下划线的名字都不会被带星号的imports语句(from module import *)导入。


7.2.4 类的命名空间

下面两个语句几乎等价:

def foo(x):return x*x

foo = lamba x:x*x


在类的定义区并不只限使用def语句:

>>>class C:

print ‘class C being defined...‘

Class C being defined...

>>>


看起来有点傻,但是看看下面的:

class MemberCounter:

members = 0

def init(self):

MemberCounter.members +=1

>>>m1 = MemberCounter()

>>>m1.init()

>>>MemberCounter.members

1

>>>m2 = MemberCounter()

>>>m2.init()

>>>MemberCounter.members

2


上面的代码中,在类作用域内定义了一个可供所有成员访问的变量,用来计算类的成员数量。注意init用来初始化所有实例。

就像方法一样,类作用域内的变量也可以被所有实例访问:

>>>m1.members

2

>>>m2.members

2

那么在实例中重绑定members特性呢?

>>>m1.members = ‘Two‘

>>>m1.members

‘Two‘

>>>m2.members

2

新numbers值被写到了m1的特性中,屏蔽了类范围内的变量。这跟函数内的局部和全部变量的行为十分类似。


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‘,‘eggs‘,‘b‘])

[‘eggs‘,‘b‘]

注意SPAMFilter定义的两个要点:

这里用提供新定义的方式重写了Filter的init定义。

filter方法的定义是从Filter类中拿过来的,所以不用重写它的定义。

第二要点揭示了继承的用处:我可以写一大堆不同的过滤类,全都从Filter继承,每一个我都可以使用已经实现的filter方法。


7.2.6 调查继承

如果想要查看一个类是否是另一个的子类:可以使用内建的issubclass函数:

>>>issubclass(SPAMFilter,Filter)

True

>>>issubclass(Filter,SPAMFilter)

False


如果想要知道已知类的基类,可以直接使用它的特殊特性__bases__:

>>>SPAMFilter.__bases__

(<class __main__.Filter at 0x177le40>.)

>>>Filter.__bases__

()


同样,还能用使用isinstance方法检查一个对象是否是一个类的实例:

>>>s = SPAMFilter.__bases__

>>>isinstance(s,SPAMFilter)

True

>>>isinstance(s,Filter)

True

>>>isinstance(s,str)

False


使用isinstance并不是好习惯,使用多态会更好一些。

可以看到,s是SPAMFilter类的成员,但是它也是Filter类的间接成员,因为SPAMFilter是Filter的子类。另外一种说法就是SPAMFilters类就是Filters类。可以从前一个例子中看到,isinstance对于类型也起作用,比如字符串类型(str)。


如果只想知道一个对象属于哪个类,可以使用__class__特性:

>>>s.__class__

<class __main__.SPAMFilter at 0x17070>


如果使用__metaclass__=type或从object继承的方式来定义新式类,那么可以使用type(s)查看实例的类。


7.2.7 多个超类

找到一个类的基类,也就是暗示基类可能会多于1个:

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


子类自己不做任何事,它从自己的超类继承所有的行为。它从Calculator类那里继承calculate方法,从Talker类那里继承talk方法,这样它就成了会说话的计算器。

>>>tc = TalkingCalculator()

>>>tc.calculate(‘1+2*3‘)

>>>tc.talk()

Hi,my value is 7

这种行为称为多重继承,是个非常有用的工具。但除非读者特别熟悉多重继承,否则应该尽量避免使用,因为有些时候会出现不可预见的麻烦。

当使用多重继承时,有个需要注意的地方,如果一个方法从多个超类继承(也就是你有两个具有相同名字的不同方法),那么必须要注意一下超类的顺序(在class语句中):先继承的类中方法会重写后继承的类中的方法。所以如果前例中Calculator类也有个叫做talk的方法,那么它就会重写talker的talk方法(使其不可访问)。如果把它们的顺序掉过来,像下面这样:

class TalkingCalculator(Talker,Calculator): pass

就会让Talter的talk方法可用了。如果超类们共享一个超类,那么在查找给定方法或者特性时访问超类的顺序称为MRO(方法判定顺序),使用的算法相当复杂。


7.2.8 接口和内省

检查所需方法是否存在:

>>>hasattr(tc,‘talk‘)

True


检查特性是否可调用:

>>>callable(getattr(tc,‘talk‘,None))

True


callable函数在python3.0已不再可用,可以使用hasattr(x,‘__call__‘)来代替callable(x)。


设置对象的特性

>>>setattr(tc,‘name‘,‘Mr. Gumby‘)

>>>tc.name

‘Mr. Gumby‘

如果要查看对象内所有存储的值,那么可以使用__dict__特性。如果真的想要找到对象是由什么组成的,可以看看inspect模块。这是为那些想要编写对象浏览器以及其他需要类似功能的程序的高级用户准备的。


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

1.将属于一类的对象放在一起。如果一个函数操纵一个全局变量,那么两者最好都在类内作为特性和方法出现。

2.不要让对象过于亲密。方法应该只关心自己实例的特性。让其他实例管理自己的状态。

3.要小心继承,尤其是多重继承。继承机制有时很有用,但也会在某些情况下让事情变得过于复杂。多继承难以正确使用,更难以调试。

4.简单就好,让你的方法小巧。尽量将代码控制在一页或者一屏之内。


1.写下问题的描述(程序要做什么),把所有名词,动词和形容词加下划线

2.对于所有名词,用作可能的类

3.对于所有动词,用作可能的方法

4.对于所有的形容词,用作可能的特性

5.把所有的方法和特性分配到类


本文出自 “linux_oracle” 博客,请务必保留此出处http://pankuo.blog.51cto.com/8651697/1661441

python之抽象二

标签:python   抽象   

原文地址:http://pankuo.blog.51cto.com/8651697/1661441

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!