python学习笔记12-类代码编写细节

一、class语句

一般形式

class         <name>(superclass,...):

         data=value

         def mothod(self,...):

                   self.member=value

在class语句内,任何赋值语句都会产生类属性。

类几乎就是命名空间,也就是定义变量名(属性)的工具,把数据和逻辑导出给客户端。

怎么样从class语句得到命名空间的呢?

过程如下。就像模块文件,位于class语句主体中的语句会建立起属性。当python执行class语句时(不是调用类)

会从头到尾执行其主体内的所有语句。在这个过程中,进行赋值运算会在这个类的作用域中创建变量名,从而成为对应

类对象中的属性。因为,类就像模块和函数:

*就像函数一样。class语句是作用域,由内嵌的赋值语句建立变量名,就存在这个本地作用域内。

*就像模块内的变量名,在class语句内赋值的变量名会变成类对象中的属性。

class是复合语句,任何种类的语句都可以位于其主体内:print ,=,if,def等。当class语句自身运行时,class语句内的所有

语句都会执行。在class语句内赋值的变量名会创建类属性,而内嵌的def语句则会创建类方法,其他的赋值语句也可以制作属性。

class顶层的赋值语句定义的属性可以用于管理贯穿所有实例的信息。

 

二、方法

方法位于class语句的主体内是由def语句建立的函数对象。抽象角度,方法替实例对象提供了要继承的行为。程序角度,

方法的工作方式与简单函数完全一致,只有一个重要差异:方法的第一个参数总是接受方法调用的隐形主体,也就是实例对象。

Python会自动把实例方法的调用对应到类方法函数。

方法调用需要通过实例,如:

instance.method(arg...)

这会自动翻译成以下形式的类方法函数调用:

class.method(instance,args...)

Uppercase(openfile('/etc/rc.conf'),output).process()【实例方法的另外一种调用】

class通过Python继承搜索流程找出方法名称所在之处。事实上,这两种调用形式在Python都有效。

类方法的第一个参数通常称为self。这个参数提供方法一个钩子,从而返回调用的主体,也就是实例对象:

因为类可以产生许多实例对象,所以需要这个参数来管理每个实例彼此各不相同的数据。

Python中self一定要在程序代码中明确地写出:方法一定要通过self来取出或修改由当前方法调用或正在处理的实例属性。

这个变量名的存在,会让你明确脚本中使用的是实例属性名称,而不是本地作用域或全局作用域中的变量名。

 

1、调用超类的构造器

方法一般是通过实例调用的。不过通过类调用【class.method(instance实例,args...)】方法也扮演了一些特殊角色。

常见的如构造器方法。像其他属性一样___init__方法是由继承进行查找。也就是说,在构造时,Python会找出并且只调用

一个__init__。如果要保证子类的构造方法也会执行超类构造器的逻辑,一般都必须通过类明确地调用超类的__init__方法。

class Super:

         def __init__(self,x):

                   ...default code...

class Sub(Super):

         def __init__(self,x,y):

                   Super.__init__(self,x)  ###还是有用到的地方。

                   ...custom code...

I=Sub(1,2)

疑问:子类__init__方法不会继承超类的吗?需要是明确手动继承?这个是重载吧?

动手验证

>>> class Super:

...     def __init__(self):

...             self.name='diege'

...

>>> class Sub(Super):

...     def setage(self,age):

...             self.age=age

...

>>> x=Sub() 

>>> x.name

'diege

实验证明子类的__init__方法也会继承,没有任何特殊,超类的任何属性子类都会继承,前面的例子是重载。

前面的例子是代码有可能直接调用运算符重载方法的环境之一。

如果真的想运行超类的构造方法并做适当的修改,自然只能用这种方法进行调用:没有这样的调用,子类会

完全取代(覆盖)超类的构造器,或者子类没有设置__init__构造器的情况下完全继承超类的构造器方法。

 

2、其他方法调用的可能。

这种通过类调用方法的模式(类中调用类的方法(不一定自己)),是扩展继承方法行为(而不是完全取代)的

一般基础。Python2.2新增的选项:静态方法、可以编写不预期第一个参数为实例对象的方法。这类方法可像简单

的无实例的函数那样运作,其变量名属于其所在类的作用域。不过,这是高级的选用扩展功能。通常情况,一定要

为方法传入实例,无论通过实例还是类调用。

 

3、继承

像class语句这样的命名空间工具的重点就是支持变量名继承。这里扩展关于属性继承的一些机制和角色。

在Python中,当对对象进行点号运算时,就会发生继承,而且涉及到搜索属性定义树(一或多个命名空间)。每次

使用obecj.attr形式的表达式时(objecj是实例或类对象),Python会从头到尾搜索命名空间树,先从对象开始,

找到第一个attr为止。这包括在方法中对self属性的引用。因为树中较低的定义会覆盖较高的定义,继承构成了

专有化的基础。

 

object是”所有类之母“,如果你的类没有继承任何其他父类,object将作为默认的父类。

 

3.1 多重继承

子类可以多重继承多个父类

class C(A,B)

  ...code...

方法解释顺序(MRO-Methon Resolution Order).2.2版本以前是深度优先,从左至右搜索,取得在子类中使用的属性。

由于类,类型,内建类型的子类经过权限改造,有了新的结构,这种算法不再可行。2.3使用C3算法。

使用广度优先的方式,优先查找同胞兄弟,也就是先查找A,然后再查找B都没查找到再查找A,B更上级父类。

 

4、属性树的构造

命名空间树构造以及填入变量名的方式,通常来说:

【*】实例属性是由对方法内self属性进行赋值运算而生成的

【*】类属性是通过class语句内顶层的语句(赋值语句)而生成的

【*】超类链接通过class语句首行的括号内列出类而生成的。

