本文将在上篇文章二维向量Vector2d类的基础上,定义表示多维向量的Vector类。

第1版:兼容Vector2d类

代码如下:

from array import array
import reprlib
import math class Vector:
typecode = 'd' def __init__(self, components):
self._components = array(self.typecode, components) # 多维向量存数组中 def __iter__(self):
return iter(self._components) # 构建迭代器 def __repr__(self):
components = reprlib.repr(self._components) # 有限长度表示形式
components = components[components.find('['):-1]
return 'Vector({})'.format(components) def __str__(self):
return str(tuple(self)) def __bytes__(self):
return (bytes([ord(self.typecode)]) +
bytes(self._components)) 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)) @classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv) # 因为构造函数入参是数组,所以不用再使用*拆包了

其中的reprlib.repr()函数用于生成大型结构或递归结构的安全表达形式,比如:

>>> Vector([3.1, 4.2])
Vector([3.1, 4.2])
>>> Vector((3, 4, 5))
Vector([3.0, 4.0, 5.0])
>>> Vector(range(10))
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])

超过6个的元素用...来表示。

第2版:支持切片

Python协议是非正式的接口,只在文档中定义,在代码中不定义。比如Python的序列协议只需要__len____getitem__两个方法,Python的迭代协议只需要__getitem__一个方法,它们不是正式的接口,只是Python程序员默认的约定。

切片是序列才有的操作,所以Vector类要实现序列协议,也就是__len____getitem__两个方法,代码如下:

def __len__(self):
return len(self._components) def __getitem__(self, index):
cls = type(self) # 获取实例所属的类
if isinstance(index, slice): # 如果index是slice切片对象
return cls(self._components[index]) # 调用构造方法,返回新的Vector实例
elif isinstance(index, numbers.Integral): # 如果index是整型
return self._components[index] # 直接返回元素
else:
msg = '{cls.__name__} indices must be integers'
raise TypeError(msg.format(cls=cls))

测试一下:

>>> v7 = Vector(range(7))
>>> v7[-1] # <1>
6.0
>>> v7[1:4] # <2>
Vector([1.0, 2.0, 3.0])
>>> v7[-1:] # <3>
Vector([6.0])
>>> v7[1,2] # <4>
Traceback (most recent call last):
...
TypeError: Vector indices must be integers

第3版:动态存取属性

通过实现__getattr____setattr__,我们可以对Vector类动态存取属性。这样就能支持v.my_property = 1.1这样的赋值。

如果使用__setitem__方法,那么只能支持v[0] = 1.1

代码如下:

shortcut_names = 'xyzt'  # 4个分量属性名

def __getattr__(self, name):
cls = type(self) # 获取实例所属的类
if len(name) == 1: # 只有一个字母
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self._components): # 落在范围内
return self._components[pos]
msg = '{.__name__!r} object has no attribute {!r}' # <5>
raise AttributeError(msg.format(cls, name)) def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
if name in cls.shortcut_names: # name是xyzt其中一个不能赋值
error = 'readonly attribute {attr_name!r}'
elif name.islower(): # 小写字母不能赋值,防止与xyzt混淆
error = "can't 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) # 其他name可以赋值

值得说明的是,__getattr__的机制是:对my_obj.x表达式,Python会检查my_obj实例有没有名为x的属性,如果有就直接返回,不调用__getattr__方法;如果没有,到my_obj.__class__中查找,如果还没有,才调用__getattr__方法

正因如此,name是xyzt其中一个时才不能赋值,否则会出现下面的奇怪现象:

>>> v = Vector([range(5)])
>>> v.x = 10
>>> v.x
10
>>> v
Vector([0.0, 1.0, 2.0, 3.0, 4.0])

对v.x进行了赋值,但实际未生效,因为赋值后Vector新增了一个x属性,值为10,对v.x表达式来说,直接就返回了这个值,不会走我们自定义的__getattr__方法,也就没办法拿到v[0]的值。

第4版:散列

通过实现__hash__方法,加上现有的__eq__方法,Vector实例就变成了可散列的对象。

代码如下:

import functools
import operator def __eq__(self, other):
return (len(self) == len(other) and
all(a == b for a, b in zip(self, other))) def __hash__(self):
hashes = (hash(x) for x in self) # 创建一个生成器表达式
return functools.reduce(operator.xor, hashes, 0) # 计算聚合的散列值

其中__eq__方法做了下修改,用到了归约函数all(),比tuple(self) == tuple(other)的写法,能减少处理时间和内存。

zip()函数取名自zipper拉链,把两个序列咬合在一起。比如:

>>> list(zip(range(3), 'ABC'))
[(0, 'A'), (1, 'B'), (2, 'C')]

第5版:格式化

Vector的格式化跟Vector2d大同小异,都是定义__format__方法,只是计算方式从极坐标换成了球面坐标:

def angle(self, n):
r = math.sqrt(sum(x * x for x in self[n:]))
a = math.atan2(r, self[n-1])
if (n == len(self) - 1) and (self[-1] < 0):
return math.pi * 2 - a
else:
return a def angles(self):
return (self.angle(n) for n in range(1, len(self))) def __format__(self, fmt_spec=''):
if fmt_spec.endswith('h'): # hyperspherical coordinates
fmt_spec = fmt_spec[:-1]
coords = itertools.chain([abs(self)],
self.angles())
outer_fmt = '<{}>'
else:
coords = self
outer_fmt = '({})'
components = (format(c, fmt_spec) for c in coords)
return outer_fmt.format(', '.join(components))

