python 面向对象编程
类
实例
绑定与方法调用
子类,派生和继承
内建函数
私有性
授权与包装
新式类的高级特性
相关模块
1 类
首先要知道的是python 2.2版本对类做出了更改。python2.2版本之后,如果你定义的类显示继承一个基类,那么这就是一个新式类。否则是旧式类。 python2.2中把类和类型做出了统一,所以对于新式类来说是下面这样的:
>>> class A(object): ... pass ... >>> a=A() >>> type(a) <class '__main__.A'> >>> type(1) <type 'int'> >>> type(A) <type 'type'> >>> type(int) <type 'type'>
这段代码里我们定义了一个新式类A。用A生成一个对象a。 type(a)等到的是<class '__main__.A'> , type(1)得到的是<type 'int'>。 但是我们type(A)和type(int)得到的都是<type 'type'>。 这说明A和int一样都是类型。定义一个新式类的时候你就生成了一个新的类型。 可是对于2.2之前的类,或者说先找的旧式类/经典类来说,是这样的:
>>> class B: ... pass ... >>> b=B() >>> type(b) <type 'instance'> >>> type(B) <type 'classobj'>
这里我们定义了一个经典类B。实例化为b。type(b)得到的是instance,所以b的类型只是一个实例对象,不是B。换句话说B不是类型只是一个类。 而我们用type(B)也很好的印证了这一点, type(B)是classobj。只是一个类对象。
1.1 类定义
类的创建语法在python中非常简单,如下:
class className(base): '''class documentation string''' class_suite
上面的代码就定义了一个类,如果是C/C++在类定义之前要先声明类。但是在python中不用。
1.2 类属性
类属性应该包括数据属性和方法。学习类属性最重要的一点就是能将它和对象属性区分开。这里仅需要记住这一点,在学习到实例对象的时候可以再回过头来分析这句话。另外一点要注意的是类的属性,本身也应该是对象。比如下面一段代码中,A是一个类,att1是类的属性,我们访问A.att1.__class__其实是访问att1的类。
>>> class A: ... pass ... >>> A.att1=1 >>> A.att1.__class__ <type 'int'>
查看类的属性可以用如下两种方式:
>>> dir(A) ['__doc__', '__module__', 'att1'] >>> print A.__dict__ {'__module__': '__main__', '__doc__': None, 'att1': 1}
第一种是用 dir 内建函数,第二种是通过__dict__属性。
python的类有一些固有的特殊属性,我们把它们列出来如下:
C.__name__ 类C的名字(字符串) C.__doc__ 类C的文档字符串 C.__bases__ 类C的所有父类构成的元组 C.__dict__ 类C的属性 C.__module__ 类C定义所在的模块(1.5 版本新增) C.__class__ 实例C对应的类(仅新式类中)
2 实例
类的实例化过程非常简单,如下:
>>> class MyClass: ... pass ... >>> myObj=MyClass() >>>
类名加()即可。 但是实例化的过程却非常值得研究。
2.1 __init__
__init__方法是类默认就有的一个方法。在实例化对象的时候会调用__init__来对对象做一些你想做的操作。不过默认这个方法什么都不做,所以你要自己重写它,来做你想做的操作。很重要的一点是,千万不要以为这个是类似于c/c++中的构造函数。构造函数应该是生成实例/对象的,而这个方法是在实例生成之后对其更改的。
另外很重要的一点是要知道 self这个概念。python的函数第一个参数一般都是self 代表对象自身。在定义函数的时候一定要有,但是在调用函数的时候不需要,因为python会自动把当前对象当做self传入。 python的函数调用 一般都是这样的:
1. 生成实例 obj=Myclass()
2. 实例调用函数 obj.foo(arg1,arg2...)
你看到的foo调用参数列表虽然是arg1,arg2...但实际上应该是 self,arg1,arg2... 这里的self就是obj。
OK。回头看__init__, 我们用下面的例子来理解__init__
>>> class A: ... def __init__(self,name): ... self.name=name ... def showName(self): ... print self.name ... >>> a=A('kramer') >>> a.showName() kramer
这里__init__第一个参数是self,这也说明了__init__不是构造函数,因为第一个参数是self就说明是要用对象来调用的,而这就意味着对象已经建立。第二个参数是name。 __init__的第一个参数之外的参数都是在实例化对象的时候要写在类名后面的括号里的,比如这里。 我们的例子里__init__的作用是在实例化对象的时候接受一个参数并赋给当前的对象作为name属性的值。 关于__init__函数就了解这么多,下面了解另外一个很重要的函数__new__
2.2 __new__
__new__是新式类中的一个方法。它的作用是在创建实例的时候被调用的,它返回当前类的实例。这与__init__不同,__init__是实例创建好后修饰实例的。可以把__new__看成是构造函数。我们用下面的例子学习一下这个方法。
>>> class A(object): ... def __init__(self): ... print 'self is ',self ... print '__init__ is running' ... def __new__(cls): ... print 'cls is ',cls ... print '__new__ is running' ... return object.__new__(cls) ... >>> a=A() cls is <class '__main__.A'> __new__ is running self is <__main__.A object at 0x1d90b0> __init__ is running
这里创建了一个新式类A 重写了其__init__和__new__方法。 __init__方法在运行的时候会打印出来self 和一句话 '__init__ is running' 而 __new__在运行的时候会打印出cls和一句话'__new__ is runing' 。 关于__new__的第一个参数 cls,和self差不多。 self代表的是实例本身,cls代表的是当前类。比如打印的结果 cls is <class '__main__.A'>。 同样的,在调用的时候你不用自己去传递cls参数,python会帮你做到这一点。而且我们还要注意到__new__在return的时候调用object的__new__而且调用的参数是cls。这就是说调用object的__new__创建一个当前类的对象返回。
分析一下输出,首先输出的是__new__,说明__new__是在__init__调用之前调用的。这与我们的理解也是相符的,__new__先创建实例,__init__由实例调用。还有一点要注意的是,__init__只有在__new__返回当前类的实例的时候才会被调用。 比如下面的代码:
>>> class B(A): ... def __init__(self): ... print '__init__ in B is running' ... print 'self is ',self ... def __new__(cls): ... print '__new__ in B is running' ... print 'cls is ',cls ... return object.__new__(A) ... >>> b=B() __new__ in B is running cls is <class '__main__.B'>
这段代码的__new__在return的时候是return object.__new__(A), 使用的是A类,也就是说返回的是A类的对象,所以__init__没有调用。
2.3 实例属性
实例的属性在任意时候都可以赋予或者删除。比如:
>>> class A: ... pass ... >>> a=A() >>> a.attr1=1 >>> a.attr2=2 >>> a.attr1 1 >>> a.attr2 2 >>>
这种情况下可以把实例当做一个名称空间。 想要查看实例的属性可以用dir函数或者实例的特殊属性__dict__
>>> dir(a) ['__doc__', '__module__', 'attr1', 'attr2'] >>> a.__dict__ {'attr2': 2, 'attr1': 1}
注意dir和__dict__返回略有不同。
2.4 实例属性vs类属性
实例有自己的属性,类也有自己的属性。如果实例中没有和类属性同名的属性,那么可以通过实例属性看到类属性,但是修改则不可以。如下:
>>> class A: ... pass ... >>> A.v1='first' >>> A.v2='second' >>> a=A() >>> a.v1 'first' >>> a.v2 'second' >>> a.v1=1 >>> A.v1 'first'
如上面所示,a是A的实例。A有v1,v2两个属性。通过a可以访问,因为a并没有同名的实例属性。但是修改的时候不可以,因为a.v1=1在python看来是为实例a创建实例属性。
3 绑定和方法调用
首先关于python类中的方法要明白如下几条:
- 方法是类内部定义的函数,意味着方法是类属性不是实例属性
- 方法只有在所属的类拥有实例时,才可以被调用,有实例的时候我们说方法被绑定到了该实例,否则说方法没有被绑定。
- 任何一个方法中定义的第一个参数都是self。self是调用此方法的实例。
3.1 调用绑定方法
方法不管调用与否在定义的时候是没有区别的,区别是是否存在一个实例对象来调用此方法。我们调用类的方法一般是下面这样的:
>>> class A: ... def foo(self): ... print 'self is ',self ... >>> a=A() >>> a.foo() self is <__main__.A instance at 0x2256c0>
如果没有实例a,我们还想调用A.foo()怎么办呢? 你需要手动传入一个参数,该参数应该是A的实例。
>>> A.foo(b) self is <__main__.A instance at 0x2255d0> >>> b <__main__.A instance at 0x2255d0>
其实上面仍然算是绑定方法了,因为调用A.foo(b)的时候,b是A的实例。
3.2 调用非绑定方法
我们总结类的方法的时候说,只有该方法所属的类拥有实例的时候,我们才可以调用,其实这是错的:
>>> class addrEntry: ... def __init__(self,nm,num): ... self.name=nm ... self.phone=num ... >>> class empAddrEntry(addrEntry): ... def __init__(self,nm,num,de,em): ... addrEntry.__init__(self,nm,num) ... self.dept=de ... self.email=em ... >>> emp1=empAddrEntry('kramer','12345678','MGR','mgr1@good.com') >>> emp1.name 'kramer' >>> emp1.phone '12345678' >>> emp1.dept 'MGR' >>> emp1.email 'mgr1@good.com'
这里我们有一个父类 addrEntry, 子类是empAddrEntry。 父类是地址簿的entry而子类是雇员,我们在子类调用__init__的时候调用了父类的__init__,而这个时候我们传入的self 是子类对象不是父类对象。 这就是一个非绑定调用的方法。
非绑定的情况适用于父类的方法采用子类的对象来调用。
3.3 静态方法和类方法
静态方法就是类里面定义的不必用实例调用的方法。如下:
>>> class Test: ... @staticmethod ... def foo(): ... print 'foo is static method' ... >>> Test.foo() foo is static method
这里的foo函数用了一个内建函数staticmethod来修饰,所以它变成了一个静态方法。这样我们就可以用类名Test来直接调用不加参数的foo了。否则会报未绑定错误。
类方法就是第一个参数需要以类作为第一个参数。普通的方法需要以self来作为第一个参数,并且在调用的时候不必显示写self。相应的,把self换成cls就是类方法了。
>>> class Test: ... @classmethod ... def foo(cls): ... print 'class method with cls',cls ... >>> Test.foo() class method with cls __main__.Test
同样的,这里也需要用一个内建函数 classmethod来修饰。
4 派生继承
派生是指通过一个类C继承另外的一个或多个类P1..,让C类获得P1,P2..Pn的一些特性。比如:
现在有父类addrEntry,里面有属性name,phone。
>>> class addrEntry: ... def __init__(self,nm,ph): ... self.name=nm ... self.phone=ph ...
我们要需要一个类empAddrEntry,也是地址薄的条目,但是里面多了部门和email信息,我们可以从父类addrEntry派生如下:
>>> class empAddrEntry(addrEntry): ... def __init__(self,nm,ph,de,em): ... addrEntry.__init__(self,nm,ph) ... self.dept=de ... self.email=em ...
继承是说派生出的子类会获得父类所有的属性。除非子类有同名的属性,那样会覆盖父类的属性。但是__doc__属性不会被子类获得,这是特殊属性。
>>> addrEntry.gender='male' >>> empAddrEntry.gender 'male'
上面我们给父类增加属性 gender 子类也会获得。
4.1 多重继承
关于多重继承只需要了解一下MRO即可。
http://www.cnblogs.com/kramer/p/3685842.html
5 内建函数
issubclass(sub,sup).
如果sub是sup的的一个子类,或者说sub是sup中一个的子类(这时sup可能是一些类组成的元组),该函数返回true否则返回false。要注意的是如果sub sup相同也返回true,这个函数认为一个类可以是自身的子类。
isinstance(obj1,obj2)
如果obj1是obj2的一个实例,或者obj2子类的一个实例返回True,否则返回false。
hasattr,getattr,setattr,delattr
这几个函数不仅仅用在类和实例上,可以用在任何地方,顾名思义就是检查是否有属性,查询属性值,设置属性值,删除属性值。
super(type [,obj...])
这个函数会找到type的父类,如果你提供了obj,就把obj和super的父类绑定起来,如下:
>>> class P1(object): ... def __init__(self,nm): ... self.name=nm ... def show1(self): ... print self.name ... >>> class P2(object): ... def __init__(self,ag): ... self.age=ag ... def show2(self): ... print self.age ... >>> class C1(P1,P2): ... def __init__(self,nm,age): ... self.name=nm ... self.age=age ... >>> type(C1) <type 'type'> >>> c=C1('kramer','20') >>> super(C1,c).show1() kramer >>> super(C1,c).show2() 20
这个例子中我们的父类P1 P2 派生出子类C1。 我们想用子类的对象c去调用父类的show1 或者show2,只需要写成super(C1,c).show1/2就可以。 super会自动找到C1合适的父类,把c绑定到这个父类来调用show1/2函数。
6 私有化
python中类的属性可以实现私有化。方法是在属性前面加上双下划线
>>> class A: ... __iv=1 ... v=2 ... def __ifoo(self): ... print 'this method is invisible' ... def foo(self): ... print 'this method is visible' ... >>> >>> a=A() >>> a.foo() this method is visible >>> a.__ifoo() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: A instance has no attribute '__ifoo' >>> a.v 2 >>> a.__iv Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: A instance has no attribute '__iv'
如图,上面类里的属性,只要前面加上双下划线,在类外面就访问不到了。当然在类的里面还是可以的。不过其实这是一种防君子不防小人的措施。因为在属性前面加了双下划线之后,python只是把属性的名字改成了_类名__属性名。 也就是前面添加了一个下划线还有一个类名。
>>> dir(A) ['_A__ifoo', '_A__iv', '__doc__', '__module__', 'foo', 'v']
所以你真的想访问还是可以访问的。
>>> a._A__ifoo() this method is invisible >>> a._A__iv 1