[Python] Parent class initializes data that child class must use: better approach and...

Discussão em 'Python' iniciado por Stack, Junho 5, 2025.

  1. Stack

    Stack Membro Participativo

    I have a parent class that initializes a matplotlib.axes._axes.Axes object with some configuration that is necessary so that the resulting plots are saved correctly. A child class can extend the parent classes _plot_initial_frame and _update_frame methods in order to specialize the plot. However, with the current design, I do not actually "force" the child class to initialize the plot with the correct configuration, rather I would have to rely on the developer knowing (presumably by providing documentation) that they need to call super()._plot_initial_frame() in the child class to get expected behavior.

    Is there a more standard approach (pythonic or otherwise) to enforce such a contract between parent and child classes? It seems like my approach is already a bit suspect in terms of design because I am essentially modifying "protected" data---having read discussions on Why is clean code suggesting avoiding protected variables and Are there any legitimate use cases for protected visibility.

    Below are some dummy classes showing what I mean in code. The image at the end also shows why a particular configuration needs to be enforced: without specifying the rectangular configuration in the Axes, the image (both are 2048 x 1024) is not displayed properly. You can see the left image has lots of white space around it (bbox_inches='tight' is not a solution here btw as it just clips the whitespace and the resulting image is no longer 2048 x 1024) while the right image is much "fuller" (i.e., no whitespace).

    from matplotlib.pyplot import axes, figure
    from cartopy.crs import Projection, PlateCarree


    class WorldMapAnimator:

    def __init__(
    self, projection: Projection = PlateCarree(), n_frames: int = 3):
    self._projection = projection
    self._n_frames = n_frames

    @staticmethod
    def get_rectangle_for_full_plot():
    """Emulating a public static const"""
    rectangle_for_full_plot = [0, 0, 1, 1]
    return rectangle_for_full_plot

    def animate(self):
    self._plot_initial_frame()
    for frame in range(self._n_frames):
    self._update_frame(frame)

    def _plot_initial_frame(self):
    self._fig = figure()

    # IMPORTANT: The axes must have this type of rectangle configuration
    self._ax = axes(
    self.get_rectangle_for_full_plot(),
    projection=self._projection)

    self._ax.coastlines()

    def _update_frame(self):
    return


    class PerlinNoiseAnimator(WorldMapAnimator):
    def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)

    def _plot_initial_frame(self):

    # IMPORTANT: here I am modifying a member of the parent class
    # and also "soft" requiring the user to call super otherwise they
    # won't get expected behavior...
    super()._plot_initial_frame()

    self._ax.pcolormesh(...) # TODO: write some perlin noise to worldmap
    return

    def _update_frame(self):
    # do something to update the map with new perlin noise here
    return


    [​IMG]

    Edit: Based on comments, I am considering the following change

    class WorldMapAnimator:
    def animate(self):
    self._configure_initial_frame()
    self._plot_initial_frame()
    for frame in range(self._n_frames):
    self._update_frame()

    def _configure_initial_frame():
    self._fig = figure()

    # IMPORTANT: The axes must have this type of rectangle configuration
    self._ax = axes(
    self.get_rectangle_for_full_plot(),
    projection=self._projection)

    def _plot_initial_frame():
    self._ax.coastlines()


    In this way, the child classes only need to worry about overriding _plot_initial_frame and the correctly configured Axes object is already handled in the public animate method.

    Continue reading...

Compartilhe esta Página