__init__.jpg

5、继承方法的专有化-【重载】

继承树搜索模式,变成了将系统专有化的最好方式。因为继承会先在子类寻找变量名,然后才查找超类,子类就可以对超类

的属性重新定义来取代默认的行为。把系统做成类的层次,再新增外部的子类来对其进行扩展,而不是在原处修改已存在的逻辑。

重新定义继承变量名的概念因出了各种专有化技术。

继承和重载

>>> class Super:

...     def method(self):

...             print 'in Super.method'

...

>>> class Sub(Super):                 

...     def method(self):             

...             print 'start Sub.method'

...             Super.method(self)     #直接调用超类的方法

...             print 'ending Sub.method'    

...

>>> Z=Super()

>>> Z.method()

in Super.metho

>>> X=Sub()

>>> X.method()

start Sub.method

in Super.method

ending Sub.method

直接调用超类的方法是这里的重点。Sub类以其专有化的版本取代了Super的方法函数。但是取代时,Sub又回调了Super所导出的版本,从而实现了默认的行为,换句话说,Sub.mothod只是扩展了Super.mothod的行为,而不是完全取代他。

这种扩展编码模式常常用于构造器方法。 也是重载

6、类接口技术

class Super:

        def method(self):

                print "in Super.method"

        def delegate(self):

                self.action()

class Inheritor(Super):

        pass

class Replacer(Super):

        def method(self):

                print "in Replacer.method"

class Extender(Super):

        def method(self):

                print "starting Extender.method"

                Super.method(self)

                print "ending Extender.method"

class Provider(Super):

        def action(self):

                print "in Provider.method"

if __name__=='__main__':

        for C in (Inheritor,Replacer,Extender):

                print '\n'+C.__name__+'...'

                C().method()   #C后面的括号表面是类时实例,这里是创建实例和方法调用一起了。分解C=Inheritor(),C.method()

                                    

                print '\nProvider...'

                x=Provider()     #创建实例对象

                x.delegate()     #实例对象条用delegate方法,delegate方法通过实例的action方法实现

结果

Inheritor...

in Super.method

Provider...

in Provider.method

Replacer...

in Replacer.method

Provider...

in Provider.method

Extender...

starting Extender.method

in Super.method

ending Extender.method

Provider...

in Provider.method

说明

Super

         定义了一个method函数和一个delegate函数

Inheritor

         没有提供任何新的变量名,因此获得Super中定义的一切内容

Replacer

         用自己的版本覆盖Super的method

Extender

         覆盖并回调默认的method,从而定制Super的method

Provider

         现实Super的delegate方法预期的action方法.这里有点不好理解

Provider继承了Super的method和delegate方法并增加了action方法,而delegate方法是调用实例的action方法实现的。

尾部的自我测试程序代码在for循环中建立了三个不同的实例。因为类是对象,可以将他们放入元组中,并可以通过这样

的方式创建实例。类有特殊的属性__name__类的名字,就像模块一样有__name__属性模块的名字。类中默认为类行首行中

的类名称的字符串。

7、抽象超类

上例中Provider类如何工作的?当通过Provider类的实例调用delegate方法时,两个独立的继承搜索会发生:

(1)最初x.delegate的调用中,Python会搜索Provider实例和它上层的对象。知道在Super中找到delegate方法。实例x

会像往常一样传递给这个方法self参数

(2)Super.delegate方法中,self.action会对self及其它上层的对象启动新的独立继承搜索,因为self指的是Provider

实例,就会找到Provider中的action方法。

抽象类就是会调用方法的类,但没有继承或定义该方法,而是期待该方法由子类填补。当行为无法预测,非得等到更为具体的子类编写

时才知道,通过可用这种方式把类通用话。

这种“填空”的代码结构一般就是OOP软件的框架。从delegate方法的角度来看,这个例子中的超类有时也称作是抽象类--

也就是类的部分行为默认是由其子类所提供的。如果预期的方法没有在子类定义,当继承搜索失败时,Python会引发为定义

变量名的异常。类的编写者偶尔会使用assert语句,使这种子类需求更为明显,或者引发内置的异常NotImplementedError

class Super:

        def method(self):

                print "in Super.method"

        def delegate(self):

                self.action()

                   def action(self):

                assert 0, 'action must be defind'

如果表达式运算结构为假,就会引发带有错误信息的异常。在这里表达式总是为假(0)。因为如果没有方法重新定义,

继承就会找到这里的版本,触发错误信息。

 

三、类属性,实例,实例属性

1、类属性

属性就是属另一个对象的数据或者函数元素。

类属性进与其被定义的类相绑定。

【通常】,python中所有方法都有一个限制:在调用前,需要创建一个实例。绑定一个实例。

步骤

(1)、定义类(和方法)

(2)、创建一个实例

(3)、用这个实例调用这个方法

1)、类的数据属性

数据属性,仅仅是定义的类的变量。这个变量要么是由类中的方法来更新,要么在主程序其他什么地方被更新。

这种数据位静态数据,或静态变量。它们表示这些数据和它们所属的类对象绑定的,不依赖于任何类实例。

2)、查看类的属性

dir()方法或者访问类字典属性__dict__.

dir(Class)返回的仅是对象属性的一个名字列表,没有属性对应的值,也就是__dict__属性的健。

Class.__dict__ 返回一个字典,它的键(key)是属性名,键值(value)是相应的属性对象的数据值。

3)、特殊的类属性

对任何类C,显示了类C的所有特殊属性:

C.__name__ 类C的名字(字符串)
C.__doc__ 类C的文档字符串
C.__bases__ 类C的所有父类构成的元组
C.__dict__ 类C的属性
C.__module__ 类C定义所在的模块(1.5 版本新增)
C.__class__ 实例C对应的类(仅新式类中)

 

