title: 对象 create: 2016.12.7 modified: 2016.12.7 tags: python 多态 封装 方法

  5

[TOC]

之前介绍了Python主要的内建对象类型(数字int、字符串str、列表list、元组tuple和字典dict),以及内建函数和标准库的用法,还有自定义函数的方式。接下来将介绍如何创建自己的对象? 为什么要自定义对象呢?使用字典、序列、数字和字符串来创建函数,完成这项工作还不够吗?这样做当然可以,但是创建自己的对象(尤其是类型或者被称为的对象)是Python的核心概念,事实上,Python被称为面向对象的语言(和C++、Java一样)。接下来会介绍如何创建对象,以及多态、封装、方法、属性、父类以及继承的概念。

1 对象的魔力

对象可以看做数据(属性)以及一系列可以存取、操作这些数据的方法所组成的集合。使用对象替代全局变量和函数的原因可能有很多,其中对象最重要的优点如下:

1.1 多态

多态(Polymorphism)意味着就算不知道变量所引用的对象类型是什么,还是能对它进行操作,而它会根据对象(或类)类型的不同而表现出不同的行为。


In [1]:
'abc'.count('a')


Out[1]:
1

In [2]:
[1,2,'a'].count('a')


Out[2]:
1

对于变量x来说,不需要知道它是字符串还是列表,就可以调用它的count方法—不用管它是什么类型(只要提供一个字符作为参数即可)。
任何不知道对象到底是什么类型,但是又要对对象“做点什么”的时候,都会用到多态。这不仅限于方法,很多内建运算符和函数都有多态的性质:


In [3]:
1+2


Out[3]:
3

In [4]:
'Fish '+'license'


Out[4]:
'Fish license'

1.2 封装

封装是可以不关心对象是如何构建的而直接进行使用。如何将变量“封装”在对象内?可以将其作为属性(attribute)存储。正如方法一样,属性是对象内部的变量。
对象有它自己的状态(state),对象的状态由它的属性(比如名称)来描述。对象的方法可以改变它的属性。

1.3 继承

如果已经有了一个类,而又想建立一个非常类似的呢?新的类可能只是添加几个方法。这时就会用到继承

2 类和类型

类将它视为种类类型的同义词,代表对象的集合。类的主要任务是定义它的实例会用到的方法。所有的对象都属于某一个类,称为类的实例。当一个对象所属的类是另外一个对象所属类的子集时,前者就被称为后者的子类(subclass),所以“百灵鸟类”是“鸟类”的子类。相反,“鸟类”是“百灵鸟类”的父类(superclass)。
Python中,习惯上使用单数名词,并且首字母大写,比如Bird和Lark,来描述对象的类。

2.1 创建自己的类

先来看一个简单的类:


In [11]:
__metaclass__=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
foo=Person()
foo.setName('Luke Skywalker')
foo.greet()


Hello, world! I'm Luke Skywalker

注意 尽管可能使用的是新版的Python,但一些功能不会在旧式类上起作用。为了确保类是新型的,需要在模块或者脚本开始的地方放置赋值语句__metaclass__=type,或者继承新式类(比如object类,也就是子类化内建类object)。新式类必然包含了更多的功能,也是之后推荐的写法,从写法上区分的话,如果当前类或者父类继承了object类,那么该类便是新式类。

在调用foo的setName和greet函数时,foo自动将自己作为第一个参数传人函数中—因此形象地命名为self。显然这就是self的用处和存在的必要性。没有它,成员方法就没法访问它们要对其属性进行操作的对象本身了。
属性是可以在外部访问的:


In [8]:
foo.name


Out[8]:
'Luke Skywalker'

2.2 属性和方法

self参数事实上正是方法和函数的区别。方法将它们的第一个参数绑定到所属的实例上,因此这个参数可以不必提供。
属性只是作为对象的一部分变量,方法则是存储在对象内的函数。

2.2.1 私有化
默认情况下,程序可以从外部访问一个对象的属性。但是有时候需要使用私有属性,这是外部对象无法访问的,但是通过getName和setName等访问器(accessor)能够访问这些私有属性。 为了让方法或者属性变为私有,只要在它的名字前面加上双下划线即可:


