(this follows some feedback from the recent thread: "WedWonder: Scripts and Modules" and commences a somewhat-related topic/invitation to debate/correct/educate)

Is it a good idea to keep a system's main-line* code as short as possible, essentially consigning all of 'the action' to application and external packages and modules?

* my choice of term: "main-line", may be taken to mean:
- the contents of main(),
- the 'then clause' of an if __name__ == __main__: construct,
- a __main__.py script.


In a previous thread I related some ?good, old days stories. When we tried to break monolithic programs down into modular units, a 'rule of thumb' was "one page-length" per module (back in the mainframe days our code was 'displayed' on lineflo(w) (continuous stationery) which was 66 - call it 60, lines per page - and back-then we could force a page-break where it suited us!). Then when we moved to time-share screens (80 characters by 24 lines), we thought that a good module-length should conform to screen-size. These days I have a large screen mounted in 'portrait mode', so on that basis I'm probably back to 50~60 lines (yes, these old eyes prefer a larger font - cue yet more cheeky, age-ist comments coming from my colleagues...)

Likely I have also picked-up and taken-to-heart the *nix mantra of code doing 'one job, and doing it well' (and hence the extensive powers of redirects, piping, etc - in Python we 'chain' code-units together with "import"). Accordingly, I tend to err on the side of short units of code, and thus more methods/functions than others might write.

In "Mastering Object-oriented Python" the author discusses "Designing a main script and the __main__ module" (Ch17):
<<<
A top-level main script will execute our application. In some cases, we may have multiple main scripts because our application does several things. We have three general approaches to writing the top-level main script: • For very small applications, we can run the application with python3.3 some_script.py . This is the style that we've shown you in most examples. • For some larger applications, we'll have one or more files that we mark as executable with the OS chmod +x command. We can put these executable files into Python's scripts directory with our setup.py installation. We run these applications with some_script.py at the command line.
• For complex applications, we might add a __main__.py module in the
application's package. To provide a tidy interface, the standard library
offers the runpy module and the -m command-line option that will use this specially named module. We can run this with python3.3 -m some_app.
[explanation of "shebang" line - the second approach, above]

Creating a __main__ module
To work with the runpy interface, we have a simple implementation. We add a small __main__.py module to our application's top-level package. We have emphasized the design of this top-level executable script file. We should always permit refactoring an application to build a larger, more sophisticated composite application. If there's functionality buried in __main__.py , we need to pull this into a module with a clear, importable name so that it can be used by other applications.
A __main__.py module should be something small like the following code:

        import simulation
        with simulation.Logging_Config():
                with simulation.Application_Config() as config:
                        main= simulation.Simulate_Command()
                        main.config= config
                        main.run()

We've done the minimum to create the working contexts for our application. All of the real processing is imported from the package. Also, we've assumed that this __main__.py module will never be imported. This is about all that should be in a __main__ module. Our goal is to maximize the reuse potential of our application.
[example]

We shouldn't need to create composite Python applications via the command-line API. In order to create a sensible composition of the existing applications, we might be forced to refactor stats/__main__.py to remove any definitions from this module and push them up into the package as a whole.
>>>

Doesn't the author thus suggest that the script (main-line of the program) should be seen as non-importable?

Doesn't he also suggest that the script not contain anything that might be re-usable?

Accordingly, the script calls packages/modules which are both importable and re-usable.

None of which discounts the possibility of having other 'main-lines' to execute sub-components of the (total) application, should that be appropriate.

An issue with 'main-line' scripts is that they can become difficult to test - or to build, using TDD and pytest (speaking personally). Pytest is great for unit tests, and can be used for integration testing, but the 'higher up' the testing pyramid we go, the less effectual it becomes (please don't shoot me, pytest is still an indispensable tool!) Accordingly, if 'the action' is pushed up/out to modules, this will ease the testing, by access and by context!

To round things out, I seem to be structuring projects as:

.projectV2
-- README
-- LICENSE
-- docs (sub-directory)
-- .git (sub-directory)
-- etc
-- __main__.py

-- project (some prefer "src") sub-directory
-- -- package directory/ies
-- -- modules directory/ies
(obviously others will be import-ed from wherever pip3 (etc) installed them)

-- test (sub-directory)
-- -- test modules
(although sometimes it seems easier to add a test sub-directory to the individual package directory, above)

I like the idea that whilst coding, the editor only needs to show the project sub-directory, because (much of) the rest is irrelevant - I have a separate window for the test code, and thus the same distraction-free virtue also applies.

Part of making the top-level "projectV2" directory almost-irrelevant in day-to-day dev-work is that __main__.py contains very little, typically three stages:
        1 config (including start logging, etc, as appropriate)
        2 create the applications central/action object
        3 terminate

Nary an if __name__ == __main__ in sight (per my last "Wednesday Wondering"), because "the plan" says there is zero likelihood of the "main-line" being treated as a (re-usable) module! (and any refactoring would, in any case, involve pushing such code out to a (re-usable) module!

When it comes to execution, the command (excluding any switches/options) becomes:

        [~/Projects]$ python3 projectV2

Which would also distinguish between project-versions, if relevant. More importantly, changes to application version numbers do not require any changes to import statements! (and when users don't wish to be expected to remember version numbers "as well", use symlinks - just as we do with python/python2/python3/python3.7...

Note that it has become unnecessary to add the -m switch!


Accordingly, very short entry-point scripts have been working for me. (recognising that Python is used in many more application-areas than do I!)

Do you see a reason/circumstance when this practice might break-down?


Ref:
Mastering Object-oriented Python, S Lott
Copyright © 2014 Packt Publishing
--
Regards,
=dn
--
https://mail.python.org/mailman/listinfo/python-list

Reply via email to