Skip to the content.
Home | Blog | Book Reading |About | All Courses

Executing a Python script


Consider this simple Python script: apples.py

count = 5
fruit = "\N{RED APPLE}"
print(fruit * count)

The expected output of this simple Python script โ€œapples.pyโ€ is

๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ

To execute this Python script on a Unix like shell ( on Mac OS, Ubuntu or other Linux like systems ) one can use this command:

 $ python3 apples.py
   ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ

Note: The above command assumes that โ€œapples.pyโ€ file is present in the current folder where you are running this command. Otherwise, we would have to specify either its absolute or relative path instead.

Directly executing Python script as a command

On Linux ( and other Unix like systems ) there is another way to execute such scripts. We need to give execute permissions to this script and also somehow tell Bash shell to use โ€œpythonโ€ interpreter to execute this script.

The second part is done using a โ€œShebangโ€ statement that is written on the first line of the script starting from the first column as show below:

#!/usr/bin/python3  # <-- This line is called the "Shebang" line
count = 5
fruit = "\N{RED APPLE}"
print(fruit * count)

Note: The word โ€œShebangโ€ is a combination of โ€œsharpโ€ for โ€œ#โ€ ( mostly coming from the Music notation ) and โ€œbangโ€ ( short for hash bang, the name for !). But, there is no evident proof for this though, apart from Wikipedia.

The Shebang line contains the path of the interpreter to be used to execute this script. After adding the Shebang we need to give execute permissions to the script and directly execute it as shown below:

 $ chmod u+x apples.py  # <--- Giving execute permissions to the script
 $ ./apples.py          # <--- Executing the script using it's relative path
   ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ
 $

Virtual Environments

What if I share this script with a friend who has python3 present in some other location ? May be in /usr/share/bin/python3 ?

Or, what if we need to execute this script using the python interpreter in the currently active virtual environment ?

Using the above Shebang would make the shell always use the Python interpreter mentioned in the Shebang, viz., /usr/bin/python3

The trick comes from the old Bash script days of using โ€œenvโ€ command to pick the correct command from the present environment variables / settings ( which is sort of a hack that assumes env is mostly present at a specific location in all systems. But, this is fine since โ€œenvโ€ command is usually present in the same location on most of the modern Unix like systems )

So, the new modified Shebang now looks like

#!/usr/bin/env python3  # <-- Ask env to pick appropriate python3 binary
count = 5
fruit = "\N{RED APPLE}"
print(fruit * count)

Here, the env command picks the python3 binary available in the existing Shell environment. This makes the script portable and can now be executed on any system or any virtual environment without worrying about the correct path for python3.

What about using this with the modern โ€œuvโ€ tool ?

The problem with running a Python script on any (client or customer) machine is that they need to have Python installed. And that too the correct version that the script expects ( based on the features that are used in it ). Apart from this, there is also the problem of dependencies. The system where we are planning to execute our Python script needs to have all the dependent modules installed. And they also need to be of the correct versions.

โ€œuvโ€ tries to solve this by adding some inline metadata to the script that mentions the dependencies for the script. This is in accordance with PEP 723 where this support in Python scripts was introduced.

Letโ€™s assume that โ€œapples.pyโ€ script depends on โ€œnumpyโ€ module. To mention this dependency in the inline metadata, we need to execute the following โ€œuvโ€ command:

 $ uv add --script apples.py 'numpy'

This modifies the โ€œapples.pyโ€ script with the inline metadata added at the start of the script as shown below :

# /// script                    # <--- inline metadata starts here
# requires-python = ">=3.12"
# dependencies = [
#     "numpy",
# ]
# ///                           # <--- inline metadata ends here

count = 5
fruit = "\N{RED APPLE}"
print(fruit * count)

And we can now use this โ€œuvโ€ command to execute โ€œapples.pyโ€

 $ uv run apples.py
   Installed 1 package in 18ms
   ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ

The above command takes care of installing the correct version of Python and the dependent modules in a temporary location on the customerโ€™s sytem and then execute the Python commands in that script

If we do not want to display the message about installing packages, then we can use the โ€œโ€“quietโ€ switch as shown below:

 $ uv run --quiet apples.py
   ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ

Combining โ€œuvโ€ with โ€œShebangโ€

Running the โ€œuvโ€ command as above lands us into the same problem of asking customers to run a command ( instead of executing just the script ) to trigger the execution of our script. We could solve it using the Shebang trick we saw earlier as mentioned in this post

We can now use the following Shebang to directly launch this script using โ€œuvโ€ command:

#!/usr/bin/env -S uv run --script   # <--- Ask env to pick "uv" binary
# /// script                        # <--- inline metadata starts here
# requires-python = ">=3.12"
# dependencies = [
#     "numpy",
# ]
# ///                               # <--- inline meatadata ends here

count = 5
fruit = "\N{RED APPLE}"
print(fruit * count)

The โ€œenvโ€ command in the Shebang is almost the same as we saw earlier, except for the โ€œ-Sโ€ switch. This switch as mentioned in the man pages allows us to pass multiple arguments to the Shebang command. In this case, using the โ€œ-Sโ€ switch now allows us to properly execute โ€œuvโ€ command with โ€œrunโ€ and โ€œโ€“scriptโ€ arguments passed on to the โ€œuvโ€ command as switches to it.

Note: This โ€œ-Sโ€ switch might behave differently on a a Mac system which uses BSD Unix. The Shebang works on a Mac OS irrespective of providing the โ€œ-Sโ€switch.

And we would execute the โ€œapples.pyโ€ script as :

 $ ./apples.py
   Installed 1 package in 18ms
   ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ

And as we saw earlier, if we do not want to display the message about installing packages, then we could use the โ€œโ€“quietโ€ switch as shown below:

#!/usr/bin/env -S uv run --script --quiet   # Masks the output of "uv"
# /// script                            # <--- inline metadata starts here
# requires-python = ">=3.12"
# dependencies = [
#     "numpy",
# ]
# ///                                   # <--- inline meatadata ends here

count = 5
fruit = "\N{RED APPLE}"
print(fruit * count)

And when we execute this script, we would get the output as:

 $ ./apples.py
   ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ

Conclusion

This way, we can have a self dependent Python script execute on its own, irrespetive of whether the customer has the correct Python version installed or not.

But, this works only on Unix like machines. I am not sure how something like this can be done on non-Unix like machines.

Ofcourse, the problem now moves from the customer having โ€œpythonโ€ installed on their systems to them having โ€œuvโ€ installed on their system for executing our Python script. But, with the growing popularity of โ€œuvโ€ it can be safe to assume that most customers would have โ€œuvโ€ pre-installed on their system, as we earlier assumed that they would have โ€œbashโ€ or โ€œenvโ€ pre-installed on their systems in the right locations.