资讯专栏INFORMATION COLUMN

流畅的python读书笔记-第十章-序列的修改、散列和切片

cpupro / 1600人阅读

摘要:例如,的序列协议只需要和两个方法。任何类如,只要使用标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。方法开放了内置序列实现的棘手逻辑,用于优雅地处理缺失索引和负数索引,以及长度超过目标序列的切片。

序列的修改、散列和切片 接着造Vector2d类
要达到的要求

为了编写Vector(3, 4) 和 Vector(3, 4, 5) 这样的代码,我们可以让 init 法接受任意个参数(通过 *args)

如果 Vector 实例的分量超过 6 个,repr() 生成的字符串就会使用 ... 省略一部
分,使用 reprlib 模块可以生成长度有限的表示形式

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)
        
print(Vector([3.1, 4.2]))
print(Vector((3, 4, 5)))
print(Vector(range(10)))

❸ 使用 reprlib.repr() 函数获取 self._components 的有限长度表示形式(如
array("d", [0.0, 1.0, 2.0, 3.0, 4.0, ...]))。
❹ 把字符串插入 Vector 的构造方法调用之前,去掉前面的 array("d" 和后面的 )。

协议和鸭子类型

在面向对象编程中,

协议是非正式的接口,只在文档中定义,在代码中不定义。

例如,Python 的序列协议只需要 lengetitem 两个方法。

任何类(如 Spam),只要使用标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。

第一章的代码再次给出
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._cards)

    def __getitem__(self, position):
        return self._cards[position]
Vector类第2版:可切片的序列
from array import array
import reprlib
import math


class Vector(object):
    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)

    def __len__(self):
        return len(self._components)

    def __getitem__(self, index):
        return self._components[index]


v1 = Vector([3, 4, 5])
print(len(v1))

print(v1[0], v1[-1])

v7 = Vector(range(7))
print(v7[1:4])

现在连切片都支持了,不过尚不完美。如果 Vector 实例的切片也是 Vector
实例,而不是数组,那就更好了。

把 Vector 实例的切片也变成 Vector 实例,我们不能简单地委托给数组切片。我们
要分析传给 getitem 方法的参数,做适当的处理。

切片原理
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, 9])

print(s[1:4:2, 7:9])

❸ 1:4 表示法变成了 slice(1, 4, None)。
❹ slice(1, 4, 2) 的意思是从 1 开始,到 4 结束,步幅为 2。
❺ 神奇的事发生了:如果 [] 中有逗号,那么 getitem 收到的是元组。
❻ 元组中甚至可以有多个切片对象。

查看 slice 类的属性
print(slice)
print(dir(slice))
["__class__", "__delattr__", "__dir__", "__doc__", "__eq__", "__format__", "__ge__", "__getattribute__", "__gt__", "__hash__", "__init__", "__init_subclass__", "__le__", "__lt__", "__ne__", "__new__", "__reduce__", "__reduce_ex__", "__repr__", "__setattr__", "__sizeof__", "__str__", "__subclasshook__", "indices", "start", "step", "stop"]

通过审查 slice,发现它有 start、stop 和 step 数据属性,以及 indices 方法。

indices 方法开放了内置序列实现的棘手逻辑,用于优雅地处理缺失索引和
负数索引,以及长度超过目标序列的切片。
这个方法会“整顿”元组,把 start、stop 和
stride 都变成非负数,而且都落在指定长度序列的边界内。

一句话 把负数索引和超出长度的索引调整成 正常的索引



aa = "ABCDE"

print(slice(None, 10, 2).indices(5))
print(slice(-3, None, None).indices(5))

print("="*40)
print(slice(None, 10, 2).indices(len(aa)))
print(slice(-3, None, None).indices(len(aa)))


print(aa[-3:])
能处理切片的__getitem__方法
from array import array
import reprlib
import math
import numbers

class Vector(object):
    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 __len__(self):
        return len(self._components)

    ##[1:4] 返回一个向量对象
    def __getitem__(self, index):
        cls = type(self)
        if isinstance(index, slice):
            return cls(self._components[index])
        elif isinstance(index, numbers.Integral):
            return self._components[index]
        else:
            msg = "{cls.__name__} indices must be integers"
            raise TypeError(msg.format(cls=cls))


