新Vector类

接原vector类定义的新Vector类,原向量类是二维,现定义多维向量类:

from array import array
import reprlib
import math class Vector:
typecode = 'd'
shortcut_names = 'xyzt' 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)

协议和鸭子类型

协议:

1.协议是非正式的接口,没有强制力;协议只在文档中定义,在代码中不定义。

2.python有很多协议,如可调用对象协议,哈希协议,序列类协议,容器类协议等等等等。

3.如果知道类的具体使用场景,通常只需要实现协议的一个部分,例如为了支持迭代,只需要实现__getitem__方法,而不必要提供__len__方法。

4.需要成为相对应的鸭子类型,那就实现相关的协议,即相关的__method__。例如实现序列协议(__len__和__getitem__),这个类就表现得像序列。

鸭子类型:

1.(DuckTyping)鸭子类型在动态语言中用的较多,是动态类型语言设计的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口决定,而是由当前方法和属性的集合决定。通俗来说就是并不关心对象是什么类型,只关心行为。

2.当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子;即它的行为是鸭子的行为,那么可以认为它是鸭子。

3.在Python文档中,如果看到“文件类对象“(表现得像文件的对象),通常说的就是协议,这个对象就是鸭子类型。这是一种简短的说法,意思是:“行为基本与文件一致,实现了部分文件接口,满足上下文相关需求的东西。”

FrenchDeck实现了序列协议,即实现了__len__和__getitem__,那么它就可以看作是序列:

import collections

Card = collections.namedtuple('Card', ['rank','suit'])

class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split() def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks] def __len__(self):
return len(self._cars) def __getitem__(self, position):
return self._cards[position]

切片原理

class Myseq:
def __getitem__(self, index):
return index #返回索引 s = Myseq()
print(s[1])
print(s[1:4])
print(s[1:4:2])
print(s[1:4:2, 7])
print(s[1:4:2, 7:9]) #结果
1 #单个索引
slice(1, 4, None) #返回slice对象,
slice(1, 4, 2)
(slice(1, 4, 2), 7)
(slice(1, 4, 2), slice(7, 9, None)) #返回slice对象元组

审查slice对象:

print(slice)
print(dir(slice)) <class 'slice'>
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'indices', 'start', 'step', 'stop']
slice是内置类型,它有'indices'方法, 'start', 'step', 'stop'数据属性
indices(...)
S.indices(len) -> (start, stop, stride) Assuming a sequence of length len, calculate the start and stop
indices, and the stride length of the extended slice described by
S. Out of bounds indices are clipped in a manner consistent with the
handling of normal slices.

给定长度为len的序列,计算S表示的扩展切片的起始和结尾索引,以及步幅。超出边界的索引会被截掉,这与常规切片处理方式一样。

假如给定一个序列长度为5:

print(slice(None, 10, 2).indices(5))
print(slice(-3, None, None).indices(5)) (0, 5, 2) #切片[None, 10, 2]将转换为[0, 5, 2],因为序列长度最多为5
(2, 5, 1) #切片[-3, None, None]将转换为[2, 5, 1],-3位置即序列的2位置,长度为5,跨度默认为1

让Vector类能正常处理切片

为了让Vector类能正确处理切片,更改__getitem__方法。

    def __getitem__(self, item):
cls = type(self)
if isinstance(item, slice):
return cls(self._components[item]) #参数是slice对象,返回
elif isinstance(item, numbers.Integral):
return self._components[item] #参数单个元素,返回单个元素
else:
msg = '{cls.__name__} indices must be integers'
raise TypeError(msg.format(cls=cls)) #抛出异常

动态存取属性

属性查找失败时解释器会调用__getattr__方法。即:对于obj.x表达式,python会检查obj实例有没有名为x的属性;如果没有,到类(obj.class)中查找,如果还没有,顺着继承树继续查找,如果依旧找不到,调用obj所属类中定义的__getattr__方法,传入self和属性名称的字符串形式(如‘x’)

    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}'
raise AttributeError(msg.format(cls, name)) if __name__ == '__main__':
V7 = Vector(range(7))
print(V7)
print(V7.y)
V7.y = 10.0
print(V7.y)
print(V7)

测试结果:

