导语:本文章记录了本人在学习Python基础之面向对象篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。
本文重点:
1、掌握编写Pythonic code背后常用的特殊方法;
2、掌握可扩展的格式化输出方法;
3、了解可散列对象的设置以及节省内存的__slots__对象。
一、自定义具有Python风格的类
自定义的向量类需要支持基本的输出,迭代,求模。
1、自定义向量类型
从自定义向量类型入手写出符合Python风格的对象,这离不开特殊方法的支持。
我们期望的自定义向量类型应支持的基本功能:
- 构造,__init__
- 输出,__repr__和__str__
- 迭代,__iter__
- 求模,__abs__
- 转化为字节序列,__bytes__
代码实现如下:
import math
from array import array
class Vector2d:
typecode='d'
def __init__(self,x,y):
self.x=float(x)
self.y=float(y)
def __str__(self):
return str(tuple(self))
def __iter__(self):
return (i for i in (self.x,self.y))
def __repr__(self):
classname=type(self).__name__
s="{}({},{})".format(classname,*self)
return s
def __abs__(self):
return math.hypot(self.x,self.y)
def __bytes__(self):
return (bytes(self.typecode,encoding='utf-8')+
bytes(array(self.typecode,self)))
2、使用一个类方法实现备选构造方法
我们能将实例转化为字节序列,那么也应构造一个将实例转化为字节序列的方法。
@classmethod
def frombytes(cls,seqs):
typecode=chr(seqs[0])
memv=memoryview(seqs[1:]).cast(typecode)
return cls(*memv)
memoryview是泛化和去数学化的数组。
3、classmethod和staticmethod两个装饰器
classmethod:定义操作类而不是操作实例的方法,类方法的第一个参数是类本身而不是实例。最常见的用途是定义备选构造方法(返回cls(*))
staticmethod:是普通的函数,只是碰巧在类的定义体中,而不是在模块层定义。
二、格式化显示
1、扩展内置的format函数
通过改写format背后的__format__可以写出可扩展的格式。
实例1:实现format对向量类的处理
def __format__(self,fmt_spec=''):
components=(format(v,fmt_spec)for v in self)
return "({},{})".format(*components)
实例2:通过尾部自定义格式代码p实现将直角坐标向量转化为极坐标向量。
def __format__(self,fmt_spec=''):
if fmt_spec[-1]=="p":
coord=(abs(self),self.angle())
spec=fmt_spec[:-1]
components=(format(v,spec)for v in coord)
outer="<{},{}>"
else:
coord=self
components = (format(v, fmt_spec) for v in self)
outer = "({},{})"
return outer.format(*components)
本段代码的重点在于判断格式中是否存在自定义格式符p,并进行对应的格式处理。
三、将对象变为可散列的
目前的向量是不可散列的,而可散列对象需要满足:
(1)支持hash()函数,并且通过hash()得到的散列值是不变的;
(2)支持通过__eq__()方法来检测相等性;
(3)若a==b为真,则hash(a)=hash(b)也为真。
所以我们需要把对象定为不可变,然后自定义__hash__。
1、将对象定为不可变的
通过使用两个前导下划线。将属性标记为私有的。
@property
def x(self):
return self.__x
@property
def y(self):
return self.__y
2、自定义__hash__()
使用异或运算符实现。
def __hash__(self):
return hash(self.x)^hash(self.y)
四、其它
1、只读属性的设置
- 私有属性的设置只是避免修改方法意外访问不应更改的值,而无法防止有意的改动。
- 通过__dict__属性可以查询Python如何存储向量的属性名,然后只要编写a._Vector2d__x=5这样的代码就会恶意赋值。
-
Python程序员约定使用一个下划线前缀编写“受保护”的属性即self._x
,他们认为应该使用命名约定来避免意外覆盖属性。
2、利用__slots__节省内存
默认情况下,Python在各个实例中名为__dict__的字典中储存实例属性,相应地会消耗大量内存。通过__slots__类属性,并让解释器把实例属性存储在元组中,可以节省大量内存。
class Vector2d:
__slots__ = ('__x','__y')
typecode='d'
#其他方法实现省略
使用__slots__应注意的问题:
- __slots__无法从超类继承而来,每个子类都需要定义__slots__属性;
- 实例只能拥有__slots__中列出的属性,除非把'__dict__'加入到__slots__中(这样做就失去了节省内存的初衷)
- 如果不把'weakref__'加入__slots__,实例就不能作为弱引用的目标。
当处理的实例规模较小时,禁止创建动态属性或不支持弱引用是比较好的选择。
3、覆盖类属性
通过创建子类可以把继承自父类的实例属性覆盖掉。
class Shortvector2d(Vector2d):
typecode = 'f'
#其它方法实现省略