您的位置:首页 > 编程语言 > Python开发

Python基础:18类和实例之二

2015-06-08 20:25 696 查看
1:绑定和非绑定

当存在一个实例时,方法才被认为是绑定到那个实例了。没有实例时方法就是未绑定的。在很多情况下,调用的都是一个绑定的方法。

调用非绑定方法并不经常用到,其中一个主要的场景是:派生一个子类,而且要覆盖父类的方法,这时需要调用那个父类中被覆盖掉的构造方法:
class EmplAddrBookEntry(AddrBookEntry):
'Employee Address Book Entry class'
def __init__(self, nm, ph, em):
AddrBookEntry.__init__(self, nm, ph)
self.empid = id
self.email = em

EmplAddrBookEntry是AddrBookEntry 的子类,重载了__init__()。这是调用非绑定方法的最佳地方了。
当一个EmplAddrBookEntry被实例化,并且调用 __init__() 时,其与AddrBookEntry的实例只有很少的差别,主要是因为我们还没有机会来自定义我们的EmplAddrBookEntry 实例,以使它与AddrBookEntry 不同。所以,可以将EmplAddrBookEntry实例传递给AddrBookEntry的__init__。
子类中 __init__() 的第一行就是对父类__init__()的调用。通过父类名来调用它,并且传递给它 self 和其他所需要的参数。一旦调用返回,我们就能定义那些与父类不同的仅存在我们的(子)类中的(实例)定制。

2:静态方法和类方法
静态方法和类方法在Python2.2中引入。经典类及新式(new-style)类中都可以使用它。

Python中的静态方法和C++或者Java这些语言中的是一样的。它们仅是类中的函数(不需要实例)。
对于类方法而言,需要类而不是实例作为第一个参数,它是由解释器传给方法。类不需要特别地命名,类似self,不过很多人使用cls作为变量名字。

下面是在经典类中创建静态方法和类方法的一些例子(也可以把它们用在新式类中):
class TestStaticMethod:
def foo():
print 'calling static method foo()'
foo = staticmethod(foo)

class TestClassMethod:
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__
foo = classmethod(foo)
内建函数staticmethod和classmethod将它们转换成相应的类型,并且重新赋值给了相同的变量名。如果没有调用这两个函数,二者都会在Python 编译器中产生错误,显示需要带self
的常规方法声明。 可以通过类或者实例调用这些函数

>>> tsm = TestStaticMethod()
>>>TestStaticMethod.foo()
calling static method foo()
>>>tsm.foo()
calling static method foo()

>>> tcm = TestClassMethod()
>>>TestClassMethod.foo()
calling class method foo()
foo() is part of class: TestClassMethod
>>>tcm.foo()
calling class method foo()
foo() is part of class: TestClassMethod

通过使用修饰符,可以避免像上面那样的重新赋值:
class TestStaticMethod:
@staticmethod
def foo():
print 'calling static method foo()'
class TestClassMethod:
@classmethod
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__

3:有两种方法可以在代码中利用类。第一种是组合(composition)。就是让不同的类混合并加入到其它类中,来增加功能和代码重用性。另一种方法是通过派生。

组合的例子如下:
class NewAddrBookEntry(object):

'new address book entry class'
def __init__(self, nm, ph):
self.name = Name(nm) #创建Name实例
self.phone = Phone(ph) #创建Phone实例
print 'Created instance for:', self.name

NewAddrBookEntry类由其它类组合而成。这就在一个类和其它组成类之间定义了一种“有一个”的关系。比如,我们的NewAddrBookEntry 类“有一个” Name 类实例和一个Phone实例。

4:子类和派生
OOP的更强大方面之一是能够使用一个已经定义好的类,扩展它或者对其进行修改,而不会影响系统中使用现存类的其它代码片段。允许类特征在子孙类或子类中进行继承。
新式类创建子类的语法:
class SubClassName (ParentClass1[, ParentClass2,...]):
'optional class documentation string'
class_suite
如果你的类没有从任何祖先类派生,可以使用object 作为父类的名字。

