Monday, June 21, 2021

Python Morsels: Module vs Script in Python

A distinction is often made between .py files that represent "modules" vs "scripts" (aka "programs").

What's a script?

A script or program is a .py file that's meant to be run directly.

Here's an extremely simple script that prints out "Howdy user":

print("Howdy user")

If that file was called hello.py, we could run it from the command-line like this:

$ python3 hello.py
Howdy user

What's a module?

A module is a .py file that's meant to be imported by other .py files. Modules should contain helpful functions, classes, or variables pointing to useful data.

Here's a module that has a single function, greet, within it:

def greet(name):
    print("Howdy", name)

If this module was named salutations.py, we could import and use the greet function within it from the REPL like this:

>>> import salutations
>>> salutations.greet("Trey")
Howdy Trey

Or we could import our salutations module (and use its greet function) within our hello.py file:

from salutations import greet

name = input("What would you like me to call you? ")
print("Noted!")
greet(name)

This way when hello.py is run, the user will be prompted to enter their name and then the greet function (which lives in the salutations module) will be run:

$ python3 hello.py
What would you like me to call you? Trey
Noted!
Howdy Trey

Scripts are modules

All .py files are Python modules, even scripts. Scripts are modules that act as the entry point to our Python process.

You can prove to yourself that scripts are modules by importing a .py file that's meant to be used as a script:

>>> import hello
What would you like me to call you? Trey
Noted!
Howdy Trey

Importing a module runs all the code in that module and sticks it into a module object.

You could even try running a module from the command-line. Usually that just defines a bunch of functions and exits (since modules aren't supposed to have side effects, meaning no printing/prompting/etc at import time):

$ python3 salutations.py

Making a script that acts nicely when used as a module (when imported)

Most scripts aren't meant to be imported as modules (it's a bit to see something start happening simply because a module was imported).

If you want to make a script that will print things out and do useful tasks when run from the command-line but won't do those things when imported, you'll need to somehow distinguish between two states:

  1. Am I currently being imported by another module?
  2. Or am I the entry point the whole Python process (I'm being run as a script)

Every module has a __name__ variable within it to help answer this question. The __name__ variable has two possible values:

  1. When our module is imported by another module, __name__ will be set to the name of our module (e.g. the string 'greet')
  2. When our module is being run from the command-line, __name__ will be set to the string '__main__'

Instead of making two files, salutations.py (meant to be imported) and hello.py (meant to be run from the command-line) we could make one file that does both if we modify salutations.py like this:

def greet(name):
    print("Howdy", name)

if __name__ == "__main__":
    name = input("What would you like me to call you? ")
    print("Noted!")
    greet(name)

Then when we import salutations, we'll have access to a greet function (but we won't be prompted for input and nothing will be printed on import):

>>> import salutations
>>> salutations.greet("Trey")
Howdy Trey

And if we run salutations.py from the command-line we'll be prompted for input and text will be printed:

$ python3 salutations.py
What would you like me to call you? Trey
Noted!
Howdy Trey

The REPL is a module!

Interestingly, this __name__ thing even exists in the REPL.

When you run python3 with no arguments, you're launching the Python REPL. Every Python process needs an entry point module. When you're in the REPL, you're creating that entry point module as you write code. As you type code at the REPL, you're basically writing a script on-the-fly.

Since the code we write at the REPL acts as our entry point Python module, __name__ is __main__:

>>> __name__
'__main__'

The __name__ variable in the REPL is set to __main__ because as we type code at the REPL, we're basically poking around within the entry point to our Python process.

Related idea: running modules (and scripts) interactively

If you pass Python the -i argument as you run a script, Python will run all the code within your script and then it will drop you into a Python REPL that's inside your module.

$ python3 -i salutations.py
What would you like me to call you? Trey
Noted!
Howdy Trey
>>> name
'Trey'
>>> greet
<function greet at 0x7f4b0b8d5040>

Normally the REPL makes a new empty module for you to start writing your code in. When running a module interactively, the REPL will instead drop you into the module you gave it, almost as if each command typed at the REPL added more code to that module (not modifying the original .py file though, just the current Python process).



from Planet Python
via read more

No comments:

Post a Comment

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...