In [3]:
class Secretive:
    def __inaccessible(self):
        print "Hello, world!"
    def accessible(self):
        print "The secret message is: "
        self.__inaccessible()

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


In [4]:
s=Secretive()
s.__inaccessible()


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-ffe7eb44855c> in <module>()
      1 s=Secretive()
----> 2 s.__inaccessible()

AttributeError: Secretive instance has no attribute '__inaccessible'

In [5]:
s.accessible()


The secret message is: 
Hello, world!

类的内部定义中,所有以双下划线开始的名字(方法或属性)都被“翻译”成前面加上单下划线和类名的形式。在了解了这些幕后的事情后,实际上还是能在类外访问这些私有方法,尽管不应该这么做:


In [7]:
s._Secretive__inaccessible()


Hello, world!

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

2.2.2 访问器方法
访问器是一个简单的方法,它能使用getHeight、setHeight这样的名字来得到或者重绑定一些属性:


In [26]:
class Rectangle:
    def __inf__(self):
        self.width=0
        self.height=0
    def setSize(self,size):
        self.width,self.height=size
    def getSize(self):
        return self.width,self.height
r=Rectangle()
r.width=10
r.height=5
r.getSize()


Out[26]:
(10, 5)

In [27]:
r.setSize((150,100))
r.width


Out[27]:
150

在上面的例子中,getSize和setSize方法是一个名为size的假想属性的访问器方法,size是由width和height构成的元组。如果有一天要改变类的实现,将size变成一个真正的属性,这样width和height就可以动态算出,那么就要把它们放到一个访问器方法中去。但如果有很多简单的属性,那么就不现实了。如果那么做就得写很多访问器方法。那么怎么解决呢?这就需要用到property函数。
property函数
property函数的使用很简单。延续上面的Rectangle类,只要增加一行代码(子类化object,或者使用__metaclass__=type):


In [2]:
__metaclass__=type
class Rectangle:
    def __inf__(self):
        self.width=0
        self.height=0
    def setSize(self,size):
        self.width,self.height=size
    def getSize(self):
        return self.width,self.height
    size=property(getSize,setSize)

在新版的Rectangle中,property函数创建了一个属性size,其中访问器方法被当做参数(先是取值,然后是赋值)。


In [33]:
w=Rectangle()
w.width=10
w.height=5
w.size


Out[33]:
(10, 5)

In [34]:
w.size=150,100
w.width


Out[34]:
150

很显然,size属性仍然取决于getSize和setSize中的计算。但它看起来就像普通的属性一样。实际上,property函数可以用fget,fset,fdel和doc-这四个参数来调用。如果没有参数,产生的属性既不可读,也不可写。如果只使用一个参数调用(一个取值方法),产生的属性是只读的。第三个参数(可选)是一个用于删除属性的方法。第四个参数(可选)是一个文档字符串。

2.2.3 特殊方法
在Python中,有的名称(方法名)在前面和后面都加上两个下划线,比如__future__,这样拼写表示名字有特殊含义,所以绝不要在自己的程序中使用这种名字。由这些名字组成的集合所包含的方法称为特殊方法。如果对象实现了这些方法的某一个,那么这个方法会在特殊的情况下被Python调用。而几乎没有直接调用它们的必要。

(1) 构造方法

首先要讨论的第一个特殊方法是构造方法。构造方法是一个很奇怪的名字,它代表着类似于以前例子中使用过的那种名为init的初始化方法。但构造方法和其他普通方法不同的地方在于,当一个对象被创建后,会立即调用构造方法。


In [25]:
class FooBar:
    def __init__(self):
        self.somevar=42
f=FooBar()
f.somevar


Out[25]:
42

(2) 重写一般方法和特殊的构造方法
如果一个方法在B类的一个实例中被调用(或一个属性被访问),但在B类中没有找到该方法,那么就会去它的父类A里面找:


In [7]:
class A:
    def hello(self):
        print "hello, I'm A"
class B(A):
    pass
a=A()
b=B()
a.hello()


hello, I'm A

In [8]:
b.hello()


hello, I'm A

