<!--order=3-->

<h1>Python: Functions, Modules, and Virtual Environments</h1>
<p>Small experiments are easy to put in one file. Useful programs become easier to maintain when their work is divided into functions and modules. A virtual environment keeps each project's packages separate from the rest of the system.</p>

<h2>Functions with Clear Inputs and Outputs</h2>
<pre>
def celsius_to_fahrenheit(celsius):
    return celsius * 9 / 5 + 32

temperature = celsius_to_fahrenheit(18)
print(f"{temperature:.1f} F")
</pre>
<p>A function should usually do one job. Give it the values it needs as arguments and return the result instead of relying on global variables.</p>

<h2>Type Hints</h2>
<p>Type hints document what a function expects and returns. Python does not enforce them at runtime, but editors and checking tools can use them.</p>
<pre>
def describe_node(name: str, battery: int) -&gt; str:
    return f"{name}: {battery}% battery"

print(describe_node("field-node-01", 87))
</pre>

<h2>Modules</h2>
<p>Any Python file can be imported as a module. Create <code>weather.py</code>:</p>
<pre>
def describe(temperature):
    if temperature &lt; 0:
        return "freezing"
    if temperature &gt; 25:
        return "warm"
    return "moderate"
</pre>
<p>Then use that function from another file:</p>
<pre>
import weather

print(weather.describe(18))
</pre>

<h2>The Main Guard</h2>
<p>When a file can be both imported and run directly, put its command-line behavior behind a main guard.</p>
<pre>
def main():
    print("This runs when the file is used as a program")

if __name__ == "__main__":
    main()
</pre>

<h2>Virtual Environments</h2>
<p>A virtual environment creates an isolated Python installation for one project. On Debian or another Unix-like system:</p>
<pre>
$ mkdir node-report
$ cd node-report
$ python3 -m venv .venv
$ . .venv/bin/activate
(.venv) $ python -m pip install --upgrade pip
(.venv) $ python --version
(.venv) $ deactivate
</pre>
<p>When the environment is active, <code>python</code> and <code>pip</code> refer to the project environment. Do not commit the <code>.venv</code> directory to Git; recreate it from a dependency list instead.</p>

<h2>Dependencies and requirements.txt</h2>
<p>When a project needs third-party packages, record them in a requirements file so another machine can reproduce the environment.</p>
<pre>
(.venv) $ python -m pip install meshtastic
(.venv) $ python -m pip freeze &gt; requirements.txt

# On another machine:
$ python3 -m venv .venv
$ . .venv/bin/activate
(.venv) $ python -m pip install -r requirements.txt
</pre>
<p>Use <code>python -m pip</code> instead of a bare <code>pip</code> command. It makes clear which Python installation receives the package.</p>

<h2>Testing a Function</h2>
<p>A small assertion can catch a broken calculation before a larger program depends on it:</p>
<pre>
def celsius_to_fahrenheit(celsius):
    return celsius * 9 / 5 + 32

assert celsius_to_fahrenheit(0) == 32
assert round(celsius_to_fahrenheit(20), 1) == 68.0
</pre>
<p>For a larger project, move these checks into a test file and use Python's <code>unittest</code> module or a test runner. Keeping functions free of input and printing makes them much easier to test.</p>

<h2>Project Layout</h2>
<pre>
node-report/
├── .venv/
├── requirements.txt
├── README.md
└── src/
    ├── report.py
    └── formatters.py
</pre>
<p>Keep generated files, virtual environments, and secrets out of version control. A <code>.gitignore</code> entry for <code>.venv/</code> and <code>__pycache__/</code> is a good start.</p>

<h2>References</h2>
<ul>
  <li><a href="https://docs.python.org/3/tutorial/modules.html" target="_blank" rel="noopener">Python Modules ↗</a></li>
  <li><a href="https://docs.python.org/3/library/venv.html" target="_blank" rel="noopener">Python Virtual Environments ↗</a></li>
</ul>