python style class design

Keywords: Python OOP

This article is the learning notes of Chapter 9 of fluent python.

Object representation

Two functions to get the string representation of an object:
repr()
For developers
str()
User oriented

Need to be implemented separately__ repr__ And__ str__ Two methods.

An example: vector

Here is an example of vector in Mathematics:

from array import array
import math

class Vector2d:
    typecode = 'd'

    def __init__(self, x, y):
        self.x = float(x)
        self.y = float(y)

    def __iter__(self):
        return (i for i in (self.x, self.y))

    def __repr__(self):
        class_name = type(self).__name__
        return '{}({!r}, {!r})'.format(class_name, *self)

    def __str__(self):
        return str(tuple(self))

    def __bytes__(self):
        return (bytes([ord(self.typecode)]) + \
            bytes(array(self.typecode, self)))

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

    def __abs__(self):
        return math.hypot(self.x, self.y)

    def __bool__(self):
        return bool(abs(self))

The alternative constructor is to construct Vector2d through bytes. At this time, it can be implemented with the decorator classmethod. Next, let's introduce the usage of classmethod:

Resources: what are the specific uses of classmethod and staticmethod in Python?

  1. Pre construction interaction

The first parameter of classmethod must be the class itself. Traditionally, we use cls:

@classmethod
def foo(cls, *args):
	pass

A typical scenario is as follows:

class Plugin(object):
    """
    Legacy plugin (v1)
    """
    def __init__(self, api_interface):
        self._api = api_interface
    
    def callback(self, event_type, event_value):
        """
        Event callback
        """
        pass    # Do nothing


class PluginV2(object):
    """
    v2 plugin
    """
    def __init__(self, api_version, api_interface):
        self._api = api_interface
    
    def callback(self, event_type, event_value):
        """
        Event callback
        """
        pass
    
    @classmethod
    def capabilities(cls, supported_versions):
        if "2.0" in supported_versions:
            return {"api_version": "2.0", "register_events": ["request_start", "request_end"]}
        else:
            raise IncompatibleVersionException("API version 2.0 is not supported")

Author: spirit sword
 Link: https://www.zhihu.com/question/20021164/answer/537385841
 Source: Zhihu
 The copyright belongs to the author. For commercial reprint, please contact the author for authorization, and for non-commercial reprint, please indicate the source.

Here, we want to be compatible with the old version of plugin, so we need to know which class should be used to construct the instance before construction.

  1. Special constructor (alternative constructor)

When an object is serialized into data and then restored into an object from the data, the serialization method is generally set as the instance method, and the deserialization method is set as the classmethod, because it is not necessary to construct an object instance first during deserialization.

  1. Provide hooks for other classmethod s

Because classmethod can only call classmethod.

As for the difference between classmethod and staticmethod, at present, there is basically no case that staticmethod must be used instead of classmethod.

Format display

This section focuses on format(my_obj, format_spec) and str.format().

If the class inherited from object does not implement__ format__ Method, the string str(my_object) will be returned by default.

def __format__(self, fmt_spec=''):
	components = (format(c, fmt_spec) for c in self)
	return '({}, {})'.format(*components)

The above code can support different fmt_spec, such as:

v = Vector2D(3.0, 4.0)
format(v)
# '(3.0, 4.0)'
format(v, '.2f')
# Not realized__ format__ An error was reported before

It is not hard to think that if you want to format your own class structure, the existing format specification Mini language is certainly not enough. Therefore, it is more practical to customize your own format_spec.

In Vector2d__ format__ Method, the current implementation can support polar coordinate representation:

def angel(self):
	return math.atan2(self.y, self.x)

def __format__(self, fmt_spec=''):
	if fmt_spec.endswith('p'):
		# The end of polar coordinates is represented by p
		fmt_spec = fmt_spec[:-1]
		coords = (abs(self), self.angel())
		outer_fmt = '<{}, {}>'
	else:
		coords = self
		outer_fmt = '({}, {})'
	components = (format(c, fmt_spec) for c in coords)
	return outer_fmt.format(*components)

Hashable Vector2d

Realize__ hash__ Methods and__ eq__ Method.

private and protected in python

Unfortunately, there are no private and protected keywords in python, which does not actually prevent programmers from accessing private variables outside of classes. So how is the private variable of a class constructed in python? There are two representative methods:

Name override

When naming a private variable, add two underscores at the beginning, and the name mangling mechanism in python will overwrite the name:

class Vector2d:
	self.__x = None
	self.__y = None
	# ......

So__ The name of X will be rewritten to_ Vector2d__x. If you want to rewrite__ X doesn't work, avoiding rewriting private variables outside the class.

But the disadvantage is that if the programmer knows the mechanism, he can rewrite it directly outside the class_ Vector2d__x.

appointment

Some python programmers are used to putting an underscore before the name of private variables and promise not to access these variables outside the class.

It looks hasty hhh.

(I prefer the second method, because neither of these methods fundamentally solves the problem of accessing private variables outside the class, so I prefer to believe in the agreement between programmers hhh)

Override class properties

The feeling is to tell us to minimize hard coding hhh.

Posted by sgs on Sat, 02 Oct 2021 15:33:58 -0700