• python序列的修改、散列和切片


    新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》

  • 相关阅读:
    bzoj1731 [Usaco2005 dec]Layout 排队布局
    loj10087 Intervals
    差分约束小结
    bzoj1112 [POI2008]砖块Klo
    bzoj3524 [POI2014]Couriers
    poj2752 Seek the Name, Seek the Fame
    1027C Minimum Value Rectangle
    bzoj2212 [POI2011]Tree Rotations
    bzoj3747 [POI2015]Kinoman
    628D Magic Numbers
  • 原文地址:https://www.cnblogs.com/lht-record/p/10296548.html
Copyright © 2020-2023  润新知