C.__doc__ 类C的文档字符串 。文档字符串不能被派生类继承,也就是说派生类必须含有它们自己的文档字符串。

类C 的全名是“__main__.C” 。在以前的版本中,没有特殊属性__module__,很难简单定位类的位置,因为类没有使用它们的全名。

 

2、实例

如果说类是一种数据结构定义类型,那么实例则声明了一个这种类型的变量。

1)、初始化:通过调用类对象来创建实例

>>> class MyClass(object): # define class 定义类
... pass
>>> mc = MyClass() # instantiate class 初始化类

2)、__init__() "构造器"方法

当类被调用,实例化的第一步是创建实例对象。一旦对象创建了,Python 检查是否实现了__init__()方法。默认情况下,如果没有定义(或覆盖)特殊方法__init__(),对实例不会施加任何特别的操作。任何所需的特定操作,都需要程序员实现__init__(),覆盖它的默认行为。如果__init__()没有实现,则返回它的对象,实例化过程完毕。

然而,如果__init__()已经被实现,那么它将被调用,【实例对象作为第一个参数(self)被传递进去】,像标准方法调用一样。【调用类时,传进的任何参数都交给__init__()】。实际中,你可以想像成这样:把创建实例的调用当成是对构造器的调用。

在实例化过程中,调用__init__()方法,当一个类被实例化时,就可以定义额外的行为,比如,设定初始值或者运行一些初步诊断代码———主要是在实例被创建后,实例化调用返回这个实例之前,去执行某些特定的任务或设置。

3)、__new__() “构造器”方法

与__init__()相比,__new__()方法更像一个真正的构造器。类型和类在版本2.2 就统一了,Python 用户可以对内建类型进行派生,因此,需要一种途径来实例化不可变对象,比如,派生字符串,数字,等等。在这种情况下,解释器则调用类的__new__()方法,一个静态方法,并且传入的参数是在类实例化操作时生成的。__new__()会调用父类的__new__()来创建对象(向上代理)。

为何我们认为__new__()比__init__()更像构造器呢?这是因为__new__()必须返回一个合法的实例,这样解释器在调用__init__()时,就可以把这个实例作为self 传给它。调用父类的__new__()来创建对象,正像其它语言中使用new 关键字一样。

__new__()和__init__()在类创建时,都传入了(相同)参数。

4)、__del__() "解构器"方法

同样,有一个相应的特殊解构器(destructor)方法名为__del__()。然而,由于Python 具有垃圾对象回收机制(靠引用计数),这个函数要直到该实例对象所有的引用都被清除掉后才会执行。Python 中的解构器是在实例释放前提供特殊处理功能的方法,它们通常没有被实现,因为实例很少被显式释放。

要注意,解构器只能被调用一次,一旦引用计数为0,则对象就被清除了。这非常合理,
因为系统中任何对象都只被分配及解构一次。
总结:
􀁺 不要忘记首先调用父类的__del__()。
􀁺 调用 del x 不表示调用了x.__del__() -----前面也看到,它仅仅是减少x 的引用计数。
􀁺 如果你有一个循环引用或其它的原因,让一个实例的引用逗留不去,该对象的__del__()可
能永远不会被执行。
􀁺 __del__()未捕获的异常会被忽略掉(因为一些在__del__()用到的变量或许已经被删除了)。
不要在__del__()中干与实例没任何关系的事情。
􀁺 除非你知道你正在干什么,否则不要去实现__del__()。
􀁺 如果你定义了__del__,并且实例是某个循环的一部分,垃圾回收器将不会终止这个循环—
—你需要自已显式调用del

 

3、实例属性

实例仅拥有数据属性(方法严格来说是类属性),后者只是与某个类的实例相关联的数据值,并且可以通过句点属性标识法来访问。

1)、“实例化”实例属性(或创建一个更好的构造器)

设置实例的属性可以在实例创建后任意时间进行,也可以在能够访问实例的代码中进行。构造器__init()__是设置这些属性的关键点之一。

Python 不仅是动态类型,而且在运行时,允许这些对象属性的动态创建。

(1)、在构造器中首先设置实例属性

构造器是最早可以设置实例属性的地方,因为__init__()是实例创建后第一个被调用的方法。再没有比这更早的可以设置实例属性的机会了。一旦__init__()执行完毕,返回实例对象,即完成了实例化过程。

(2)、默认参数提供默认的实例安装

默认参数放置在位置参数的后面

(3)、__init__()应当返回None

2)、查看实例属性

内建函数dir()可以显示类属性,同样还可以打印所有实例属性:
>>> class C(object):
... pass
>>> c = C()
>>> c.foo = 'roger'
>>> c.bar = 'shrubber'
>>> dir(c)
['__class__', '__delattr__', '__dict__', '__doc__','__getattribute__', '__hash__', '__init__', '__module__','__new__', 'bar', 'foo']

与类相似,实例也有一个__dict__特殊属性(可以调用vars()并传入一个实例来获取),它是【实例属性】构成的一个字典:
>>> c.__dict__
{'foo': 'roger', 'bar': 'shrubber'}

3)、特殊的实例属性

实例仅有两个特殊属性
I.__class__ 实例化I 的类
I.__dict__ I 的属性

4)、内建类型属性

内建类型也是类,它们有没有像类一样的属性呢?那实例有没有呢?对内建类型也可以使用dir(),与任何其它对象一样,可以得到一个包含它属性名字的列表:

 

4、实例属性 vs 类属性

