Getting started with Python on FreeBSD => why & how!

Hi gang!

Editorial

Fun fact: I've been using FreeBSD for quite a while now and to be perfectly honest with you lot I'm (still) quite passionate about the whole thing. Always have, always will be. At the time of writing my backup VPS server is ("was") building 15.0-RELEASE (source hosted on my main server for security reasons). And speaking of said main server... well, today I've reached a personal milestone: Java no longer really exists on my server(s). No more JDK ("sorta"), no more (home brewed) Java programs ("class files") which perform certain maintenance functions, no more 'weird' folder structures to support a (somewhat) limited design for packages.

SO => 'Duke' ☕has been (officially) replaced by (a) Python 🐍, surely a much better companion for a daemon? 👿

Anyway, I've had the intention to look deeper into Python for quite a few years, and last year... it actually happened! The more I learned the more excited I became and well, here we are. Time for some advocating! ;)

So why Python?​

Just so we're clear? => I'm not a professional developer, just a hobby (Java) programmer who learned Python and got quite passionate about it. In addition I'm also quite familiar with shell scripting (obviously), VBA, C# and ASP.NET.

Interpreted language
(and in my opinion also more accessible than lang/perl...)

Python, just like Perl (and any other shell) isn't just a programming language, but also an interpreter. Meaning? Well...

