1

Over the years I have written a few hundred functions in matlab for space engineering purposes which I use on a daily basis. They are all nicely put in one folder ordered in subfolders, and in matlab I just have an addpath() command for the root folder in the startup.m file, and then I can use any of the functions right away after starting up matlab.

I am now trying to do the same in python.

As far as I understand in python I shouldn't have 1 file for every function like in matlab, but rather bundle all the functions together in 1 py file. Is this correct? I am trying to avoid this, since I have a strong preference for short scripts rather than 1 huge one, due to it being way more intuitive for me that way.

And then, once I have all my python scripts, can I place them anywhere in order to use them? Because I read that python works differently than matlab in this aspect, and scripts need to be in the working directory in order to import them. However, I want to load the scripts and be able to use them regardless of my active working directory. So I am guessing I have to do something with paths. I have found that I can append to pythonpath using sys.path.insert or append, however this feels like a workaround to me, or is it the way to go?

So considering I have put all my rewritten matlab fuctions in a single python file (lets call it agfunctions.py) saved in a directory (lets call it PythonFunctions). The core of my startup.py would then be something like (I have added the startup file to PYTHONSTARTUP path):

# startup.py
import os, sys
import numpy as np
import spiceypy as spice

sys.path.append('C:\Users\AG5\Documents\PythonFunctions')
import agfunctions as ag

Does any of this make sense? Is this the way to go, or is there a better way in python?

Wolfie
  • 27,562
  • 7
  • 28
  • 55