v7 = Vector(range(7))
print(v7[-1])

print(v7[1:4])

print(v7[-1:])

Vector类第3版:动态存取属性

我们可以在 Vector 中编写四个特性,但这样太麻烦。
特殊方法 getattr 提供了更好的方式。

属性查找失败后,解释器会调用 getattr 方法。
简单来说,对 my_obj.x 表达式,

Python 会检查 my_obj 实例有没有名为 x 的属性;
如果没有,到类(my_obj.__class__)中查找;
如果还没有,顺着继承树继续查找。
如果依旧找不到,调用 my_obj 所属类中定义的 getattr 方法,传入 self 和属性名称的字符串形式(如 "x")。

from array import array
import reprlib
import math
import numbers


class Vector(object):
    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)

    shortcut_names = "xyzt"

    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))

    def __setattr__(self, name, value):
        cls = type(self)
        if len(name) == 1:

            # 如果 name 是 xyzt 中的一个,设置特殊的错误消息。
            if name in cls.shortcut_names:
                error = "readonly attribute {attr_name!r}"

            # 如果 name 是小写字母,为所有小写字母设置一个错误消息。
            elif name.islower():
                error = "can"t set attributes "a" to "z" in {cls_name!r}"

            #否则,把错误消息设为空字符串。
            else:
                error = ""

            #如果有错误消息,抛出AttributeError。
            if error:
                msg = error.format(cls_name=cls.__name__, attr_name=name)
            raise AttributeError(msg)

        # 默认情况:在超类上调用 __setattr__ 方法,提供标准行为。
        super().__setattr__(name, value)


v = Vector(range(5))
print(v)

# 这个设置法 没用
v.p = 10
print(v.x)

print(v)

super() 函数用于动态访问超类的方法,对 Python 这样支持多重继承的动态
语言来说,必须能这么做。程序员经常使用这个函数把子类方法的某些任务委托给超
类中适当的方法

注意,我们没有禁止为全部属性赋值,只是禁止为单个小写字母属性赋值,以防与只读属
性 x、y、z 和 t 混淆。

Vector类第4版:散列和快速等值测试

functools.reduce() 可以替换成 sum()

这里的原理

它的关键思想是,把一系列值归约成单个值。

reduce() 函数的第一个参数是接受两个参数的函数,第二个参数是一个可迭代的对象。 假如有个接受两个参数的 fn 函数和一个 lst
列表。

调用 reduce(fn, lst) 时,fn 会应用到第一对元素上,即 fn(lst[0],lst[1]),生成第一个结果r1。然后,fn 会应用到 r1 和下一个元素上,即 fn(r1,lst[2]),生成第二个结果 r2。

接着,调用 fn(r2, lst[3]),生成 r3……直到最后一个元素,返回最后得到的结果 rN。

如:

>>> import functools
>>> functools.reduce(lambda a,b: a*b, range(1, 6))
120
reduce接着用
import functools

aa = functools.reduce(lambda a, b: a ^ b, range(1,6))
print(aa)

# operator--操作符函数
# https://blog.csdn.net/shengmingqijiquan/article/details/53005129
import operator
bb = functools.reduce(operator.xor, range(6))
print(bb)
使用我喜欢的方式编写 Vector.__hash__ 方法,我们要导入 functools 和

operator 模块。(任性的作者)

import functools  # ➊
import operator  # ➋


class Vector:
    typecode = "d"

    # 排版需要,省略了很多行...
    def __eq__(self, other):  # ➌
        return tuple(self) == tuple(other)

    def __hash__(self):
        hashes = (hash(x) for x in self._components)  # ➍
        return functools.reduce(operator.xor, hashes, 0)  # ➎

    # 排版需要,省略了很多行...

    

❹ 创建一个生成器表达式,惰性计算各个分量的散列值。
❺ 把 hashes 提供给 reduce 函数,使用 xor 函数计算聚合的散列值;第三个参数,0 是
初始值(参见下面的警告框)。

eq 方法更有效率
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  # ➍

