⇤ ← Revision 1 as of 2006-01-09 06:12:38
Size: 17431
Comment:
|
Size: 18673
Comment: 2005-01-11 WeiZhong做了一次较大规模的修订
|
Deletions are marked like this. | Additions are marked like this. |
Line 7: | Line 7: |
::-- WeiZhong [[[DateTime(2006-01-09T23:58:38Z)]]] | |
Line 10: | Line 10: |
= python中的 new-tyle 类及其实例 = (原文是python in a nutshell5.2节) == 5.2新版类及其实例 == 从python2.2起,如果一个类继承自内建的object类型(或者它子类了任何内建类型如list,dict,file等等),那它就是一个 新版的类。 在这之前,不允许以内建类别为基类生成新的类,而且根本没有object这个类别。在本章5.4节的后半部分,我介绍了不管一个类是否有基类,都能生成一个新版类的其它方法。 在本章的开头,我就建议每个人养成使用新版类的编程习惯(当然你得用Python2.2以上版本)。 === 5.2.1 The Built-in object Type === objet 类是所有内建类及新版类的祖先。 object类定义了一系列特殊方法(本章5.3节后半部分有它们的文档)来实现所有对象的默认行为。 `__new__ , __init__` 你可以创建 object 类的直接子类,这样静态方法 __new__()方法就用来创建该类的实例,然后使用该实例的__init__()方法初始化这个实例。__init__()方法会忽略你传递过来的任何参数。 `__delattr__ , __getattribute__, __setattr__` 对象用这些方法来处理属性引用。本章前半部分已经做了详细介绍。 `__hash__ , __repr__, __str__` 一个对象可以作为参数传递给 hash函数 repr函数及 str 类。 object的子类可以覆盖这些方法,并且|或添加新的方法 === 5.2.2 Class-Level Methods === ''' 类级别方法''' 新版对象模型提供了两种类级别方法(传统对象模型没有这些方法):静态方法和类方法。类级别方法仅存在于python2.2以上。 不过在python2.2及更新版本中,传统对象也已经拥有这些类级别方法。新版对象模型提供了诸多新特性,仅有此特性被传统对象模型全功能实现。 ==== 5.2.2.1 Static methods ==== ''' 5.2.2.1静态方法''' 静态方法可以直接被类或类实例调用。不具有常规方法那样的特殊行为(绑定、非绑定、第一个参数等)。你完全可以将静态方法当成一个用属性引用方式调用的普通函数来看待。任何时候定义静态方法都不是必须的(你完全可以定义一个普通函数) 某些程序员认为,当有一堆函数仅仅为某一特定类编写时,这种方式可以提供一致性。 以上言论过时,根据python2.4提供的新特性,你可以象下面这样做。 {{{#!python class AClass(object): @staticmethod #静态方法修饰符,表示下面的方法是一个静态方法 def astatic( ): print 'a static method' anInstance = AClass( ) AClass.astatic( ) # prints: a static method anInstance.astatic( ) # prints: a static method }}} 以上言论过时. ==== 5.2.2.2 Class methods ==== 一个类方法就是你可以通过类或它的实例来调用,python将类绑定到该方法的第一个参数. 不管你是用类调用这个方法还是类的实例调用这个方法,方法的第一个参数都是类对外服而不是实例对象. 按照惯例,类方法的第一个形参被命名为 cls. 任何时候定义类方法都不是必须的(你完全可以定义一个以该类为参数的普通函数).某些程序员认为这个特性会提供一定的一致性. 以上言论过时,根据python2.4提供的新特性,你可以象下面这样做。 {{{#!python class ABase(object): @classmethod #类方法修饰符 def aclassmet(cls): print 'a class method for', cls.__name__ class ADeriv(ABase): pass bInstance = ABase( ) dInstance = ADeriv( ) ABase.aclassmet( ) # prints: a class method for ABase bInstance.aclassmet( ) # prints: a class method for ABase ADeriv.aclassmet( ) # prints: a class method for ADeriv dInstance.aclassmet( ) # prints: a class method for ADeriv }}} 以上言论过时. === 5.2.3 New-Style Classes === ''' 5.2.3 新版的类''' 本章前面提到的传统类的全部特性,新版类同样都有.新版的类添加了关于__init__特殊方法的其它特性,新版的类拥有一个名为 __new__的静态方法 ==== 5.2.3.1 __init__ ==== 新版类C从 object 继承了没有被覆盖的 __init__方法.它允许你传递任意的参数来调用C,只是这些参数都被丢弃而已. 你也许会觉得这很让人惊讶,呵呵,事实确实就是如此. 我建议你在所有的新版类中覆盖 __init__方法. 只有极少的情况你的类的 __init__方法无事可做. {{{#!python class C(object): def __init__(self): pass # rest of class body omitted }}} ==== 5.2.3.2 __new__ ==== 每一个新版类都有一个名为`__new__`的静态方法. 当你调用 `C(*args,**kwds)`创建一个C实例时,python内部调用的是 `C.__new__(C,*args,**kwds).python` 使用`__new__`方法的返回值 x 做为新生成的实例返回. 在确认 x 是C的实例以后, python调用`C.__init__(x,*args,**kwds)`来初始化实例. 也就是说,对新类C来讲,语句 x=C(23)等同于:{{{ x = C.__new__(C, 23) if isinstance(x, C): C.__init__(x, 23) }}} `object.__new__`创建一个新的,未初始化的类实例,它接收传递过来的第一个参数(也就是类对象本身),忽略其它的参数. 当你覆盖`__new__`方法时,你不必使用函数修饰符` @staticmethod`, python解释器根据上下文会认出`__new__()`方法. 如果你需要重绑定` C.__new__`方法,在类外,你只需要使用` C.__new__=staticmethod(你想使用的新方法)` 即可.(很少有这样的需求) `__new__`方法拥有函数工厂的绝大部分弹性. `__new__`根据实际情况可以返回一个已有的实例或者创建一个新的实例. 下面举一个通过覆盖__new__方法实现独身对象的设计模式的例子: {{{#!python class Singleton(object): _singletons = {} def __new__(cls, *args, **kwds): if not cls._singletons.has_key(cls): #若还没有任何实例 cls._singletons[cls] = object.__new__(cls) #生成一个实例 return cls._singletons[cls] #返回这个实例 }}} Singleton的所有子类(当然是没有覆盖`__new__`方法的子类)都只可能有一个实例. 如果子类定义了一个`__init__`方法, 那么子类必须确保它的`__init__`方法是多次对同一实例调用是安全的. === 5.2.4 New-Style Instances === ''' 5.2.4新版类实例''' 所有传统类实例有的特性,新版类实例全都有.新版类实例还拥有以下新特性: `__slots__`特殊属性影响着实例属性的访问 新版对象模型同样添加了一个新的方法` __getattribute__ `比原有的` __getattr__ `方法更通用. 不同的实例可以拥有这些特殊方法的不同实现. ==== 5.2.4.1 Properties ==== property 就是一个实例中具有特殊功能的属性. 你可以使用常规语法对property进行引用,绑定或解除绑定.如: {{{ print x.prop x.prop=23 del x.pro }}} 下面介绍如何定义一个只读property {{{#!python class Rectangle(object): def __init__(self, width, heigth): self.width = width self.heigth = heigth def getArea(self): return self.width * self.heigth area = property(getArea, doc='area of the rectangle') }}} properties 能做的任务与那些特殊方法是相似的,不过更简单更快捷. 你通过调用内建property类别来生成一个property,并将其结果绑定为一个类属性.就象绑定类属性,通常在类定义内来创建property, 当然你也可选择在其它地方.假设在新版类 C 定义内,使用以下语法: {{{ attrib = property(fget=None, fset=None, fdel=None, doc=None)}}} x是C的一个实例,当你引用 x.attrib 时,python调用 fget方法取值给你. 当你为x.attrib赋值: x.attrib=value 时,python调用 fset方法,并且value值做为fset方法的参数 这一段是讲述如何在传统对象模型中实现property,不看也罢,将来传统对象模型是要被丢弃的. {{{#!python class Rectangle: def __init__(self, width, heigth): self.width = width self.heigth = heigth def getArea(self): return self.width * self.heigth def __getattr__(self, name): if name= ='area': return self.getArea( ) raise AttributeError, name def __setattr__(self, name, value): if name= ='area': raise AttributeError, "can't bind attribute" self.__dict__[name] = value }}} ==== 5.2.4.2 __slots__ ==== '''' 5.2.4.2 __slots__属性''' 通常,每个实例对象 x 都拥有一个字典 x.__dict__ python通过此字典允许你绑定任意属性给 x 对象. 为节省内存, 你可以定义一个 类属性 __slots__,这是一个字符串序列(通常是一个tuple) 当类 C 拥有 __slots__属性, x的直接子类就没有 x.__dict__属性. 如果试图绑定一个 __slots__中不存在的类型给实例的话,将引发一个异常. 使用__slots__你失去了绑定任意属性的能力,却能帮你节省内存消费,有助于生成小的实例对象. 提醒: 当一个类会生成很多很多实例时,即一个实例节省几十个字节都可节省一大笔内存时,__slots__就值得使用.典型的类能有数百万而不是几千个实例同时存在.不象大多数其它的类属性, 就象我刚刚描述的那样,只有类定义语句可以绑定它做为类属性.任何后来的修改,重新绑定或解除绑定对__slots__都不起作用.即使对从一个类继承过来的__slots__也一样. 下面介绍了如何通过添加 __slots__属性给刚才定义的 Rectangle 类,以便得到瘦身的类实例: {{{#!python class OptimizedRectangle(Rectangle): __slots__ = 'width', 'heigth' }}} 我们不需给area property也定义一个slot. __slots__里不能包含 properties, 只能包含常规实例属性. 如果不定义 __slots__属性,那些属性只能保存在实例的__dict__属性中. ==== 5.2.4.3 __getattribute__ ==== 对新版类的实例来说, 所有的属性引用都是通过特殊属性__getattribute__方法完成的.这个方法由基类对象提供,由它实现对象属性引用的全部细节.在本章的前面有详细的文档. 如果有特殊需求,你也可以覆盖 __getattribute__属性. 比如说在你的子类实例中隐藏某些父类的属性或方法.下面的例子演示了实现一个没有append方法的list类: {{{#!python class listNoAppend(list): def __getattribute__(self, name): if name = = 'append': raise AttributeError, name return list.__getattribute__(self, name) }}} 这个类的实例几乎与内建list对象完全相同,除了性能更差以外. 任何对该类实例append方法的调用都会引发一个异常. {{{#!python class AttributeWatcher: def __init__(self): # note the caution to avoid triggering __setattr__, and the # emulation of Python's name-mangling for a private attribute self.__dict__['_AttributeWatcher__mydict']={ } def __getattr__(self, name): # as well as tracing every call, for demonstration purposes we # also fake "having" any requested attribute, EXCEPT special # methods (__getattr__ is also invoked to ask for them: check by # trying a few operations on an AttributeWatcher instance). print "getattr", name try: return self.__mydict[name] except KeyError: if name.startswith('__') and name.endswith('__'): raise AttributeError, name else: return 'fake_'+name def __setattr__(self, name, value): print "setattr", name, value self.__mydict[name] = value def __delattr__(self, name): print "delattr", name try: del self.__mydict[name] except KeyError: pass }}} ==== 5.2.4.4 Per-instance methods ==== ''' 个体实例方法''' 经典与新版对象模型都允许一个实例绑定私有的属性和方法. 对一个方法来讲,和其它属性一样,一个私有的属性绑定将覆盖类定义中的同名属性:只要在实例中发现有这个属性,就不再继续查找类定义了. 经典与新版对象模型的不同之处在于实例私有的特殊方法. 在经典对象模型中,一个实例可以有效的覆盖由类定义提供的特殊方法. 在新版对象模型中,实例特殊方法的隐式调用总是依赖类定义中的特殊方法而不是实例中新绑定的特殊方法.下面这个例子可以说明这一点: {{{#!python def fakeGetItem(idx): return idx class Classic: pass c = Classic( ) c.__getitem__ = fakeGetItem print c[23] # prints: 23 class NewStyle(object): pass n = NewStyle( ) n.__getitem__ = fakeGetItem print n[23] # results in: # Traceback (most recent call last): # File "<stdin>", line 1, in ? # TypeError: unindexable object }}} 可以看到,在调用n[23]时,这是一个隐式的__getitem__方法调用,因为NewStyle类中并未定义该方法,所以引发了异常 不过如果你使用n.__getitem__(23)这种方式来显式调用特殊方法时,它还是可以工作的 === 5.2.5 新版对象模型中的继承 === ''' Inheritance in the New-Style Object Model''' 在新版对象模型中,继承的使用方式与传统模型大致相同.一个主要的区别是新版类可以从一个内建类型中继承. 新版的类仍然支持多继承,不过只有多个内建类型是特殊设计并允许相互兼容的情况下才可以被继承.通常情况,一个新版类只允许继承一个真实类型.这意味着在多继承时,除object以外,至多一个内建类型可以是所有内建类型及新版类的超类. ==== 5.2.5.1 Method resolution order ==== ''' 方法解析顺序:''' 在传统对象模型中,方法和属性按 从左至右 深度优先的顺序查找.显然,当多个父类继承自同一个基类时,这会产生我们不想要的结果. 举例来说, A是B和C的子类,而B和C继承自D,传统对象模型的的查找方法是 A-B-D-C-D. 由于Python先查找D后查找C,即使C对D中的方法进行了重定义,也只能使用D中定义的版本.因为这个继承模式,该问题会导致一些实际麻烦. 在新版对象模型中,所有类均直接或间生成子类对象. 当D是object 的直接子类时,新版对象模型的搜索顺序就变为 A-B-C-D 每个内建类型及新版的类均内建一个特殊的只读属性 __mro__ ,这是一个tuple,保存着方法解析类型. 你只能通过类来引用 __mro__,不能通过实例. ==== 5.2.5.2 Cooperative superclass method calling ==== '''' 协作式调用超类方法'''' 前面我们提丟,当一个子类覆盖了一个方法,这个覆盖的方法通常要调用被覆盖的方法.python传统对象模型惯用的方式是用非绑定方法语法调用父类的同名方法.当多继承时,这种方法是有缺限的,见下例: {{{#!python class A(object): def met(self): print 'A.met' class B(A): def met(self): print 'B.met' A.met(self) class C(A): def met(self): print 'C.met' A.met(self) class D(B,C): def met(self): print 'D.met' B.met(self) C.met(self) }}} 在上面的代码中,当我们调用 D().met()方法时, A.met()方法被调用了两次. 我们怎样才可以保证每个父类的实现均被顺序调用且仅仅调用一次呢?不采取点特殊措施这个问题很难解决.从python2.2起,提供了这样一个特殊手段. 那就是 super类型.super(aclass,obj)返回一个与obj相关的特殊对象. 当我们调用该对象的一个属性或方法时,这一段真不好翻译,总之,这样就保证了每个父类的实现均被调用且仅仅调用一次了. 改写后的代码如下: {{{#!python class A(object): def met(self): print 'A.met' class B(A): def met(self): print 'B.met' super(B,self).met( ) class C(A): def met(self): print 'C.met' super(C,self).met( ) class D(B,C): def met(self): print 'D.met' super(D,self).met( ) }}} 如果你养成了总是使用superclass调用父类方法,你的类就能适应无论多复杂的继承结构. |
= python中的新型类及其实例详解= (原文见《Python In a Nutshell(2003)》5.2节) == 写在前面 == 刚刚接触python不久,对python中的Classic Class 及 New-Style Class 的区别一头雾水中,在啃《Python In a Netshell》时发现第五章第二节讲得非常好,于是边读边译,就有了这篇东西。对于原文中已经过时的东西,我做了删减,对python2.4中新增的东西,我做了部分补充。由于对python术语不是很熟悉,有翻译错误之处,还请大家指正。 常见术语: Classic 被翻译为 传统, 如 Classic Class(传统类), classic object model (传统对象模型) New-Style 在视上下文被翻译为 新型 或者 新的, 如 New-Style Class(新型类),New-Style object model (新的对象模型) == 5.2 新型类及其实例 == 前面我已经讲了python 2.2中引入的 new-style 对象模型. 新型类及其实例象传统类一样,都是第一层次对象,都可以拥有任意的属性,通过调用一个类生成该类的一个实例等等. 在这一小节,我来向大家揭示新的对象模型及传统对象模型的不同. 从python2.2起,如果一个类继承自内建的object类型(或者它是任何内建类型如list,dict,file的子类),那它就是一个新型类。在这之前,不允许通过继承内建类别生成新类,也根本没有object这个对象。在本章5.4节的后半部分,我介绍了将一个传统类改造成新型类的方法。 在本章的开头,我建议每个人从现在开始养成只使用新型类的编程习惯(当然你得用Python2.2以上版本)。新的对象模型与传统对象模型相比有小但却很重要的优势,可以说接近完美. 很简单,认准新的对象模型,好好学没错. == 5.2.1 内建的object类型 == objet类是所有内建类型及新型类的祖先。 object类定义了一系列特殊方法(本章5.3节后半部分有它们的文档)来实现所有对象的默认行为。 `__new__,__init__`方法 你可以创建 object 的直接子类,静态方法 `__new__()`用来创建类的实例,实例的`__init__()`方法用来初始化自己。默认的`__init__()`方法会忽略你传递过来的任何参数。(2.4版好象做了改动,`__new__和__init__`方法默认都不接收任何参数,除非你重新实现这两个方法) `__delattr__ , __getattribute__, __setattr__ `方法 对象用这些方法来处理属性引用。本章前半部分已经做了详细介绍。 `__hash__ , __repr__, __str__ `方法 一个对象可以作为参数传递给这些方法. 允许object的子类重载这些方法,或添加新方法。 == 5.2.2 类方法 == 新的对象模型提供了两种类方法(传统对象模型没有这些方法):静态方法和类方法。只有python2.2及更新版本才支持类方法. 需要提一下的是,在python2.2及更新版本中,传统类也实现了类方法。新的对象模型提供的诸多新特性中,有且仅有类方法这一特性被传统对象模型全功能实现。 === 5.2.2.1静态方法 === 静态方法可以直接被类或类实例调用。它没有常规方法那样的特殊行为(绑定、非绑定、默认的第一个参数规则等等)。完全可以将静态方法当成一个用属性引用方式调用的普通函数来看待。任何时候定义静态方法都不是必须的(静态方法能实现的功能都可以通过定义一个普通函数来实现). 某些程序员认为,当有一堆函数仅仅为某一特定类编写时,这种方式可以提供使用上的一致性。 根据python2.4提供的新的语法,你可以象下面这样来创建一个静态方法, {{{#!python class AClass(object): @staticmethod #静态方法修饰符,表示下面的方法是一个静态方法 def astatic( ): print 'a static method' anInstance = AClass( ) AClass.astatic( ) # prints: a static method anInstance.astatic( ) # prints: a static method }}}注:staticmethod是一个内建函数,用来将一个方法包装成静态方法,在2.4以前版本,要用下面的方式定义一个静态方法(不再推荐使用): {{{#!python class AClass(object): def astatic( ): print 'a static method' astatic=staticmethod(astatic) }}} 这种方法在函数定义本身比较长时经常会忘记后面这一行. === 5.2.2.2 类方法 === 一个类方法就是你可以通过类或它的实例来调用的方法, 不管你是用类调用这个方法还是类的实例调用这个方法,python只会将实际的类对象做为该方法的第一个参数.记住:方法的第一个参数都是类对象而不是实例对象. 按照惯例,类方法的第一个形参被命名为 cls. 任何时候定义类方法都不是必须的(静态方法能实现的功能都可以通过定义一个普通函数来实现,只要这个函数接受一个类对象做为参数就可以了).某些程序员认为这个特性当有一堆函数仅仅为某一特定类编写时会提供使用上的一致性. 你可以象下面这样来生成一个类方法: {{{#!python class ABase(object): @classmethod #类方法修饰符 def aclassmet(cls): print 'a class method for', cls.__name__ class ADeriv(ABase): pass bInstance = ABase( ) dInstance = ADeriv( ) ABase.aclassmet( ) # prints: a class method for ABase bInstance.aclassmet( ) # prints: a class method for ABase ADeriv.aclassmet( ) # prints: a class method for ADeriv dInstance.aclassmet( ) # prints: a class method for ADeriv }}} 注:staticmethod是一个内建函数,用来将一个方法封装成类方法,在2.4以前版本,你只能用下面的方式定义一个类方法: {{{#!python class AClass(object): def aclassmethod(cls): print 'a class method' aclassmethod=staticmethod(aclassmethod) }}} 并没有人要求必须封装后的方法名字必须与封装前一致,但建议你总是这样做(如果你使用python2.4版本以下时). 这种方法在函数定义本身比较长时经常会忘记后面这一行. == 5.2.3 新型类 == 除了拥有传统类的全部特性之外,新型类当然还具有一些新特性.`__init__`特殊方法的行为与传统类相比有了一些变化,另外还新增了一个名为` __new__`的静态方法 === 5.2.3.1 __init__方法 === 新型类C中,从 object 继承来的原始 `__init__`方法,可以认为就是一个 pass 语句,因为它几乎什么都不做,建议你在所有的新型类中重载 `__init__`方法. {{{#!python class C(object): def __init__(self): pass # rest of class body omitted }}} 示例中的的类只允许无参数调用,硬要传递一个参数给它会产生异常(如用C('xyz')). 如果C没有重载`__init__`方法,调用C('xyz')会象'xyz'根本不存在一样忽略参数继续执行. 注意: (根据我的试验,2.4版中这点发生了变化,即使没有重载`__init__`方法,象C('xyz')这样调用一样会引发异常) === 5.2.3.2 __new__方法 === 每一个新型类都有一个名为`__new__`的静态方法. 当你调用 C(*args,**kwds)创建一个C实例时,python内部调用的是 C.`__new__`(C,*args,**kwds). __new__方法的返回值 x 就是该类的实例. 在确认 x 是C的实例以后, python调用C.`__init__`(x,*args,**kwds)来初始化这个实例. 也就是说,对新类C来讲,语句 x=C(23)等同于: {{{#!python x = C.__new__(C, 23) if isinstance(x, C): C.__init__(x, 23) }}}{{{ object.__new__创建一个新的,未初始化的类实例,它接收传递过来的第一个参数(也就是类对象本身),忽略其它的参数.当你重载__new__方法时,你不必使用函数修饰符@staticmethod, python解释器根据上下文会认出__new__()方法是一个静态方法. 如果你需要重绑定 C.__new__方法,你只需要在类外面执行 C.__new__=staticmethod(你想使用的新方法)就可以了.(极少有这样的需求) __new__方法拥有函数工厂的绝大部分弹性. 根据实际需求,我们可以让__new__返回一个已有的实例或者创建一个新的实例. 下面举一个通过重载__new__方法实现独身对象的设计模式的例子:}}} {{{#!python class Singleton(object): _singletons = {} def __new__(cls, *args, **kwds): if not cls._singletons.has_key(cls): #若还没有任何实例 cls._singletons[cls] = object.__new__(cls) #生成一个实例 return cls._singletons[cls] #返回这个实例 }}}{{{ Singleton的所有子类(当然是没有重载__new__方法的子类)都只可能有一个实例. 如果该类的子类定义了一个__init__方法,那么它必须保证它的__init__方法能够安全的对同一实例进行多次调用. }}} == 5.2.4新型类实例 == 新型类实例除了拥有传统类实例的全部特性之外,还拥有一种称为property的新属性及一个叫作`__slots__`的特殊属性,该属性会对实例其它属性的访问产生重要影响. 新的对象模型同样添加了一个新的方法 `__getattribute__ 比原有的 __getattr__ `方法更通用. 不同的实例可以拥有这些特殊方法的不同实现. === 5.2.4.1 Properties === property 是实例中具有特殊功能的属性. 你可以使用常规语法对property进行引用,绑定或解除绑定.如: {{{#!python print x.prop x.prop=23 del x.pro }}} 然而,property如果只有这点功能那就和普通属性没什么两样了,property有它的独到之处,请往下读. 下面介绍如何定义一个只读property:{{{#!python class Rectangle(object): def __init__(self, width, heigth): self.width = width self.heigth = heigth def getArea(self): return self.width * self.heigth area = property(getArea, doc='area of the rectangle')}}}{{{ 矩形类的每一个实例 r 均拥有一个只读属性 r.area ,该属性由 r.getArea()方法实时计算得来. Rectangle.area.__doc__是'area of the rectangle',这个属性是只读的(试图对它进行重绑定或解除绑定的企图都注定会失败),这是因为我们在property定义中只指定了该属性 get 方法. properties 干的活与那些特殊方法__getattr__,__setattr__,__delattr__等是极其相似的,不过同样的活它干起来更简单更快捷. 内建property类别(我倒宁愿把当成一个函数来看)用来生成一个property,并将其返回值绑定为一个类属性.如同绑定类的常规属性,一般在定义类时就创建property,当然也有其它选择.假设在定义新型类 C 时,使用以下语法: attrib = property(fget=None, fset=None, fdel=None, doc=None) x是C的一个实例,当你引用 x.attrib 时,python调用 fget方法取值给你. 当你为x.attrib赋值: x.attrib=value 时,python调用 fset方法,并且value值做为fset方法的参数,当你执行del x.attrib 时,python调用fdel方法,你传过去的名为doc的参数即为该属性的文档字符串. 在矩形类中,因为我们没有为area属性指定fset和fdel参数,所以该属性只能读取. }}} === 5.2.4.2 __slots__属性 === {{{通常,每个实例对象 x 都拥有一个字典 x.__dict__. python通过此字典允许你绑定任意属性给 x 实例. 定义一个名为__slots__的类属性可以有效减少每个实例占用的内存数量.__slots__是一个字符串序列(通常是一个tuple). 当类 C 拥有 __slots__属性, x的直接子类就没有 x.__dict__属性. 如果试图绑定一个__slots__中不存在属性给实例的话,就会引发异常. __slots__属性虽然令你失去绑定任意属性的方便,却能有效节省每个实例的内存消耗,有助于生成小而精干的实例对象. 注: 当一个类会生成很多很多实例时(有些类同时拥有数百万而不是几千个实例),即使一个实例节省几十个字节都可节省一大笔内存时,就值得使用__slots__属性.只有在类定义中可以使用 __slots__=aTuple 语句来为一个类添加__slots__属性,其它任何位置对一个类或其父类的__slots__属性的修改,重新绑定或解除绑定都是无效的. 下面介绍如何通过添加 __slots__属性给刚才定义的 Rectangle 类,以得到瘦身的类实例: }}}{{{#!python class OptimizedRectangle(Rectangle): __slots__ = 'width', 'heigth' }}}{{{ __slots__里不能包含 properties, 只能包含常规实例属性.我们不需也不允许给area property也定义一个slot. 若不定义 __slots__属性,常规属性则保存在实例的__dict__属性中. }}} === 5.2.4.3 __getattribute__方法 === {{{ 对新型类的实例来说, 所有的属性引用都是通过特殊方法__getattribute__()完成的.该方法由基类对象提供,负责实现对象属性引用的全部细节.在本章的前面有该方法详细的文档. 如果有特殊需求,你也可以重载 __getattribute__属性(比如你打算在子类实例中隐藏父类的某些属性或方法).下面的例子演示了实现一个没有append方法的list类: }}}{{{#!python class listNoAppend(list): def __getattribute__(self, name): if name = = 'append': raise AttributeError, name return list.__getattribute__(self, name) }}} 除了功能不全以外,该类的实例与内建list对象完全相同.任何调用该类实例append方法的企图都会引发一个异常. === 5.2.4.4个体实例方法 === 传统与新的对象模型都允许一个实例拥有私有的属性和方法(通过绑定或重绑定) . 实例的私有属性会屏蔽掉类定义中的同名属性.举例来说: {{{#!python class abc(object): def attrib_a(self): print 'aMethod defined in class abc' b=abc() def afunc(): print 'hello,world!' b.attrib_a=afunc b.attrib_a() }}} 该例子将打印 'hello,world!' 在python隐式调用实例的私有(后绑定)特殊方法时,新的对象模型改变了传统对象模型的行为.在传统对象模型中,无论是显示调用,还是隐式调用,都会调用这个实例的后绑定特殊方法.而在新的对象模型中,除非显示调用实例的特殊方法,否则python总是去调用在类中定义的特殊方法.下面这个例子可以说明这一点: {{{#!python def fakeGetItem(idx): return idx class Classic: pass c = Classic( ) c.__getitem__ = fakeGetItem print c[23] # prints: 23 class NewStyle(object): pass n = NewStyle( ) n.__getitem__ = fakeGetItem print n[23] # 程序执行到这步会出错. 如果将代码改为 print n.__getitem__(23) 则正常运行 # Traceback (most recent call last): # File "<stdin>", line 1, in ? # TypeError: unindexable object }}}{{{ 调用n[23],将产生一个隐式的__getitem__方法调用,因为新型类 n 中并未定义该方法,所以引发了异常.不过如果你使用n.__getitem__(23)这种方式来显式调用特殊方法时,它还是可以工作的. }}} == 5.2.5 新的对象模型中的继承 == 在新的对象模型中,继承的使用方式与传统模型大致相同.一个关键的区别就是新型类能从一个内建类型中继承而传统类不能. 新型类仍然支持多继承,若要从多个内建类型继承生成一个新类,则这些内建类型必须是经过特殊设计能够相互兼容. python不支持随意的从多个内建类型进行多继承,通常情况都是通过至多从一个内建类型继承得到新类.这意味着在多继承时,除object以外,至多有一个内建类型可以是其它内建类型和新型类的超类. === 5.2.5.1方法解析顺序: === 在传统对象模型中,方法和属性按 从左至右 深度优先 的顺序查找.显然,当多个父类继承自同一个基类时,这会产生我们不想要的结果. 举例来说, A是B和C的子类,而B和C继承自D,传统对象模型的的属性查找方法是 A-B-D-C-D. 由于Python先查找D后查找C,即使C对D中的方法进行了重定义,也只能使用D中定义的版本.由于这个继承模式固有的问题,在实际应用中会造成一些麻烦. 在新的对象模型中,所有类均直接或间生成子类对象. python改变了传统对象模型中的解析顺序,使用上面的例子,当D是一个新型类(比如D是object 的直接子类),新的对象模型的搜索顺序就变为 A-B-C-D. 每个内建类型及新型类均内建一个特殊的只读属性 `__mro__ `,这是一个tuple,保存着方法解析类型. 只允许通过类来引用 `__mro__`(不允许通过实例). === 5.2.5.2 协作式调用超类方法 === 前面我们提到,当一个子类重载了父类中一个方法,子类中的方法通常要调用父类中的同名方法来做一些事.这也是python传统对象模型惯用的方式,即使用非绑定方法语法调用父类的同名方法.当多继承时,这种方法是有缺限的,见下例: {{{#!python class A(object): def met(self): print 'A.met' class B(A): def met(self): print 'B.met' A.met(self) class C(A): def met(self): print 'C.met' A.met(self) class D(B,C): def met(self): print 'D.met' B.met(self) C.met(self) }}} 在上面的代码中,当我们调用 D().met()方法时, A.met()方法被调用了两次. 我们怎样才可以保证每个父类的实现均被顺序调用且仅仅调用一次呢?不采取点特殊措施这个问题很难解决.从python2.2起,提供了这样一个特殊手段. 那就是 super类型. super(aclass,obj)返回对象obj的一个特殊的超对象(superobject). 当我们调用该超对象的一个属性或方法时,就保证了每个父类的实现均被调用且仅仅调用一次了. 改写后的代码如下: {{{#!python class A(object): def met(self): print 'A.met' class B(A): def met(self): print 'B.met' super(B,self).met( ) class C(A): def met(self): print 'C.met' super(C,self).met( ) class D(B,C): def met(self): print 'D.met' super(D,self).met( ) }}} 现在就可以得到期望的结果了. 如果你养成了总是使用superclass调用父类方法,你的类就能适应无论多复杂的继承结构. |
[http://spaces.msn.com/members/shukebeta/Blog/cns!1psZEmYfE1uAECP30_KFiC1Q!128.entry 魏忠的Space发布] ::-- WeiZhong [DateTime(2006-01-09T23:58:38Z)] ::-- ZoomQuiet [DateTime(2006-01-09T06:12:38Z)] TableOfContents = python中的新型类及其实例详解= (原文见《Python In a Nutshell(2003)》5.2节)
1. 写在前面
刚刚接触python不久,对python中的Classic Class 及 New-Style Class 的区别一头雾水中,在啃《Python In a Netshell》时发现第五章第二节讲得非常好,于是边读边译,就有了这篇东西。对于原文中已经过时的东西,我做了删减,对python2.4中新增的东西,我做了部分补充。由于对python术语不是很熟悉,有翻译错误之处,还请大家指正。
常见术语:
Classic 被翻译为 传统, 如 Classic Class(传统类), classic object model (传统对象模型) New-Style 在视上下文被翻译为 新型 或者 新的, 如 New-Style Class(新型类),New-Style object model (新的对象模型)
2. 5.2 新型类及其实例
前面我已经讲了python 2.2中引入的 new-style 对象模型. 新型类及其实例象传统类一样,都是第一层次对象,都可以拥有任意的属性,通过调用一个类生成该类的一个实例等等. 在这一小节,我来向大家揭示新的对象模型及传统对象模型的不同.
从python2.2起,如果一个类继承自内建的object类型(或者它是任何内建类型如list,dict,file的子类),那它就是一个新型类。在这之前,不允许通过继承内建类别生成新类,也根本没有object这个对象。在本章5.4节的后半部分,我介绍了将一个传统类改造成新型类的方法。
在本章的开头,我建议每个人从现在开始养成只使用新型类的编程习惯(当然你得用Python2.2以上版本)。新的对象模型与传统对象模型相比有小但却很重要的优势,可以说接近完美. 很简单,认准新的对象模型,好好学没错.
3. 5.2.1 内建的object类型
objet类是所有内建类型及新型类的祖先。 object类定义了一系列特殊方法(本章5.3节后半部分有它们的文档)来实现所有对象的默认行为。
__new__,__init__方法 你可以创建 object 的直接子类,静态方法 __new__()用来创建类的实例,实例的__init__()方法用来初始化自己。默认的__init__()方法会忽略你传递过来的任何参数。(2.4版好象做了改动,__new__和__init__方法默认都不接收任何参数,除非你重新实现这两个方法)
__delattr__ , __getattribute__, __setattr__ 方法 对象用这些方法来处理属性引用。本章前半部分已经做了详细介绍。
__hash__ , __repr__, __str__ 方法 一个对象可以作为参数传递给这些方法.
允许object的子类重载这些方法,或添加新方法。
4. 5.2.2 类方法
新的对象模型提供了两种类方法(传统对象模型没有这些方法):静态方法和类方法。只有python2.2及更新版本才支持类方法. 需要提一下的是,在python2.2及更新版本中,传统类也实现了类方法。新的对象模型提供的诸多新特性中,有且仅有类方法这一特性被传统对象模型全功能实现。
4.1. 5.2.2.1静态方法
静态方法可以直接被类或类实例调用。它没有常规方法那样的特殊行为(绑定、非绑定、默认的第一个参数规则等等)。完全可以将静态方法当成一个用属性引用方式调用的普通函数来看待。任何时候定义静态方法都不是必须的(静态方法能实现的功能都可以通过定义一个普通函数来实现). 某些程序员认为,当有一堆函数仅仅为某一特定类编写时,这种方式可以提供使用上的一致性。 根据python2.4提供的新的语法,你可以象下面这样来创建一个静态方法,
注:staticmethod是一个内建函数,用来将一个方法包装成静态方法,在2.4以前版本,要用下面的方式定义一个静态方法(不再推荐使用):
这种方法在函数定义本身比较长时经常会忘记后面这一行.
4.2. 5.2.2.2 类方法
一个类方法就是你可以通过类或它的实例来调用的方法, 不管你是用类调用这个方法还是类的实例调用这个方法,python只会将实际的类对象做为该方法的第一个参数.记住:方法的第一个参数都是类对象而不是实例对象. 按照惯例,类方法的第一个形参被命名为 cls. 任何时候定义类方法都不是必须的(静态方法能实现的功能都可以通过定义一个普通函数来实现,只要这个函数接受一个类对象做为参数就可以了).某些程序员认为这个特性当有一堆函数仅仅为某一特定类编写时会提供使用上的一致性.
你可以象下面这样来生成一个类方法:
1 class ABase(object):
2 @classmethod #类方法修饰符
3 def aclassmet(cls): print 'a class method for', cls.__name__
4 class ADeriv(ABase): pass
5 bInstance = ABase( )
6 dInstance = ADeriv( )
7 ABase.aclassmet( ) # prints: a class method for ABase
8 bInstance.aclassmet( ) # prints: a class method for ABase
9 ADeriv.aclassmet( ) # prints: a class method for ADeriv
10 dInstance.aclassmet( ) # prints: a class method for ADeriv
注:staticmethod是一个内建函数,用来将一个方法封装成类方法,在2.4以前版本,你只能用下面的方式定义一个类方法:
并没有人要求必须封装后的方法名字必须与封装前一致,但建议你总是这样做(如果你使用python2.4版本以下时). 这种方法在函数定义本身比较长时经常会忘记后面这一行.
5. 5.2.3 新型类
除了拥有传统类的全部特性之外,新型类当然还具有一些新特性.__init__特殊方法的行为与传统类相比有了一些变化,另外还新增了一个名为 __new__的静态方法
5.1. 5.2.3.1 __init__方法
新型类C中,从 object 继承来的原始 __init__方法,可以认为就是一个 pass 语句,因为它几乎什么都不做,建议你在所有的新型类中重载 __init__方法.
示例中的的类只允许无参数调用,硬要传递一个参数给它会产生异常(如用C('xyz')). 如果C没有重载__init__方法,调用C('xyz')会象'xyz'根本不存在一样忽略参数继续执行. 注意: (根据我的试验,2.4版中这点发生了变化,即使没有重载__init__方法,象C('xyz')这样调用一样会引发异常)
5.2. 5.2.3.2 __new__方法
每一个新型类都有一个名为__new__的静态方法. 当你调用 C(*args,**kwds)创建一个C实例时,python内部调用的是 C.__new__(C,*args,**kwds). new方法的返回值 x 就是该类的实例. 在确认 x 是C的实例以后, python调用C.__init__(x,*args,**kwds)来初始化这个实例. 也就是说,对新类C来讲,语句 x=C(23)等同于:
object.__new__创建一个新的,未初始化的类实例,它接收传递过来的第一个参数(也就是类对象本身),忽略其它的参数.当你重载__new__方法时,你不必使用函数修饰符@staticmethod, python解释器根据上下文会认出__new__()方法是一个静态方法. 如果你需要重绑定 C.__new__方法,你只需要在类外面执行 C.__new__=staticmethod(你想使用的新方法)就可以了.(极少有这样的需求) __new__方法拥有函数工厂的绝大部分弹性. 根据实际需求,我们可以让__new__返回一个已有的实例或者创建一个新的实例. 下面举一个通过重载__new__方法实现独身对象的设计模式的例子:
Singleton的所有子类(当然是没有重载__new__方法的子类)都只可能有一个实例. 如果该类的子类定义了一个__init__方法,那么它必须保证它的__init__方法能够安全的对同一实例进行多次调用.
6. 5.2.4新型类实例
新型类实例除了拥有传统类实例的全部特性之外,还拥有一种称为property的新属性及一个叫作__slots__的特殊属性,该属性会对实例其它属性的访问产生重要影响. 新的对象模型同样添加了一个新的方法 __getattribute__ 比原有的 __getattr__ 方法更通用. 不同的实例可以拥有这些特殊方法的不同实现.
6.1. 5.2.4.1 Properties
property 是实例中具有特殊功能的属性. 你可以使用常规语法对property进行引用,绑定或解除绑定.如:
然而,property如果只有这点功能那就和普通属性没什么两样了,property有它的独到之处,请往下读. 下面介绍如何定义一个只读property:
矩形类的每一个实例 r 均拥有一个只读属性 r.area ,该属性由 r.getArea()方法实时计算得来. Rectangle.area.__doc__是'area of the rectangle',这个属性是只读的(试图对它进行重绑定或解除绑定的企图都注定会失败),这是因为我们在property定义中只指定了该属性 get 方法. properties 干的活与那些特殊方法__getattr__,__setattr__,__delattr__等是极其相似的,不过同样的活它干起来更简单更快捷. 内建property类别(我倒宁愿把当成一个函数来看)用来生成一个property,并将其返回值绑定为一个类属性.如同绑定类的常规属性,一般在定义类时就创建property,当然也有其它选择.假设在定义新型类 C 时,使用以下语法: attrib = property(fget=None, fset=None, fdel=None, doc=None) x是C的一个实例,当你引用 x.attrib 时,python调用 fget方法取值给你. 当你为x.attrib赋值: x.attrib=value 时,python调用 fset方法,并且value值做为fset方法的参数,当你执行del x.attrib 时,python调用fdel方法,你传过去的名为doc的参数即为该属性的文档字符串. 在矩形类中,因为我们没有为area属性指定fset和fdel参数,所以该属性只能读取.
6.2. 5.2.4.2 __slots__属性
{{{通常,每个实例对象 x 都拥有一个字典 x.dict. python通过此字典允许你绑定任意属性给 x 实例. 定义一个名为slots的类属性可以有效减少每个实例占用的内存数量.slots是一个字符串序列(通常是一个tuple). 当类 C 拥有 slots属性, x的直接子类就没有 x.dict属性. 如果试图绑定一个slots中不存在属性给实例的话,就会引发异常. slots属性虽然令你失去绑定任意属性的方便,却能有效节省每个实例的内存消耗,有助于生成小而精干的实例对象. 注: 当一个类会生成很多很多实例时(有些类同时拥有数百万而不是几千个实例),即使一个实例节省几十个字节都可节省一大笔内存时,就值得使用slots属性.只有在类定义中可以使用 slots=aTuple 语句来为一个类添加slots属性,其它任何位置对一个类或其父类的slots属性的修改,重新绑定或解除绑定都是无效的. 下面介绍如何通过添加 slots属性给刚才定义的 Rectangle 类,以得到瘦身的类实例: }}}
__slots__里不能包含 properties, 只能包含常规实例属性.我们不需也不允许给area property也定义一个slot. 若不定义 __slots__属性,常规属性则保存在实例的__dict__属性中.
6.3. 5.2.4.3 __getattribute__方法
对新型类的实例来说, 所有的属性引用都是通过特殊方法__getattribute__()完成的.该方法由基类对象提供,负责实现对象属性引用的全部细节.在本章的前面有该方法详细的文档. 如果有特殊需求,你也可以重载 __getattribute__属性(比如你打算在子类实例中隐藏父类的某些属性或方法).下面的例子演示了实现一个没有append方法的list类:
除了功能不全以外,该类的实例与内建list对象完全相同.任何调用该类实例append方法的企图都会引发一个异常.
6.4. 5.2.4.4个体实例方法
传统与新的对象模型都允许一个实例拥有私有的属性和方法(通过绑定或重绑定) . 实例的私有属性会屏蔽掉类定义中的同名属性.举例来说:
该例子将打印 'hello,world!'
在python隐式调用实例的私有(后绑定)特殊方法时,新的对象模型改变了传统对象模型的行为.在传统对象模型中,无论是显示调用,还是隐式调用,都会调用这个实例的后绑定特殊方法.而在新的对象模型中,除非显示调用实例的特殊方法,否则python总是去调用在类中定义的特殊方法.下面这个例子可以说明这一点:
1 def fakeGetItem(idx): return idx
2 class Classic: pass
3 c = Classic( )
4 c.__getitem__ = fakeGetItem
5 print c[23] # prints: 23
6 class NewStyle(object): pass
7 n = NewStyle( )
8 n.__getitem__ = fakeGetItem
9 print n[23] # 程序执行到这步会出错. 如果将代码改为 print n.__getitem__(23) 则正常运行
10 # Traceback (most recent call last):
11 # File "<stdin>", line 1, in ?
12 # TypeError: unindexable object
调用n[23],将产生一个隐式的__getitem__方法调用,因为新型类 n 中并未定义该方法,所以引发了异常.不过如果你使用n.__getitem__(23)这种方式来显式调用特殊方法时,它还是可以工作的.
7. 5.2.5 新的对象模型中的继承
在新的对象模型中,继承的使用方式与传统模型大致相同.一个关键的区别就是新型类能从一个内建类型中继承而传统类不能. 新型类仍然支持多继承,若要从多个内建类型继承生成一个新类,则这些内建类型必须是经过特殊设计能够相互兼容. python不支持随意的从多个内建类型进行多继承,通常情况都是通过至多从一个内建类型继承得到新类.这意味着在多继承时,除object以外,至多有一个内建类型可以是其它内建类型和新型类的超类.
7.1. 5.2.5.1方法解析顺序:
在传统对象模型中,方法和属性按 从左至右 深度优先 的顺序查找.显然,当多个父类继承自同一个基类时,这会产生我们不想要的结果. 举例来说, A是B和C的子类,而B和C继承自D,传统对象模型的的属性查找方法是 A-B-D-C-D. 由于Python先查找D后查找C,即使C对D中的方法进行了重定义,也只能使用D中定义的版本.由于这个继承模式固有的问题,在实际应用中会造成一些麻烦.
在新的对象模型中,所有类均直接或间生成子类对象. python改变了传统对象模型中的解析顺序,使用上面的例子,当D是一个新型类(比如D是object 的直接子类),新的对象模型的搜索顺序就变为 A-B-C-D.
每个内建类型及新型类均内建一个特殊的只读属性 __mro__ ,这是一个tuple,保存着方法解析类型. 只允许通过类来引用 __mro__(不允许通过实例).
7.2. 5.2.5.2 协作式调用超类方法
前面我们提到,当一个子类重载了父类中一个方法,子类中的方法通常要调用父类中的同名方法来做一些事.这也是python传统对象模型惯用的方式,即使用非绑定方法语法调用父类的同名方法.当多继承时,这种方法是有缺限的,见下例:
在上面的代码中,当我们调用 D().met()方法时, A.met()方法被调用了两次. 我们怎样才可以保证每个父类的实现均被顺序调用且仅仅调用一次呢?不采取点特殊措施这个问题很难解决.从python2.2起,提供了这样一个特殊手段. 那就是 super类型. super(aclass,obj)返回对象obj的一个特殊的超对象(superobject). 当我们调用该超对象的一个属性或方法时,就保证了每个父类的实现均被调用且仅仅调用一次了. 改写后的代码如下:
现在就可以得到期望的结果了. 如果你养成了总是使用superclass调用父类方法,你的类就能适应无论多复杂的继承结构.