极坐标和球面坐标是啥?我也不知道,略过就好。

小结

经过上下两篇文章的介绍,我们知道了Python风格的类是什么样子的,跟常规的面向对象设计不同的是,Python的类通过魔法方法实现了Python协议,使Python类在使用时能够享受到语法糖,不用通过get和set的方式来编写代码

参考资料:

《流畅的Python》第10章 序列的修改、散列和切片

Python如何设计面向对象的类(下)的更多相关文章

  1. Python如何设计面向对象的类(上)

    Python是一门高级语言,支持面向对象设计,如何设计一个符合Python风格的面向对象的类,是一个比较复杂的问题,本文提供一个参考,表达一种思路,探究一层原理. 目标 期望实现的类具有以下基本行为: ...

  2. python进阶01 面向对象、类、实例、属性封装、实例方法

    python进阶01 面向对象.类.实例.属性封装.实例方法 一.面向对象 1.什么是对象 #一切皆对象,可以简单地将“对象”理解为“某个东西” #“对象”之所以称之为对象,是因为它具有属于它自己的“ ...

  3. 22.python中的面向对象和类的基本语法

    当我发现要写python的面向对象的时候,我是踌躇满面,坐立不安呀.我一直在想:这个坑应该怎么爬?因为python中关于面向对象的内容很多,如果要讲透,最好是用面向对象的思想重新学一遍前面的内容.这个 ...

  4. python初步学习-面向对象之 类(二)

    方法重写 如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法: #!/usr/bin/env python #coding:utf8 class Parent: def myMeth ...

  5. python学习之【第十七篇】:Python中的面向对象(类和对象)

    1.什么是类和类的对象? 类是一种数据结构,我们可以用它来定义对象,后者把数据值和行为特性融合在一起,类是现实世界的抽象的实体以编程形式出现.实例是这些对象的具体化.类是用来描述一类事物,类的对象指的 ...

  6. python学习day20 面向对象(二)类成员&成员修饰符

    1.成员 类成员 类变量 绑定方法 类方法 静态方法 属性 实例成员(对象) 实例变量 1.1实例变量 类实例化后的对象内部的变量 1.2类变量 类中的变量,写在类的下一级和方法同一级. 访问方法: ...

  7. Python:笔记(3)——面向对象编程

    Python:笔记(3)——面向对象编程 类和面向对象编程 1.类的创建 说明:和Java不同的是,我们不需要显示的说明类的字段属性,并且可以在后面动态的添加. 2.构造函数 构造函数的功能毋庸置疑, ...

  8. [python 译] 基于面向对象的分析和设计

    [python 译] 基于面向对象的分析和设计 // */ // ]]>   [python 译] 基于面向对象的分析和设计 Table of Contents 1 原文地址 2 引言 2.1 ...

  9. Python之面向对象与类

    本节内容 面向对象的概念 类的封装 类的继承 类的多态 静态方法.类方法 和 属性方法 类的特殊成员方法 子类属性查找顺序 一.面向对象的概念 1. "面向对象(OOP)"是什么? ...

随机推荐

  1. Java处理Excel中的日期格式

    Java处理Excel中的日期格式 2011-12-23 17:34:03|  分类: java |举报 |字号 订阅 下载LOFTER 我的照片书  |   在Excel中的日期格式,其数值为距离1 ...

  2. 巧用Reflections库实现包扫描

    1.需求 需要扫描某个包中的某个接口的实现类的需求 2.maven 依赖引入 <dependency> <groupId>org.reflections</groupId ...

  3. springboot中引入zookeeper,生成 配置类

    1.application.properties zookeeper.address=@mvn.zookeeper.address@ zookeeper.namespace=@mvn.zookeepe ...

  4. 3D重建算法原理

    3D重建算法原理 三维重建(3D Reconstruction)技术一直是计算机图形学和计算机视觉领域的一个热点课题.早期的三维重建技术通常以二维图像作为输入,重建出场景中的三维模型.但是,受限于输入 ...

  5. deeplearning量化

    deeplearning量化 量化配置 通过字典配置量化参数 TENSORRT_OP_TYPES = [ 'mul', 'conv2d', 'pool2d', 'depthwise_conv2d', ...

  6. 导出 Excel 模板自动生成规则,避免用户来回修改

    一句话总结 Excel 导出.导入时,根据注解自动添加单元格验证规则,避免用户因填写错误的枚举字段而反复修改 Excel 需求背景 对于 Java Web 项目,总是不可避免的出现 Excel 导入. ...

  7. 【UG二次开发】装配设计

    与装配相关的术语 tag 部件或对象的标识,还可能是部件原型.实例和事例 piece part 单个部件 occurrence 部件或对象的事例,装配中的是部件事例 object occurrence ...

  8. Java基础面试题(史上最全、持续更新、吐血推荐)

    文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...

  9. bzoj2839 集合计数(容斥+组合)

    集合计数 内存限制:128 MiB 时间限制:1000 ms 标准输入输出     题目描述 一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得 ...

  10. Mysql权限管理以及sql数据备份

    权限管理和备份 用户管理 可视化管理 SQL命令操作 用户表:msql.user 同样就是对表的操作,就是对这张表的增删改查 -- 创建用户 create user kuangshen identfi ...