AG5
  • 11
  • 1
  • 1
    Having all functions in separate files is a good idea. Consider creating your own library (called 'package' in Python). See e.g. [this page](https://packaging.python.org/tutorials/packaging-projects/). One thing to consider is that you probably want to share code with other people who have their harddisk organized in another way. – JohanC Nov 12 '19 at 10:10
  • But if I have separate files, I have to add a "import foo.py" for every function in the startup.py file right? I indeed looked up packaging, and considered it. However, I very often modify my functions (get rid of bugs, update algorithms, etc) which is why I figured a package would not be the best option for me as I would have to build it rather than just updating the script and save. Also, I do not intend to share my code, which is also why a package is problably not the way to go for me (I think?) – AG5 Nov 12 '19 at 10:16

2 Answers2

2

Well, the python package is probably the best way to solve your problem. You can read more here. Python packages have no need to be built and not always are created for sharing, so do not worry.

Assume you had this file structure:

Documents/
  startup.py
  PythonFunctions/
    FirstFunc.py
    SecondFunc.py

Then you can add file __init__.py in your PythonFunctions directory with next content:

__all__ = ['FirstFunc', 'SecondFunc']

Init file must be updated if you change filenames, so maybe it isn't best solution for you. Now, the directory looks like:

Documents/
  startup.py
  PythonFunctions/
    __init__.py
    FirstFunc.py
    SecondFunc.py

And it's all - PythonFunctions now is a package. You can import all files by one import statement and use them:

from PythonFunctions import *

result_one = FirstFunc.function_name(some_data)
result_two = SecondFunc.function_name(some_other_data)

And if your startup.py is somewhere else, you can update path before importing as next:

sys.path.append('C:\Users\AG5\Documents')

More detailed explanations can be found here

  • Thanks for your answer. I've been trying what you mention, but it's not working. I am testing with only 1 function now to make my life easier. The only thing I have is a single file (except the `__init__.py`) called with the same name as the parent directory to make things easier, so the folder `PythonFunctions` contains `PythonFunctions.py`. It now only contains this: `def test(): print("hello")`. When I call `pf.test()` it gives me `AttributeError: module 'PythonFunctions' has no attribute 'test'`. Any ideas? – AG5 Nov 12 '19 at 13:34
  • @AG5 You need to call the function as `pf.PythonFunctions.test()`. General syntax is: `..`. – Oleksii Bunyk Nov 12 '19 at 13:41
  • Thanks for your reply Oleksii. I'm aware of that, but it still doesn't work. Going back to the very roots: I have a folder called PythonFunctions, that contains a file called Hello.py that only has the following code `def World(): print("Hello World")`, when I run pf.Hello.World() I get `AttributeError: module 'PythonFunctions' has no attribute 'Hello'`, ideas? – AG5 Nov 12 '19 at 14:05
  • You didn’t add the `__init__` file as explained in the answer, and confused as to why it doesn’t work? Please follow the instructions in the answer. – Cris Luengo Nov 12 '19 at 14:09
  • The `__init__.py` is in the folder. – AG5 Nov 12 '19 at 14:12
  • @AG5 Could you please provide your import statement? – Oleksii Bunyk Nov 12 '19 at 14:20
  • I just have `import PythonFunctions as pf` as you also mentioned in your comment. But I've tried a few things, and it only works if I type `import PythonFunctions.Hello`, then I can correctly call `PythonFunctions.Hello.World()`. How come the `import PythonFunctions` command does not make it possible to use all the functions from the PythonFunctions folder? – AG5 Nov 12 '19 at 14:26
  • @AG5 I missed really hard. Sorry. My import statement really was not working, as `__init__.py` shouldn't be empty if someone really wants to use one import statement. Please look into updated solutions as well as into last link. – Oleksii Bunyk Nov 12 '19 at 14:58
  • Thanks for the update. So basically this means I am better off putting literally everything into just 1 file, right? Such that I can just call `pf.functionName`, rather than having to remember in which file I saved a specific function such as `pf.someName.functionName` which basically just adds 1 layer of complexity. Still this is not really ideal since I have a lot of functions spread out over different topics and it's just messy to have them all in 1 file. – AG5 Nov 12 '19 at 15:06
  • Well, python's "Explicit better than implicit" suggests using long but understandable names, and a lot of import statements. One file, or many - it depends. As for me, one function for one file - it is an overkill. Better to group them by some criteria, and then import specifically by using `import PythonFunctions.FourierTranformation as fourier' and use long naming. Or create suggested and use a slightly shorter ones `__init__.py` – Oleksii Bunyk Nov 12 '19 at 15:19
0

There is no need to write all your matlab functions within one file. You can also keep (kind of) your desired folder structure of your library. However, you should write all functions in the deepest subfolder into one *.py file.

Suppose your MATLAB library is in the folder space_engineering and is set up like this:

space_engineering\
    subfolder1\
        functiongroup1\
            printfoo.m
            printbar.m
    subfolder2\
        ...
    subfolder3\
        ...
    ...

After doing addpath(genpath('\your_path\space_engineering')) all functions in subfolders subfolder* and functiongroup* are available in the global namespace like this

>> printfoo    % function printfoo does only do fprintf('foo')
foo

I understand this is an behaviour your want to preserve in your migrated python library. And there is a way to do so. Your new python library would be structured like this:

space_engineering\
    __init__.py
    subfolder1\
        __init__.py
        functiongroup1.py
        functiongroup2.py
    subfolder2\
        __init__.py
        ...
    subfolder3\
        __init__.py
        ...
    ...

As you can see the deepest subfolder layers functiongroup* of the MATLAB structure is replaced now by functiongroup*.py files, so called modules. The only compromise you have to allow for is that functions printfoo() and printbar() are now defined in this .py modules instead of having individual .py files.

# content of functiongroup1.py
def printfoo():
    print(foo)

def printbar():
    print(bar)

To allow for doing the same function calling as in MATLAB you have to make the function names printfoo and printbar available in the global namespace by adjusting the __init__.py files of each subfolder

# content of space_enginieering\subfolder1\__init__.py
from .functiongroup1 import *
from .functiongroup2 import *

as well as the __init__.py of the main folder

# content of space_engineering\__init__.py
from .subfolder1 import *
from .subfolder2 import *
from .subfolder3 import *

The from .functiongroup1 import * statement loads all names from module functiongroup1 into the namespace of subfolder1. Successively from .subfolder1 import * will forward them to the global namespace.

Like this you can do in an python console (or in any script) e.g.:

>>> sys.path.append('\your_path\space_engineering')
>>> from space_engineering import *
>>> printfoo()
foo

This way you can use your new python library in the same way as you former used the MATLAB library.


HOWEVER: The usage of from xyz import * statement is not recommend in python (see here why, it is similar to why not using eval in MATLAB) and some Pythonistas may complain recommending this. But for your special case, where you insist on creating a python library with MATLAB like comfort, it is a proper solution.

gehbiszumeis
  • 3,525
  • 4
  • 24
  • 41