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