Code:
peter@zefiris:/home/peter/temp $ python
Python 3.13.13 (main, Apr 11 2026, 21:11:22) [Clang 19.1.7 (https://github.com/llvm/llvm-project.git llvmorg-19.1.7-0-gcd7080 on freebsd14
Type "help", "copyright", "credits" or "license" for more information.
>>> name = "ShelLuser"
>>> print(len(name))
9
>>> print(type(name))
<class 'str'>
>>> print(name)
ShelLuser
>>> dir(name)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>>

As you can see: this means that you can easily start it, after which you get onto a new command line from which you can do "Python stuff".

But that's not what makes this feature so interesting: this also means that you can easily add a shebang to your scripts (you know, something like: #!/usr/local/bin/python?), set the execution bit ( chmod +x myscript.py) and then you can just run 'm from your shell as if it were a 'real' program. Which obviously makes it a lot more usable and accessible for automating administrative processes rather than having to manually start runtime all the time (which you normally always need to do with Java).

However, don't be fooled by this simple looking example... even though Python can be awesome for coding scripts it's still a full fledged OOP environment as well. So, you can easily utilize broader logic by using classes and the likes.

Re-using code is very easy (and logical!)
Let's say I coded a script called getStats.py. It has a function called "usersOnlineNow()" which doesn't only list all currently online users, it also recognizes aliases of my closest friends and actively filters on them. So this might be something I want to re-use in some of my other scripts. When dealing with Java you're now getting into the territory of (virtual) packages which can become annoying to use because they rely on specific folder structures which represent the whole package (and its domain). For example: lan/intranet/getStats.java.

With Python otoh you can easily do this: import getStats, right from within the same folder where the original script resides. Or maybe: from getStats import usersOnlineNow, this would only make the function 'usersOnlineNow()' available in my new script; so everything else in getStats would be ignored.

And if you want to create (virtual) packages to combine several of your modules? Also easy: just create a new folder, optionally add a file called __init__.py to provide some documentation (and maybe some optional initialization routine(s)). Then add your modules (so: "the scripts which contain functions that you plan to re-use in other scripts"), and done! Now you can simply 'import' the folder name, or import individual modules or, as shown earlier, individual functions.

Documenting your code is really easy
In my opinion it's good practice to comment your code and/or shell scripts. Unfortunately many programming languages rely on other tools to take full advantage of such documentation. For example an IDE which can show you what a certain function or class is supposed to do. Well... Python supports this right out of the box, while also providing a nice tool which allows you to take more advantage of it.

First things first... Python has its own help system (!) which might not be a full substitute for reference documentation, but it can still be quite useful:

Code:
>>> help
Welcome to Python 3.13's help utility! If this is your first time using
Python, you should definitely check out the tutorial at
https://docs.python.org/3.13/tutorial/.

Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules.  To get a list of available
modules, keywords, symbols, or topics, enter "modules", "keywords",
"symbols", or "topics".

Each module also comes with a one-line summary of what it does; to list
the modules whose name or summary contain a given string such as "spam",
enter "modules spam".

To quit this help utility and return to the interpreter,
enter "q", "quit" or "exit".
However, this also leads to the question: where does this documentation come from? If you ask help about, say, the print() function then how does it get all that information? Well, from so called docstrings. A docstring is a line which is surrounded by three double quotes: """This is an example of a docstring in Python.""".

So here's where things get more interesting: if you provide such a docstring below any kind of definition then you'll automatically document whatever object you just defined. So... whenever you create (or define) a new function, (global) variable, class or a method: one docstring is all you need to provide documentation. And the best part? You can either use oneliners, or write out a small summary, there are no strict rules here.

And all those docstrings can then be automatically used by Python. Either within its help system (as shown above), or when using the pydoc utility: this can parse any Python script and print (or save) a summary, either in text or HTML format. Oh, and that pydoc utility? It's actually a Python script of its own ;)

Of course there are many more (awesome) reasons to start using Python (like being able to run a debugger straight from the command line!), but these are simply my personal favorites.

How to get started?​

Well, as one could imagine: Python is fully included (and supported) within the ports collection. At the time of writing you can pick between Python 3.10 all the way up to 3.14. SO... if you plan on building Python yourself then do take note of /usr/ports/Mk/bsd.default-versions.mk; right now the default version is 3.11, but you're fully free to use another of course. Just make sure to define this in your /etc/make.conf.

Which then begs the question: "What version should you pick?". When in doubt then I'd stick with the default, however... it might be useful to start by checking the active Python releases. Right now 3.12 seems like a more logical choice to me, also considering that it'll be supported for another 2 years. Or, if you want to go a little more "bleeding edge" but without the actual risks then 3.13 is also a solid choice.

Once you have this out of the way then it's time to install your pick. In addition I also suggest installing lang/python-doc-text and/or lang/python-doc-html because not only does this provide some solid information, it also includes the official Python tutorial which, in my opinion anyway, can be an awesome help & reference. Fun fact: this tutorial also seriously helped me out to get started with Python.

Oh, and of course you can also install binary packages if you'd like: # pkg install python313 python-doc-text for example.

Libraries

As you might know Python is a very popular programming language, and there are also a lot of people who spend their time coding API's and libraries which can help to make our lives a little easier. Many of these libraries are also available in the ports collection. Some examples:
  • www/py-scrapy => Solid web crawling framework which can make it (very) easy to build your own crawlers.
  • security/py-gnupg => Fancy utilizing GnuPG in your Python scripts? Well, this could be a great help for that.
  • devel/py-gitpython => Want to interact with Git to automate a few tasks? This could help...
  • www/py-playwright => Need to automate the testing of a website? This library can provide some awesome help.
The thing is though... while these libraries are certainly useful, there are other (and sometimes better) ways to utilize these. Of course, context is a thing here; what works for me might not necessarily work for you.

When you install some of these libraries then they become available system-wide: so, accessible for all your scripts. Usually this isn't much of a problem, but what if you need a specific version of a library, or maybe another variant? Or what if... some names overlap?

PIP
In case you're wondering: Pip Installs Packages ;)

Python has its own package manager called PIP, and as you might have guessed: it's even written in Python itself. And PIP can also be used to install packages like the ones I mentioned above. By default PIP is only accessible on FreeBSD once you set up a so called virtual environment:

Code:
peter@zefiris:/home/peter/temp $ pip
/usr/local/bin/ksh: pip: not found
peter@zefiris:/home/peter/temp $ python -m pip
/usr/local/bin/python: No module named pip
peter@zefiris:/home/peter/temp $ python -m venv fbsd-test
peter@zefiris:/home/peter/temp $ cd fbsd-test/
peter@zefiris:/home/peter/temp/fbsd-test $ . bin/activate
(fbsd-test) peter@zefiris:/home/peter/temp/fbsd-test $ python -m pip --version
pip 26.0.1 from /home/peter/temp/fbsd-test/lib/python3.13/site-packages/pip (python 3.13)
(fbsd-test) peter@zefiris:/home/peter/temp/fbsd-test $
So what is happening here... by using the venv module I told Python to set up a so called virtual environment: this is a separated environment with its own Python interpreter and its own Python libraries (or "modules"). And because it's separated you can install (and use) whatever library you want and without the risk of possibly disrupting other projects. For example... there are many good libraries available for cryptography, but I've become quite keen of pycryptodome:

Code:
(fbsd-test) peter@zefiris:/home/peter/temp/fbsd-test $ which pip
/home/peter/temp/fbsd-test/bin/pip
(fbsd-test) peter@zefiris:/home/peter/temp/fbsd-test $ pip install pycryptodome
Collecting pycryptodome
  Downloading pycryptodome-3.23.0.tar.gz (4.9 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.9/4.9 MB 43.8 MB/s  0:00:00
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: pycryptodome
  Building wheel for pycryptodome (pyproject.toml) ... done
  Created wheel for pycryptodome: filename=pycryptodome-3.23.0-cp37-abi3-freebsd_14_4_release_p1_amd64.whl size=1726115 sha256=85e8503b8cca046108f081d42c82f533c88e3151c4dc9d69aee5d53198ee9263
  Stored in directory: /home/peter/.cache/pip/wheels/29/eb/c7/c569c89bdc7331f61e744a1847d02798ce31bf1bd1cb13cb33
Successfully built pycryptodome
Installing collected packages: pycryptodome
Successfully installed pycryptodome-3.23.0
PyPi working its magic here :cool:

So now I have access to all the features of pycryptodome, but only within this virtual environment, and also only when it's activated (noticed the change in my prompt btw?):
Code:
(fbsd-test) peter@zefiris:/home/peter/temp/fbsd-test $ python -c "import Crypto"
(fbsd-test) peter@zefiris:/home/peter/temp/fbsd-test $ deactivate
peter@zefiris:/home/peter/temp/fbsd-test $ python -c "import Crypto"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
    import Crypto
ModuleNotFoundError: No module named 'Crypto'
See what I mean?

And now?
Well, if you followed my tutorial up to this point then you should have everything installed to start using Python. And the best way to learn... is to actually start using it. So... why not build yourself a Python script (remember the shebang!) and then try to make it "do" something? Seriously, the best way to learn a programming language is to simply start using it.

And there you have it!

Thanks for reading, I hope you guys found this useful.
 
(Didn't realize this was a howto, sorry for replying)

Very nice written!

I think Python is a lot of fun and with tkinter you can do GUI stuff as well if you just want a small local app.

I noticed you have gotten rid of Java - does that mean you don't use devel/pycharm as IDE :) ?

/grandpa
 
I think Python is a lot of fun and with tkinter you can do GUI stuff as well if you just want a small local app.

I noticed you have gotten rid of Java - does that mean you don't use devel/pycharm as IDE :) ?
When it comes to scripting / building for my servers then I basically rely on 2 editors: VS Code for my efforts on Windows and I'm also a die-hard vi user on the console (even up to a point where I have vi-mode enabled on my shell). So.. never used Pycharm, especially not on my servers (which don't use X11 at all).
 
I think Python is a lot of fun and with tkinter you can do GUI stuff as well if you just want a small local app.
Are you aware that tkinter is tk with the whole tcl inside python?
Why python with the whole tcl inside if you can do the same with tcl?

And I do not think installing is a problem, more interesting would be a short introduction to python, or well, to tcl.
 
Are you aware that tkinter is tk with the whole tcl inside python?
<snip>
And I do not think installing is a problem, more interesting would be a short introduction to python, or well, to tcl.
I agree, but in all fairness the focus of the forum is of course on FreeBSD. Still, now that it's fully clear that this thread specifically addresses Python I don't see any reason why I wouldn't be allowed to expand a little bit on the learning process later on ;)

But to answer your question here... I actually already gave an implied tip on how you could start with this. Recall my comments about Python's internal help system and also pydoc?

To be perfectly honest with you: it's only been a few days since I started digging into Python's graphical options myself (so: tkinter in specific), and to make this even 'worse' my servers don't have any graphical support (so Python's GUI options won't initialize) and I only mess around with a graphical interface on Windows.

Even so: if you run this command you'll get a full introduction for tkinter, and even some example code to set up your own "Hello World": pydoc tkinter |more.

And if you just want a quick peek into what tkinter would look like: python -m tkinter.

"To be continued".
 
Recall my comments about Python's internal help system and also pydoc?
Internal help have sense when the interpreter not only run in UNIX like systems with man command.
Also R has such an internal help system. It makes also sense because one interacts with the interpreter, not only program.
tcl, although it is also run in Windows, is fully documented in its man pages.

By the way, R is really worth to learn, the programming language is interesting, the lots of functions and packages very usable not only for statistics. It is not a scripting language more, as I would qualify python. It also embeds tcl/tk for GUI.
 
Welcome to the dark side...Python: the language I hate to love. LOL Virtually everything I do anymore is rapid-app-prototyped in python, then converted to c++ if need be, but usually the python app works well enough as-is...and python has bindings to just about every open source library in existence.

These are some of my favorite python shortcomings:
1) GIL means that python will never have true cpu bound concurrent multi-threading...attempts in that direction are pipe dreams
2) languages that are "indentation sensitive" make me grind my teeth.
3) implicit declaration of vars can/does lead to subtle errors...ALWAYS explicitly define your vars just as a safety-practice
4) the type-neutral nature of objects can lead to subtle side-effects that you always have to be on the lookout for
 