类属性仅是与类相关的数据值,和实例属性不同,类属性和实例无关。这些值像静态成员那样被引用,即使在多次实例化中调用类,它们的值都保持不变。不管如何,静态成员不会因为实例而改变它们的值,除非实例中显式改变它们的值。(实例属性与类属性的比较,类似于自动变量和静态变量。

类和实例都是名字空间。类是类属性的名字空间,实例则是实例属性的。关于类属性和实例属性,还有一些方面需要指出。你可采用类来访问类属性,如果实例没有同名的属性的话,你也可以用实例来访问。

(1)访问类属性

类属性可通过类或实例来访问。下面的示例中,类C 在创建时,带一个version 属性,这样通过类对象来访问它是很自然的了,比如,C.version。当实例c 被创建后,对实例c 而言,访问c.version 会失败,不过Python 首先会在实例中搜索名字version,然后是类,再就是继承树中的基类。本例中,version 在类中被找到了:
>>> class C(object): # define class 定义类
... version = 1.2 # static member 静态成员
...
>>> c = C() # instantiation 实例化
>>> C.version # access via class 通过类来访问
1.2
>>> c.version # access via instance 通过实例来访问
1.2
>>> C.version += 0.1 # update (only) via class 通过类(只能这样)来更新
>>> C.version # class access 类访问
1.3
>>> c.version # instance access, which 实例访问它,其值已被改变
1.3 # also reflected change

(2)、从实例中访问类属性须谨慎

与通常Python 变量一样,任何对实例属性的赋值都会创建一个实例属性(如果不存在的话)并
且对其赋值。如果类属性中存在同名的属性,有趣的副作用即产生。(经典类和新式类都存在)

类属性在不可变的情况下正常。

在类属性可变的情况下,一切都不同了:
>>> class Foo(object):
... x = {2003: 'poe2'}
...
>>> foo = Foo()
>>> foo.x
{2003: 'poe2'}
>>> foo.x[2004] = 'valid path'
>>> foo.x
{2003: 'poe2', 2004: 'valid path'}
>>> Foo.x # it works!!! 生效了
{2003: 'poe2', 2004: 'valid path'}
>>> del foo.x # no shadow so cannot delete 没有遮蔽所以不能删除掉
Traceback (most recent call last): File "<stdin>", line 1, in ?
del foo.x
AttributeError: x

(3)类属性持久性

静态成员,如其名所言,任凭整个实例(及其属性)的如何进展,它都不理不采(因此独立于实例)。同时,当一个实例在类属性被修改后才创建,那么更新的值就将生效。类属性的修改会影响到所有的实例:

注意:使用类属性来修改自身(不是实例属性)
正如上面所看到的那样,使用实例属性来试着修改类属性是很危险的。原因在于实例拥有它们自已的属性集,在Python 中没有明确的方法来指示你想要修改同名的类属性。修改类属性需要使用类名,而不是实例名。

 

四、绑定和方法调用

总结:

1)显式传入self为非绑定方法

2)隐式传入slef参数为绑定方法,即通过实例来调用方法。

首先,方法仅仅是类内部定义的函数。(这意味着方法是类属性而不是实例属性)。
其次,方法只有在其所属的类拥有实例时,才能被调用。当存在一个实例时,方法才被认为是绑定到那个实例了。没有实例时方法就是未绑定的。
最后,任何一个方法定义中的第一个参数都是变量self,它表示调用此方法的实例对象。

关于self

self 变量用于在类实例方法中引用方法所绑定的实例。因为方法的实例在任何方法调用中总是作为第一个参数传递的,self 被选中用来代表实例。你必须在方法声明中放上self

但可以在方法中不使用实例(self)。如果你的方法中没有用到self , 那么请考虑创建一个常规函数,除非你有特别的原因,比如子类继承需要重载父类的方法。

1、调用绑定方法

方法,不管绑定与否,都是由相同的代码组成的。唯一的不同在于是否存在一个实例可以调用此方法。记得self 在每一个方法声明中都是作为第一个参数传递的。当你在实例中调用一个绑定的方法时,self 不需要明确地传入了。这算是"必须声明self 作为第一个参数"对你的报酬。当你还没有一个实例并且需要调用一个非绑定方法的时候你必须传递self 参数。

2、调用非绑定方法

调用非绑定方法并不经常用到。需要调用一个还没有任何实例的类中的方法的一个主要的场景是:你在派生一个子类,而且你要覆盖父类的方法,这时你需要调用那个父类中想要覆盖掉的构造方法。

重载父类构造器__init__

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

 

五、静态方法和类方法

静态方法和类方法在Python2.2 中引入。【未使过,也不打算使用】

1 staticmethod()和classmethod()内建函数

看一下在经典类中创建静态方法和类方法的一些例子:
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)

2、使用函数修饰符【可以考虑使用】

现在,看到像foo=staticmethod(foo)这样的代码会刺激一些程序员。很多人对这样一个没意义的语法感到心烦,即使van Rossum 曾指出过,它只是临时的,有待社区对些语义进行处理。函数修饰符,一种在Python2.4 中加入的新特征。你可以用它把一个函数应用到另个函数对象上, 而且新函数对象依然绑定在原来的变量。我们正是需要它来整理语法。通过使用decorators,我们可以避免像上面那样的重新赋值:

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__

 

六、类、实例和其他对象的内建函数

内建函数 描述
issubclass(sub, sup)  如果类sub是类sup的子类,则返回True,反之,为False。
isinstance(obj1, obj2)  如果实例obj1是类obj2,或者obj2子类的一个实例;或者如果obj1是obj2的类型,则返回True;反之,为False。
hasattr(obj, attr)    如果obj有属性attr(用字符串给出),返回True,反之,返回False
getattr(obj, attr[, default])  获取obj的attr属性;与返回obj.attr 类似;如果提供了默认值,则返回默认值;如果attr不是obj的属性,就会引发一个AttributeError 异常。会在你试图读取一个不存在的属性时,引发AttributeError 异常,除非给出那个可选的默认参数。

