0

I need a class router (for lack of a better word). The router needs to instantiate a class & call a function of that class instance based on variables passed to it.

How do I properly define the class function?

How do I properly call the class function?

Example code:

class ClassWorker1:

    def function_1(self):
        print('1a')

    def function_2(self):
        print('2a')

    def function_3(self):
        print('3a')


class ClassWorker2:

    def function_1(self):
        print('1b')

    def function_2(self):
        print('2b')

    def function_3(self):
        print('3b')


class ClassRouter(object):
    def __init__(self, class_name, class_function):
        self.class_instance = class_name()
        self.class_function = class_function
        self.main()

    def main(self):
         # how should I call the class function here?
         self.class_instance.class_function()
         return


a = 1
b = 1


if a == 1:
    class_name = ClassWorker1
else:
    class_name = ClassWorker1

if b == 1:
    # Strings won't work as class function definition
    # I won't know the class at this point.  I will only know
    # the shared function name at this point.
    # how could this class function be defined directly?
    class_function = 'function_1'

elif b == 2:
    class_function = 'function_2'

else:
    class_function = 'function_3'


ClassRouter(class_name, class_function)
3
  • How would I properly define the method if I don't yet know the class? Then how would I properly invoke that object using a variable & not the method name? Commented Oct 21, 2016 at 11:05
  • What's the point of using a class if it directly calls the "routed" method ? A plain function would work just the same. Commented Oct 21, 2016 at 11:51
  • Primary reason is because the instances of the router will be running on threads. There will be hundreds of threads. Each thread will need a unique instance of the router, and they'll need to work concurrently. I'm not sure how to do that without using a class? Commented Oct 21, 2016 at 13:13

3 Answers 3

3

I need a class router (for lack of a better word).

Are you sure you need a class for this ?

The router needs to instantiate a class & call a function of that class instance

When it belongs to a class or instance, a function is usually named a "method". Not really important but it makes things clearer. Also, an "instance" is obviously always, by definition, an instance of a class ;)

How do I properly define the class function? How do I properly call the class function?

Does the router really have to be a class ? But anyway...

There are a couple distinct issues here (I of course assume you need something that's generic enough).

The first one is that your class (the one that will be instanciated by the "router") constructor may need some args - position or named or both. If it's the router's duty to instanciate the class (but should it be ?), you'll have to pass those args (both position and named) to the router. And since your router has to be generic (else it's useless) you cannot explicitely name these args in your router's constructor.

Hopefully, Python has a way to "unpack" tuples (for position args) and dicts (for named args) when calling a function, using respectively the * and ** operators at call time, ie:

def somefunc(arg1, arg2, arg3="foo", arg4=None):
    print arg1, arg2, arg3, arg4

args = ("one", "two", "three")
kwargs = {"arg4": "four"}
somefunc(*args, **kwargs)

This let you pass arguments to a function in a generic way.

So if you want your router to be in charge of instanciating the "target" class, you'll have to support this:

class Router(object):
    def __init__(self, cls, clsargs=None, clskwargs=None):
        if clsargs is None:
            clsargs = ()
        if clskwargs is None:
            clskwargs = {}
        self._obj = cls(*clsargs, **clskwargs)


class Worker(object):
    def __init__(self, name):
        self.name = name
            print self.name


r = Router(Worker, clsargs=("foo",))
# or
r = Router(Worker, clskwargs={"name":"foo"})

Now note that at this point you really don't gain anything (except for more code) from having the router instanciating the Worker - since you need to have the Worker class and it's constructor's args to instanciate the router, you could as well just instanciate the Worker yourself and pass the Worker instance to the router:

Since you must have a reference to the class passed to the router (else you can't pass it ), you could as well

class Router(object):
    def __init__(self, obj):
        self._obj = obj

class Worker(object):
    def __init__(self, name):
        self.name = name
            print self.name

r = Router(Worker("foo"))
# or 
r = Router(Worker(name="foo"))

The cases where it would make sense to have the router instanciate the worker are:

1/ if the Worker's constructor arguments are not known when the router is instanciated and are to be passed later (which requires a distinct router method to pass those args)

2/ if the Worker's instanciation is very costly and you're not even sure you'll really need it, in which case you want to wait until the router's "main" method is called to instanciate the worker.

The second issue is "how do I get the worker's method by name". This one has already been answered by Lukas: you use getattr(obj, attrname).

The third issue is "if my worker method needs arguments, how do I pass them". This is the same problem as with the worker's constructor arguments, so the solution is obviously the same. Depending on the concrete use case, you'll have to pass those args either when instanciating the router or when calling it's "main" method.

wrt/ this "main" method, remember that you can define your own callable types by implementing the __call__ method, ie

class NotAFunc(object):
    def __init__(self, wot):
        self.wot = wot

    def __call__(self, count):
        print self.wot * count


notafunc = NotAFunc("wot ? ")
notafunc(42)

So it might make sense to use this as your router's "main" method

Now do you really need a router class at all ? Python functions are object on their own (so a function can take a function and/or return a function), and moreover act as closures (a closure is a function that "captures" part of the environment where it's been defined):

def route(instance, methodname, methargs=None, methkwargs=None):
    method = getattr(instance, methodname)
    if methargs is None:
        methargs = ()
    if methkwargs is None:
        methkwargs = {}
    def func():
        return method(*methargs, **methkwargs)
    return func

class Worker(object):
    def __init__(self, name):
        self.name = name

    def work(self, count):
        return [self.name for i in range(count)]


r = route(Worker("foo"), "work", (42,))
print r()

Note that while I kept your "router" term, most of what I described above are known patterns. You may want to search for "proxy", "proxy method", and (for the last exemple) "partial evaluation".

Sign up to request clarification or add additional context in comments.

3 Comments

Would you still use a function if your were threading the router?
The router constructor will also add to the variables that are passed to the instance. Before the instantiation, the router will also do a "setup" & afterword do "clean up" work. I can imagine this could be done with functions & decorators? But class seemed to be the best solution. With that in mind, would you still use a function?
@Emily no of course, I'd use a class then - even if only for readability. Also wrt/ your first question: if you're new to Python (which is probably the case given your question), you may want to think twice before using threads - in most cases they won't buy you much in python (search for "global interpreter lock") so you might be better using subprocesses. Here again it depends on the context you didn't provide but I thought it migh be a useful information...
1

You are looking for dynamic attribute lookup.

class C:
    def c1(self, x):
        return 2*x

instance = C()
method_name = 'c1'

method = getattr(instance, method_name)
print(method(1))  # call method and print result

Comments

0

You'll need to override the __new__ method of your (new-style!) class.

class ClassRouter(object):
    def __new__(self, class_name, *args):
        if arg=="Class1":
            new_instance = ClassWorker1(*args)
            new_instance.method()
            return new_instance
        elif arg=="Class2":
            return ClassWorker2(*args)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.