在子类中增加功能最基本的方式就是增加方法。但是也可以重写一些父类的方法来自定义继承的行为。B类也能重写这个方法。


In [9]:
class B(A):
    def hello(self):
        print "hello, I'm B"
b=B()
b.hello()


hello, I'm B

重写是继承机制中的一个重要内容,但是对于构造方法尤其重要。构造方法用来初始化新创建对象的状态,大多数子类不仅要拥有自己的初始化代码,还要拥有父类的初始化代码。虽然重写的机制对于所有方法来说都是一样的,但是当重写构造方法时,更可能遇到特别的问题:如果一个类的构造方法被重写,那么就需要调用父类的构造方法,否则对象可能不会被正确的初始化。如下:


In [24]:
class Bird:
    def __init__(self):
        self.hungry=True
    def eat(self):
        if self.hungry:
            print 'Aaaah...'
            self.hungry=False
        else:
            print 'No,thanks!'
b=Bird()
b.eat()


Aaaah...

In [15]:
b.eat()


No,thanks!

可以看到,鸟吃过了以后,就不会再饥饿。现在考虑子类SongBird,它添加了唱歌的行为。


In [18]:
class SongBird(Bird):
    def __init__(self):
        self.sound='Squawk!'
    def sing(self):
        print self.sound
sb=SongBird()
sb.sing()


Squawk!

因为SongBird是Bird的一个子类,它继承了eat方法,但如果调用eat方法,就会产生一个问题:


In [19]:
sb.eat()


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-19-05f67b3cf162> in <module>()
----> 1 sb.eat()

<ipython-input-14-d86c7aaa626a> in eat(self)
      3         self.hungry=True
      4     def eat(self):
----> 5         if self.hungry:
      6             print 'Aaaah...'
      7             self.hungry=False

AttributeError: SongBird instance has no attribute 'hungry'

异常很清楚地说明了错误:SongBird没有hungry属性。原因是:在SongBird中,构造方法被重写,但新的构造方法没有任何关于初始化hungry属性的代码。为了达到预期的效果,SongBird的构造方法必须调用其父类Bird的构造方法来确保进行基本的初始化。有两种方法能达到这个目的,如下:

调用未绑定的父类构造方法


In [28]:
class SongBird(Bird):
    def __init__(self):
        Bird.__init__(self)
        self.sound='Squawk!'
    def sing(self):
        print self.sound
sb=SongBird()
sb.sing()


Squawk!

In [21]:
sb.eat()


Aaaah...

In [22]:
sb.eat()


No,thanks!

通过将当前的实例作为self参数提供给未绑定方法,SongBird就能够使用其父类构造方法的所有实现,也就是说属性hungry能被设置。

使用super函数
super函数只能在新式类中使用。当前的类和对象可以作为super函数的参数使用,调用函数返回的是父类的方法,而不是当前类的方法。如下:


In [25]:
__metaclass__=type
class SongBird(Bird):
    def __init__(self):
        super(SongBird,self).__init__()
        self.sound='Squawk!'
    def sing(self):
        print self.sound
sb=SongBird()
sb.sing()


Squawk!

In [26]:
sb.eat()


Aaaah...

In [27]:
sb.eat()


No,thanks!

(3) 成员访问方法

接下来介绍一些处理对象访问的方法,这些方法允许你创建自己的序列或者映射。
基本的序列和映射规则很简单,但如果要实现它们全部功能就需要实现很多特殊函数。下面将会说到:

基本的序列和映射规则
序列和映射是对象的集合。为了实现它们基本的行为(规则),如果对象是不可变的(如字符串和元组)。那么就需要使用两个特殊方法,如果是可变的(列表和字典),则需要使用4个。

a. __len__(self):这个方法返回集合中所含对象的数量。对于序列来说,这就是元素的个数;对于映射来说,则是键-值对的数量。

b. __getitem__(self,key):这个方法返回与所给键对应的值。对于序列来说,键应该是一个0~n-1的整数(或者像后面所说的负数);对于映射来说,可以使用任何种类的键。

c. __setitem__(self,key,value):这个方法按一定的方式存储和key关联的value,该值随后可使用 __getitem__来获取。当然,只能为可以修改的对象定义这个方法。

