Sequence Hacking, Hashing, and Slicing

1. Vector Take #1: Vector2d Compatible

The best practice for a sequence constructor is to take the data as an iterable argument in the constructor, like all built-in sequence types do.

Example 10-2. vector_v1.py: derived from vector2d_v1.py

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)  # To allow iteration, we return an iterator over self._components.

    def __repr__(self):
        components = reprlib.repr(
            self._components)  # Use reprlib.repr() to get a limited-length representation of 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))  # We sum the squares fo the components and compute the sqrt of that.

    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)


"""
reprlib.repr function produces safe representation of large or recursive structures by limiting the length of the output
string and marking the cut with '...'.

repr() role: debugging
"""


# Example 10-1. Tests of Vector.__init__ and Vector.__repr__
"""
>>> Vector([3.1, 4.2])
Vector([3.1, 4.2])
>>> Vector((3,4,5))
Vector([3.0, 4.0, 5.0])
>>> Vector(range(10))
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
"""

 

2. Protocols and Duck Typing

You don't need to inherit from any special class to create a fully functional sequence type in Python; you just need to implement the methods that fulfill the sequence protocol.

In the context of object-oriented programming, a protocol is an informal interface, defined only in documentation and not in code. For example, the sequence protocol in Python entails just the __len__ and __getitem__ methods. Any class Spam that implements those methods with the standard signature and semantics can be used anywhere a sequence is expected. Whether Spam is a subclass of this or that is irrelevant; all that matters is that it provides the necessary methods.

Example 10-3. Code from Example 1-1, reproduced here for convenience

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]

The FrenchDeck implements the sequence protocol, even if that is not declared anywhere in the code. Any experienced Python coder will look at it and understand that it is a sequence, even if it subclasses object. We say it is a sequence because it behaves like one, and that is what matters.

This became known as duck typing.

Because protocols are informal and unenforced, you can often get away with implementing just part of a protocol, if you know the specific context where a class will be used. For example, to support iteration, only __getitem__ is required; there is no need to provide __len__ .

 

 

 

 

 

end...

 

Guess you like

Origin www.cnblogs.com/neozheng/p/12310116.html