Python基础教程第七章学习笔记——更加抽象

7 更加抽象—创建自己的对象

前面讲了:
Python主要的内建对象类型(数字、字符串、列表、元组和字典)
内建函数和标准库的用法
自定义函数的方式
本章主讲:
创建自己的对象(尤其是类型或者被称为类的对象)—Python的核心概念
如何创建
多态、封装、方法、特性、超类及集成的概念

7.1 对象的魔力

面向对象程序设计中的术语——对象(object):基本可看做数据(特性)以及由一系列可以存取、操作这些数据的方法所组成的集合。使用对象代替全局变量和函数的原因很多
对象最重要的优点包括:
多态(polymorphism):可对不同类的对象使用同样的操作——会像“施了魔法般一样工作”
封装(encapsulation):对外部世界隐藏对象的工作细节
继承(inheritance):以普通的类为基础建立专门的类对象。

7.1.1 多态:就算不知道变量所引用的对象类型是什么,还是能对它操作,它也会根据对象(或类)类型的不同表现出不同的行为

获得一商品及其价格,首先想到如何具体表现它—— 如作为元组接收:
('SPAM', 2.50)
如果需要价格可变,代码每次询问价格时,对象都需要检查当前价格(通过网络某些功能)——所以价格不能固定在元组中:
def getPrice(object):
    if isinstance(object, tuple):               #isinstance函数查看对象是否为元组(类型检查并不是好方法,能不用就不用)
        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)
当再有新的可能性或新需求的话,还需要再次更新getPrice函数(模块)—这是个不灵活且不切实际的实现多种行为的代码编写方式。
解决:让对象自己进行操作——每个新的对象类型都可以检索和计算自己的价格并返回结果:只需向它询问价格即可——多态(某种程度上还有封装)出场
1 多态和方法
程序得到一个对象,不知道它是怎么实现的(它可能有多种‘形状’)。而我要做的就是询问价格:
>>> object.getPrice()           #绑定到对象特性上面的函数称为方法(method),字符串、列表和字典方法已见过。
2.5
多态曾出现:
>>> 'abc'.count('a')
1
>>> [1, 2, 'a'].count('a')
1
对变量x:不需知道它是字符串还是列表,就可调用它的count方法(不用管x是什么类型)
>>> from random import choice                         调用random中的函数choice:从数列中随机选出元素
>>> x = choice(['Hello, world!', [1, 2, 'e', 'e', 4]])     x被随机赋予元素,可能是列表也可能是字符串,类型不定
>>> x.count('e')                                                        count方法都可计算‘e’出现的次数
2
2 多态的多种形式 
任何不知道对象类型,但又要操作对象时,都需要用到多态——不仅限于方法,很多内建运算符和函数都有多态的性质
>>> 1 + 2
3
>>> '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
很多函数和运算符都是多态的,只要使用多态函数和运算符,多态就‘消除’了(相当于确定了类型)。
唯一能毁掉多态的就是使用函数显示的检查类型,如:type、isinstance和issubclass函数等——应尽力避免使用

7.1.2 封装——对全局作用域中其他区域隐藏多余信息的原则

多态和封装都会帮助处理程序组件而不用过多关心多余细节,但是封装并不等于多态。
多态可让用户对于不知道是什么类的对象进行方法调用,而封装是可以不用关心对象是如何构建的而直接进行使用。
区分两者:用多态而不用封装写个例子
假设有个OpenObject的类:
>>> o = OpenObject()    #This is how we create objects...        #创建了一个对象,将变量o绑定到该对象上
>>> o.setName('Sir Lancelot')
>>> o.getName()                                          #可以使用setName和getName方法(假设已经由OpenObject类提供)
'Sir Lancelot'
但是如果变量o将它的名字存储在全局变量globalName当中:
>>> globalName
'Sir Lancelot'
此时使用OpenObject类的实例的时候,不得不关心globalName的内容,要确保不会对它进行任何更改:
>>> globalName = 'Sir Gumby'
>>> o.getName
'Sir Gumby'
创建了多个OpenObject实例的话就会出现问题,因为变量相同,所以可能会混淆:
>>> o1 = OpenObject()
>>> o2 = OpenObject()
>>> o1.setName('Robin Hood')
>>> o2.getName()
'Robin Hood'                      #设定一个名字后,其他名字也就自动设定了,这不是想要的结果。
想要在调用方法时不用关心其他的东西(如是否干扰了全局变量),可将名字封装在对象内——将其作为特性(attribute)存储。
正如方法一样,特性是对象内部的变量;(方法更像是绑定到函数的特性)
如果不用全局变量而用特性重写类,并命名为CloseObject:
>>> c = CloseObject()
>>> c.setName('Sir lancelot')
>>> c.getName()
'Sir lancelot'                  #值还是可能存储在全局变量中
再次创建另一个对象:
>>> r = CloseObject()
>>> r.setName('Sir Robin')
>>> r.getName()
'Sir Robin'
此时第一个对象呢?
>>> c.getName()
'Sir lancelot'                #没有发生改变,因为对象有它自己的状态(state),对象的状态由他的特性描述,对象的方法可改变它的特性。

