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 =v15 >>>x, y6 (3.0, 4.0)7 >>>v18 Vector2d(3.0, 4.0)9 >>> v2 =eval(repr(v1))10 >>> v1 ==v211 True12 >>> print(v1)13 (3.0, 4.0)14 >>> octets =bytes(v1)15 >>>octets16 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 importmath2 from array importarray3 classVector2d:4 """
5 自定义一个二维向量类6 """
7 typecode = 'd'
8 def __init__(self, x, y):9 self.x =x10 self.y =y11
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 returnmath.hypot(self.x, self.y)26
27 def __bool__(self):28 returnbool(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 =v137 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 classVector2d:2 """
3 自定义一个二维向量类4 """
5 typecode = 'd'
6 def __init__(self, x, y):7 self.x =x8 self.y =y9
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 returnmath.hypot(self.x, self.y)24
25 def __bool__(self):26 returnbool(abs(self))27
28 def __bytes__(self):29 return bytes([ord(self.typecode)])+bytes(array(self.typecode, self))30
31 @classmethod32 deffrombytes(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 =v141 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 classVector2d:2 typecode = 'd'
3 def __init__(self, x, y):4 self.x, self.y =x, y5 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 returnstr(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 returnmath.hypot(self.x, self.y)18 def __bool__(self):19 returnbool(abs(self))20 @classmethod21 deffrombytes(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 =polar29 outer_fmt = '<{}, {}>'
30 else:31 s =self32 outer_fmt = '({}, {})'
33 components = (format(c, fmt.rstrip('p')) for c ins)34 return outer_fmt.format(*components)35 defangle(self):36 returnmath.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 classVector2d:2 __slots__ = ('_x', '_y')3 ...
ps:如果要处理数百万个数值对象,应该使用numpy。
__slots__设计之初是为了节省内存,但是也带来了一个副作用,那就是不能再为实例添加__slots__之外的属性。
需要明确的是:使用__slots__的充分理由应该是为了节省内存,而不是限制实例属性,__slots__是用于优化的,而非约束程序员。