本文转载自微信公众号“东方儿”,作者东方儿。转载本文请联系东方儿公众号。Python是一种支持面向对象设计的高级语言。如何设计一个符合Python风格的面向对象类是一个比较复杂的问题。这篇文章提供了一个参考,表达了一种思路,探索了一层道理。目标期望的类具有以下基本行为:__repr__提供对repr()的支持,返回一个易于开发人员理解的对象的字符串表示形式。__str__提供对str()的支持,返回对象的用户友好字符串表示形式。__bytes__提供对bytes()的支持,它返回对象的二进制表示形式。__format__为format()和str.format()提供支持,以使用特殊格式代码显示对象的字符串表示形式。Vector2d是向量类,预计支持如下操作:>>>v1=Vector2d(3,4)>>>print(v1.x,v1.y)#直接访问3.04.0>>>x通过属性,y=v1#支持解包>>>x,y(3.0,4.0)>>>v1#支持reprVector2d(3.0,4.0)>>>v1_clone=eval(repr(v1))#验证repr描述为准确>>>v1==v1_clone#Support==operatorTrue>>>print(v1)#Supportstr(3.0,4.0)>>>octets=bytes(v1)#Supportbytes>>>octetsb'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'>>>abs(v1)#实现__abs__5.0>>>bool(v1),bool(Vector2d(0,0))#实现__bool__(True,False)基本实现代码及分析如下:fromarrayimportarrayimportmathclassVector2d:#Usetypecode='whenconvertingbetweenVector2dinstancesandbinaryd'def__init__(self,x,y):#转换为浮点数self.x=float(x)self.y=float(y)def__iter__(self):#Generator表达式,转Vector2d实例变成一个可迭代的对象,这样它就可以被解包return(iforiin(self.x,self.y))def__repr__(self):class_name=type(self).__name__#{!r}是通用格式字符#*self是解包,*代表所有元素返回'{}({!r},{!r})'.format(class_name,*self)def__str__(self):#Vector2d实例是一个可迭代对象,可以得到一个元组,strreturnstr(tuple(self))def__bytes__(self):#转换为二进制return(bytes([ord(self.typecode)])+bytes(array(self.typecode,self)))def__eq__(self,other):#比较相等returntuple(self)==tuple(other)def__abs__(self):#向量的模是直角三角形斜边的长度(self))@classmethoddeffrombytes(cls,octets):#classmethod不传self要传clstypecode=chr(octets[0])memv=memoryview(octets[1:]).cast(typecode)returncls(*memv)#unpacking最后,构造方法需要的一对参数代码终于用上了。@classmethod装饰器,容易和@staticmethod混淆@classmethod的用法是:定义操作类,而不是操作实例的方法。通常用于定义替代构造函数方法。@staticmethod其实就是一个普通的函数,只是恰好放在了类的定义体中。实际定义可以在类中或模块中。格式化显示代码及解析如下:defangle(self):returnmath.atan2(self.y,self.x)def__format__(self,fmt_spec=''):iffmt_spec.endswith('p'):#endswith'p',使用极坐标fmt_spec=fmt_spec[:-1]coords=(abs(self),self.angle())#计算极坐标(量级,角度)outer_fmt='<{},{}>'#Anglebracketselse:coords=self#不要以'p'结尾,构建笛卡尔坐标(x,y)outer_fmt='({},{})'#Parentscomponents=(format(c,fmt_spec)forcincoords)#使用内置格式化函数Formatstringreturnouter_fmt.format(*components)#解包后代入外层格式,实现如下效果:直角坐标:>>>format(v1)'(3.0,4.0)'>>>format(v1,'.2f')'(3.00,4.00)'>>>format(v1,'.3e')'(3.000e+00,4.000e+00)'极坐标:>>>format(Vector2d(1,1),'p')#doctest:+ELLIPSIS'<1.414213...,0.785398...>'>>>format(Vector2d(1,1),'.3ep')'<1.414e+00,7.854e-01>'>>>format(Vector2d(1,1),'0.5fp')'<1.41421,0.78540>'Hashableimplementation__hash__特殊方法可以让Vector2d变成hashable,但是这里之前,需要让属性不可变,代码如下:def__init__(self,x,y):#双下划线前缀,变成privateself.__x=float(x)self.__y=float(y)@property#标记为属性defx(self):returnself.__x@propertydefy(self):returnselff.__y这样x和y是只读的,不可写的。属性名的双下划线前缀称为namemangling,相当于_Vector2d__x和_Vector2d__y,可以避免被子类覆盖。然后使用位运算符异或将x和y的哈希值混合:def__hash__(self):returnhash(self.x)^hash(self.y)节省内存Python会将实例属性存储在__dict__字典中默认情况下,字典的底层是哈希表,数据量大时会消耗大量内存(以空间换时间)。通过__slots__类属性,可以将实例属性存储在元组中,大大节省内存空间。示例:classVector2d:__slots__=('__x','__y')typecode='d'有几点需要注意:所有属性都必须在__slots__元组中定义。子类还必须定义__slots__。如果实例要支持弱引用,就需要在__slots__中加上__weakref。重写类属性实例重写有一个Python非常独特的特性:类属性可以用来为实例属性提供默认值。示例代码中的typecode可以直接通过self.typecode获取。但是,如果给一个不存在的实例属性赋值,则会创建一个新的实例属性,而类属性不会受到影响。self.typecode得到的是实例属性的typecode。示例:>>>v1=Vector2d(1,2)>>>v1.typecode='f'>>>v1.typecode'f'>>>Vector2d.typecode'd'子类覆盖类属性是公共的,所以它可以直接修改为Vector2d.typecode='f'。但更Pythonic的方法是定义一个子类:classShortVector2d(Vector2d):typecode='f'Django的基于类的视图广泛使用了这种技术。总结本文首先介绍了如何实现特殊的方法来设计一个Python风格的类,然后分别实现格式化显示和可散列对象,使用__slots__为类节省内存,最后讨论类属性重写技术,子类Overlays是一个被大量使用的技术通过Django的基于类的视图。参考资料:《流畅的Python》Chapter9Python-styleobjectshttps://www.jianshu.com/p/7fc0a177fd1f
