1.序列协议
Python的序列协议只需实现__len__和__getitem__方法
首先创建一个 Ve类的升级版
class Ve:
test = 'd'
def __init__(self, components):
self._components = array(self.test, components)
def __iter__(self):
return iter(self._components)
def __repr__(self):
components = reprlib.repr(self._components) 用于限制输出, 当元组个数大于6时
components = components[components.find('['):-1] 其余部分用...代替
return 'Ve({})'.format(components) 参数的列表显示
def __str__(self):
return str(tuple(self))
def __eq__(self, other):
return tuple(self) == tuple(other)
def __abs__(self):
return math.sqrt(sum(x * x for x in self)) 计算模长的函数
def __bool__(self):
return bool(abs(self))
def __len__(self):
return len(self._components)
obj = Ve([1, 2])
print(str(obj), repr(obj), len(obj), abs(obj))
(1.0, 2.0) Ve([1.0, 2.0]) 2 2.23606797749979
设置切片功能
def __getitem__(self, index):
cls = type(self) type(实例化对象) 获取实例所属的类
if isinstance(index, slice): 如果参数为切片对象
return cls(self._components[index]) 调用类的构造方法,创建新实例化对象
elif isinstance(index, numbers.Integral): 如果参数为整形索引,获取其值
return self._components[index]
else:
msg = 'operate error' 否则报错
raise TypeError(msg)
obj = Ve([1, 2, 3, 4])
print(obj[1])
print(obj[1:3])
print(obj[1, 2])
2.0
(2.0, 3.0)
TypeError: operate error
只有序列才有索引、切片等功能,而使用索引、切片时会默认调用类的__getitem__方法
names = 'xyzt' 在类中定义
def __getattr__(self, name):
""
动态获取属性,使 xyzt 4个分量分别获取对象的前4个元素数值
""
cls = type(self)
if len(name) == 1:
pos = cls.names.find(name) 获取参数的索引位置
if 0 <= pos < len(self._components): 如果索引位置合法
return self._components[pos]
msg = '{.__name__!r} object has no attribute {!r}'
return AttributeError(msg.format(cls, name))
def __setattr__(self, name, value):
""
禁止实例化对象属性值修改或添加新属性
""
cls = type(self)
if len(name) == 1:
if name in cls.names:
error = 'readonly attribute {attr_name!r}'
elif name.islower(): 不允许添加新属性
error = "can not set attributes 'a' to 'z' in {cls_name!r}"
else:
error = ''
if error:
msg = error.format(cls_name=cls.__name__, attr_name=name)
raise AttributeError(msg)
super().__setattr__(name, value) 继承超类的setattr方法
obj = Ve([1, 2, 3, 4, 5])
print(obj.z)
3.0
小提示,为什么要有 setattr 方法
实际上, __getattr__方法是一种后备机制,仅当对象没有指定名称的属性时,python才会调用这个方法。
如果有像 obj.x = 10 这样的赋值后,则默认不会调用此函数,所以我们要禁止这种操作
4.优化__eq__方法
优点:减少处理时间和内存用量
代码
def __eq__(self, other):
return len(self) == len(other) and all(a ==b for a, b in zip(self, other))
zip函数会在最短的操作数耗尽时停止,且支持多个对象--->zip(a, b, c)
5.setitem 方法
完成参数的赋值
def __setitem__(self, dict, key, value):
self.dict[key] = value
知识点—判断类型
isinstance(x, numbers.Integral) 判断是否为整形
isinstance(x, numbers.Real) 判断是否为浮点数
isinstance(obj, Hashable) 判断对象是否可散列
6.继承
当我们需要继承内置类型如 List, Dict时, 不要直接继承
1.内置类型dict的__init__和__update__方法会忽略我们覆盖的__setitem__方法
2.dict.update方法会忽略我们覆盖的__getitem__方法
3.需要继承时我们通常选择collections.UserList, UserDict
4.实际上, 所有关于索引或切片的获取值都会调用__getitem__方法; 且不论key是否存在都会返回规定值
区别如下
class Answer(dict):
def __getitem__(self, key):
return 13
a = Answer(s='foo') 如果继承 collections.UserDict, 则输出如下
print(a)
t = {
}
t.update(a)
print(t)
{
's': 'foo'} {
's': 'foo'}
{
's': 'foo'} {
's': 13}
内置的 dict、list 和 str 类型是Python的底层基础,因此速度必须快。
这些内置类型由CPython实现,CPython走了捷径,故意不调用被子类覆盖的方法
7.解析顺序
1.直接在类上调用实例方法时,必须显式传入self参数
2.类的__mro__属性会显示继承方法的执行顺序
如
class A:
pass
class B:
pass
class C(B, A):
pass
print(C.__mro__)
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
8.补充
重新定义 eq 方法, 判断是否为类的实例对象
def __eq__(self, other):
if isinstance(other, Ve):
return len(self) == len(other) and all(a == b for a, b in zip(self, other))
else:
return TypeError('not obj')
9.重载运算符
实现向量加法
def __add__(self, other):
try:
pairs = itertools.zip_longest(self, other, fillvalue=0.0)
return Ve(a + b for a, b in pairs)
except TypeError:
return NotImplemented
def __radd__(self, other): 反向加法, 备选之用
return self + other
obj = Ve([1, 2, 3, 4, 5])
obj1 = Ve([1, 2, 3, 4])
print(obj + obj1)
(2.0, 4.0, 6.0, 8.0, 5.0)
注释
1.pairs 是一个生成器, 它会生成(a, b)形式的元组, 其中a来自self, b来自other。
2.使用 fillvalue参数填充较短的那个可迭代对象, 此处用 0 填充
3.构建一个新Ve实例.
乘法同理
增值运算符 +=, *= 不会修改不可变目标,而是新建实例然后重新绑定