《流暢的Python》第二版 第12章

limalove發表於2024-07-21

from array import array
import math
import functools
import itertools
import operator
import reprlib


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 f'Vector({components})'


    def __str__(self):
        return str(tuple(self))



    def __bytes__(self):
        return (bytes([ord(self.typecode)]) + bytes(self._components))


    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)


    def __abs__(self):
        return math.hypot(*self)


    def __bool__(self):
        return bool(abs(self))


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


    def __getitem__(self, key):
        if isinstance(key, slice):
            cls = type(self)
            return cls(self._components[key])

        index = operator.index(key)
        return self._components[index]


    __match_args__ = ('x','y', 'z', 't')
    def __getattr__(self, name):
        cls = type(self)
        try:
            pos = cls.__match_args__.index(name)
        except ValueError:
            pos = -1

        if 0 <= pos < len(self._components):
            return self._components[pos]

        msg = f'{cls.__name__!r} object has no attribute {name!r}'
        raise AttributeError(msg)


    def __setattr__(self, name, value):
        cls = type(self)
        if len(name) == 1:
            if name in cls.__match_args__:
                error = 'readonly attribute {attr_name!r}'
            elif name.islower():
                error = "can't set attribute '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 angle(self, n):
        r = math.hypot(*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, format_spec = ''):
        if format_spec.endswith('p'):
            format_spec = format_spec[:-1]
            coords = itertools.chain([abs(self)], self.angles())
            outer_fmt = '<{}>'
        else:
            coords = self
            outer_fmt = '({})'

        components = (format(c, format_spec) for c in coords)
        return outer_fmt.format(", ".join(components))


    @classmethod
    def frombytes(cls, octets):
        typecode = chr(octets[0])
        memv = memoryview(octets[1:]).cast(typecode)
        return cls(memv)

相關文章