1. Protocols and Duck Typing
- 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. 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
- This became known as duck typing: Don’t check whether it is-a duck: check whether it quacks-like-a duck, walks-like-a duck, etc., depending on exactly what subset of duck-like behavior you need to play your language-games with
- Since Python 3.8, typing.Protocol supports static protocols, which formalize structural subtyping for static type checkers. One key difference is that static protocol implementations must provide all methods defined in the protocol class
1 | from typing import Protocol |
2. A Sliceable Sequence
- The best practice for a sequence constructor is to take the data as an iterable argument in the constructor
1 | from array import array |
2.1. How Slicing Works
1 | class MySeq: |
2.2. A Slice-Aware __getitem__
1 | class Vector: |
3. Dynamic Attribute Access
- We are now dealing with vectors that may have a large number of components, it is convenient to support shortcut names for the first few components: x, y, z, t. We could write four properties, but the __getattr__ special method provides a better way
- 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 the attribute as a string (e.g., ‘x’)
- Very often when you implement __getattr__, you need to code __setattr__ as well, to avoid inconsistent behavior in your objects
1 | class Vector: |
4. Hashing and a Faster ==
- Vector may have thousands of components, so building a tuple only for hashing may be too costly
1 | class Vector: |
5. Formatting
- Instead of providing a custom display in polar coordinates, Vector will use spherical coordinates—also known as “hyperspherical” coordinates. Vector will be formatted as
<r, angle1, angle2, ...>, where r is the magnitude (abs(v)), and the remaining numbers are the angular components
1 | class Vector: |
References
- Fluent Python