ch7 更加抽象

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
  • 类:类代表对象的集合,每个对象都有一个类,类的主要任务是定义它的实例会用到的方法
  • 多态:多态是实现将不同类型和类的对象进行同样对待的特性
  • 封装:对象可以将它们的内部状态隐藏起来
  • 继承:一个类可以是一个或者多个类的子类,子类从父类继承所有方法,可以使用多个超类。
  • 接口和内省
  • 面向对象设计
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值