Part II => Putting things to some real use​

Hi gang,

Well, I promised that I was going to do a follow up, and now seems like a good time as any. Today we're going to dive more into Python and we'll take a closer look at how we can use it to "do" things on FreeBSD. We'll take a (brief?) look into the language itself, talk about some of the standards (and 'PEP') and I think that building ourselves a calculator can make for a fun example.

OOP: Object Oriented Programming

Before we're getting into the good stuff there's one very important detail I want to address first... Learning a specific programming language in todays world isn't necessarily the most important part anymore. I'm not kidding: what do languages like Java, Perl, C# and of course also Python: what do all of these have in common? Well, obviously OOP; otherwise I wouldn't make such a big fuss about this, eh? My problem here though is that OOP isn't just a fancy standard, it's a way of life when working with development: just as much as it is a standard it's also a thought process which you can use to help optimize your work.

The only problem.. it's also rather abstract and when you look it up you'll get bombarded with buzzwords. No, there's merit to those words but if you're new(ish) then I can easily imagine that it'll be a lot of "blah, blah, blah..." => "c'mon: show me the good stuff already!". Not to mention that I've met plenty of veteran programmers who still sometimes struggle a bit with all this.

So what IS OOP?

The kitchenware factory
So... imagine that we have this awesome kitchenware factory: they make forks, knives and spoons. The factory actually started out small, at first they were only making knives and it was a solid process: first there were some machines that made the handles, and also some machines which made the blades. Add those together and ... easy!

