Using a dictionary to select function to execute

144,222

Solution 1

Not proud of it, but:

def myMain(key):
    def ExecP1():
        pass
    def ExecP2():
        pass
    def ExecP3():
        pass
    def ExecPn():
        pass 
    locals()['Exec' + key]()

I do however recommend that you put those in a module/class whatever, this is truly horrible.


If you are willing to add a decorator for each function, you can define a decorator which adds each function to a dictionary:

def myMain(key):
    tasks = {}
    
    def task(task_fn):
        tasks[task_fn.__name__] = task_fn
    
    @task
    def ExecP1():
        print(1)
    @task
    def ExecP2():
        print(2)
    @task
    def ExecP3():
        print(3)
    @task
    def ExecPn():
        print(4)
    
    tasks['Exec' + key]()

Another option is to place all the functions under a class (or in a different module) and use getattr:

def myMain(key):
    class Tasks:
        def ExecP1():
            print(1)
        def ExecP2():
            print(2)
        def ExecP3():
            print(3)
        def ExecPn():
            print(4)
    
    task = getattr(Tasks, 'Exec' + key)
    task()

Solution 2

Simplify, simplify, simplify:

def p1(args):
    whatever

def p2(more args):
    whatever

myDict = {
    "P1": p1,
    "P2": p2,
    ...
    "Pn": pn
}

def myMain(name):
    myDict[name]()

That's all you need.


You might consider the use of dict.get with a callable default if name refers to an invalid function—

def myMain(name):
    myDict.get(name, lambda: 'Invalid')()

(Picked this neat trick up from Martijn Pieters)

Solution 3

Simplify, simplify, simplify + DRY:

tasks = {}
task = lambda f: tasks.setdefault(f.__name__, f)

@task
def p1():
    whatever

@task
def p2():
    whatever

def my_main(key):
    tasks[key]()

Solution 4

# index dictionary by list of key names

def fn1():
    print "One"

def fn2():
    print "Two"

def fn3():
    print "Three"

fndict = {"A": fn1, "B": fn2, "C": fn3}

keynames = ["A", "B", "C"]

fndict[keynames[1]]()

# keynames[1] = "B", so output of this code is

# Two

Solution 5

This will call methods from dictionary

This is python switch statement with function calling

Create few modules as per the your requirement. If want to pass arguments then pass.

Create a dictionary, which will call these modules as per requirement.

    def function_1(arg):
        print("In function_1")

    def function_2(arg):
        print("In function_2")

    def function_3(fileName):
        print("In function_3")
        f_title,f_course1,f_course2 = fileName.split('_')
        return(f_title,f_course1,f_course2)


    def createDictionary():

        dict = {

            1 : function_1,
            2 : function_2,
            3 : function_3,

        }    
        return dict

    dictionary = createDictionary()
    dictionary[3](Argument)#pass any key value to call the method
Share:
144,222
JohnnyDH
Author by

JohnnyDH

Updated on July 05, 2022