经典类的声明唯一不同之处在于其没有从祖先类派生--此时,没有圆括号:
class ClassicClassWithoutSuperclasses:
pass

下面还有一个简单的例子:
class Parent(object):

def parentMethod(self):
print 'calling parent method'

class Child(Parent):

def childMethod(self):
print 'calling child method'
>>> p = Parent()
>>> p.parentMethod()
calling parent method
>>>
>>> c = Child()
>>> c.childMethod()
calling child method
>>>c.parentMethod()
calling parent method

5:继承
一个子类可以继承它的基类的任何属性,不管是数据属性还是方法。举个例子如下。P 是一个没有属性的简单类。C 从P 继承而来,也没有属性:
class P(object):
pass
class C(P):
pass
>>> c = C()
>>> c.__class__
<class '__main__.C'>
>>>C.__bases__
(<class '__main__.P'>,)

下面给 P 添加一些属性:
class P:
'P class'
def __init__(self):
print
'created an instance of', self.__class__.__name__
class C(P):
pass

现在P 有文档字符串__doc__和__init__:
>>> p = P()
created an instance of P
>>> p.__class__
<class '__main__.P'>
>>> P.__bases__
(<type 'object'>,)
>>> P.__doc__
'P class'

>>> c = C()
created an instance of C
>>> c.__class__
<class '__main__.C'>
>>> C.__bases__
(<class '__main__.P'>,)
>>>C.__doc__
>>>
C没有声明__init__()方法,然而在类C 的实例c 被创建时,还是会有输出信息。原因在于C
继承了P 的__init__()。
需要注意的是,文档字符串对类,函数/方法,还有模块来说都是唯一的,所以特殊属性__doc__不会从基类中继承过来。

对任何(子)类,其__bases__类属性是一个包含其父类(parent)的集合的元组。
那些没有父类的类,它们的__bases__属性为空。下面我们看一下如何使用__bases__的。

>>> class A(object): pass
...
>>> class B(A): pass
...
>>> class C(B): pass
...
>>> class D(A, B): pass
...
>>> A.__bases__
(<type 'object'>, )
>>> B.__bases__
(<class '__main__.A'>,)
>>> C.__bases__
(<class '__main__.B'>,)
>>> D.__bases__
(<class '__main__.B'>, <class '__main__.A'>)

在上面的例子中,尽管C 是A 和B 的子类(通过B 传递继承关系),但C的父类是B,所以,只有B 会在C.__bases__中显示出来。

在父类 P 中再写一个函数,然后在其子类中对它进行覆盖:
class P(object):
def foo(self):
print 'Hi, I am P-foo()'
>>> p = P()
>>> p.foo()
Hi, I am P-foo()

class C(P):
def foo(self):
print 'Hi, I am C-foo()'
>>> c = C()
>>> c.foo()
Hi, I am C-foo()
尽管C继承了P 的foo()方法,但因为C 定义了它自已的foo()方法,所以 P 中的foo() 方法被覆盖。
尽管父类中的foo被覆盖了,但是还是可以调用那个被覆盖的基类方法。这时就需要去调用一个未绑定的基类方法,需要明确给出子类的实例,例如下边:
>>> P.foo(c)
Hi, I am P-foo()

典型情况下,你不会以这种方式调用父类方法,你会在子类的重写方法里显式地调用基类方法。
class C(P):
def foo(self):
P.foo(self)
print 'Hi, I am C-foo()'

在这个(未绑定)方法调用中我们显式地传递了self. 一个更好的办法是使用super()内建方法:
class C(P):
def foo(self):
super(C, self).foo()
print 'Hi, I am C-foo()'

super()不但能找到基类方法,而且还为我们传进self:
>>> c = C()
>>> c.foo()
Hi, I am P-foo()
Hi, I am C-foo()

