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

前言:
对于python的很多功能不是通过继承实现的,而是通过鸭子类型实现的,Python中鸭子类型被广泛应用。

正文
在 Python 中创建功能完善的序列类型无需使用继承,只需实现符合序列协议的方法即可!不过,这里说的协议是什么呢?

在面向对象编程中,协议是非正式的接口,只在文档中定义,在代码中不定义。

例如,Python 的序列协议只需要 __len____getitem__ 两个方法。任何类(如 Spam),只要使用标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。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]


FrenchDeck 类能充分利用 Python 的很多功能,因为它实现了序列协议,不过代码中并没有声明这一点。任何有经验的 Python 程序员只要看一眼就知道它是序列,即便它是 object 的子类也无妨。我们说它是序列,因为它的行为像序列,这才是重点。

协议是非正式的,没有强制力,因此如果你知道类的具体使用场景,通常只需要实现一个协议的部分。例如,为了支持迭代,只需实现 __getitem__ 方法,没必要提供 __len__ 方法。

切片原理(例子说明):
python中序列的修改、散列和切片_第1张图片
s[a:b:c]的形式的时候,返回值index 是slice对象,当时s[a]的形式返回值index是number类型。

由此可见上面的代码可以实现序列的大多数功能,但不能实现切片,下面是代码的完善。

from array import array
import reprlib
import math
import numbers
import functools
import operator


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):
        # 方案一、
        # if len(self) != len(other):
        #     return False
        # for a, b in zip(self, other):
        #     if a != b:
        #         return False
        # return True
        
        # 方案二、
        return len(self) == len(other) and all(a == b for a, b in zip(self, 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):
        cls = type(self)
        # print(cls, type(cls))
        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))

    def __getattr__(self, name):
        cls = type(self)
        if len(name) == 1:
            print(cls.shortcut_names)
            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:
            if name in cls.shortcut_names:
                error = 'readonly attribute {attr_name!r}'
            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)

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


if __name__ == "__main__":
    v7 = Vector(range(7))
    print(v7)
    print(v7[-1])
    print(v7[1:4])
    print(v7.x)
    v7.K = 10
    print(v7.K)
    print(v7)

由此可见,上面的__getitem__方法,判断index是切片类型,就直接再次生成新对象,当index是numbers.Integral类型就返回具体的的值,其他情况,参数错误了。

并且代码中还有还有就是__getattr__方法,是为了获取对象熟悉的作用,当然我们也可以使用装饰器@property 来设置只读属性的,但这样太麻烦。

下面来说一下__getattr__方法解释器会在什么情况下执行它:

属性查找失败后,解释器会调用 __getattr__ 方法。简单来说,对 my_obj.x 表达式,Python 会检查 my_obj 实例有没有名为 x 的属性;如果没有,到类(my_obj.__class__)中查找;如果 还没有,顺着继承树继续查找。4 如果依旧找不到,调用 my_obj 所属类中定义的 __getattr__ 方法,传入 self 和属性名称的字符串形式(如 ‘x’),上面的代码已经实现,但这样也有缺点,如果我们不定义__setattr__方法,当我们去给my_obj.x=10 赋值的时候会如何?显然python会创建x属性,就达不到我们要的效果,因此我们在定义__getattr__的同时,一般要定义__setattr__方法,对属性加以限制,如例子中,限制已有属性只能是只读,不能赋值,并且不能创建单个小写属性等。(或许有人会说用__slots__类属性加以限制,而不像这里所做的,实现 __setattr__ 方法,不建议只为了避免创建实例属性而使用 __slots__ 属 性。__slots__ 属性只应该用于节省内存,而且仅当内存严重不足时才应该 这么做,并且这样还会限制其他实例属性的创建)并且,如果想允许修改分量,可以使用 __setitem__ 方法,支持 v[0] = 1.1 这样的赋值,以及 (或者)实现 __setattr__ 方法,支持 v.x = 1.1 这样的赋值。

代码中还有 __hash__ 方法和 __eq__ 方法,可以实现将Vector 实例变为可散列对象,在这个例子中,我们要使 用 ^(异或)运算符依次计算各个分量的散列值,像这样:v[0] ^ v[1] ^ v[2]…。这正 是 functools.reduce 函数的作用。__eq__方法的实现这里提供了俩种方法,他们的优劣也一幕了然,显然第二种方案更加好一些。

你可能感兴趣的:(python,特殊方法)