(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)
1.0
10.0 #第二项为10
(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0) #第二项为1,矛盾

造成这种结果的原因是__getattr__的运作方式:仅当对象没有指定名称的属性时,python才会调用那个方法,这是一种后备机制。可是运行过V7.y = 10之后V7就有y属性了,因此使用V7.y属性值时不会调用__getattr__方法,解释器直接返回绑定值,即10。另一方面,__getattr__方法的实现没有考虑到self._components之外的实例属性,而是从这个属性中获取shortcut_names中所列的虚拟属性。

修改在Vector类中设置属性的逻辑以应对前后矛盾:

    def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
if name in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}' #xyzt只读
elif name.islower():
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) #委托给超类的方法

多数时候,如果实现了__getattr__方法,那么也要实现__setattr__方法,以防出现示例那样对象行为不一致。如果想允许修改分量,可以使用__setitem__方法或实现__setattr__方法。

为Vector类实现散列协议

    def __hash__(self):         #法一,for循环迭代
hashes = (hash(x) for x in self._components)
n = 0
for i in hashes:
n ^= i
return n def __hash__(self): #法二lambda表达式
hashes = (hash(x) for x in self._components)
return functools.reduce(lambda a, b: a ^ b, hashes, 0) def __hash__(self): #库函数,推荐
hashes = (hash(x) for x in self._components)
return functools.reduce(operator.xor, hashes, 0)

reduce()函数第三个参数是初始值,防止序列为空的异常。一般 +,|,^来说用0;而*和&用1。

或者使用隐射规约计算:

    def __hash__(self):
hashes = map(hash, self._components)
return functools.reduce(operator.xor, hashes, 0)

相等性判断优化

对于有上千万个分量的Vector实例来说,相等性的比较效率十分低下,因为要复制两个操作数创建两个元组。可修改为:

    def __eq__(self, other):
if len(self) != len(other):
return False
for a, b in zip(self, other):
if a != b:
return False
return True

或者:

    def __eq__(self, other):
return len(self) == len(other) and all(a == b for a, b in zip(self, other))

zip函数生成一个由元组构成的生成器,元组中各个元素来自参数传入的各个可迭代对象。它返回的元组可以拆包成变量,分别对应于各个并行输入中的一个元素。示例:

from itertools import zip_longest
list1 = list(zip(range(3), 'ABC'))
print(list1)
list2 = list(zip(range(3), 'ABC', [0.1, 1.2, 2.3, 3.4]))
print(list2)
list3 = list(zip_longest(range(3), 'ABC', [0.1, 1.2, 2.3, 3.4], fillvalue=-1))
print(list3) #结果
[(0, 'A'), (1, 'B'), (2, 'C')]
[(0, 'A', 0.1), (1, 'B', 1.2), (2, 'C', 2.3)]
[(0, 'A', 0.1), (1, 'B', 1.2), (2, 'C', 2.3), (-1, -1, 3.4)]

1.当一个可迭代对象耗尽后,它不发出警告就停止

2.zip_longest有所不同,fillvalue作为填充(默认是None),直到最长的可迭代对象耗尽

以上来自《流畅的python》

