Python is a high-level language that supports object-oriented design. How to design an object-oriented class in line with Python style is a complex problem. This paper provides a reference, expresses an idea and explores a layer of principles.
target
Yi Yan novel network https://www.2267.infoThe expected class has the following basic behavior:
- __ repr__ Provide support for repr() and return the object string representation that is easy for developers to understand.
- __ str__ Provides support for str() and returns an object string representation that is easy for users to understand.
- __ bytes__ Provides support for bytes() and returns the binary representation of the object.
- __ format__ Provides support for format() and str.format(), using special format codes to display string representations of objects.
Vector2d is a vector class, which is expected to support the following operations:
>>> v1 = Vector2d(3, 4) >>> print(v1.x, v1.y) # Direct access through attributes 3.0 4.0 >>> x, y = v1 # Support unpacking >>> x, y (3.0, 4.0) >>> v1 # Support repr Vector2d(3.0, 4.0) >>> v1_clone = eval(repr(v1)) # Verify that the repr description is accurate >>> v1 == v1_clone # Support = = operator True >>> print(v1) # Support str (3.0, 4.0) >>> octets = bytes(v1) # Support bytes >>> octets b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@' >>> abs(v1) # Realize__ abs__ 5.0 >>> bool(v1), bool(Vector2d(0, 0)) # Realize__ bool__ (True, False)
Basic implementation
The code and analysis are as follows:
from array import array import math class Vector2d: # Used when converting between Vector2d instance and binary typecode = 'd' def __init__(self, x, y): # Convert to floating point number self.x = float(x) self.y = float(y) def __iter__(self): # Generator expression to turn the Vector2d instance into an iteratable object, so as to unpack return (i for i in (self.x, self.y)) def __repr__(self): class_name = type(self).__name__ # {! r} is a universal formatter # *self is unpacking, and * indicates all elements return '{}({!r}, {!r})'.format(class_name, *self) def __str__(self): # Vector2d instance is an iteratable object. You can get a tuple and str return str(tuple(self)) def __bytes__(self): # switching sites return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self))) def __eq__(self, other): # Compare equal return tuple(self) == tuple(other) def __abs__(self): # The module of a vector is the diagonal length of a right triangle return math.hypot(self.x, self.y) def __bool__(self): # 0.0 is False and non-zero value is True return bool(abs(self)) @classmethod def frombytes(cls, octets): # classmethod does not pass self to cls typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(*memv) # After unpacking, a pair of parameters required by the construction method are obtained
The @ classmethod decorator is used at the end of the code, which is easily confused with @ staticmethod.
@The usage of classmethod is to define the operation class, not the method of the operation instance. Commonly used to define alternative construction methods.
@Static method is actually an ordinary function, but it is just placed in the definition body of the class. It can be actually defined in a class or module.
Format display
The code and analysis are as follows:
def angle(self): return math.atan2(self.y, self.x) def __format__(self, fmt_spec=''): if fmt_spec.endswith('p'): # Ends with 'p', using polar coordinates fmt_spec = fmt_spec[:-1] coords = (abs(self), self.angle()) # Calculate polar coordinates (magnetic, angle) outer_fmt = '<{}, {}>' # Angle bracket else: coords = self # Do not end with 'p', construct Cartesian coordinates (x, y) outer_fmt = '({}, {})' # Parentheses components = (format(c, fmt_spec) for c in coords) # Use the built-in format function to format the string return outer_fmt.format(*components) # Replace the outer format after unpacking
It achieves the following effects:
Rectangular coordinates:
>>> format(v1) '(3.0, 4.0)' >>> format(v1, '.2f') '(3.00, 4.00)' >>> format(v1, '.3e') '(3.000e+00, 4.000e+00)'
Polar coordinates:
>>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' >>> format(Vector2d(1, 1), '.3ep') '<1.414e+00, 7.854e-01>' >>> format(Vector2d(1, 1), '0.5fp') '<1.41421, 0.78540>'
Hashable
Realize__ hash__ The special method can make Vector2d hashable, but before that, you need to make the attribute immutable. The code is as follows:
def __init__(self, x, y): # Double underscore prefix, becomes private self.__x = float(x) self.__y = float(y) @property # Mark as property def x(self): return self.__x @property def y(self): return self.__y
In this way, x and y are read-only and not writable.
The double underlined prefix of the attribute name is called name mangling, which is equivalent to_ Vector2d__x and_ Vector2d__y. Can avoid being overridden by subclasses.
The hash values of x and y are then distinguished or mixed using bit operators:
def __hash__(self): return hash(self.x) ^ hash(self.y)
Save memory
Python stores instance properties by default in__ dict__ In the dictionary, the bottom layer of the dictionary is hash table. When the amount of data is large, it will consume a lot of memory (space for time). Pass__ slots__ Class attributes can store instance attributes in tuples, which greatly saves memory space.
Example:
class Vector2d: __slots__ = ('__x', '__y') typecode = 'd'
There are several points to note:
- All attributes must be defined to__ slots__ Tuple.
- Subclasses must also be defined__ slots__.
- If you want to support weak references, you need to__ weakref also joined__ slots__.
Override class properties
Instance coverage
Python has a unique feature: class properties can be used to provide default values for instance properties. The typecode in the example code can be directly used by self typecode got it. However, if you assign a value to a nonexistent instance property, the instance property will be created and the class property will not be affected. Self The typecode gets the typecode of the instance attribute.
Example:
>>> v1 = Vector2d(1, 2) >>> v1.typecode = 'f' >>> v1.typecode 'f' >>> Vector2d.typecode 'd'
Subclass coverage
Class properties are public, so you can directly use vector2d TypeCode = 'f' to modify. But a more Python style approach is to define subclasses:
class ShortVector2d(Vector2d): typecode = 'f'
Django's class based view makes extensive use of this technology.
Summary
This paper first introduces how to implement special methods to design a Python style class, and then implements formatted display and hashable objects respectively__ slots__ It can save memory for classes. Finally, the class attribute coverage technology is discussed. Subclass coverage is a technology widely used in Django's class based view.
reference material:
Fluent Python Chapter 9 Python style objects
https://www.jianshu.com/p/7fc0a177fd1f