setattr(obj, attr, val) 设置obj的attr属性值为val,替换任何已存在的属性值;不然,就创建属性;类似于obj.attr=val
delattr(obj, attr) 从obj中删除属性attr(以字符串给出);类似于del obj.attr。
dir(obj=None) 返回obj的属性的一个列表;如果没有给定obj,dir()则显示局部名字空间空间中的属性,也就是locals().keys()
super(type, obj=None) 返回一个表示父类类型的代理对象;如果没有传入obj,则返回的super对象是非绑定的;反之,如果obj 是一个type,issubclass(obj,type)必为True:否则,isinstance(obj,type)就必为True。
vars(obj=None) 返回obj的属性及其值的一个字典;如果没有给出obj,vars()显示局部名字空间字典(属性及其值),也就是locals()

 

dir()补充

􀁺 dir()作用在实例上(经典类或新式类)时,显示实例变量,还有在实例所在的类及所有它的基类中定义的方法和类属性。
􀁺 dir()作用在类上(经典类或新式类)时,则显示类以及它的所有基类的__dict__中的内容。但它不会显示定义在元类(metaclass)中的类属性。
􀁺 dir()作用在模块上时,则显示模块的__dict__的内容。(这没改动)。
􀁺 dir()不带参数时,则显示调用者的局部变量。(也没改动)。
􀁺 关于更多细节:对于那些覆盖了__dict__或__class__属性的对象,就使用它们;出于向后兼容的考虑,如果已定义了__members__和__methods__,则使用它们。

 

七、运算符重载

1、用特殊方法定制类

􀁺 模拟标准类型
􀁺 重载操作符

2、重载的关键概念

*运算符重载让类拦截常规的Python运算。

*类可重载所有Python表达式运算。

*类可重载打印,函数调用,属性点号运算等运算。

*重载使类实例的行为像内置类型。

*重载是通过提供特殊名称的类方法来实现的。

如果类中提供了某些特殊名称的方法,当类实例出现在运算有关的表达式的时候,Python就会自动调用这些方法。

类Number放到number.py模块中:

class Number():

        def __init__(self,start):

                self.data=start

        def __sub__(self,other):

                return Number(self.data-other)

>>> from number import Number

>>> X=Number(5)

>>> Y=X-2      

>>> Y.data

3

>>> Z=X+5

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

TypeError: unsupported operand type(s) for +: 'instance' and 'int'

这个例子Number类提供一个方法来拦截实例的构造器(__init__),此外还有一个方法捕捉减法表达式(__sub__).

这种特殊的方法是钩子,可与内置运算相绑定。

3、 常见的运算符重载方法

方法                   重载                   调用

__init__   构造器方法     对象建立:X=Class()

__del__             析构方法         对象收回

__add__            运算符+            X+Y,X+=Y

__sub__            运算符-             X-Y,X-=Y

__or__               运算符|(位OR)       X|Y X|=Y

__repr__    运行时的字符串输出;内建repr(X) 和‘‘ 操作符,让直接访问对象时 有print的效果 注意方法里return的是字符串对象

__str__   可打印的字符输出;内建str(X)及print X语句,print X才有输出,运行时不输出 注意方法里return的是字符串对象

__call__   函数调用         X() ##??

__getattr__     点号运算         X.undefined

__setattr__     属性赋值语句         X.any=Value

__getitem__    索引运算         X[key],没有__iter__时的for循环和其他迭代器

__setitem__    索引赋值语句         X[key]=value

__len__             长度                            len(X),真值测试

__cmp__           比较                            X==Y,X

__lt__                特定的比较              X<Y(or else __cmp__)

__eq__              特定的比较              X==Y(or else __cmp__)

__radd__ 左侧加法 +              Noninstance + X

__iadd__ 实地(增强的)的加法         X+=Y(or else __add__)

__iter__   迭代环境                   用于循环,测试,理解,列表,映射及其他

所有重载方法的名称前后都有两个下划线字符,以便把同类中定义的变量名区别开来。特殊方法名称和表达式或运算的

映射关系,是由Python语言预先定义好的。

所有运算符重载的方法都是选用的:如果没有写某个方法,那么定义的类就不支持该运算。多数重载方法只用在需要对象

行为表现得就像内置函数一样的高级程序中。然而,__init__构造方法常出现在绝大多数类中。

__getitem__拦截索引运算

__getitem__方法拦截实例的索引运算。当实例X出现X[i]这样的索引运算中时,Python会调用这个实例继承的__getitem__方法。

(如果有),把X作为第一个参数传递,并且放括号内的索引值传递给第二个参数。

>>> class index:

...     def __getitem__(self,index):

...             return index**2 

...

>>> X=index()

>>> X[2]

4

>>> for i in range(5):

...     print   X[i],

...

0 1 4 9 16

__getitem__和__iter__实现迭代

for循环的作用是从0到更大的索引值,重复对序列进行索引运算,直到检测到超出边界的异常。

__getitem__也可以是Python中一种重载迭代的方式,如果定义了这个方法,for循环每次循环时都会调用类的__getitem__

>>> class stepper:

...     def __getitem__(self,i):

...             return self.data[i]

...

>>> X=stepper

>>> X=stepper()

>>> X.data='diege'

>>> X[1]

'i'

>>> for item in X:

...     print item,

...

d i e g e

任何支持for循环的类也会自动支持Python所有迭代环境,包括成员关系测试in,列表解析,内置函数map,列表和元组赋值运算

以及类型构造方法也会自动调用__getitem__(如果定义的话)

如今,Python中所有的迭代环境都会先尝试__iter__方法,再尝试__getitem__。如果对象不支持迭代协议,就会尝试索引运算。