python序列的修改、散列和切片的更多相关文章

  1. Fluent_Python_Part4面向对象,10-seq-hacking,序列的修改、散列和切片

    第四部分第10章,序列的修改.散列和切片 中文电子书P423 这一章接第1章.第9章,以第9章定义的Vector2d类为基础,定义表示多为向量的Vector类.这个类的行为与Python中标准的不可变 ...

  2. 7、python基本数据类型之散列类型

    前言:python的基本数据类型可以分为三类:数值类型.序列类型.散列类型,本文主要介绍散列类型. 一.散列类型 内部元素无序,不能通过下标取值 1)字典(dict):用 {} 花括号表示,每一个元素 ...

  3. 流畅python学习笔记:第十章:序列的修改,散列和切片

    前面在介绍了类的很多内置方法,比如__add__,__eq__,这里继续介绍类的两个内置方法,这2个内置方法可以将一个类实例变成一个序列的形式.代码如下 class vector(object):   ...

  4. Python 序列的修改、散列和切片

    Vector类:用户定义的序列类型 我们将使用组合模式实现 Vector 类,而不使用继承.向量的分量存储在浮点数数组中,而且还将实现不可变扁平序列所需的方法. p.p1 { margin: 0.0p ...

  5. 流畅的python第十章序列的修改,散列和切片学习记录

    只要实现了__len__和__getitem__两个方法即可将该类视为序列. 切片原理 动态存取属性 如果实现了__getattr__方法,也要定义__setattr__方法,以防对象行为不一致

  6. 『无为则无心』Python基础 — 16、Python序列之字符串的下标和切片

    目录 1.序列的概念 2.字符串的下标说明 3.字符串的切片说明 1.序列的概念 序列sequence是Python中最基本的数据结构.指的是一块可存放多个值的连续内存空间,这些值按一定顺序排列,可通 ...

  7. 【数据结构与算法Python版学习笔记】查找与排序——散列、散列函数、区块链

    散列 Hasing 前言 如果数据项之间是按照大小排好序的话,就可以利用二分查找来降低算法复杂度. 现在我们进一步来构造一个新的数据结构, 能使得查找算法的复杂度降到O(1), 这种概念称为" ...

  8. Python序列的切片操作与技巧

    切片操作 对于具有序列结构的数据来说,切片操作的方法是:consequence[start_index: end_index: step]. start_index: 表示是第一个元素对象,正索引位置 ...

  9. Python:说说字典和散列表,散列冲突的解决原理

    散列表 Python 用散列表来实现 dict.散列表其实是一个稀疏数组(总是有空白元素的数组称为稀疏数组).在一般书中,散列表里的单元通常叫做表元(bucket).在 dict 的散列表当中,每个键 ...

随机推荐

  1. mysql使用 分区表使用,常用sql

    mysql使用 分区表使用,常用sql 前言 本文的原文连接是: https://blog.csdn.net/freewebsys/article/details/84839478未经博主允许不得转载 ...

  2. Set重写hashCode和equals方法实现引用对象去重

    运作原理: 首先判断hashCode是否相同,如果不同,直接判定为两个不同的对象.如果hashCode相同,再去比较equals是否一样,如果一样,则为同一个对象.如果不一样,则是两个不同对象. 那么 ...

  3. 算法入门 - 基于动态数组的栈和队列(Java版本)

    之前我们学习了动态数组的实现,接下来我们用它来实现两种数据结构--栈和队列.首先,我们先来看一下栈. 什么是栈? 栈是计算机的一种数据结构,它可以临时存储数据.那么它跟数组有何区别呢? 我们知道,在数 ...

  4. exportfs命令 – 管理NFS服务器共享的文件系统

    exportfs命令需要参考配置文件"/etc/exportfs".也可以直接在命令行中指定要共享的NFS文件系统. 语法格式: export [参数] [目录] 常用参数: -a ...

  5. Dos 获取网络的命令

    Netsh mbn 命令 https://docs.microsoft.com/zh-cn/windows-server/networking/technologies/netsh/netsh-mbn ...

  6. 【spring 注解驱动开发】spring自动装配

    尚学堂spring 注解驱动开发学习笔记之 - 自动装配 自动装配 1.自动装配-@Autowired&@Qualifier&@Primary 2.自动装配-@Resource& ...

  7. 判断宽度的js

    <script language="javascript" type="text/javascript">/*将获取的值存到变量里*/width_s ...

  8. 前端使用a标签启动本地.exe程序

    目录 1,需求 2,效果图 3,实现原理 4,代码 5,注意事项 1,需求 最近有一个需求,在web页面上有一个按钮,点击按钮,调起本地的.exe程序客户端,我在网上找了很多,感觉都不完整,所以自己总 ...

  9. <题解>「LibreOJ NOIP Round #1」序列划分

    solutions 题面loj#542 对我来说,这或许已经超出了我的能力,我,只能看题解 不知道我写完这一篇题解之后,会不会对我的构造题有一点点的帮助 让我在这类题的解决上能过有一些提升 直接说明白 ...

  10. HCNP Routing&Switching之动态路由协议IS-IS基础

    前文我们了解了OSPF的特殊区域相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15236330.html:今天我们来聊一聊另一动态路由协议IS-IS相 ...