7.1.3 继承

已经有了一个类,而又想建立一个非常类似的(只是添加几个方法或其他),却不想将旧类的代码全部复制到新类中去
如: 有个shape类(可在屏幕上画出指定的形状),现在创建一个叫Rectangle的类(既可以画出指定形状,还可计算此形状面积)
不想重复写shape里面的draw方法代码,可以让Rectangle从Shape类继承方法:在Rectangle对象上调用draw方法时,程序自动从Shape类调用该方法

7.2 类和类型

7.2.1 类到底是什么

类——一种对象。所有对象都属于某一个类,称为类的实例(instance)
鸟就是“鸟类”的实例——“鸟类”就是一个有很多子类的一般(抽象)类:
“鸟类”是所有鸟的集合,“百灵鸟类”是其中的一个子集(当一个对象所属的类是另一个对象所属类的子集时,前者就被称为后者的子类(subclass),而“鸟类”就是“百灵鸟类”的超类)        #Python中习惯使用单数名词且首字母大写来描述对象的类,如:Bird和Lark
面向对象程序设计中,子类的关系是隐式的:因为一个类的定义取决于它所支持的方法。类的所有实例都会包含这些方法,所以所有子类的所有实例都有这些方法。
定义子类只是定义更多(也有可能是重载已经存在的)的方法的过程:
如:Bird可能支持fly方法,而Penguin(Bird的子类)可能会增加eatFish方法。
   而创建Penguin类时,可能会想要重写(override)超类的fly方法,对于Penguin实例来说,这个方法要么什么也不做,要么就产生异常——penguin(企鹅)不会fly(飞)

7.2.2 创建自己的类

_metaclass_ = type #确定使用新式类


class Person:                                                #Person为类的名字,class会在函数定义的地方创建自己的命名空间

    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语句里面,self参数是对于对象自身的引用

>>> foo = Person()

>>> bar = Person()

>>> foo.setName('Luke Skywalker')

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

>>> foo.greet()

Hello, world! I'm Luke Skywalker.

>>> bar.greet()

Heloo, world! I'm Anakin Skywalker.

在调用foo的setName和greet函数时,foo自动将自己作为第一个参数传入函数中——因此形象命名为self。

特性是可以在外部访问的:

>>> foo.name

'Luke Skywalker'

>>> bar.name = 'Yoda'

>>> bar.greet()

Hello, world! I'm Yoda

7.2.3 特性、函数和方法


方法将它们的第一个参数绑定到所属的实例上,因此这个参数可以不必提供。所以将特性绑定到一个普通函数上,这样就不会有特殊的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 seif.song
>>> bird = Bird()
>>> bird.sing()
Squaawk!
>>> birdsong = bird.sing
>>> birdsong()                           #还是对self参数的访问(它仍绑定到类的相同实例上)
Squaawk!
再论私有化
其他程序员可能不知道(可能也不应该知道)你对象内部的具体操作。
应该使用私有特性(外部对象无法访问,但是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()
现在__inaccessible从外界是无法访问的,而在类内部还能使用(如从accessible)访问
>>>  s =  Secretive()
>>> s.__inaccessible()
AttributeError:......
>>> s.accessible()
The secret message is:
Bet you can't see me...
类的内部定义中,所有以双下划线开始的名字都被“翻译”成前面加上单下划线和类名的形式。
>>> Secretive._Secretive__inaccessible
<unbound method Secretive.__inaccessible>
所以在类外访问这些私有方法,尽管不应该这么做:
>>> s._Secretive__inaccessible()
Bet you can't see me...

7.2.4 类的命名空间

def foo(x): return x*x
foo = lambda x: x*x
变量foo可在全局(模块)范围内定义,也可处于局部的函数和方法内。
定义类时,同样的事情也会发生,所有位于class语句中的代码都在特殊的命名空间中执行——类命名空间,其可由类内所有成员访问。 类的定义其实就是执行代码块
比如,在类的定义区并不只限使用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'                                   #新members值被写到了m1的特性中,屏蔽了类范围内的变量
>>> 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):        #定义SPAMFilter是Filter的子类
    def init(self):                       #重写Filter超类中的init方法
        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'])          #此处filter方法的定义是从Filter类中拿过来(继承)的,所以不用重写其定义。——懒惰