d. __delitem__(self,key):这个方法在对一部分对象使用del语句时被调用,同时删除和键关联的值。这个方法也是为可修改的对象定义的。

对这些方法的附件要求:
a. 对于一个序列来说,如果键是负整数,那么要从末尾开始计数。换句话说就是x[-n]和x[len(x)-n]是一样的;
b. 如果键是不合适的类型(例如,对序列使用字符串作为键),会引发一个TypeError异常;
c. 如果序列的索引是正确的类型,但超出了范围,会引发一个IndexError异常。
让我们实践一下—看看如果创建一个无穷序列,会发生什么:


In [3]:
def checkIndex(key):
    """所给的键能接受索引吗?
    为了能被接受,键应该是一个非负的整数,如果它不是一个整数,比如是字符串,会引发TypeError;
    如果它是负数,则会引发IndexError(因为序列是无限长的)。
    """
    if not isinstance(key,(int,long)):
        raise TypeError
    if key<0:
        raise IndexError
class ArithmeticSequence:
    def __init__(self,start=0,step=1):
        """初始化算数序列
        初始值-序列中的第一个值
        步长-两个相邻值之间的差别
        改变-用户修改的值的字典
        """
        self.start=start
        self.step=step
        self.changed={}  #没有项被修改
    def __getitem__(self,key):
        """Get an item from the arithmetic sequence.
        """
        checkIndex(key)
        try:
            return self.changed[key]             #修改了吗?
        except KeyError:                         #否则...
            return self.start+key*self.step      #...计算值
    def __setitem__(self,key,value):
        """修改算术序列中的一个项
        """
        checkIndex(key)
        self.changed[key]=value
s=ArithmeticSequence(1,2)
s[4]


Out[3]:
9

In [4]:
s[4]=2
s[4]


Out[4]:
2

In [5]:
s[5]


Out[5]:
11

注意,没有实现__del__方法的原因是我希望删除元素是非法的:


In [6]:
del s[4]


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-6-9cf88d1604ce> in <module>()
----> 1 del s[4]

AttributeError: ArithmeticSequence instance has no attribute '__delitem__'

这个类没有__len__方法,因为它是无限长的。
索引检查是通过用户自定义的checkIndex函数实现的。如果使用了一个非法类型的索引,就会引发TypeError异常,如果索引的类型是正确的但超出了范围,则会引起IndexError异常:


In [7]:
s['four']


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-f34801bc947b> in <module>()
----> 1 s['four']

<ipython-input-3-6350fc4de34b> in __getitem__(self, key)
     21         """Get an item from the arithmetic sequence.
     22         """
---> 23         checkIndex(key)
     24         try:
     25             return self.changed[key]             #修改了吗?

