python自带的对象拥有很多有趣的行为,用户自己定义的类对象也可以实现跟python对象相同的行为。
对象的表示形式
python关于对象的表示提供了两种形式:
repr()
便于开发者理解的返回对象的字符串形式
str()
便于用户理解的返回对象的字符串形式
也正是对象的__repr__和__str__两个特殊方法为repr()和str()提供支持。另外还有两个方法,__bytes__和__format__。__bytes__方法跟__str__类似:bytes()函数调用它获取对象的字节序列表示形式;__format_-方法会被内置的format()函数和str.format()方法调用。
向量类
自己定义一个Vector2d类,我们期望他能有下面的表现形式。
1 >>> v1 = Vector2d(3, 4) 2 >>> print(v1.x, v1.y) 3 3.0 4.0 4 >>> x, y = v1 5 >>> x, y 6 (3.0, 4.0) 7 >>> v1 8 Vector2d(3.0, 4.0) 9 >>> v2 = eval(repr(v1)) 10 >>> v1 == v2 11 True 12 >>> print(v1) 13 (3.0, 4.0) 14 >>> octets = bytes(v1) 15 >>> octets 16 b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' 17 >>> abs(v1) 18 5.0 19 >>> bool(v1), bool(Vector2d(0, 0)) 20 (True, False)
实现代码如下:
1 import math 2 from array import array 3 class Vector2d: 4 """ 5 自定义一个二维向量类 6 """ 7 typecode = 'd' 8 def __init__(self, x, y): 9 self.x = x 10 self.y = y 11 12 def __iter__(self): 13 return (i for i in (self.x, self.y)) 14 15 def __repr__(self): 16 return '{}({!r}, {!r})'.format(type(self).__name__, self.x, self.y) 17 18 def __str__(self): 19 return str(tuple(self)) #已经实现了__iter__方法,self实例是可迭代对象 20 21 def __eq__(self, other): 22 return tuple(self) == tuple(other) 23 24 def __abs__(self): 25 return math.hypot(self.x, self.y) 26 27 def __bool__(self): 28 return bool(abs(self)) 29 30 def __bytes__(self): 31 return bytes([ord(self.typecode)])+bytes(array(self.typecode, self)) 32 33 v1 = Vector2d(3, 4) 34 print(v1.x, v1.y) 35 #3.0 4.0 36 x, y = v1 37 print(x, y) 38 #(3.0, 4.0) 39 print(v1) 40 #Vector2d(3.0, 4.0) 41 print("repr(v1),", repr(v1)) 42 v2 = eval(repr(v1)) 43 print(v1 == v2) 44 #True 45 print(v1) 46 #(3.0, 4.0) 47 octets = bytes(v1) 48 print(octets) 49 #b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' 50 print(abs(v1)) 51 #5.0 52 print(bool(v1), bool(Vector2d(0, 0))) 53 #(True, False)
备选构造方法
我们可以Vector2d实例转换成字节序列了,那么也应该提供一个方法转换回来。array.array中有个frombytes方法
1 class Vector2d: 2 """ 3 自定义一个二维向量类 4 """ 5 typecode = 'd' 6 def __init__(self, x, y): 7 self.x = x 8 self.y = y 9 10 def __iter__(self): 11 return (i for i in (self.x, self.y)) 12 13 def __repr__(self): 14 return '{}({!r}, {!r})'.format(type(self).__name__, self.x, self.y) 15 16 def __str__(self): 17 return str(tuple(self)) #已经实现了__iter__方法,self实例是可迭代对象 18 19 def __eq__(self, other): 20 return tuple(self) == tuple(other) 21 22 def __abs__(self): 23 return math.hypot(self.x, self.y) 24 25 def __bool__(self): 26 return bool(abs(self)) 27 28 def __bytes__(self): 29 return bytes([ord(self.typecode)])+bytes(array(self.typecode, self)) 30 31 @classmethod 32 def frombytes(cls, octets): 33 typecode = chr(octets[0]) 34 memv = memoryview(octets[1:]).cast(typecode) 35 return cls(*memv) 36 37 v1 = Vector2d(3, 4) 38 print(v1.x, v1.y) 39 #3.0 4.0 40 x, y = v1 41 print(x, y) 42 #(3.0, 4.0) 43 print(v1) 44 #Vector2d(3.0, 4.0) 45 print("repr(v1),", repr(v1)) 46 v2 = eval(repr(v1)) 47 print(v1 == v2) 48 #True 49 print(v1) 50 #(3.0, 4.0) 51 octets = bytes(v1) 52 print(octets) 53 #b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' 54 print(abs(v1)) 55 #5.0 56 print(bool(v1), bool(Vector2d(0, 0))) 57 #(True, False) 58 v2 = Vector2d.frombytes(octets) 59 print(v2)
classmethod和staticmethod
classmethod定义了操作类的方法而不是实例。classmethod改变了调用方法的方式,第一个参数是类本身,而不是实例。classmethod最常见的用途就是定义备选函数构造方法,如上面的frombytes,按照约定,类方法的第一个参数名为cls。(实际上,怎么命名都可以)
staticmethod也会改变方法的调用方式,但是第一个蚕食不是特殊的值,事实上,静态方法就是普通的函数,只是碰巧在类的定义体中。
格式化显示
1 class Vector2d: 2 typecode = 'd' 3 def __init__(self, x, y): 4 self.x, self.y = x, y 5 def __iter__(self): 6 return (i for i in (self.x, self.y)) 7 def __repr__(self): 8 return '{}({!r}, {!r})' %(type(self).__name__, self.x, self.y) 9 def __str__(self): 10 return str(tuple(self)) 11 def __bytes__(self): 12 return (bytes([ord(self.typecode)]) + 13 bytes(array(self.typecode, self))) 14 def __eq__(self, other): 15 return tuple(self)==tuple(other) 16 def __abs__(self): 17 return math.hypot(self.x, self.y) 18 def __bool__(self): 19 return bool(abs(self)) 20 @classmethod 21 def frombytes(cls, octets): 22 typecode = chr(octets[0]) 23 memv = memoryview(octets[1:]).cast(typecode) 24 return cls(*memv) 25 def __format__(self, fmt=""): 26 if fmt.endswith('p'): 27 polar = (abs(self), self.angle()) 28 s = polar 29 outer_fmt = '<{}, {}>' 30 else: 31 s = self 32 outer_fmt = '({}, {})' 33 components = (format(c, fmt.rstrip('p')) for c in s) 34 return outer_fmt.format(*components) 35 def angle(self): 36 return math.atan2(self.y, self.x) 37 v1 = Vector2d(3, 4) 38 print(format(v1, '.2f')) 39 print(format(v1, '.4e')) 40 print(format(v1, '.3ep'))
使用__slots__类属性节省空间
python在各个实例中的__dict__字典里存储实例属性,通过之前的学习,我们知道dict字典这种结构是典型的空间换时间,会消耗掉大量的内存,如果要处理数百万的实例,通过__slots__属性,就能节省大量内存,方法是让解释器在类似元组的结构中存储实例属性,而不是字典。(超类的__slots__属性,对子类没有效果)
1 class Vector2d: 2 __slots__ = ('_x', '_y') 3 ...
ps:如果要处理数百万个数值对象,应该使用numpy。
__slots__设计之初是为了节省内存,但是也带来了一个副作用,那就是不能再为实例添加__slots__之外的属性。
需要明确的是:使用__slots__的充分理由应该是为了节省内存,而不是限制实例属性,__slots__是用于优化的,而非约束程序员。