Eventually they expanded and started to produce forks and spoons as well, but ... where to start? They wanted to start producing as quickly as possible and figured that you probably shouldn't change a working formula. So they simply copied the process to make the handles and set that up in the new sections during which other teams started to make the machines to produce the fork and spoon heads.

This worked out perfectly! Soon they had 3 separate sections in the factory where knives, spoons and forks were made and because all three sections followed the same design pattern and also roughly the same process it was even quite easy to get new staff up to speed too.

...and then the brand name changed.

So now they had a small problem on their hands. You see: the company always engraved the company name in the handles. Yet now they had 3 sections where these handles were produced so now they had to redesign all 3 different machines. They didn't have enough staff on hand to change all of them at once, but the demand for their stuff also made it quite costly to just stop the full production line.

Here's the thing: they could also have opted to use one machine (or section!) to make all those handles and then distribute those to all the other areas which made the rest of the parts. Sure, the moment they wanted to make forks and spoons they would also need to upgrade ("expand") the production of handles, but even so: it could have saved them a lot of hassle in the longer run.

Copying the design was easier at that time, but ... it also led to a bit of an impasse. Of course this is a very simplistic story example; but that's not the point. My point here is to try and help you better understand some of those abstract OOP concepts. And to realize the importance of re-usability.

And believe it or not, but this is something you'd actually be facing when working with an OOP programming language like Python.

Now for some Python...​

One of the main reasons why I love working with Python is because of its flexibility. It's a full fledged OOP environment, yet generally speaking it doesn't really force anything on you. For example... when you start working with Java the first thing you need to do is define a class, then a method... if you want something simpler to begin with.. then you should have picked another language.