<ipython-input-3-6350fc4de34b> in checkIndex(key)
      5     """
      6     if not isinstance(key,(int,long)):
----> 7         raise TypeError
      8     if key<0:
      9         raise IndexError

TypeError: 

In [8]:
s[-4]


---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-8-d1e9953ed446> in <module>()
----> 1 s[-4]

<ipython-input-3-6350fc4de34b> in __getitem__(self, key)
     21         """Get an item from the arithmetic sequence.
     22         """
---> 23         checkIndex(key)
     24         try:
     25             return self.changed[key]             #修改了吗?

<ipython-input-3-6350fc4de34b> in checkIndex(key)
      7         raise TypeError
      8     if key<0:
----> 9         raise IndexError
     10 class ArithmeticSequence:
     11     def __init__(self,start=0,step=1):

IndexError: 

(4) __getattr__和__setattr__
拦截(intercept)对象的所有属性访问是可能的,这样可以用旧式类实现属性。为了在访问属性的时候可以执行代码,必须使用一些特殊方法。下面的4种方法提供了需要的功能(在旧式类中只需要后3个)

a. __getattribute__(self,name):当属性name被访问时自动被调用(只能在新式类中使用);

b. __getattr__(self,name):当属性name被访问且对象没有相应的属性时被自动调用;

c. __setattr__(self,name,value): 当试图给属性name赋值时会被自动调用;

d. __delattr__(self,name): 当试图删除属性name时会被自动调用。

尽管和使用property函数相比有点复杂(而且在某些方面效率更低),但是这些特殊方法是很强大的,因为可以对处理很多属性的方法进行再编码。
下面还是Rectangle的例子,但这次使用的是特殊方法:


In [73]:
class Rectangle:
    def __init__(self):
        self.width=0
        self.height=0
    def __setattr__(self,name,value):
        if name =='size':
            self.width,self.height=value
        else:
            self.__dict__[name]=value
    def __getattr__(self,name):
        if name =='size':
            return self.width,self.height
        else:
            raise AttributeError

In [74]:
w=Rectangle()
w.size


Out[74]:
(0, 0)

In [75]:
w.__dict__


Out[75]:
{'height': 0, 'width': 0}

In [76]:
w.size=(2,6)
w.size


Out[76]:
(2, 6)

In [77]:
w.width


Out[77]:
2

In [78]:
hasattr(w,'size')


Out[78]:
True

In [79]:
w.age=28
w.age


Out[79]:
28

In [80]:
w.__dict__


Out[80]:
{'age': 28, 'height': 6, 'width': 2}

注意: __setattr__方法在所涉及的属性不是size时也会被调用。如果属性是size,那么就像前面那样执行操作,否则就要使用特殊方法__dict__,该方法包含一个字典,字典里是所有实例的属性;
__getattr__方法只在普通的属性没有被找到的时候调用。

(5) 迭代器
迭代的意思是重复做一些事很多次—就像在循环中做的那样。到现在为止只有在for循环中对序列和字典进行迭代,但实际上也能对其他的对象进行迭代:实现__iter__特殊方法的对象。
__iter__方法返回一个迭代器(iterator),所谓的迭代器就是具有next方法(这个方法在调用时不需要任何参数)的对象。在调用next方法时,迭代器会返回它的下一个值。
迭代规则的关键是什么?为什么不使用列表?因为列表的杀伤力太大。如果有可以一个接一个地计算值的函数,那么在使用时可能是计算一个值时获取一个值-而不是通过列表一次性获取所有值。如果有很多值,列表就会占用太多的内存。另外,使用迭代器更通用、更简单、更优雅。让我们看看一个不使用列表的例子,因为要用的话,列表的长度必须无限。
这里的“列表”是一个斐波那契数列。使用的迭代器如下:


In [84]:
class Fibs:
    def __init__(self):
        self.a=0
        self.b=1
    def next(self):
        self.a,self.b=self.b,self.a+self.b
        return self.a
    def __iter__(self):
        return self
fibs=Fibs()
for f in fibs:
    if f>10:
        print f
        break


13

在很多情况下,__iter__被放到会在for循环中使用的对象中。
注意 正式的说法是,一个实现了__iter__方法的对象是可迭代的,一个实现了next方法的对象则是迭代器

内建函数iter可以从可迭代的对象中获得迭代器:


In [86]:
a=[1,2,3]
a.next()


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-86-9cc7ef1283fa> in <module>()
      1 a=[1,2,3]
----> 2 a.next()

AttributeError: 'list' object has no attribute 'next'

In [93]:
it=iter([1,2,3])
it.next()


Out[93]:
1

In [94]:
it.next()


Out[94]:
2

从迭代器中得到序列:
使用list函数显式地将迭代器转化为列表。


In [100]:
it=iter([1,2,3])
it


Out[100]:
<listiterator at 0x43e8978>

In [101]:
list(it)


Out[101]:
[1, 2, 3]

2.3 类的命名空间

所有位于class语句中的代码都在特殊的命名空间中执行—类命名空间。这个命名空间可由类内所有成员访问。


In [10]:
class C:
    print 'Class C being defined...'


Class C being defined...

从上可以看出,类的定义其实就是执行代码块,这一点很有用,比如,在类的定义区并不只限使用def语句:


In [8]:
class MemberCounter:
    members=0
    def init(self):
        MemberCounter.members+=1
m1=MemberCounter()
m1.init()
MemberCounter.members


Out[8]:
1

In [9]:
m2=MemberCounter()
m2.init()
MemberCounter.members


Out[9]:
2

上面的代码中,在类作用域内定义了一个可供所有成员(实例)访问的变量,用来计算类的成员数量。
就像方法一样,类作用域内的变量也可以被所有实例(对象)访问:


In [11]:
m1.members


Out[11]:
2

In [12]:
m2.members


Out[12]:
2

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


In [13]:
m1.members='Two'
m1.members


Out[13]:
'Two'

In [14]:
m2.members


Out[14]:
2

2.4 继承父类

子类可以扩展父类的定义。将其他类名写在class语句后的圆括号内就可以继承父类:


In [3]:
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']

In [4]:
f=Filter()
f.init()
f.filter([1,2,3])


Out[4]:
[1, 2, 3]

Filter类的用处在于它可以用作其他类的父类,比如SPAMFilter类,可以将序列中“SPAM”过滤出去。


In [3]:
s=SPAMFilter()
s.init()
s.filter(['SPAM','SPAM','SPAM','SPAM','eggs','bacon'])


Out[3]:
['eggs', 'bacon']

2.4.1 子类化列表,字典和字符串
如果希望实现一个和内建对象类型(例如列表,字符串和字典)行为相似的序列或映射,可以使用子类化内建类型。
注意 当子类化一个内建类型,比如list的时候,也就间接的将object子类化了。因此该类就自动成为新式类,意味着可以使用像super函数这样的特性了。
看看下面的例子-带有访问计数的列表。


In [10]:
class CounterList(list):
    def __init__(self,*args):
        super(CounterList,self).__init__(*args)
        self.counter=0
    def __getitem__(self,index):
        self.counter+=1
        return  super(CounterList,self).__getitem__(index)

CounterList类严重依赖于它的子类化父类(list)的行为。CounterList类没有重写任何的方法,能直接调用列表的任何方法(如append、extend、index)。在两个被重写的方法中,super方法被用来调用相应的父类的方法,只有在__init__中添加了所需的初始化counter属性的行为,并在__getitem__中更新了counter属性。


In [17]:
c1=CounterList('aaa')
c1


Out[17]:
['a', 'a', 'a']

In [18]:
c1=CounterList((1,2,3))
c1


Out[18]:
[1, 2, 3]

In [20]:
c1=CounterList({'first':1,'second':2})
c1


Out[20]:
['second', 'first']

In [28]:
c1=CounterList(range(10))
c1


Out[28]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [29]:
c1.reverse()
c1


Out[29]:
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

In [30]:
del c1[3:6]
c1


Out[30]:
[9, 8, 7, 3, 2, 1, 0]

In [31]:
c1.counter


Out[31]:
0

In [33]:
c1[0]+c1[1]+c1[2]


Out[33]:
24

In [34]:
c1.counter


Out[34]:
3

可以看到,CounterList在很多方面和列表的作用一样,但它有一个counter属性(被初始化为0),每次列表元素被访问时,它都会自增。

2.5 调查继承

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


In [4]:
issubclass(SPAMFilter,Filter)


Out[4]:
True

In [5]:
issubclass(Filter,SPAMFilter)


Out[5]:
False

如果想要知道已知类的父类(们),可以直接使用它的特殊属性__bases__:


In [7]:
SPAMFilter.__bases__


Out[7]:
(<class __main__.Filter at 0x00000000041247C8>,)

In [8]:
Filter.__bases__


Out[8]:
()

同样,还能使用isinstance函数检测一个对象是否是一个类的实例:


In [5]:
s=SPAMFilter()
isinstance(s,SPAMFilter)


Out[5]:
True

In [10]:
isinstance(s,str)


Out[10]:
False

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


In [11]:
s.__class__


Out[11]:
<class __main__.SPAMFilter at 0x00000000042657C8>

In [6]:
type(s)


Out[6]:
__main__.SPAMFilter

In [10]:
type([1,2])


Out[10]:
list

2.6 多个父类

一个类的父类可能多于一个,如下:


In [16]:
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)自己不做任何事,它从自己的父类继承所有的行为。这样它就成了会说话的计算器(talking calculator)。


In [17]:
tc=TalkingCalculator()
tc.calculate('1+2+3')
tc.talk()


Hi,my value is  6

这种行为称为多重继承(multiple inheritance),是个非常有用的工具。
一般来说,对于对象不用探讨过深。程序员可以靠多态调用自己需要的方法。不过如果想要知道对象到底有什么方法和属性,有些函数可以帮助完成这项工作。如下可以检查对象的方法或属性是否已经存在:


In [18]:
hasattr(tc,'talk')


Out[18]:
True

In [19]:
hasattr(tc,'fnord')


Out[19]:
False

In [21]:
getattr(tc,'talk','None')   #获得对象属性的值,可选择提供默认值,以便在属性不存在时使用


Out[21]:
<bound method TalkingCalculator.talk of <__main__.TalkingCalculator instance at 0x0000000004379E08>>

In [24]:
getattr(tc,'value','None')


Out[24]:
6

In [22]:
setattr(tc,'name','Mr. Gumby')   #与getattr相对应的函数是setattr,用来设置对象的属性及值
tc.name


Out[22]:
'Mr. Gumby'

如果要查看对象内所有存储的值,那么可以使用__dict__属性。


In [23]:
tc.__dict__


Out[23]:
{'name': 'Mr. Gumby', 'value': 6}

3 生成器

生成器是Python新引入的概念。生成器是一种普遍的函数语法定义的迭代器。接下来介绍怎么创建和使用生成器,了解它的内部机制。

3.1 创建生成器

创建一个生成器就像创建函数一样简单。首先我们创建一个可以展开嵌套列表的函数。参数是一个列表:


In [116]:
def flatten(nested):
    for sublist in nested:
        for element in sublist:
            yield element
nested=[[1,2],[3,4],5]

任何包含yield语句的函数称为生成器。除了名字不同以外,它的行为和普通的函数也有很大的差别。这就在于它不像return语句那样返回值,而是每次产生一个值。每次产生一个值(使用yield语句),函数就会被冻结:即函数停在那点等待被激活。函数被激活后就从停止的那点开始执行。
接下来可以通过在生成器上迭代来使用所有的值:


In [118]:
flatten(nested)


Out[118]:
<generator object flatten at 0x00000000043F3EE8>

In [119]:
for num in flatten(nested):
    print num


1
2
3
4
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-119-b385b8be1106> in <module>()
----> 1 for num in flatten(nested):
      2     print num

<ipython-input-116-d7d394ff2981> in flatten(nested)
      1 def flatten(nested):
      2     for sublist in nested:
----> 3         for element in sublist:
      4             yield element
      5 nested=[[1,2],[3,4],5]

TypeError: 'int' object is not iterable

从上可以看到,试图对一个数值5进行迭代会引发一个TypeError异常。
生成器由两部分组成:生成器的函数和生成器的迭代器。生成器的函数是用def语句定义的,包含yield部分,生成器的迭代器是这个函数返回的部分。


In [120]:
nested=[[1,2],[3,4],[5]]

In [121]:
list(flatten(nested))


Out[121]:
[1, 2, 3, 4, 5]

3.2 递归生成器

如果要处理任意层的嵌套该怎么办?每次嵌套需要增加一个for循环,但因为不知道有几层嵌套,所以必须把解决方案变得更灵活。这就需要用到递归


In [123]:
def flatten(nested):
    try:
        for sublist in nested:
            for element in flatten(sublist):
                yield element
    except TypeError:
        yield nested

当flatten被调用时,有两种可能性(处理递归时大部分都是这种情况):基本情况和需要递归的情况。在基本的情况中,函数被告知展开一个元素(比如一个数字),这种情况下,for循环会引发一个TypeError异常(因为试图对一个数字进行迭代),生成器会产生一个元素。如果展开的是一个列表,那么就要进行特殊处理。程序必须遍历所有的子列表,并对他们调用flatten。然后使用另一个for循环来产生被展开的子列表的所有元素。


In [124]:
list(flatten([[[1],2],3,4,[5,[6,7]],8]))


Out[124]:
[1, 2, 3, 4, 5, 6, 7, 8]

到目前为止,Python语言的大部分知识都介绍了。