1. Vector Class Redux
- Thanks to the Python Data Model, your user-defined types can behave as naturally as the built-in types. And this can be accomplished without inheritance, in the spirit of duck typing: you just implement the methods needed for your objects to behave as expected
1 | from array import array |
2. classmethod Versus staticmethod
- classmethod decorator defines a method that operates on the class and not on instances. classmethod changes the way the method is called, so it receives the class itself as the first argument, instead of an instance. Its most common use is for alternative constructors
- staticmethod decorator changes a method so that it receives no special first argument. In essence, a static method is just like a plain function that happens to live in a class body, instead of being defined at the module level
1 | class Demo: |
3. Formatted Displays
- The f-strings, the format() built-in function, and the str.format() method delegate the actual formatting to each type by calling their .__format__(format_spec) method. The format_spec is a formatting specifier, which is either:
- The second argument in format(my_obj, format_spec)
- Whatever appears after the colon in a replacement field delimited with {} inside an f-string or the fmt in fmt.str.format()
1 | # https://docs.python.org/3/library/string.html#formatspec |
4. Private and “Protected” Attributes
- In Python, there is no way to create private variables like there is with the private modifier in Java. What we have in Python is a simple mechanism to prevent accidental overwriting of a “private” attribute in a subclass
- If you name an instance attribute with two leading underscores and zero or at most one trailing underscore, Python stores the name in the instance __dict__ prefixed with a leading underscore and the class name. This language feature goes by the lovely name of name mangling
1 | v1 = Vector2d(3, 4) |
- The name mangling functionality is not loved by all Pythonistas, and neither is the skewed look of names written as self.__x. Some prefer to avoid this syntax and use just one underscore prefix to “protect” attributes by convention (e.g., self._x)
- The single underscore prefix has no special meaning to the Python interpreter when used in attribute names, but it’s a very strong convention among Python programmers that you should not access such attributes from outside the class. It’s easy to respect the privacy of an object that marks its attributes with a single _, just as it’s easy respect the convention that variables in ALL_CAPS should be treated as constants
- In modules, a single _ in front of a top-level name does have an effect: if you write from mymod import *, the names with a _ prefix are not imported from mymod. However, you can still write from mymod import _privatefunc
5. Saving Memory with __slots__
- By default, Python stores the attributes of each instance in a dict named __dict__. A dict has a significant memory overhead. But if you define a class attribute named __slots__ holding a sequence of attribute names, Python uses an alternative storage model for the instance attributes: the attributes named in __slots__ are stored in a hidden array or references that use less memory than a dict
- The __slots__ class attribute may provide significant memory savings if properly used, but there are a few caveats:
- You must remember to redeclare __slots__ in each subclass to prevent their instances from having __dict__
- Instances will only be able to have the attributes listed in __slots__, unless you include ‘__dict__‘ in __slots__ (but doing so may negate the memory savings)
- Classes using __slots__ cannot use the @cached_property decorator, unless they explicitly name ‘__dict__‘ in __slots__
- Instances cannot be targets of weak references, unless you add ‘__weakref__‘ in __slots__
1 | class Pixel: |
6. Overriding Class Attributes
- A distinctive feature of Python is how class attributes can be used as default values for instance attributes. In Vector2d there is the typecode class attribute, we read it as self.typecode. Because Vector2d instances are created without a typecode attribute of their own, self.typecode will get the Vector2d.typecode class attribute by default. But if you write to an instance attribute that does not exist, you create a new instance attribute, and the class attribute by the same name is untouched. However, from then on, whenever the code handling that instance reads self.typecode, the instance typecode will be retrieved
1 | v1 = Vector2d(1.1, 2.2) |
References
- Fluent Python