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__是用于优化的,而非约束程序员。