定义Python 的 Class 比较特别,和我们习惯的静态语言类型定义有很大区别。
1. 使用一个名为 __init__ 的方法来完成初始化。
2. 使用一个名为 __del__ 的方法来完成类似析购操作。
3. 所有的实例方法都拥有一个 self 参数来传递当前实例,类似于 this。
4. 可以使用 __class__ 来访问类型成员。
>>> class Class1: def __init__(self): print "initialize..." def test(self): print id(self) >>> a = Class1() initialize... >>> a.test() 13860176 >>> id(a) 13860176
Class 有一些特殊的属性,便于我们获得一些额外的信息。
>>> class Class1(object): """Class1 Doc.""" def __init__(self): self.i = 1234 >>> Class1.__doc__ # 类型帮助信息 'Class1 Doc.' >>> Class1.__name__ # 类型名称 'Class1' >>> Class1.__module__ # 类型所在模块 '__main__' >>> Class1.__bases__ # 类型所继承的基类 (<type 'object'>,) >>> Class1.__dict__ # 类型字典,存储所有类型成员信息。 <dictproxy object at 0x00D3AD70> >>> Class1().__class__ # 类型 <class '__main__.Class1'> >>> Class1().__module__ # 实例类型所在模块 '__main__' >>> Class1().__dict__ # 对象字典,存储所有实例成员信息。 {'i': 1234}
继承
Python 支持多继承,但有几点需要注意:
1. 基类 __init__ / __del__ 需显示调用。
2. 继承方法的调用和基类声明顺序有关。
>>> class Base1: def __init__(self): print "Base1" def test(self): print "Base1 test..." >>> class Base2: def __init__(self): print "Base2" def test(self): print "Base2 test..." >>> class Class1(Base2, Base1): def __init__(self): Base1.__init__(self) Base2.__init__(self) print "Class1" >>> a = Class1() Base1 Base2 Class1 >>> a.test() Base2 test...
成员
Python Class 同样包含类型和实例两种成员。
>>> class Class1: i = 123 # Class Field def __init__(self): self.i = 12345 # Instance Field >>> print Class1.i 123 >>> print Class1().i 12345
有几个很 "特殊" 的 "规则" 需要注意。
(1) 我们可以通过实例引用访问类型成员。因此下面的例子中 self.i 实际指向 Class1.i,直到我们为实例新增了一个成员 i。
>>> class Class1: i = 123 def __init__(self): print self.i print hex(id(self.i)) >>> hex(id(Class1.i)) # 显示 Class1.i '0xab57a0' >>> a = Class1() # 创建 Class1 实例,我们会发现 self.i 实际指向 Class1.i。 123 0xab57a0 >>> Class1.__dict__ # 显示 Class1 成员 {'i': 123, '__module__': '__main__', '__doc__': None, '__init__': <function __init__ at 0x00D39470>} >>> a.__dict__ # 显示实例成员 {} >>> a.i = 123456789 # 为实例新增一个成员 i >>> hex(id(a.i)) # 显示新增实例成员地址 '0xbbb674' >>> a.__dict__ # 显示实例成员 {'i': 123456789} (2) 调用类型内部方法,需要省略 self 参数。 >>> class Class1: def __init__(self): self.__test("Hello, World!") def __test(self, s): print s >>> Class1() Hello, World! <__main__.Class1 instance at 0x00D37B48>
我们可以在成员名称前添加 "__" 使其成为私有成员。
>>> class Class1: __i = 123 def __init__(self): self.__x = 0 def __test(self): print id(self) >>> Class1.i Traceback (most recent call last): File "<pyshell#102>", line 1, in <module> Class1.i AttributeError: class Class1 has no attribute 'i' >>> Class1().__x Traceback (most recent call last): File "<pyshell#103>", line 1, in <module> Class1().__x AttributeError: Class1 instance has no attribute '__x' >>> Class1().test() Traceback (most recent call last): File "<pyshell#104>", line 1, in <module> Class1().test() AttributeError: Class1 instance has no attribute 'test'
事实上这只是一种规则,并不是编译器上的限制。我们依然可以用特殊的语法来访问私有成员。
>>> Class1._Class1__i 123 >>> a = Class1() >>> a._Class1__x 0 >>> a._Class1__test() 13860376
除了静态(类型)字段,我们还可以定义静态方法。
>>> class Class1: @staticmethod def test(): print "static method" >>> Class1.test() static method
从设计的角度,或许更希望用属性(property)来代替字段(field)。
>>> class Class1: def __init__(self): self.__i = 1234 def getI(self): return self.__i def setI(self, value): self.__i = value def delI(self): del self.__i I = property(getI, setI, delI, "Property I") >>> a = Class1() >>> a.I 1234 >>> a.I = 123456 >>> a.I 123456
如果只是 readonly property,还可以用另外一种方式。
>>> class Class1: def __init__(self): self.__i = 1234 @property def I(self): return self.__i >>> a = Class1() >>> a.I 1234
用 __getitem__ 和 __setitem__ 可以实现 C# 索引器的功能。
>>> class Class1: def __init__(self): self.__x = ["a", "b", "c"] def __getitem__(self, key): return self.__x[key] def __setitem__(self, key, value): self.__x[key] = value >>> a = Class1() >>> a[1] 'b' >>> a[1] = "xxxx" >>> a[1] 'xxxx'
重载
Python 支持一些特殊方法和运算符重载。
>>> class Class1: def __init__(self): self.i = 0 def __str__(self): return "id=%i" % id(self) def __add__(self, other): return self.i + other.i >>> a = Class1() >>> a.i = 10 >>> str(a) 'id=13876120' >>> b = Class1() >>> b.i = 20 >>> a + b 30
通过重载 "__eq__",我们可以改变 "==" 运算符的行为。
>>> class Class1: pass >>> a = Class1() >>> b = Class1() >>> a == b False >>> class Class1: def __eq__(self, x): return True >>> a = Class1() >>> b = Class1() >>> a == b True
Open Class
这是个有争议的话题。在 Python 中,我们随时可以给类型或对象添加新的成员。
1. 添加字段
>>> class Class1: pass >>> a = Class1() >>> a.x = 10 >>> a.x 10 >>> dir(a) ['__doc__', '__module__', 'x'] >>> b = Class1() >>> dir(b) ['__doc__', '__module__'] >>> del a.x >>> dir(a) ['__doc__', '__module__']
2. 添加方法
>>> class Class1: pass >>> def test(): print "test" >>> def hello(self): print "hello ", id(self) >>> a = Class1() >>> dir(a) ['__doc__', '__module__'] >>> Class1.test = test >>> dir(a) ['__doc__', '__module__', 'test'] >>> b = Class1() >>> dir(b) ['__doc__', '__module__', 'test'] >>> a.hello = hello >>> a.hello(a) hello 13860416 >>> dir(a) ['__doc__', '__module__', 'hello', 'test'] >>> dir(b) ['__doc__', '__module__', 'test']
3. 改变现有方法
>>> class Class1: def test(self): print "a" >>> def test(self): print "b" >>> Class1.test = test >>> Class1().test() b
另外,有几个内建函数方便我们在运行期进行操作。
>>> hasattr(a, "x") False >>> a.x = 10 >>> getattr(a, "x") 10 >>> setattr(a, "x", 1234) >>> a.x 1234
Python Open Class 是如何实现的呢?我们看一下下面的代码。
>>> class Class1: pass >>> a = Class1() >>> a.__dict__ {} >>> a.x = 123 >>> a.__dict__ {'x': 123} >>> a.x 123 >>> a.test = lambda i: i + 1 >>> a.test(1) 2 >>> a.__dict__ {'test': <function <lambda> at 0x00D39DB0>, 'x': 123}
原来,Python Class 对象或类型通过内置成员 __dict__ 来存储成员信息。
我们还可以通过重载 __getattr__ 和 __setattr__ 来拦截对成员的访问,需要注意的是 __getattr__ 只有在访问不存在的成员时才会被调用。
>>> class Class1: def __getattr__(self, name): print "__getattr__" return None def __setattr__(self, name, value): print "__setattr__" self.__dict__[name] = value >>> a = Class1() >>> a.x __getattr__ >>> a.x = 123 __setattr__ >>> a.x 123
如果类型继承自 object,我们可以使用 __getattribute__ 来拦截所有(包括不存在的成员)的获取操作。
注意在 __getattribute__ 中不要使用 "return self.__dict__[name]" 来返回结果,因为在访问 "self.__dict__" 时同样会被 __getattribute__ 拦截,从而造成无限递归形成死循环。
>>> class Class1(object): def __getattribute__(self, name): print "__getattribute__" return object.__getattribute__(self, name) >>> a = Class1() >>> a.x __getattribute__ Traceback (most recent call last): File "<pyshell#3>", line 1, in <module> a.x File "<pyshell#1>", line 4, in __getattribute__ return object.__getattribute__(self, name) AttributeError: 'Class1' object has no attribute 'x' >>> a.x = 123 >>> a.x __getattribute__ 123