Calling functions by array index in Python
Asked Answered
C

5

33

I have a bunch of functions in Python out1, out2, out3 etc. and would like to call them based on an integer I pass in.

def arryofPointersToFns (value):
     #call outn where n = value

Is there an easy way to do this?

Charbonnier answered 18/4, 2011 at 19:4 Comment(0)
G
3

Actually, I have exactly this problem and it is quite realistic: I needed to display a table where each row requires a quite different method to compose the cell content. My solution was to create a class that returns an empty value, then subclass it and implement different value methods, then instantiate each subclass into an array, then call the instance's method depending on the row number. Global namespace polution is limited by making the subclasses inner to the table generator class. The code looks something like this:

class Table(object):
    class Row(object):
        def name(self):
            return self.__class__.__name__
    class Row1(Row):
        def value(self):
            return 'A'
    class Row2(Row):
        def value(self):
            return 'B'
    class Row3(Row):
        def value(self):
            return 'C'
    def __init__(self):
        self._rows = []
        for row in self.Row.__subclasses__():
            self._row.append(row())
    def number_of_rows(self):
        return len(self._rows)
    def value(self,rownumber):
        return self._rows[rownumber].value()

Obviously, in a realistic example, each of the subclass value methods would be quite different. The 'name' method is included to indicate how to provide a row title, if needed, using the arbitrary name of the inner class. This approach also has the advantage that one can easily implement a suitable 'size' method. The rows will appear in the output in the same order they appear in the code, but this may be an advantage.

Caution: the above is not tested code, just a precis of my actual code laid out to illustrate an approach.

Greeneyed answered 3/5, 2018 at 17:5 Comment(0)
F
55

tl;dr: Write an out(n) function rather than out1(), out2(), ..., outN() and don't bother with this hack.

I cannot imagine a reasonable scenario where this question would come up in practice. Please reconsider the architecture of the problem; there is likely to be a much better way to do this (because storing them in a list implies there is nothing meaningful about the functions except the index; for example, I can only imagine that you'd want to do this if you were creating a bunch of dynamically-generated thunks where their temporal ordering matters, or something similar). Especially any novice users you are reading this answer, consider making a more general function that can handle everything, or associating to each function some more identifying information, or sticking it as part of a class, etc.

That said, this is how you'd do it.

myFuncs = [f0,f1,f2]
myFuncs[2](...) #calls f2

or

myFuncs = {'alice':f1, 'bob':f2}
myFuncs['alice'](...) #calls f1

this is just the following two steps in one step:

myFuncs = [f0,f1,f2]
f = myFuncs[i]
f(...) #calls fi

or if you don't have a registry of functions 'myFunc' like the OP said above, you can use globals(), though it is extremely hackish form and to be avoided (unless you want those functions to be available in your module namespace, in which case maybe it's fine... but this is probably rarely the case, and you'd probably rather define those functions in a submodule then from mysubmodule import * them, which is in turn slightly frowned upon):

def fN(n):
    return globals()['f'+str(n)]

def f2():
    print("2 was called!")

fN(2)(...) #calls f2

here are two other ideas (added after answer was accepted and first two comments):

You can also create a decorator like this:

>>> def makeRegistrar():
...     registry = {}
...     def registrar(func):
...         registry[func.__name__] = func
...         return func  # normally a decorator returns a wrapped function, 
...                      # but here we return func unmodified, after registering it
...     registrar.all = registry
...     return registrar

and use it like so:

>>> reg = makeRegistrar()
>>> @reg
... def f1(a):
...  return a+1
... 
>>> @reg
... def f2(a,b):
...  return a+b
... 
>>> reg.all
{'f1': <function f1 at 0x7fc24c381958>, 'f2': <function f2 at 0x7fc24c3819e0>}

then you can call reg.all['f1']. You could adapt the reg decorator to keep track of the indexing and do something like:

registry = []
index = int(re.regextofindthenumber(func.__name__))
if not index==len(registry):
    raise Exception('Expected def f{} but got def f{}')
else:
    registry[index] = func

Alternatively, to avoid globals(), you could define a class:

class Funcs(object):
    def f1():
        ...
    def f2():
        ...
    def num(n):
        [code goes here]

If your number of functions is small, you could get away with ['f1','f2','f3'][i].

Of course without further information, all these suggestions are just ignoring the real problem: this situation should never come up, and is a possibly a sign of a serious architecture flaw, when you'd probably rather have something (to use your example) like:

# a possibly-better world
def out(n):
    # output to N, whatever that means

rather than

# what you have now
def out1():
    # output to 1
def out2():
    # output to 2
def outN(n):
    # ???
Feverish answered 18/4, 2011 at 19:5 Comment(5)
Although The latter is only acceptable if some poor fellow didn't realize the real solutions (read: the first two and similar ones) and get you stuck with global functions f0, f1, etc.. Don't be that fool.Anticipatory
Just a note, use of globals is generally a bad plan. Putting your functions in a list is almost always a better idea.Fascista
@Winston Ewert; how would yoy write this using a list as you suggest?Ereshkigal
@Weholt, the answer already explains how to do it just before showing how to use globals().Fascista
Is there a way to use the registry idea to identify the methods in a particular object that have a given decorator?Messy
S
4

One can access methods through a dictionary:

def one1():
    print("Method one1 called")
def one2():
    print("Method one2 called")
def one3():
    print("Method one3 called")
methodDictionary = {1: one1, 2:one2, 3: one3}
method1 = methodDictionary[1]
method1()
method2 = methodDictionary[2]
method2()
method3 = methodDictionary[3]
method3()
Softball answered 19/7, 2020 at 20:30 Comment(0)
G
3

Actually, I have exactly this problem and it is quite realistic: I needed to display a table where each row requires a quite different method to compose the cell content. My solution was to create a class that returns an empty value, then subclass it and implement different value methods, then instantiate each subclass into an array, then call the instance's method depending on the row number. Global namespace polution is limited by making the subclasses inner to the table generator class. The code looks something like this:

class Table(object):
    class Row(object):
        def name(self):
            return self.__class__.__name__
    class Row1(Row):
        def value(self):
            return 'A'
    class Row2(Row):
        def value(self):
            return 'B'
    class Row3(Row):
        def value(self):
            return 'C'
    def __init__(self):
        self._rows = []
        for row in self.Row.__subclasses__():
            self._row.append(row())
    def number_of_rows(self):
        return len(self._rows)
    def value(self,rownumber):
        return self._rows[rownumber].value()

Obviously, in a realistic example, each of the subclass value methods would be quite different. The 'name' method is included to indicate how to provide a row title, if needed, using the arbitrary name of the inner class. This approach also has the advantage that one can easily implement a suitable 'size' method. The rows will appear in the output in the same order they appear in the code, but this may be an advantage.

Caution: the above is not tested code, just a precis of my actual code laid out to illustrate an approach.

Greeneyed answered 3/5, 2018 at 17:5 Comment(0)
D
1

It can be done something like below also:

def f1():
    print("F1() Called")

def f2():
    print("F2() Called")



af = {}

af["F1"] = f1
af["F2"] = f2

for fn in af:
    print(fn)
    af[fn]()

print("Done")
``

Result:
F1
F1() Called
F2
F2() Called
Done
Deaminate answered 7/11, 2022 at 12:42 Comment(1)
Please consider adding some explanation to the source code explaining how it solves the problem.Reporter
T
-3
  def array(ar=[]):
     ar = np.array(ar)
     return ar
Timothytimour answered 12/10, 2017 at 12:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.