从技术角度来将,迭代环境是通过调用内置函数iter去尝试寻找__iter__方法来实现的,而这种方法应该返回一个迭代器对象。

如果已经提供了,Python就会重复调用这个迭代器对象的next方法,直到发生StopIteration异常。如果没有找到__iter__方法

,Python会改用__getitem__机制,就像之前那样通过偏移量重复索引,直到引发IndexError异常。

class Squares:

        def __init__(self,start,stop):

                self.value=start-1

                self.stop=stop

        def __iter__(self):

                return self

        def next(self):

                if self.value==self.stop:

                        raise StopIteration

                self.value+=1

                return self.value**2

>>> from test29 import Squares

>>> for i in Squares(1,5):   

...     print i,         

...

1 4 9 16 25

迭代器对象就是实例self,因为next方法是这个类的一部分。在较为复杂的的场景中,迭代器对象可定义为个别的类或对象,

有自己的状态信息,对相同数据支持多种迭代。以Python raise语句发出信号表示迭代结束。

__iter__对象会在调用过程中明确地保留状态信息。所以比__getitem__具体更好的通用性。

__iter__迭代器比__getitem__更复杂和难用。迭代器是用来迭代,不是随机的索引运算。事实上,迭代器根本没有重载索引表达式

>>> X=Squares(1,5)

>>> X[1]

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

AttributeError: Squares instance has no attribute '__getitem__'

__iter__机制是在__getitem__中所见到的其他所有迭代环境的实现方式(成员关系测试,类型构造器,序列赋值运算)。

和__getitem__不同的是,__iter__只循环一次,而不是循环多次,循环之后就变为空,每次新的循环,都得创建一个新的。

>>> X=Squares(1,5)

>>> [n for n in X]

[1, 4, 9, 16, 25]

>>> [n for n in X]

[]

如果使用生成器函数编写,这个例子可能更简单

>>> from __future__ import generators

>>> def gsquares(start,stop):       

...     for i in range(start,stop+1):

...             yield i**2           

...

>>> for i in gsquares(1,5):

...     print i,          

...

1 4 9 16 25

和类不同的是,这个函数会自动在迭代中存储存其状态。这是假设的例子,实际上,可以跳过这两种技术,只用for循环,map或列表解析

一次创建这个列表。

>>> [x**2 for x in range(1,6)]