With Python otoh. you can literally start working on a simple script project, slowly expand the project with some modules (= "reusable code"), even interim ones! And when push comes to shove... it's also easy to define packages ("module collections"?) and start working with full on classes whenever you need it.

A calculator script

Let's start with a small, simple, script example which adds up 2 numbers. Well... it tries to:
Python:
#!/usr/local/bin/python

a = input("Please enter a number: ")
b = input("Please enter a 2nd number: ")
c = a + b

print("The sum of your numbers is: " + c)
I left the 'shebang' in with this first example, but from now on I'll only be showing the actual Python code. This is yet another example of why I really enjoy working with Python on FreeBSD: I just set up the script, give it an execution bit and I'm all set:

peter@zefiris:/home/peter/temp $ ./calc.py
Please enter a number: 5
Please enter a 2nd number: 20
The sum of your numbers is: 5 20
Weird, huh? The script seems simple enough I think: I'm asking the user to enter 2 numbers and then I just add them up for them. I think it's logical enough, but as you can see it doesn't quite work as I want.

Now, to me it's obvious what is going on here: for "some reason" Python didn't treat my variables like numbers but as text. It literally added the text (or: string) '20' to the other text of '5'. Resulting in the literal mention of "5 20".

Variables & 'casting'

As simple as that script may look to you, there's actually a lot going on in there:
  • We can use variables to 'store' some kind of value for us which we can then re-use later.
    • BUT: variables in Python get assigned dynamically. In other words: I don't have to first define a type for them like integer, string, float, etc. but instead I can just assign a value and let Python handle the rest.
  • When you use "Python commands" you're mostly using internal ("builtin") keywords, functions and methods. That input() line? Well, you're actually using ("calling") a so called function, and functions often return a specific type of variable.
    • As mentioned before: variables can be of a different type depending on what we use 'm for. Integer implies that you're working with whole numbers, a string would imply that you're working with text whereas a float means that we're working with numbers that have a decimal point; so 'extra value' behind the comma.
Now let's get back to our small calculator... The solution seems simple enough: we just have to tell Python to treat our inputs as numbers, integers:

Python:
a = input("Please enter a number: ")
b = input("Please enter a 2nd number: ")
c = int(a) + int(b)

print("The sum of your numbers is: " + c)
In case you're wondering: int() is an actual class but it's a little bit too early to be talking about those right now. What we're doing here is often described as casting: we're changing one type of variable straight into another! We already determined that input() sends back string ("str") type variables. So we're now simply telling Python: "Ey, turn that into a number!" (or: integer).

Now, 2 things... remember my spiel about OOP and designs and what not? See, I could have handled this issue in different ways. I could also have simply used int() in combination with input() to make sure we'd always be working with numbers. Yet I didn't, and there's a good reason why: KISS. In other words: "Keep It Simple, Stupid". In combination with OOP design, of course.

My design here is quite clear: I have one section in my script which handles the input, and then another (even if this is but one single line!) ... so then I have another 'section' which "handles" the value(s). In other words: even in these early stages I try to keep things "separated", which makes it easier to read but also easier to "fix" or "enhance" if/when needed later on. You'll see.

Houston, we have a problem!
peter@zefiris:/home/peter/temp $ ./calc.py
Please enter a number: 5
Please enter a 2nd number: 20
Traceback (most recent call last):
File "/home/peter/temp/./calc.py", line 7, in <module>
print("The sum of your numbers is: " + c)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~
TypeError: can only concatenate str (not "int") to str
So the good news is that we obviously succeeded: we're now clearly working with numbers ("integers"). The bad news, as you can see here, is that we now have a new problem with our print() statement because we're now trying to "add" (or combine?) strings and integers together, and Python doesn't seem to like this very much.

The formatted string ("f-string")

Fortunately for us there's an easy fix here, and that's called the f-string, another one of my favorite Python features. When you're working with literal string values (so: any text that's between double quotes) then there's an easy way to tell Python that it should allow us to embed variables within our line of text.

And we do that by defining this as an "f-string", it's really super easy:
Python:
print(f"The sum of your numbers is: {c}")