Comments

  • JohnnyDH
    JohnnyDH almost 2 years

    I am trying to use functional programming to create a dictionary containing a key and a function to execute:

    myDict={}
    myItems=("P1","P2","P3",...."Pn")
    def myMain(key):
        def ExecP1():
            pass
        def ExecP2():
            pass
        def ExecP3():
            pass
            ...
        def ExecPn():
            pass  
    

    Now, I have seen a code used to find the defined functions in a module, and I need to do something like this:

        for myitem in myItems:
            myDict[myitem] = ??? #to dynamically find the corresponding function
    

    So my question is, How do I make a list of all the Exec functions and then assign them to the desired item using the a dictionary? so at the end I will have myDict["P1"]() #this will call ExecP1()

    My real problem is that I have tons of those items and I making a library that will handle them so the final user only needs to call myMain("P1")

    I think using the inspect module, but I am not so sure how to do it.

    My reason to avoid:

    def ExecPn():
        pass
    myDict["Pn"]=ExecPn
    

    is that I have to protect code as I am using it to provide a scripting feature within my application.

  • JohnnyDH
    JohnnyDH about 12 years
    I know, it was my first choice, but I want the final user to have a limited access, so the user can't change the contents in the dictionary in run-time.
  • user1066101
    user1066101 about 12 years
    The user can always change anything they want at run-time. It's Python. They have the source.
  • JohnnyDH
    JohnnyDH about 12 years
    I know it is easy to do it that way, I really do, but then my dictionary has no protection, I really need to prevent the end user to modify that dictioanry
  • synthesizerpatel
    synthesizerpatel about 12 years
    Then make it a class guarded by (and I use this term loosely) write-only enforcement decorators and use __ prefixed variables in a (lets be honest, vain attempt) to hide the implementation details from the user. Take a look at fightingquaker.com/pyanno for some inspiration..
  • JohnnyDH
    JohnnyDH about 12 years
    You are right the end user will know the name and I really appreciate your tip, I am a starter, but just in python, I am very well aware of some programming principles but I am not implementing this in a personal project, is work related and customer asks for a robust application and the truth is that this software will be used by a bunch of monkeys and they want to see everything but a simple and plain application...
  • user1066101
    user1066101 about 12 years
    "It depends of the user"? What does that mean? Not every user can tweak the code? If that's what you mean then you should spend even less time worrying about "limited access". Since -- effectively -- all users can tweak the code, it's hardly worth trying to add code to create "limited access". Go for simple in all cases.
  • Misha Akovantsev
    Misha Akovantsev about 12 years
    @JohnnyDH, describe at least few different scenarios with those monkeys taking over your application. This might get you some answers more suitable for your situation.
  • Ohad
    Ohad about 12 years
    Works, but I'd create a filtered version of locals (e.g, filter(lambda x: x.startswith('Exec'), locals()) ). As far as safety goes, locals will always contain functions defined in local scope.
  • JohnnyDH
    JohnnyDH about 12 years
    The application is GUI interface for a device, we offer "scripting" features within our application, but we do not offer "full" support for python since this is our main goal, limit the access. The final users are sellers, people that only need to follow a manual to write basic "scripts", they did not accept a proprietary "language", they wanted a python based one, so yeah, the user can modify the dictionary in a single buggy line and the product won't work, so yes, they just need to restart but the costumer will complain.
  • JohnnyDH
    JohnnyDH about 12 years
    I know, but as I posted below:"The application is GUI interface for a device, we offer "scripting" features within our application... ...The final users are sellers, people that only need to follow a manual to write basic "scripts", they did not accept a proprietary "language", they wanted a python based one, so yeah, the user can modify the dictionary in a single buggy line.." & "and they want to see everything but a simple and plain application..." The costumer knows nothing about programming but they want what they want and if they want to see x = x+y/y instead of x++ we must deliver...
  • user1066101
    user1066101 about 12 years
    "as I posted below"? Please update the question to contain all the facts. There's no "above" or "below" since the order of the answers changes. "the user can modify the dictionary in a single buggy line" doesn't mean anything. They can also reformat their hard drive in a single buggy line that calls subprocess.Popen. There's no good reason for extra complexity. It doesn't prevent the user from causing havoc with "single buggy line". Indeed, the more complexity you layer on, the less transparency and the more mysteries to debug.
  • JohnnyDH
    JohnnyDH about 12 years
    I have to disagree, as I said, we do not support all python capabilities, we have a filter and we only provide access to some modules, I do not care what they do with their hard drives of if they know something about python, I only care the guy writing the script can't modify the modules, just read them, it is a basic scripting feature, sadly Python does not provide much security in cases like this. So, I need to make the modules safer, and it does not matter if code has more complexity, the costumer knows the employees and so they request for such "safety" measures.
  • user1066101
    user1066101 about 12 years
    "I need to make the modules safer". How? You can't. "they request for such "safety" measures". Good for them. Nothing you do can actually have any impact except to make things more complex. But. You keep repeating yourself that it's essential that you create useless, no-value complexity. I guess that's it. Cheers.
  • JohnnyDH
    JohnnyDH about 12 years
    I guess you missed the line I posted "The costumer knows nothing about programming but they want what they want and if they want to see x = x+y/y instead of x++ we must deliver... " I hope you do not work providing software as I do, tell your costumer "Good for you" and you will find yourself searching for a job. I keep repeating my self because if you were able to understand that I could not use the simple way, then everything would be easier...
  • wim
    wim about 12 years
    @JohnnyDH This is a crazy argument. If you have a real requirement to make the code more "secure" then you must use a compiled language - python is not the right tool for the job and it's your responsibility to explain that to the customer. Adding lines of crap to your code to try and workaround python's inherent openness is not a safety measure - it can't be done properly, and attempting to do so will only make your code more ugly and fragile
  • JohnnyDH
    JohnnyDH about 12 years
    @wim I know :(, I have never used python precisely because it's too flexible and open, sadly I am not the leader engineer, this costumer is new and the company wants to hold them, so I am with you people, but I can't do much, too late to start again and the reason we did not want to use Python is the same reason they think they need it... Sad, but money is the king...
  • JohnnyDH
    JohnnyDH about 12 years
    We always use C (for embedded), C#, Java and ASP. However, the former provider for this costumer was using Python for other device's tools, so that's why they think they need to keep using it.
  • Ben
    Ben about 12 years
    @JonnyDH Your arguments don't really make sense. "With a single buggy line" they can clobber your main function just as easily as they could modify a dictionary. In truth it will about as unlikely that either will happen, because neither resembles a legitimate normal operation. But this answer is suggesting that you don't even use a dictionary, you just give them a single public module that only contains the things the end user is supposed to call. There is nothing you can do if you're worried about them importing other modules and messing with their internals.
  • user1066101
    user1066101 about 12 years
    @JohnnyDH: I've only been providing software for 30 years, so I have seen a few examples of useless complexity before. This is another. There's a difference between complexity and security. The "if they want to see..." line is a really, really bad example, because Python has no "++" operator like C does. And. The "Scripting Features" explanation must be folded into your question so that your question makes any sense at all. Repeating requirements in comments is bad.
  • JohnnyDH
    JohnnyDH about 12 years
    I am sure you understood my x++ example. Well, my bad if I did not say we are providing scripting feature, I did not realize that people needed that much information
  • JohnnyDH
    JohnnyDH about 12 years
    @Ben Believe me, I am very aware of that, I have 8 years providing software solutions, this is the first time I use Python and I wish I could use many security features from other languages, however I can not control it, I need to make the module somewhat "Read-Only", the costumer asks us to give them "proofs" that the scripting feature is as safe as possible, we offered a proprietary language so we had full control of the scripting process but they refused, and we can't control all without compromising flexibility for the feature.
  • cs95
    cs95 almost 6 years
    See stackoverflow.com/a/9168387/4909087 for how to actually and correctly do this
  • Aran-Fey
    Aran-Fey almost 6 years
    That's not DRY, that's obfuscated.
  • Vladimir Panteleev
    Vladimir Panteleev over 5 years
    This answer repeats every function name 3 times in the source. More concise solutions exist, see below.
  • Vladimir Panteleev
    Vladimir Panteleev over 5 years
    I think referring to advanced language features like lambdas and custom decorators as obfuscation is misleading, to say the least.
  • Joe
    Joe over 5 years
    Sure. The lambda here is only for brevity. Whether you stick with that or rewrite to def task(f):\n tasks[f.__name__] = f, the principle remains the same. Being DRY removes your responsibility to keep the dictionary keys in sync with function names. It also makes it easier to spot mistakes, such as a missing @task (vs a missing dictionary item).
  • John Lunzer
    John Lunzer over 4 years
    This is elegant and DRY, but definitely not simplified.
  • Alex Povel
    Alex Povel about 3 years
    With integers as keys, a list would also do instead.
  • alex
    alex about 3 years
    This isn't working for me. p1() has args. In the dictionary, "P1": p1, is called without args. When I specify args like "P1": p1(arg1, arg2), - the dictionary automatically calls the p1 function.
  • alex
    alex about 3 years
    What if p1() and p2() have parameters?
  • Faraaz Kurawle
    Faraaz Kurawle over 2 years
    @S.Lott , I don't agree as we can convert Python files to exe using pyinstaller, by which the user don't get the source code.