Chapter 13. OOP
子类如果不定义自己的构造器(__init__()),基类的构造器就会被调用。然而,子类如果重写基类的构造器,基类的构造器就不会被自动调用了。这样,基类的构造器就必须显式地写出来才会被执行。
类名通常用大写字母打头,这是标准惯例。
Python不支持纯虚函数。
bound method 和 unbound method:
方法必须绑定到一个实例才能被直接调用;非绑定的方法可能可以被调用,但实例对象一定要明确给出,才能保证调用成功。
类的属性
要知道一个类有哪些属性,有2个方法:
- 用内建函数dir()
- 访问类的字典属性__dict__
内建的vars()函数接受类对象作为参数,返回类的__dict__属性的内容。
对于任何类C,其必有的所有特殊属性如下:
- C.__name__ 类C的名字(字符串)
- C.__doc__ 类C的文档字符串(文档字符串不会被派生类继承)
- C.__bases__ 类C的所有父类构成的元组
- C.__dict__ 类C的所有属性
- C.__module__ 类C定义所在的模块
- C.__class__ 实例C所对应的类(仅新式类),如’type’
__init_()_ 和 __new__()
用户可以对内建类型(如字符串、数字)进行派生,因此,需要一种途径来实例化不可变对象,比如派生字符串、派生数字等。在这种情况下,解释器调用类的__new__()方法,一个静态方法,并且传入的参数是类实例化操作时生成的。__new__()会调用父类的__new__()来创建对象(向上代理)。
__new__()必须返回一个合法的实例,这样解释器在调用__init__()时,就可以把这个实例作为self传给它。所以,__new__()的执行是在__init__()之前的。
__del__()
相应的desctructor方法名为__del__(). 由于Python具有垃圾对象回收机制(靠引用计数),这个函数要直到该实例对象所有的引用都被清除掉后才会执行。
关于__del__()的总结:
- 不要忘记首先调用父类的__del__()
- 调用 del x 仅仅表示减少 x 的引用计数,并不会去调用 __del__()
- 一般不要去实现 __del__()
类属性与实例属性
对实例属性的赋值会创建一个实例属性(如果不存在的话),但如果类属性中存在同名属性,就会有副作用。
看下面的代码:
class Foo(object):
x = 1.5
foo = Foo()
print foo.x # 1.5
foo.x = 2
print foo.x # 2 (实例属性)
print Foo.x # 1.5
del foo.x
print foo.x # 1.5 (类属性)
再看一段代码,在类属性可变的情况下,就有不同了:
class Foo(object):
x = {2003: 'poe2'}
foo = Foo()
print foo.x # {2003: 'poe2'}
foo.x[2004] = 'poe3'
print foo.x # {2003: 'poe2', 2004: 'poe3'}
print Foo.x # {2003: 'poe2', 2004: 'poe3'} 竟然一样了!
del foo.x # 会报AttributeError,因为没有遮蔽掉类属性
绑定和方法调用
方法只有在其所属的类拥有实例的时候,才能被调用。当存在一个实例时,方法才被认为是绑定到那个实例的;没有实例时,方法就是未绑定的。
调用非绑定方法:
需要调用一个还没有任何实例的类中的方法的一个主要场景是:在派生一个子类,而且要覆盖父类的构造方法,这时你需要调用父类中那个想要被覆盖掉的构造方法。
class B(A):
def __init__(self, name, phone):
A.__init__(self)
self.name = name
self.phone = phone
所以,典型的非绑定方式调用函数如下:
MyClass.method(instance)
静态方法(staticmethod) 和 类方法(classmethod)
class TestStaticMethod(object):
@staticmethod
def foo():
print "calling static method foo()"
class TestClassMethod(object):
@classmethod
def foo(cls):
print "calling class method foo()"
print "foo() is part of class: ", cls.__name__
TestStaticMethod.foo()
TestClassMethod.foo()
执行效果如下:
>>> execfile("./1.py")
calling static method foo()
calling class method foo()
foo() is part of class: TestClassMethod
方法解释顺序(MRO: Method Resolution Order)
经典类,使用深度优先算法。
新式类,使用新的C3算法,广度优先,子类在前,父类在后。
新式类有一个 __mro__ 属性,告诉你查找顺序是怎样的。
类、实例 和 其他对象的内建函数
issubclass(son, parent)
判断一个类是否是另一个类的子类或子孙类.
从Python2.3开始,issubclass()的第二个参数可以是一个元组(tuple)。这时,只要第一个参数是给定元组中任何一个类的子类,就会返回True.
isinstance(obj, cls)
如果obj是类cls的一个实例,或者是cls的子类的一个实例,就返回True.
isinstance()也可以使用一个元组作为第二个参数。如果第一个参数是第二个参数(元组)中的任何一个类的实例,就返回True.
hasattr(), getattr(), setattr(), delattr()
*attr()系列函数可以在各种对象下工作,不限于类和实例。第一个参数是需要被处理的对象,第二个参数是某属性名字的字符串。
- hasattr(obj, attr): 检查某个对象是否具有某个特定的属性。
- getattr(obj, attr[, default]): 取得某个对象的某属性值。
- setattr(obj, attr, val): 要么加入一个新的属性,要么取代一个已存在的属性。
- delattr(obj, attr): 从一个对象中删除属性。
dir(obj=None)
- dir()作用在实例上(经典类或新式类)时,显示实例变量,还有实例所在的类以及所有它的基类中定义的方法和类属性;
- dir()作用在类上(经典类或新式类)时,则显示类以及它的所有基类的__dict__中的内容;不会显示定义在元类(metaclass)中的类的属性;
- dir()作用在模块上时,显示模块的__dict__的内容;
- dir()不带参数时,显示调用者的局部变量。
super()
另有博文详述。
vars(obj=None)
vars()内建函数与dir()相似,只是给定的对象参数都必须有一个__dict__属性。
vars()返回一个字典,包含了对象存储于其__dict__中的属性(key)和值。
如果vars()不带参数,则显示一个包含本地名字空间的属性(key)及其值的字典,也就是locals().
迭代器 (用类实现)
先看示例代码randSeq.py:
from random import choice
class RandSeq(object):
def __init__(self, seq):
self.data = seq
def __iter__(self):
return self
def next(self):
return choice(self.data)
以上代码中,重要的是函数__iter__(), 它仅返回self,就正是如何将一个对象声明为迭代器的方式;
最后调用next()来得到迭代器中连续的值。这个迭代器是一个没有终点的迭代器。
__slots__ 类属性
用户可以用__slots__属性代替__dict__.
基本上,__slots__是一个类变量,由一个序列型对象组成,由所有合法标识构成的实例属性的集合来表示。它可以是一个列表、元组或可迭代对象,也可以是标识实例能拥有的唯一的属性的简单字符串。
任何试图创建一个其名不在__slots__中的名字的实例属性都将导致AttributeError异常。
class SlottedClass(object):
__slots__ = ('foo', 'bar')
>>> c = SlottedClass()
>>> c.xxx = "don't think so"
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'SlottedClass' object has no attribute 'xxx'
这种特性的主要目的是节约内存。其副作用是某种类型的“安全”,它能防止用户随心所欲地动态增加实例的属性。
带__slots__的类定义不会包含__dict__了。
__getattribute__ 特殊方法
- __getattr__(),它仅当属性无法在实例的 __dict__ 或 它的类的__dict__ ,或 祖先类的__dict__中找到时,才被调用。
- __getattribute__ (), 执行对每一个属性的访问,无论是否能找到。
- 如果类同时定义了__getattribute__()和 __getattr__() 方法,除非明确从__getattribute__()调用,或__getattribute__()引发了AttributeError异常,否则后者不会被调用。