类似于上面的覆盖非特殊方法,当从一个带__init()__的类派生,如果不覆盖__init__(),它将会被继承并自动调用。但如果在子类中覆盖了__init__(),子类被实例化时基类的__init__()就不会被自动调用。
class P(object):
def __init__(self):
print "calling P's constructor"
class C(P):
def __init__(self):
print "calling C's constructor"
>>> c = C()
calling C's constructor
如果还想调用基类的 __init__(),需要明确指出,使用一个子类的实例去调用基类(未绑定)方法。相应地更新类C,会出现下面预期的执行结果:
class C(P):
def __init__(self):
P.__init__(self)
print "calling C's constructor"
>>> c = C()
calling P's constructor
calling C's constructor
上边的例子中,子类的__init__()方法首先调用了基类的的__init__()方法。这是相当普遍(不是强制)的做法,用来设置初始化基类,然后可以执行子类内部的设置。super()内建函数引入到Python中,可以这样写:
class C(P):
def __init__(self):
super(C,
self).__init__()
print "calling C's constructor"
使用super()的漂亮之处在于,不需要明确提供父类。这意味着如果改变了类继承关系,只需要改一行代码(class 语句本身)而不必在大量代码中去查找所有被修改的那个类的名字。


如果一个类的构造方法被重写,那么就需要调用超类(你所继承的类)的构造方法,否则对象可能不会被正确地初始化。比如下面的例子:
>>> class Bird:
... def __init__(self):
... self.hungry=True
... def eat(self):
... if self.hungry:
... print 'aaah...'
... self.hungry=False
... else:
... print 'no thanks'
>>> b=Bird()
>>> b.eat()
aaah...
>>> b.eat()
no thanks

现在考虑子类SongBird,它添加了唱歌的行为:
>>> class SongBird(Bird):
... def __init__(self):
... self.sound='Squawk'
... def sing(self):
... print self.sound
...
>>> s=SongBird()
>>> s.sing()
Squawk
因为SongBird是Bird的一个子类,它继承了eat方法,但如果调用eat方法,就会产生一个问题:
>>> s.eat()
Traceback (most recent calllast):
File"<input>", line 1, in <module>
File"<input>", line 5, in eat
AttributeError:SongBird instance has no attribute 'hungry'

原因是:在SongBird中,构造方法被重写,但新的构造方法没有任何关于初始化hungry特性的代码。为了达到预期的效果,SongBird的构造方法必须调用其超类Bird的构造方法来确保进行基本的初始化。

6:继承标准类型
经典类中的一个问题是,不能对标准类型进行子类化。幸运的是,随着类型(types)和类(class)的统一和新式类的引入。这一点已经被修正。下面,介绍两个子类化Python 类型的相关例子。
一个处理浮点数的子类,它带两位小数位:
class RoundFloat(float):
def __new__(cls, val):
return float.__new__(cls, round(val, 2))

覆盖__new__()特殊方法来定制对象,使之和标准Python 浮点数(float)有一些区别。最好是使用super()内建函数去捕获对应的父类以调用它的__new()__方法,下面,对它进行这方面的修改:
class RoundFloat(float):
def __new__(cls, val):
return super(RoundFloat, cls).__new__(cls,
round(val, 2))

下面是一些样例输出:
>>>RoundFloat(1.5955)
1.6
>>>RoundFloat(1.5945)
1.59
>>>RoundFloat(-1.9955)
-2.0
object.__new__(cls, ...),它会创建cls的实例,__new__是静态方法(因其特殊性,不需要显示声明)。而且它的第一个参数是要创建实例的类型。他返回一个cls的实例。
一般的用法中,创建子类的实例,可以调用父类的new:
super(currentcls, cls).__new__(cls, ...)。比如:
float.__new__(cls, 3.456),创建的就是cls的实例。