❷ zip 函数生成一个由元组构成的生成器,元组中的元素来自参数传入的各个可迭代对
象。如果不熟悉 zip 函数,请阅读“出色的 zip 函数”附注栏。前面比较长度的测试是有
必要的,因为一旦有一个输入耗尽,zip 函数会立即停止生成值,而且不发出警告。

使用 zip 和 all 函数实现 Vector.__eq__ 方法

def __eq__(self, other):
 return len(self) == len(other) and all(a == b for a, b in zip(self, other))
zip 内置函数的使用示例
>>> zip(range(3), "ABC") # ➊

>>> list(zip(range(3), "ABC")) # ➋
[(0, "A"), (1, "B"), (2, "C")]
>>> list(zip(range(3), "ABC", [0.0, 1.1, 2.2, 3.3])) # ➌
[(0, "A", 0.0), (1, "B", 1.1), (2, "C", 2.2)]
>>> from itertools import zip_longest # ➍
>>> list(zip_longest(range(3), "ABC", [0.0, 1.1, 2.2, 3.3], fillvalue=-1))
[(0, "A", 0.0), (1, "B", 1.1), (2, "C", 2.2), (-1, -1, 3.3)]

❸ zip 有个奇怪的特性:当一个可迭代对象耗尽后,它不发出警告就停止。
❹ itertools.zip_longest 函数的行为有所不同:使用可选的 fillvalue(默认
值为 None)填充缺失的值,因此可以继续产出,直到最长的可迭代对象耗尽。

Vector类第5版:格式化

__format__提供格式化方法,详情和具体代码 page 348

小总结

repr 如果信息展示过长. 用reprlib 模块可以缩短

2.切片原理
slice(None, 10, 2).indices(5) 负责转换成可用的索引
len_getitem 实现切片的重要方法

属性查找失败后,解释器会调用 getattr 方法。利用这个特性,可以搞一些事情

4.reduce 的使用方法

5.zip函数 简单理解矩阵对应

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41752.html

相关文章

  • Python学习之路29-序列修改列和切片

    摘要:具体方法和上一篇一样,也是用各个分量的哈希值进行异或运算,由于的分量可能很多,这里我们使用函数来归约异或值。每个分量被映射成了它们的哈希值,这些哈希值再归约成一个值这里的传入了第三个参数,并且建议最好传入第三个参数。 《流畅的Python》笔记。本篇是面向对象惯用方法的第三篇。本篇将以上一篇中的Vector2d为基础,定义多维向量Vector。 1. 前言 自定义Vector类的行为...

    马忠志 评论0 收藏0
  • Python序列修改列和切片

    摘要:一基本的序列协议首先,需要就维向量和二维向量的显示模的计算等差异重新调整。假设维向量最多能处理维向量,访问向量分量的代码实现如下若传入的参数在备选分量中可进行后续处理判断分量的位置索引是否超出实例的边界不支持非法的分量访问,抛出。 导语:本文章记录了本人在学习Python基础之面向对象篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。 本文重点: 1、了解协议的...

    jubincn 评论0 收藏0
  • 流畅python读书笔记-二章Python 数据结构

    摘要:把具名元组以的形式返回,我们可以利用它来把元组里的信息友好地呈现出来。数组支持所有跟可变序列有关的操作,包括和。双向队列和其他形式的队列类双向队列是一个线程安全可以快速从两端添加或者删除元素的数据类型。 列表表达式 >>> symbols = $¢£¥€¤ >>> codes = [ord(symbol) for symbol in symbols] >>> codes [36, 16...

    syoya 评论0 收藏0
  • 流畅python读书笔记-十章-继承优缺点

    摘要:继承的优缺点推出继承的初衷是让新手顺利使用只有专家才能设计出来的框架。多重继承的真实应用多重继承能发挥积极作用。即便是单继承,这个原则也能提升灵活性,因为子类化是一种紧耦合,而且较高的继承树容易倒。 继承的优缺点 推出继承的初衷是让新手顺利使用只有专家才能设计出来的框架。——Alan Kay 子类化内置类型很麻烦 (如 list 或 dict)) ,别搞这种 直接子类化内置类型(如 ...

    morgan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<