zoukankan      html  css  js  c++  java
  • 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__ .

    3. Vector Take #2: A Sliceable Sequence

    class Vector:
        # many lines omitted
        # ...
    
        def __len__(self):
            return len(self._components)
    
        def __getitem__(self, index):
            return self._components[index]
    
    
    # 运行结果: __len__ , __getitem__ version 1
    """
    >>> v1 = Vector([3,4,5])
    >>> 
    >>> len(v1)
    3
    >>> v1[0],v1[-1]
    (3.0, 5.0)
    >>> v7 = Vector(range(7))
    >>> v7[1:4]
    array('d', [1.0, 2.0, 3.0])
    """
    
    # 补充:
    """
    As you can see above, even slicing is supported -- but not very well. It would be better if a slice of a Vector was also
    a Vector instance and not a array.
    
    Consider the built-in sequence types: every one of them, when sliced, produces a new instance of its own type, and not 
    of some other type.
    
    To make Vector produce slices as Vector instances, we can't just delegate the slicing to array. We need to analyze the 
    arguments we get in __getitem__ and do the right thing.
    """

    4. How Slicing Works

    Example 10-4. Checking out the behavior of __getitem__ and slices

    >>> class MySeq:
    ...     def __getitem__(self, index):
    ...         return index    # For the demonstration, __getitem__ merely returns whatever is passed to it.
    ...
    >>> s = MySeq()
    >>> s[1]
    1
    >>> s[1:4]
    slice(1, 4, None)       # The notation 1:4 becomes slice(1,4,None)
    >>> s[1:4:2]            # slice(1,4,2) means start at 1, stop at 4, step by 2.
    slice(1, 4, 2)
    >>> s[1:4:2, 9]
    (slice(1, 4, 2), 9)     # The presence of commas inside the [] means __getitem__ receive a tuple.
    >>> s[1:4:2, 7:9]
    (slice(1, 4, 2), slice(7, 9, None))     # The tuple may even hold several slice objects.
    
    
    # Example 10-5. Inspecting the attributes of the slice class.
    """
    >>> slice
    <class 'slice'>
    >>> 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 is a built-in type
    # Inspecting a slice we find the data attributes start, stop, and step, and an indices method.
    
    
    # Here is what help(slice.indices) reveals:
    """
    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.
    """
    # In other words, indices exposes the tricky login that's implemented in the built-in sequence to gracefully handle
    # missing or negative indices and slices that are no longer than the target sequence. This method produces "normalized"
    # tuples of nonnegative start, stop, and stride integers adjusted to fit within the bounds of a sequence of the given length.
    
    
    # Here are a couple of examples, considering a sequence of len == 5, e.g., 'ABCDE':
    >>> slice(None, 10, 2).indices(5)
    (0, 5, 2)
    >>> slice(-3, None, None).indices(5)
    (2, 5, 1)
    # 'ABCDE'[:10:2] is the same as 'ABCDE'[0:5:2]
    # 'ABCDE'[-3:] is the same as 'ABCDE'[2:5:1]

    5. A Slice-Aware __getitem__

    Example 10-6 lists the two methods needed to make Vector behave as a sequnce. __len__ and __getitem__ (the latter now implemented to handle slicing correctly).

    Example 10-6. Part of vector_v2.py: __len__ and __getitem__ methods added to Vector class from vector_v1.py (see Example 10-2)

        def __len__(self):
            return len(self._components)
    
        def __getitem__(self, index):
            cls = type(self)
            if isinstance(index, slice):       # If the index argument is a slice ...
                return cls(self._components[index])     # ...invoke the class to build another Vector instance from a slice of the _components array.
            elif isinstance(index, numbers.Integral):   # If the index is an int or some other kind of integer ...
                return self._components[index]
            else:
                msg = '{cls.__name__} indices must be integers'
                raise TypeError(msg.format(cls=cls))
    
    
    # Example 10-7. Tests of enhanced Vector. getitem from Example 10-6.
    """
    >>> v7 = Vector(range(7))
    >>> 
    >>> v7[-1]
    6.0
    >>> v7[1:4]
    Vector([1.0, 2.0, 3.0])     # A slice index creates a new Vector.
    >>> v7[-1:]
    Vector([6.0])       # A slice of len == 1 also creates a Vector.
    >>> v7[1,2]         # Vector does not support multidimensional indexing, so a tuple of indices or slices raises an error.
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/Users/zhenxink/neo/learning/fluent-py/ch10-sequence_hacking-hashing-slicing/ex10_2_vector_v1.py", line 55, in __getitem__
        raise TypeError(msg.format(cls=cls))
    TypeError: Vector indices must be integers
    >>> 
    """

    6. Vector Take #3: Dynamic Attribute Access

    Access the first few components with shortcut letters such as x,y,z instead of v[0], v[1] and v[2].

    Example 10-8. Part of vector_v3.py: __getattr__ method added to Vector class from vector_v2.py

        shortcut_names = 'xyzt'
    
        def __getattr__(self, name):
            cls = type(self)
            if len(name) == 1:      # If the name is one character, it may be one of the short_names.
                pos = self.shortcut_names.find(name)
                if 0 <= pos < len(self._components):    # If the position is within range, return the array element.
                    return self._components[pos]
            msg = '{.__name__!r} object has not attribute {!r}'
            raise AttributeError(msg.format(cls, name))
    
    
    # 运行结果: __getattr__
    """
    >>> v = Vector(range(10))
    >>> 
    >>> v.x
    0.0
    >>> v.y, v.z, v.t
    (1.0, 2.0, 3.0)
    """
    # __getattr__ 执行顺序:
    """
    The __getattr__ method is invoked by the interpreter when attribute lookup fails. In simple terms, given the expression
    my_obj.x, Python checks if the my_obj instance has an attribute named x; if not, the search goes to the class 
    (my_obj.__class__), and then up the inheritance graph. If the x attribute is not found, then the __getattr__ method 
    defined in the class of my_obj is called with self and the name of attribute as a string (e.g., 'x')
    """

    Example 10-9. Inappropriate behavior: assigning to v.x raises no error, but introduces an inconsistency.

    >>> v = Vector(range(4))
    >>> v
    Vector([0.0, 1.0, 2.0, 3.0])
    >>> v.x     # Access element v[0] as v.x
    0.0
    >>> v.x = 10    # Assign new value to v.x. This should raise an exception.
    >>> v.x         # Reading v.x show the new value, 10.
    10
    >>> v
    Vector([0.0, 1.0, 2.0, 3.0])        # However, the vector components did not change.
    >>>
    
    
    # Explanation:
    """
    The inconsistency in Example 10-9 was introduced because of the way __getattr__ works: Python only calls that method as
    a fall back, when the object does not have the named attribute. However, after we assign v.x = 10, the v object now has
    an x attribute, so __getattr__ will no longer be called to retrieve v.x: the interpreter will just return the value 10
    that is bound to v.x .
    """

    We need to customize the logic for setting attributes in our Vector class in order to avoid this inconsistency. 

    Example 10-10. Part of vector_v3.py: __setattr__ method in Vector class.

        def __setattr__(self, name, value):
            cls = type(self)
            if len(name) == 1:
                if name in cls.shortcut_names:      # If name is one of xyxt, set specific error message.
                    error = 'readonly attribute {attr_name!r}'
                elif name.islower():       # If name is lowercase, set error message about all single-letter names.
                    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)    # Default case: call __setattr__ superclass for standard behavior.  
            # super() function is used to delegate some task from a method in a subclass to a suitable method in a superclass.
            
            # We are not disallowing setting all attributes, only single-letter, lowercase ones.
        

    Very often when you implement __getattr__ you need to code __setattr__ as well, to avoid inconsistent behavior in your objects.

    7. Vector Take #4: Hashing and a Faster ==

    Once more we get to implement a __hash__ method. Together with the existing __eq__, this will make Vector instances hashable.

    Reducing functions -- reduce, sum, any, all --  produce a single aggregate result from a sequence or from any finite iterable object.

    Example 10-11. Three ways of calculating the accumulated xor of integers from 0 to 5.

    >>> n = 0
    >>> for i in range(1,6):
    ...     n ^= i
    ...
    >>> n
    1
    >>> import functools
    >>> functools.reduce(lambda a, b: a ^ b, range(6))
    1
    >>> import operator
    >>> functools.reduce(operator.xor, range(6))    # functools.reduce replacing custom lambda with operator.xor
    1
    
    # This example shows the idea of computing the aggregate xor by doing it in three ways: with a for loop and two reduce calls.
    
    # operator provides the functionality of all Python infix operators in function form, lessening the need for lambda.

    Example 10-12. Part of vector_v4.py: two imports and __hash__ method added to Vector class from vector_v3.py

    from array import array
    import reprlib
    import math
    import functools
    import operator
    
    
    class Vector:
        typecode = 'd'
        
        def __eq__(self, other):    # __eq__ and __hash__ need to work together.
            return tuple(self) == tuple(other)
    
        def __hash__(self):
            hashes = (hash(x) for x in self._components)    # Create a generator expression to lazily compute the hash of each component.
            return functools.reduce(operator.xor, hashes, 0)    # Feed hashes to reduce with the xor function to compute the aggregate hash value; the third argument, 0, is the initializer.
            # For +, |, ^ the initializer should be 0, but for *,& it should be 1.

    Map-reduce: apply function to each item to generate a new series(map), then compute aggregate (reduce).

    The mapping step produces one hash for each component, and the reduce step aggregates all hashes with the xor operator. Using map instead of a genexp makes the mapping step even more visible:

        def __hash__(self):
            hashes = map(hash, self._components)
            return functools.reduce(operator.xor, hashes)
    
    
    """
    The solution with map would be less efficient in Python 2, where the map function builds a new list with the results.
    But in Python 3, map is lazy: it creates a generator that yields the results on demand, thus saving memory -- just like
    the generator expression we used in the __hash__ method of Example 10-8.
    """

    While we are on the topic of reducing functions , we can replace our quick implementation of __eq__ with another one that will be cheaper in terms of processing and memory, at least for large vectors. As introduced in Example 9-2, we have this very concise implementation of __eq__ :

        def __eq__(self, other): 
            return tuple(self) == tuple(other)

    This works for Vector2d and for Vector -- it even considers Vector([1,2]) equal to (1,2), which may be a problem, but we will overlook that for now. But for Vector instances that may have thousands of components, it's very inefficient. It builds two tuples copying the entire contents of the operands just to use the __eq__ of the tuple type. For Vector2d (with only two components), it's a good shortcut, but not for the large multidimensional vectors. A better way of comparing one Vector to another Vector or iterable would be Example 10-13.

    Example 10-13. Vector.eq using zip in a for loop for more efficient comparison.

        def __eq__(self, other):
            if len(self) != len(other):
                return False
            for a, b in zip(self, other):   # zip produces a generator of tuples made from the items in each iterable arguemnt. The len comparison above is needed because zip stops producing values without warning as soon as one of the inputs is exhausted.
                if a != b:      # As soon as two components are different, exit returning False.
                    return False
            return True

    Example 10-13 is efficient, but the all function can produce the same aggregate computation of the for loop in one line: if all comparisons between corresponding components in the operands are True, the result is True. As soon as one comparison is False, all returns False. Example 10-14 shows how __eq__ looks using all.

    Example 10-14. Vector.eq using zip and all: same logic as Example 10-13.

        def __eq__(self, other):
            return len(self) == len(other) and all(a == b for a, b, in zip(self, other))    # We first check that the operands jave equal length, because zip will stop at the shortest operand.

    8. The Awesome zip

    Having a for loop that iterates over items without fiddling with index variables is great and prevents lots of bugs, but demands some special utility functions. One of them is the zip built-in, which makes it easy to iterate in parallel over two or more iterables by returning tuples that you can unpack into variables, one for each item in the parallel inputs.

    Example 10-15. The zip built-in at work.

    >>> zip(range(3), 'ABC')        # zip returns a generator that produces tuple on demand.
    <zip object at 0x103330648>
    >>> list(zip(range(3), 'ABC'))      # Here we build a list from it just for display; usually we iterate over the generator.
    [(0, 'A'), (1, 'B'), (2, 'C')]
    >>> list(zip(range(3), 'ABC', [0.0, 1.1, 2.2, 3.3]))    # zip stops without warning when one of the iterables is exhausted.
    [(0, 'A', 0.0), (1, 'B', 1.1), (2, 'C', 2.2)]
    >>> from itertools import zip_longest       # The itertools.zip_longest function behaves differently: it uses an optional fillvalue(None by default) to complete missing values so it can generate tuples until the last iterable is exhausted.
    >>> 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)]
    
    # The enumerate built-in is another generator function often used in for loops to avoid manual handling of index variables.

    9. Vector Take #5: Formatting

    The __format__ method of Vector will resemble that of Vector2d, but instead of providing a custom display in polar coordinates, Vector will use spherical coordinates -- also known as "hyperspherical" coordinates, because now we support n dimensions, and spheres are "hyperspheres" in 4D and beyond. Accordingly, we'll change the custom format suffix from 'p' to 'h'.

    For example, given a Vector object in 4D space (len(v) == 4), the 'h' code will produce a display like <r, φ1, φ2, φ3> where r is the magnitude (abs(v)) and the remaining numbers are the angular coordinates φ1, φ2, φ3.

    Before we can implement the minor changes required in __format__ , we need to code a pair of support methods: angle(n) to compute one of the angular coordinates(e.g., φ1), and angles() to return an iterable of all angular coordinates.

    Example of __format__

        def angle(self, n):
            # Compute one of the angular coordinates, using formulas adapted from the n-sphere article(https://en.wikipedia.org/wiki/N-sphere)
            r = math.sqrt(sum(x * x for x in 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):
            # Create generator expression to compute all angular coordinates on demand.
            return (self.angle(n) for n in range(1, len(self)))
    
        def __format__(self, fmt_spec=''):
            if fmt_spec.endswith('h'):      # hyperspherical coordinates
                fmt_spec = fmt_spec[:-1]
                coords = itertools.chain([abs(self)], self.angles())    # Use itertools.chain to produce genexp to iterate seamlessly over the magnitude and the angular coordinates.
                outer_fmt = '<{}>'
            else:
                coords = self
                outer_fmt = '({})'
            components = (format(c, fmt_spec) for c in coords)      # Create generator expression to format each coordinate item on demand.
            return outer_fmt.format(', '.join(components))      # Plug formatted components separated by commas inside brackets or parethneses.
    
    
    # Tests of ``format()`` with Cartesian coordinates in 2D::
    """
    >>> v1 = Vector([3, 4])
    >>> format(v1)
    '(3.0, 4.0)'
    >>> format(v1, '.2f')
    '(3.00, 4.00)'
    >>> format(v1, '.3e')
    '(3.000e+00, 4.000e+00)'
    """
    
    # Tests of ``format()`` with Cartesian coordinates in 3D and 7D::
    """
    >>> v3 = Vector([3,4,5])
    >>> format(v3)
    '(3.0, 4.0, 5.0)'
    >>> format(Vector(range(7)))
    '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)'
    """
    
    # Tests of ``format()`` with spherical coordinates in 2D, 3D and 4d::
    """
    >>> format(Vector([1,1]), 'h')
    '<1.4142135623730951, 0.7853981633974483>'
    >>> format(Vector([1,1]), '.3eh')
    '<1.414e+00, 7.854e-01>'
    >>> format(Vector([1,1]), '0.5fh')
    '<1.41421, 0.78540>'
    >>> format(Vector([1,1,1]), 'h')
    '<1.7320508075688772, 0.9553166181245093, 0.7853981633974483>'
    >>> format(Vector([2,2,2]), '.3eh')
    '<3.464e+00, 9.553e-01, 7.854e-01>'
    >>> format(Vector([0,0,0]), '0.5fh')
    '<0.00000, 0.00000, 0.00000>'
    >>> format(Vector([-1,-1,-1,-1]), 'h')
    '<2.0, 2.0943951023931957, 2.186276035465284, 3.9269908169872414>'
    >>> format(Vector([2,2,2,2]), '.3eh')
    '<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>'
    >>> format(Vector([0,1,0,0]), '0.5fh')
    '<1.00000, 1.57080, 0.00000, 0.00000>'
    """

    itertools.chain 用法参考链接: https://blog.csdn.net/smart_liu8/article/details/81708620

    Example 10-16. vector_v5.py: all code for final Vector class

    from array import array
    import reprlib
    import math
    import numbers
    import functools
    import operator
    import itertools
    
    
    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 __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)
    
        def __len__(self):
            return len(self._components)
    
        def __getitem__(self, index):
            cls = type(self)
            if isinstance(index, slice):       # If the index argument is a slice ...
                return cls(self._components[index])     # ...invoke the class to build another Vector instance from a slice of the _components array.
            elif isinstance(index, numbers.Integral):   # If the index is an int or some other kind of integer ...
                return self._components[index]
            else:
                msg = '{cls.__name__} indices must be integers'
                raise TypeError(msg.format(cls=cls))
    
        # def __getitem__(self, index):
        #     return self._components[index]
    
        shortcut_names = 'xyzt'
    
        def __getattr__(self, name):
            cls = type(self)
            if len(name) == 1:      # If the name is one character, it may be one of the short_names.
                pos = self.shortcut_names.find(name)
                if 0 <= pos < len(self._components):    # If the position is within range, return the array element.
                    return self._components[pos]
            msg = '{.__name__!r} object has not 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:      # If name is one of xyxt, set specific error message.
                    error = 'readonly attribute {attr_name!r}'
                elif name.islower():       # If name is lowercase, set error message about all single-letter names.
                    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)    # Default case: call __setattr__ superclass for standard behavior.
            # super() function is used to delegate some task from a method in a subclass to a suitable method in a superclass.
    
            # We are not disallowing setting all attributes, only single-letter, lowercase ones.
    
        # def __eq__(self, other):    # __eq__ and __hash__ need to work together.
        #     return tuple(self) == tuple(other)
    
        # def __hash__(self):
        #     hashes = (hash(x) for x in self._components)    # Create a generator expression to lazily compute the hash of each component.
        #     return functools.reduce(operator.xor, hashes, 0)    # Feed hashes to reduce with the xor function to compute the aggregate hash value; the third argument, 0, is the initializer.
        #     For +, |, ^ the initializer should be 0, but for *,& it should be 1.
    
        # def __eq__(self, other):
        #     if len(self) != len(other):
        #         return False
        #     for a, b in zip(self, other):   # zip produces a generator of tuples made from the items in each iterable arguemnt. The len comparison above is needed because zip stops producing values without warning as soon as one of the inputs is exhausted.
        #         if a != b:      # As soon as two components are different, exit returning False.
        #             return False
        #     return True
    
        def __eq__(self, other):
            return len(self) == len(other) and all(a == b for a, b, in zip(self, other))    # We first check that the operands jave equal length, because zip will stop at the shortest operand.
    
        def __hash__(self):
            hashes = map(hash, self._components)
            return functools.reduce(operator.xor, hashes)
    
        def angle(self, n):
            # Compute one of the angular coordinates, using formulas adapted from the n-sphere article(https://en.wikipedia.org/wiki/N-sphere)
            r = math.sqrt(sum(x * x for x in 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):
            # Create generator expression to compute all angular coordinates on demand.
            return (self.angle(n) for n in range(1, len(self)))
    
        def __format__(self, fmt_spec=''):
            if fmt_spec.endswith('h'):      # hyperspherical coordinates
                fmt_spec = fmt_spec[:-1]
                coords = itertools.chain([abs(self)], self.angles())    # Use itertools.chain to produce genexp to iterate seamlessly over the magnitude and the angular coordinates.
                outer_fmt = '<{}>'
            else:
                coords = self
                outer_fmt = '({})'
            components = (format(c, fmt_spec) for c in coords)      # Create generator expression to format each coordinate item on demand.
            return outer_fmt.format(', '.join(components))      # Plug formatted components separated by commas inside brackets or parethneses.

    end...

  • 相关阅读:
    DOM节点类型
    javascript中的变量、作用域
    this 不同情况指代的对象
    BOM对象节点
    浏览器兼容性
    总结
    javascript事件流讲解和实例应用
    7.20
    7.16总结
    飞机大战
  • 原文地址:https://www.cnblogs.com/neozheng/p/12310116.html
Copyright © 2011-2022 走看看