So: just adding the letter f right in front of my string allows me to embed variables between curly brackets, and that tells Python to parse those variables and, well, display them. The result should be obvious:
peter@zefiris:/home/peter/temp $ ./calc.py
Please enter a number: 5
Please enter a 2nd number: 20
The sum of your numbers is: 25
Neat, huh?

Never make assumptions though!​

Once again we're diving into something that's not specifically related to Python but rather coding in general, but even so... Python does provide us with some pretty nifty tools to help us out.

Anyway, I proudly showed off my script to a friend of mine and sure enough... the first reaction I got was: "it's bork!!". When asking what was wrong they told me that "it doesn't work properly" which is weird, because ... well, look above? Works perfectly, right?

PS D:\temp> py .\calc.py
Please enter a number: 5
Please enter a 2nd number: twintig
Traceback (most recent call last):
File "D:\temp\calc.py", line 3, in <module>
c = int(a) + int(b)
~~~^^^
ValueError: invalid literal for int() with base 10: 'twintig'
In case you're wondering: "twintig" is Dutch for "twenty". Oh, dear... Window users, right? ;)

But it does raise a fair point I think: error handling, we basically assumed that a user would only input numbers. But what if they don't? Fortunately for us we have plenty of options to fix this. You see... when you're working with an OO language then most of the "items" you work with are actually virtual objects, and objects tend to have plenty of "members" to 'handle' them further.

In other words: the moment you define a string ("str") you basically have a "string class" on your hands, which also provides plenty of methods to "do" something with that string. How about... using isnumeric() to help us check if we're actually working with numeric values.. We'll just use some "if statements" to check, and we'll be all set!

Python:
a = input("Please enter a number: ")

if (a.isnumeric() == False):
        print("You didn't enter a valid number, please try again...")
        exit()
else:
        b = input("Please enter a 2nd number: ")
        if (b.isnumeric() == False):
                print("You didn't enter a valid number, please try again...")
                exit()
        else:
                c = int(a) + int(b)
                print(f"The sum of your numbers is: {c}")
Technically speaking... this script 'works'. But the harsh reality is that this script has actually become a complete mess. Remember my story about the factory and those different machines which all the did the same thing? Well, that's what you see happening right here as well.

