0

My package has the following structure:

mypackage
|-__main__.py
|-__init__.py
|-model
  |-__init__.py
  |-modelfile.py
|-simulation
  |-sim1.py
  |-sim2.py

The content of the file __main__.py is

from mypackage.simulation import sim1

if __name__ == '__main__':
    sim1

So that when I execute python -m mypackage, the script sim1.py runs. Now I would like to add an argument to the command line, so that python -m mypackage sim1 runs sim1.py and python -m mypackage sim2 runs sim2.py.

I've tried the follwing:

import sys
from mypackage.simulation import sim1,sim2

if __name__ == '__main__':
    for arg in sys.argv:
        arg

But it runs boths scripts instead of the one passed in argument.

In sim1.py and sim2.py I have the following code

from mypackage.model import modelfile

print('modelfile.ModelClass.someattr')
DeepNet
  • 161
  • 2
  • 11

3 Answers3

1

You can simply call __import__ with the module name as parameter, e.g.:

new_module = __import__(arg)

in your loop.

So, for example, you have your main program named example.py:

import sys

if __name__ == '__main__':
    for arg in sys.argv[1:]:
        module=__import__(arg)
        print(arg, module.foo(1))

Note that sys.argv[0] contains the program name.

You have your sim1.py:

print('sim1')

def foo(n):
    return n+1

and your sim2.py:

print('sim2')

def foo(n):
    return n+2

then you can call

python example.py sim1 sim2

output:

sim1
sim1 2
sim2
sim2 3
keyboarder
  • 36
  • 3
  • `if __name__ == '__main__': for arg in sys.argv: __import__(arg)` and calling `python -m mypackage sim1` returns a `ModuleNotFoundError` – DeepNet May 05 '21 at 10:56
  • You have to take about the parameters, because the first parameter is the program name, so you should skip that. I include this in more detail in the answare. – keyboarder May 05 '21 at 11:08
  • This still produces a `ModuleNotFoundError` – DeepNet May 05 '21 at 11:35
  • You need to assign the result of `__import__` to the global name space to have the symbol name exposed as a module. See my answer for an example. The code in this answer simply assigns it to `new_module`, or, in later examples, simply discards it. But if all the useful action happens when you `import`, being able to refer to the module by its original name seems less important anyway. – tripleee May 05 '21 at 11:51
0

Suppose you have you files with following content.


sim1.py

def simulation1():
    print("This is simulation 1")

simulation1()

main.py

import sim1

sim1.simulation1()

output

This is simulation 1
This is simulation 1

When you import sim1 into main.py and calls its function simulation1, then This is simulation 1 gets printed 2 times. Because, simulation1 is called inside sim1.py and also in main.py.

If you want to run that function in sim1.py, but don't want to run when sim1 is imported, then you can place it inside if __name__ == "__main__":.


sim1.py

def simulation1():
    print("This is simulation 1")

if __name__ == "__main__":
    simulation1()

main.py

import sim1

sim1.simulation1()

output

This is simulation 1
nobleknight
  • 755
  • 6
  • 15
0

Your code doesn't do what you want it to do. Just sim1 doesn't actually call the function; the syntax to do that is sim1().

You could make your Python script evaluate random strings from the command line as Python expressions, but that's really not a secure or elegant way to solve this. Instead, have the strings map to internal functions, which may or may not have the same name. For example,

if __name__ == '__main__':
     import sys
     for arg in sys.argv[1:]:
         if arg == 'sim1':
            sim1()
         if arg == 'mustard':
            sim2()
         if arg == 'ketchup':
            sim3(sausages=2, cucumber=user in cucumberlovers)
         else:
            raise ValueError('Anguish! Don\'t know how to handle %s' % arg)

As this should hopefully illustrate, the symbol you accept on the command line does not need to correspond to the name of the function you want to run. If you want that to be the case, you can simplify this to use a dictionary:

if __name__ == '__main__':
    import sys
    d = {fun.__name__: fun for fun in (sim1, sim2)}
    for arg in sys.argv[1:]:
        if arg in d:
            d[arg]()
        else:
            raise ValueError('Anguish! etc')

What's perhaps important to note here is that you select exactly which Python symbols you want to give the user access to from the command line, and allow no others to leak through. That would be a security problem (think what would happen if someone passed in 'import shutil; shutil.rmtree("/")' as the argument to run). This is similar in spirit to the many, many reasons to avoid eval, which you will find are easy to google (and you probably should if this is unfamiliar to you).

If sim1 is a module name you want to import only when the user specifically requests it, that's not hard to do either; see importing a module when the module name is in a variable but then you can't import it earlier on in the script.

if __name__ == '__main__':
    import sys
    modules = ['sim1', 'sim2']
    for arg in sys.argv[1:]:
        if arg in modules:
            globals()[arg] = __import__(arg)
        else:
            raise ValueError('Anguish! etc')

But generally speaking, modules should probably only define functions, and leave it to the caller to decide if and when to run them at some time after they import the module.

Perhaps tangentially look into third-party libraries like click which easily allow you to expose selected functions as "subcommands" of your Python script, vaguely similarly to how git has subcommands init, log, etc.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • I am not sure why you're assuming `sim1` to be a function, to me `sim1.py` it is simply a module without a `sim1()` function in it – DeepNet May 05 '21 at 11:02
  • Oh, right, if your packages run some code immediately when you `import` them, that explains your arrangement; but then you have to `import` the package you want only when you have decided which one you want. I'll add a brief update to cover this scenario. (Then @keyboarder's answer is the one you need, though they should probably explain in more detail why.) – tripleee May 05 '21 at 11:07