[1, 4, 9, 16, 25

有多个迭代器的对象。

__getattr__和__setattr__捕捉属性的引用。

__getattr__方法是拦截属性点号运算。__getattr__可以作为钩子来通过通用的方式相应属性请求。

>>> class empty:

...     def __getattr__(self,attrname):

...             if attrname=="age":

...                     return 40

...             else:

...                     raise AttributeError,attrname

...

>>> X=empty()

>>> X.age

40

这里,empty类和其实例X本身并没有属性。所以对X.age的存取会转换至__getattr__方法,self则赋值为实例(X),

而attrname则赋值为未定义的属性名称字符串("age"),这个类传回一个实际值作为X.age点号表达式的结果(40),

让age看起来像实际的属性。实际上,age变成了动态计算的属性。

__setattr__会拦截所有赋值语句,一般不用。

__repr__,__str__ 打印,转换      print X、repr(X)、str(X)

__call__拦截调用:如果定义了,Python就会为实例应用函数调用表达式运行__call__方法。

当需要为函数的API编写接口时,__call__就变得很用有:这可以编写遵循所需要的函数来调用接口对象。

函数接口和回调代码

__del__是析构器

每当实例产生时,就会调用__init__构造方法,每当实例空间被收回执行__del__方法。

>>> x.__class__

<class trac.wrapper at 0x28503f8c>

>>> x.__class__.__name__

'wrapper'

每个实例都有内置的__class__属性,引用了它所继承的类,而每个类都有__name__属性,用用了首行中的变量名,所以self.__class__.__name__

是取出实例的类的名称。

__str__和__repr

>>> class jin():

...     def __init__(self,data):

...             self.data=data

...     def __str__(self):

...             return str(self.data)

...

>>> X=jin(5)

>>> X

<__main__.jin instance at 0x2b9418a18950>

>>> print X

5

>>> class jin():

...     def __init__(self,data):

...             self.data=data

...     def __repr__(self):

...             return str(self.data)

...

>>> X=jin(5)

>>> X

5

>>> print x

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

NameError: name 'x' is not defined

如果要运行时和print都打印,__str__和__repr__都需要重载

核心编程中有一个很好的方式

__repr__=__str__

>>> class jin():

...     def __init__(self,data):

...             self.data=data

...     def __str__(self):

...             return str(self.data)

...     __repr__=__str_

>>> X=jin(5)

>>> X

5

>>> print X

5

4、用来定制类的特殊方法
特殊方法 描述
基本定制型
C.__init__(self[, arg1, ...]) 构造器(带一些可选的参数)
C.__new__(self[, arg1, ...]) 构造器(带一些可选的参数);通常用在设置不变数据类型的子类。
C.__del__(self) 解构器
C.__str__(self) 可打印的字符输出;内建str()及print 语句
C.__repr__(self) 运行时的字符串输出;内建repr() 和‘‘ 操作符
C.__unicode__(self) Unicode 字符串输出;内建unicode()
C.__call__(self, *args) 表示可调用的实例
C.__nonzero__(self) 为object 定义False 值;内建bool() (从2.2 版开始)
C.__len__(self) “长度”(可用于类);内建len()
对象(值)比较
C.__cmp__(self, obj) 对象比较;内建cmp()
C.__lt__(self, obj) and 小于/小于或等于;对应<及<=操作符
C.__gt__(self, obj) and 大于/大于或等于;对应>及>=操作符
C.__eq__(self, obj) and 等于/不等于;对应==,!=及<>操作符
属性
C.__getattr__(self, attr) 获取属性;内建getattr();仅当属性没有找到时调用
C.__setattr__(self, attr, val) 设置属性
C.__delattr__(self, attr) 删除属性
C.__getattribute__(self, attr) 获取属性;内建getattr();总是被调用
C.__get__(self, attr) (描述符)获取属性
C.__set__(self, attr, val) (描述符)设置属性
C.__delete__(self, attr) (描述符)删除属性
定制类/模拟类型
数值类型:二进制操作符
C.__*add__(self, obj) 加;+操作符
C.__*sub__(self, obj) 减;-操作符
C.__*mul__(self, obj) 乘;*操作符
C.__*div__(self, obj) 除;/操作符
C.__*truediv__(self, obj) e True 除;/操作符
C.__*floordiv__(self, obj) e Floor 除;//操作符
C.__*mod__(self, obj) 取模/取余;%操作符
C.__*divmod__(self, obj) 除和取模;内建divmod()
C.__*pow__(self, obj[, mod]) 乘幂;内建pow();**操作符
C.__*lshift__(self, obj) 左移位;<<操作符
C.__*rshift__(self, obj) 右移;>>操作符
C.__*and__(self, obj) 按位与;&操作符
C.__*or__(self, obj) 按位或;|操作符
C.__*xor__(self, obj) 按位与或;^操作符
数值类型:一元操作符
C.__neg__(self) 一元负
C.__pos__(self) 一元正
C.__abs__(self) 绝对值;内建abs()
C.__invert__(self) 按位求反;~操作符
数值类型:数值转换
C.__complex__(self, com) 转为complex(复数);内建complex()
C.__int__(self) 转为int;内建int()
C.__long__(self) 转为long;内建long()
C.__float__(self) 转为float;内建float()
数值类型:基本表示法(String)
C.__oct__(self) 八进制表示;内建oct()
C.__hex__(self) 十六进制表示;内建hex()
数值类型:数值压缩
C.__coerce__(self, num) 压缩成同样的数值类型;内建coerce()
C.__index__(self)g 在有必要时,压缩可选的数值类型为整型(比如:用于切片索引等等)
序列类型
C.__len__(self) 序列中项的数目
C.__getitem__(self, ind) 得到单个序列元素
C.__setitem__(self, ind,val) 设置单个序列元素
C.__delitem__(self, ind) 删除单个序列元素
序列类型
C.__getslice__(self, ind1,ind2) 得到序列片断
C.__setslice__(self, i1, i2,val) 设置序列片断
C.__delslice__(self, ind1,ind2) 删除序列片断
C.__contains__(self, val) f 测试序列成员;内建in 关键字
C.__*add__(self,obj) 串连;+操作符
C.__*mul__(self,obj) 重复;*操作符
C.__iter__(self) e 创建迭代类;内建iter()
映射类型
C.__len__(self) mapping 中的项的数目
C.__hash__(self) 散列(hash)函数值
C.__getitem__(self,key) 得到给定键(key)的值
C.__setitem__(self,key,val) 设置给定键(key)的值
C.__delitem__(self,key) 删除给定键(key)的值
C.__missing__(self,key) 给定键如果不存在字典中,则提供一个默认值

 

 

八、命名空间:完整的内容

点号和无点号的变量,会用不同的方式处理,而有些作用域是用于对对象命名空间做初始设定的。

*无点号运算的变量名(例如,X)与作用域相对应

*点号的属性名(如object.X)使用的是对象的命名空间。

*有些作用域会对对象的命名空间进行初始化(模块和类)

1、简单变量名:如果赋值就不是全局变量

无点号的简单运算名遵循函数LEGB作用域法则:

赋值语句(X=value)

         使变量名为本地变量:在当前作用域内,创建或改变变量名X,除非声明它是全局变量。如在函数的内的赋值语句。

引用(X)

         在当前作用域内搜索变量名X,之后是在任何以及所有的嵌套函数中,然后是在当前的全局作用域中搜索,最后在内置作用域中搜索。

2、属性名称:对象命名空间

点号的属性名称指的是特定对象的属性,并且遵守模块和类的规则。就类和实例对象而言,引用规则增加了继承搜索这个流程。

赋值语句(object.X=value)

         在进行点号运算的对象的命名空间内创建或修改属性名X,并没有其他作用。继承树的搜索只发生在属性引用时,而不是属性的赋值运算时

引用(object.X)

         就基于类的对象而言,会在对象内搜索属性名X,然后是其上所有可读取的类(使用继承搜索流程).对于不是基于类的对象而言,

例如模块,则是从对象中直接读取X。

3、命名空间:赋值将变量名分类

在Python中,赋值变量名的场所相当重要:这完全决定了变量名所在作用域或对象。以下实例总结了命名空间的概念。

# vim manynames.py

X=11         #模块属性 全局

def f():

        print X  #函数(本地)作用域内没有X,嵌套函数没有X变量,当前全局作用域(模块的命名空间内)有,显示全局

def g():

        X=22  #定义本地作用域变量X

        print X #搜索函数(本地)作用域内变量X,有打印

class C():

        X=33        #定义的类属性,类的命名空间

        def m(self):

                X=44         #貌似在这里没有什么意义

                self.X=55 #定义类实例的属性,实例的命名空间

if __name__=='__main__':

        print X      #打印模块属性 结果11

        f()     #调用f(),f()返回模块全局变量的X 11

        g()    #调用g(),g()返回函数内局部变量X 22

        print X #打印 模块全局变量的里变量,模块的属性 11

 

        obj=C()     #调用类的方法产生实例

        print obj.X #打印实例的属性X X继承类的属性,所以为33

 

        obj.m() #实例调用类的m方法,

        print obj.X #显示这个X属性 因为上一步m方法设置了实例的属性X,为55#注意是实例的属性,不是类的属性

# python manynames.py

11

11

22

11

33

55

作用域总是由源代码中赋值语句的位置来决定,而且绝不会受到其导入关系的影响。属性就像是变量,在赋值之后才会存在。

而不是在赋值前。通常情况下,创建实例属性的方法是在类的__init__构造器方法内赋值。

通常说来,在脚本内不应该让每个变量使用相同的命变量名。

4、命名空间字典

模块的命名空间实际上是以字典的形式实现的,并且可以由内置属性__dict__显示这一点。类和实例对象也是如此:属性点号运算

其内部就是字典的索引运算,而属性继承其实就是搜索链接的字典而已。实际上,实例和类对象就是Python中带有链接的字典而已,

>>> class Super():

...     def hello(self):

...             self.data1='diege'

...

>>> class Sub(Super):

...     def hola(self):

...             self.data2='eggs'

...

制作子类的实例时,该实例一开始会是空的命名空间字典,但是有链接会指向它的类,让继承搜索能顺着寻找。

实际上,继承树可在特殊的属性中看到,你可以进行查看。实例中有个__class__属性链接到了它的类,而类有个__base__属性。

就是元组,其中包含了通往更高的超类的连接。

>>> X=Sub()                     

>>> X.__dict__

{}

>>> X.__class__

<class __main__.Sub at 0x2850353c>

>>> Y=Super()

>>> Y.__dict__

{}

>>> Y.__class__

<class __main__.Super at 0x285034ac>

>>> Sub.__bases__

(<class __main__.Super at 0x285034ac>,)

>>> Super.__bases__   

()

当类为self属性赋值时,会填入实例对象。也就是说,属性最后会位于实例的属性命名空间字典内,而不是类的。

实例对象的命名空间保存了数据,会随实例的不同而不同,而self正是进入其命名空间的钩子。

>>> Y=Sub()

>>> X.hello()

>>> X.__dict__

{'data1': 'diege'}

>>> X.hola()   

>>> X.__dict__

{'data1': 'diege', 'data2': 'eggs'}

>>> Sub.__dict__

{'__module__': '__main__', '__doc__': None, 'hola': <function hola at 0x284954c4>}

>>> Super.__dict__

{'__module__': '__main__', 'hello': <function hello at 0x28495f0c>, '__doc__': None}

>>> Sub.__dict__.keys(),Super.__dict__.keys()

(['__module__', '__doc__', 'hola'], ['__module__', 'hello', '__doc__'])

>>> Y.__dict__

{}

Y是这个类的第2个实例。即时X的字典已由方法内的赋值语句做了填充,Y还是空的命名空间字典。

每个实例都有独立的命名空间字典,一开始是空的,可以记录和相同类的其他实例命名空间字典中属性,完全不同的属性。

因为属性实际上市python的字典键,其实有两种方式可以读取并对其进行赋值:通过点号运算,或通过键索引运算。

>>> X.data1,X.__dict__['data1']

('diege', 'diege')

>>> X.data3='lily'

>>> X.__dict__                

{'data1': 'diege', 'data3': 'lily', 'data2': 'eggs'}

>>> dir(X)

['__doc__', '__module__', 'data1', 'data2', 'data3', 'hello', 'hola']

>>> dir(Sub)

['__doc__', '__module__', 'hello', 'hola']

>>> dir(Super)

['__doc__', '__module__', 'hello']

对实例赋值,只影响实例,不会影响实例的类和超类

5、命名空间连接

__class__和__bases__这些属性可以在程序代码内查看继承层次。可以用他来显示类树

6、一个较复杂的例子

vim person.py

class GenericDisplay:

        def gatherAttrs(self):

                attrs='\n'

                for key in self.__dict__:

                        attrs+='\t%s=%s\n' % (key,self.__dict__[key])

                return attrs

        def __str__(self):

                return '<%s:%s>' % (self.__class__.__name__,self.gatherAttrs())

 

class Person(GenericDisplay):

        def __init__(self,name,age):

                self.name=name

                self.age=age

        def lastName(self):

                return self.name.split()[-1]

        def birthDay(self):

                self.age+=1

class Employee(Person):

        def __init__(self,name,age,job=None,pay=0):

                Person.__init__(self,name,age)

                self.job=job

                self.pay=pay

        def birthDay(self):

                self.age+=2

        def giveRaise(self,percent):

                self.pay*=(1.0+percent)

if __name__=='__main__':

        diege=Person('diege wang',18)

        print diege

        print diege.lastName()

        diege.birthDay()

                   print diege

 

        sue=Employee('Sue Jones',44,job='dev',pay=1000)

        print sue

        print sue.lastName()

        sue.birthDay()

        sue.giveRaise(.10)

        print sue

# python person.py

<Person:

        age=18

        name=diege wang

>

wang

<Employee:

        job=dev

        pay=1000

        age=44

        name=Sue Jones

>

Jones

<Employee:

        job=dev

        pay=1100.0

        age=46

        name=Sue Jones

>

可以作为模块导入测试

>>> from person import Person

>>> dir(Person)

['__doc__', '__init__', '__module__', '__str__', 'birthDay', 'gatherAttrs', 'lastName']

>>> diege.__dict__

>>> diege=Person('diege wang',18)

{'age': 18, 'name': 'diege wang'}

>>> print diege

<Person:

        age=18

        name=diege wang

>

>>> print diege.lastName()

wang

>>> diege.age

18

>>> diege.birthDay()

>>> diege.age

19

posted on 2012-09-22 20:50  @Jin  阅读(458)  评论(0编辑  收藏  举报

导航