But there's also something to learn here. As you can see I made sure to apply indentation in my code. I didn't do that because I think things look better (I actually do, but that's besides the point), no: the main reason I did this is because this is mandatory with Python. If you want to make it clear that some code should be grouped together... then you need to use indenting.

Even so... it may look nice, but as said: this code is no good.

A (desperate!) need for functions!

First: if you see the same lines of code get repeated and re-used multiple times then that's usually not good. I mean, what if we want to change this code somewhere in the future? What if we're actually going to accept words and parse those? Then we'd have to rewrite the whole routine again, multiple times even! And that's hoping that we'll still remember that some routines were copied.

Second problem: this code is also very hard to read right now, when you start 'nesting' checks very deeply then you should probably look for cleaner solutions. I mean... "If 'a' isn't a non-numeric value then we'll check if 'b' isn't a non-numeric value after which we'll do the calculation". It's just messy: "if a is not, not a numeric, then...".

Not to mention that we're doing the exact same check on both variables; there is no excuse here: that code snippet needs to be separated so that we can more easily re-use it. And the best way for that.. is to use a function. In fact... while we're at it, why don't we do the same thing for the whole input routine as well?

Python:
def ask_number(vraag):
        x = input(f"Please enter {vraag}: ")
        if (x.isnumeric()):
                return int(x)
        else:
                print("You didn't enter a valid number, please try again...")
                exit()

a = ask_number("a number")
b = ask_number("a 2nd number")

print(f"The sum of your numbers is: {a + b}")
See what I mean? I pretty much managed to cut the script in half. Not only that: it's much easier to read now as well. By using 'def' I told Python that I wanted to define a function: a separate snippet of code with its own name, a name which I can then re-use later on. A bit comparable to functions in shell scripts.

Because I'm using a string to ask the user for input there's no reason why I wouldn't turn that into an f-string as well, this allows me to set up the question a little more dynamically.

But there's more: my "ask_number()" routine ("function") also looks like something I might also be able to (re)use in another script of mine. You see.. here I am adding up 2 numbers, but I've also been secretly experimenting with... (drumroll) => subtractions! :rolleyes:

Now, I suppose I could just copy&paste that function snippet right into my 2nd script but... copying & pasting isn't exactly the best practice while coding. Instead, I'm just going to tell Python that I want to use this part of my original script:

Python:
from calc import ask_number as ask

a = ask("first number")
b = ask("second number")

print(f"{a} minus {b} equals: {a - b}")
So now I have 2 scripts: calc.py (which I showed earlier), and I made this second script called subtract.py and placed it in the same folder. First I told Python that I only want to use the function in this script, and it should be named "ask".

Let's see what happens:

peter@zefiris:/home/peter/temp $ ./subtract.py
Please enter a number: 5
Please enter a 2nd number: 4
The sum of your numbers is: 9
Please enter first number: 5
Please enter second number: 4
5 minus 4 equals: 1
That's no good... now it somehow ran both script in sequence. But that's not what I wanted?!

Making our first module

But no worries, once again this makes perfect sense. You see... by using import I literally asked Python to load and parse my other script so that it can pick up on this method. However... my script is an actual script which also "does" something right after you start it. Yet in this case I actually want to use it more like a module rather than a script. In other words: when I'm using 'import' then it doesn't need to "do" anything, all I want is to re-use its main function.

Fortunately I can... you see, there's a little trick involved with a system variable called __name__. This is a system ("builtin") variable which gets the name of the main routine ("session") which is running the scripts. When I "just" fire up a script then this variable will get a very specific name: __main__. But when you re-use a script by using import then all of a sudden this variable gets the name of the currently active script, because at that time that's the currently active session.

Let me show you what I mean... I'm going to add a 2nd print statement within my calc.py script:

Python:
print(f"The sum of your numbers is: {a + b}")
print(f"My name is currently: {__name__}.")
Now let's see what happens:

peter@zefiris:/home/peter/temp $ ./calc.py
Please enter a number: 5
Please enter a 2nd number: 4
The sum of your numbers is: 9
My name is currently: __main__.
peter@zefiris:/home/peter/temp $ ./subtract.py
Please enter a number: 5
Please enter a 2nd number: 4
The sum of your numbers is: 9
My name is currently: calc.
Please enter first number: 5
Please enter second number: 4
5 minus 4 equals: 1
See? First it's __main__, but then it turned into calc.

Now this is something which we can use to ensure that our script only gets fired up when we want it to. We're going to add another function and also a little check:

Python:
def ask_number(vraag):
        x = input(f"Please enter {vraag}: ")
        if (x.isnumeric()):
                return int(x)
        else:
                print("You didn't enter a valid number, please try again...")
                exit()

def _main():
        a = ask_number("a number")
        b = ask_number("a 2nd number")

        print(f"The sum of your numbers is: {a + b}")

if (__name__ == "__main__"): _main()
See? This will make sure that the routines in our calc.py script will only get fired up when we actually start the script itself. But the moment this script gets parsed from merely using an import statement somewhere else then this _main() function will get fully ignored, simply because the __name__ variable doesn't match anymore.

What's with all the _'s?​

Last but not least... you've seen a lot of _ characters being used so far, and there's actually a good reason for all that. Whenever a variable is surrounded by 2 underscores (like __name__) then this indicates that you're working with a system variable. So something build into the Python system itself. Some other examples are: __doc__, __file__, __package__, __spec__, and so on. In case you're interested then the dir() function is an awesome way to study this a little bit more.

Second... as soon as you use a single _ character in front of a name then that indicates that we're dealing with a "hidden" item. It's not really hidden of course, but the name indicates that it's something which should only be used within the current "scope" itself.

In other words: In my previous example it is perfectly fine to import ask_number() but trying to import _main() would be frowned upon as bad practice because its name indicates that it wasn't meant to be used like that.

End of Part II, "to be continued..."​

And that concludes part 2 of my tutorial. Thanks for reading, I hope this was useful for some of you.

In the next part we're going to be looking at documenting your code ("docstrings"), a better way to group some of our modules together using "packages", we'll be looking at some better ways to do "flow control" (there's more than asking "if... then") and we'll also briefly check out how we can debug some of our code. Any exceptions (lol)?

Anywhoo, see you next time ;)
 
Back
Top