['egg', 'bacon']

7.2.6 调查继承

想查看一个类是否是另一个的子类,可使用内建的issubclass函数:
>>> issubclass(SPAMFilter, Filter)
True
>>> issubclass(Filter, SPAMFilter)
False
若想知道已知类的基类(们),可直接使用它的特殊特性__bases__( 前后都是双下划线):
>>> SPAMFilter.__bases__
(<class __main__.Filter at 0x171e40>.)
>>> Filter.__bases__
()
还可使用isinstance方法检查一个对象是否是一个类的实例:
>>> s = SPAMFilter()
>>> isinstance(s, SPAMFilter)
True
>>> isinstance(s, Filter)
True
>>> isinstance(s, str)
False                               isinstance对类型也起作用,例如字符串类型(str)
如果只想知道一个对象属于哪一类,可使用__class__特性( 双下划线):
>>> s.__class__
<class __main__.SPAMFilter at 0x1707c0>

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
子类(TalkingCalculator)自己不做任何事,它从自己的超类继承所有的行为(从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   就会让Talker中的talk方法可用了。
超类们共享一个超类时,在查找给定方法或者特性时访问超类的顺序成为MRO。

7.2.8 接口和内省

“接口”的概念与多态有关。
在处理多态对象时,只要关心它的接口(或称‘协议’)即可——也就是公开的方法和特性。
在Python中,不用显示的指定对象必须包含哪些方法才能作为参数接收。一般来说只需要让对象符合当前接口。
可检查所需的方法是否已经存在:
>>> hasattr(tc, 'talk')
True                                #对象tc中有个叫talk的特性(包含一个方法)
>>> hasattr(tc, 'fnord')
False                              #对象tc中没有fnord特性
还可检查talk特性能否调用:
>>> callable(getattr(tc, 'talk', None))
True
>>> callable(getattr(tc, 'fnord', None))
False
查看对象内所有存储的值,可使用__dict__特性。想找到对象由什么组成,可看看inspect模块。

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

一些要点:
1 将属于一类的对象放在一起。若一个函数操纵一个全局变量,那两者最好都在类内作为特性和方法出现。
2 不要让对象过于亲密。方法应只关心自己实例的特性。让其他实例管理自己的状态
3 要小心继承,尤其多重继承。会使事情变得过于复杂。多重继承难正确使用,难调试。
4 简单就好。使方法小巧——多数方法都应能在30秒内被读完(及理解),尽量将代码行数控制在一页或一屏之内。
当考虑需要什么类及类要有什么方法时,应尝试以下方法:
1 写下程序描述(要做什么),把所有名词。动词和形容词加下划线。
2 所有名词,用作可能得类。
3 所有动词, 用作可能得方法。
4 所有形容词,用作可能得特性。
5 把所有方法和特性分配到类。
做完以上,就有了面向对象模型的草图了。还可考虑类和对象之间的关系(如继承或协作)及他们的作用,可用以下步骤精炼模型:
1 写下(或想)一系列的使用实例——程序应用场景,试着包含所有功能
2考虑每个使用实例,保证模型包括所有需要的东西。遗漏就添加,不正确就改正,直到满意。

7.4 小结

几个概念:
对象。 对象包括特性和方法。特性只作为对象的一部分的变量,方法是存储在对象内的函数。(绑定)方法和其他参数的区别在于方法总将对象作为自己的第一个参数(self)
类。 代表对象的集合(或一类对象),每个对象(实例)都有一个类。类的主要任务是定义它的实例会用到的方法。
多态。是实现将不同类型和类的对象进行同样对待的特性——不需知道对象属于哪个类就能调用方法。
封装。 对象可将它们内部状态隐藏(封装)起来。
继承。 一个类可是一个或多个类的子类。子类从超类继承所有方法。
接口和内省。 对象不用探讨过深,可靠多态调用自己需要的方法。但若想知道对象由什么方法和特性,有些函数可达成。
面向对象设计。完全理解问题,并且创建容易理解的设计很重要

7.4.1 本章新函数

callable(object)                                               #确定对象是否可调用(如方法或函数)
getattr(object,name[, default])                        #确定特性的值,可选择提供默认值
hasattr(object, name)                                     #确定对象是否有给定的特性
isinstance(object, class)                                 #确定对象是否为类的实例
issubclass(A, B)                                             #确定A是否为B的子类
random.choice(sequence)                             #从非空序列中随机选择元素
setattr(object, name, value)                           #设定对象的给定特性为value
type(object)                                                    #返回对象类型
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值