7:多重继承
Python允许子类继承多个基类。也就是多重继承。这里的难点在于:如何正确找到没有在当前(子)类定义的属性。也就是所谓的:方法解释顺序(MRO)。
在Python 2.2 以前的版本中,算法非常简单:深度优先,从左至右进行搜索,多重继承则取找到的第一个名字。
由于类,类型和内建类型的子类,都经过全新改造, 有了新的结构,这种算法不再可行。这样一种新的MRO 算法被开发出来,新的查询方法是采用广度优先,而不是深度优先。
下面的示例,展示经典类和新式类中,方法解释顺序有什么不同。
class P1: #(object)
def foo(self):
print 'called P1-foo()'
class P2: #(object)
def foo(self):
print 'called P2-foo()'
def bar(self):
print 'called P2-bar()'
class C1(P1, P2): pass
class C2(P1, P2):
def bar(self):
print 'called C2-bar()'
class GC(C1, C2): pass


上面这些类的关系如下图所示:




首先来使用经典类,可以验证经典类使用的解释顺序,深度优先,从左至右:
>>> gc = GC()
>>> gc.foo() # GC ==> C1 ==> P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> P1 ==> P2
called P2-bar()

当调用foo()时,它首先在当前类(GC)中查找。如果没找到,就向上查找最亲的父类,C1。查找未遂,就继续沿树上访到父类P1,foo()被找到。
同样,对bar()来说,它通过搜索GC,C1,P1 然后在P2 中找到。因为使用这种解释顺序的缘故,C2.bar()根本就不会被搜索了。
如果需要调用C2 的bar()方法,则必须调用它的合法的全名,采用典型的非绑定方式去调用,并且提供一个合法的实例:
>>> C2.bar(gc)
called C2-bar()

对于新式类,取消类P1 和类P2 声明中的对(object)的注释,重新执行一下。新式方法的查询有一些不同:
>>> gc = GC()
>>> gc.foo() # GC==> C1 ==> C2 ==> P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> C2
called C2-bar()
与沿着继承树一步一步上溯不同,它首先查找同胞兄弟,采用一种广度优先的方式。当查找foo(),它检查GC,然后是C1 和C2,然后在P1 中找到。如果P1 中没有,查找将会到达P2。
然而,bar()的结果是不同的。它搜索GC 和C1,紧接着在C2 中找到了。这样,就不会再继续搜索到祖父P1 和P2。

新式类有一个__mro__属性,告诉你查找顺序是怎样的。它是类属性,实例没有该属性。它是新式类的属性,经典类没有:
>>> GC.__mro__
(<class '__main__.GC'>, <class'__main__.C1'>, <class '__main__.C2'>, <class '__main__.P1'>, <class '__main__.P2'>, <type'object'>)

为什么经典类MRO 会失败?在版本2.2 中,类型与类的统一,带来了一个新的“问题”,波及所有从object派生出来的(根)类,一个简单的继承结构变成了一个菱形。
比如,有经典类B 和C,C 覆盖了构造器,B 没有,D 从B 和C 继承而来:
class B:
pass
class C:
def __init__(self):
print "the default constructor"
class D(B, C):
pass
当我们实例化D,得到:
>>> d = D()
the default constructor



上图为B,C 和D 的类继承结构,现在把代码改为采用新式类的方式,问题也就产生了:
class B(object):
pass
class C(object):
def __init__(self):
print "the default constructor"

由于在新式类中,需要出现基类,这样就在继承结构中,形成了一个菱形。D 的实例上溯时,不应当错过C, 但不能两次上溯到A(因为B 和C 都从A 派生)。
继承结构已变成了一个菱形,如果使用经典类的MRO,当实例化D 时,不再得到C.__init__()的结果,而是得到object.__init__()!这就是为什么MRO
需要修改的真正原因。

尽管我们看到了,在上面的例子中,类GC的属性查找路径被改变了,但你不需要担心会有大量的代码崩溃。经典类将沿用老式MRO,而新式类将使用它自己的MRO。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: