Friday Finking: 'main-lines' are best kept short

DL Neil PythonList at DancesWithMice.info
Thu Sep 12 23:58:17 EDT 2019


(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



More information about the Python-list mailing list