What's the point of having a Main class in Python?

Viewed 99

I have come across examples where people used a Main class, and from there, inside the Main class, write logic that instantiates other classes and calls their functions.

Example:

# ^^^ code containing class OtherClass() ^^^

class Main:
    def __init__(self):
        print("\n **** Main class from which we call other classes **** \n")

        other_object = OtherClass()
        
        self.plot_graphs(other_object)

        
    def plot_graphs(self, other_object):
        # plot stuff
        pass

# vvv code containing main code which instantiates main = Main() vvv

Upon implementing this, I see warnings that all of my methods/functions in the Main class (like plot_graphs) are/should be static. In that sense, why have a Main class at all if you could just as well take out those functions outside of the Main?

I know that there are workarounds like adding a decorator like @staticmethod or a macro like #noinspection PyMethodMayBeStatic. But this got me wondering whether it is Pythonic at all to have a Main class if you don't alter it's attributes, or not.

1 Answers

There is no point. You're looking at code written by a Java programmer who never bothered to learn Python idioms.

Idiomatic Python scripts will have a main function, but not some top-level class wrapping it. A standard structure would be:

#!/usr/bin/env python3

# imports here

# Globals (usually constants) here

# Non-main functions & classes (possibly for use when imported as a module) here

def main():
    # Parse arguments and do main script things here

if __name__ == '__main__':
    main()

The Java developer who wrote the code you're looking at is used to Java, where everything must be part of a class (and for that matter, a given source file can have only one public top-level class, so the entry point for a Java program typically sits alone, and relies on utility classes from other files). In Python, the module itself it executable. Even if you needed some class-like behaviors, the module itself acts like one for most basic use cases (e.g. global variables handle the job of instance attributes). About the only conceivable use for such a class would be if you were going to run several executions of the entry point in parallel and wanted them each to have their own separate "globals", but the contortions you'd need to go through, in which you want to do that and simpler functions don't handle the job adequately, would be painful.

Technically, you don't need main or if __name__ == '__main__': for simple scripts that are never imported as modules, but it's a good idea to get in the habit, because some things, e.g. the multiprocessing module running in non-fork mode, assume the main module can be imported to establish state matching the parent process; if you haven't guarded the "script" behaviors properly, they'll get rerun in every worker.


Strictly speaking, the contents of the main function don't need to be in a function; you could just inline them within the if __name__ == '__main__': block. The advantages to putting them in a dedicated function are:

  1. Namespace separation between "script" stuff and "module" stuff. If you don't wrap the script behaviors in a function, you pollute the global namespace, and you may accidentally rely on those global names for the utility functions. When you later try to import the module into another script, those variables aren't defined, and the utility functions don't work
  2. Performance: When run at global scope, all variables are globally scoped, which, on CPython, means loading or storing to them involves a dict operation. Doing the same work inside a function with locally scoped variables reduces it to a C level array lookup. While both are average case O(1), dict operations have greater variability and higher fixed overhead than directly loading from a C array.
Related