-2

I am testing a small functionality where the manage() function within barbar invokes call_central, which has functions call2 and call3. I would like an object on call_central so that I can invoke call2 and call3 where needed.

Some background: What I intend to do with this is. In my program, call2 is instantiating another class, and call3 is using this information to perform various other operations.

EDIT: More details: my basis to create nested functions is as follows. Let's assume you are trying to do some scheduling work and testing various schedulers. The barbar function will get all the parameters from "all" the scheduling algorithms you are going to implement. Each scheduling algorithm has class in itself. In this MWE, it is CallThisClass. What "call_central" does is instanting this particular scheduling algorithm in call2 and using it to actually schedule in call3

A few questions:

  1. Why does it ask me to pass "self" as an argument.
  2. Why does s(self).call2() return None
  3. Is there a way to avoid using nested functions for this?

Code:

#!/usr/bin/env python3

class CallThisClass:
    def __init__(self):
        pass
    def do_something_here(self):
        while True:
            print ("doing something")

class CallThisClass1:
    def __init__(self):
        pass
    def do_something_here(self):
        while True:
            print ("doing something")


class barbar:
    def __init__(self, which_function=None):
        print ("do nothing")
        self.which_function = which_function
        self.manage()

    def manage(self):
        FUNCTIONS = globals()['barbar']
        s  = getattr(FUNCTIONS, self.which_function)
        print (s(self))#.call2())

    def call_central(self):
        print ("in call central")
        def call2():
            self.invoke_callthisclass = CallThisClass()
            print ("do nothing from call2")
        def call3():
            self.invoke_callthisclass.do_something_here()
            print ("do nothing from call3")
            return 0

    def call_central2(self):
        print ("in call central")
        def call4():
            self.invoke_callthisclass1 = CallThisClass1()
            print ("do nothing from call4")
        def call5():
            self.invoke_callthisclass1.do_something_here()
            print ("do nothing from call5")
            return 0


d = barbar(which_function="call_central")
tandem
  • 2,040
  • 4
  • 25
  • 52
  • What are you trying to achieve here? Why doesn't `manage` just `print(self.call_central())`? – jonrsharpe Apr 25 '19 at 07:34
  • Because there are multiple such functions. there is also `call_central1`, and that is determined through a string. – tandem Apr 25 '19 at 07:35
  • 1
    Then why don't you use `getattr` on the instance, `self`, instead of the class? If you do want to do it on the class, why access it via `globals` instead of by name? Also defining functions inside a method call doesn't achieve much; you never call them, and they're inaccessible after the call finishes. – jonrsharpe Apr 25 '19 at 07:36
  • Do you suggest creating an instance of another class in the manage() function and avoid using "call2" altogther? – tandem Apr 25 '19 at 07:40
  • It's not clear enough to me what you're actually trying to do to suggest anything. You've stripped out all of the context, the code you've posted is pointless. – jonrsharpe Apr 25 '19 at 07:42
  • provided an MWE – tandem Apr 25 '19 at 07:43
  • 2
    Your MWE is still pretty unclear. What are you fundamentally trying to accomplish? What line of reasoning originally lead you to believe that you'd need to nest functions like this in the first place? – ymbirtt Apr 25 '19 at 08:00

1 Answers1

2

You're doing something very strange here, but I'll try and give some sensible answers.

Why does it ask me to pass "self" as an argument.

Your code looks like this:

def manage(self):
    FUNCTIONS = globals()['barbar']
    s  = getattr(FUNCTIONS, "call_central")
    print (s(self))#.call2())

Rather than going via the globals dict, you could have equivalently written s = barbar.call_central. barbar.call_central is an unbound method. This means that it's an instance-level method without any instance over which to be called.

As a quick detour into how objects in Python work, let's talk about a much simpler class:

class Foo(object):
  def bar(self):
    pass

f = Foo()
print(Foo.bar) # => "<unbound method Foo.bar>"
print(f.bar) # => "bound method Foo.bar of <__main__.Foo object at 0x...>>"

So, f is an instance of the class Foo. bar is an instance method - it's a method that instances of Foo can call. Foo.bar is bound to no instance; to call it, it needs an instance of Foo. f.bar is the method Foo.bar, bound to the instance f. All this binding means is that, whenever you call the method, the bound instance gets passed in as the first argument. This means that f.bar() and Foo.bar(f) do basically the same thing.

So, why do you need to pass self as an argument to barbar.call_central? Because barbar.call_central is an unbound instance method, and requires an instance to be called. self.call_central is your bound method, and you might want to call that instead. getattr(self, self.which_function) will give you your bound method.

Why does s(self).call2() return None

Well, here's the definition of call2:

def call2():
    self.invoke_callthisclass = CallThisClass()
    print ("do nothing from call2")

There's no return, execution just drops off the end. When execution drops off the end of a function it returns None.

You've written something like this:

def call2():
    self.invoke_callthisclass = CallThisClass()
    print ("do nothing from call2")
def call3():
    self.invoke_callthisclass.do_something_here()
    print ("do nothing from call3")
    return 0

Because you've unindented, you've left the scope of call2, so none of the following lines are particularly relevant to that function. It's worth pointing out that call3 also won't ever return 0, since your do_something_here method will never return.

Is there a way to avoid using nested functions for this?

So far as I understand your problem, it looks like this is the sort of thing you wanted to make:

class BaseScheduler(object):
    def do_the_thing(self):
        raise NotImplementedError("bla bla bla please define a method in {}", type(self))

class Scheduler1(BaseScheduler):
    pass

class Scheduler2(BaseScheduler):
    pass

class BarBarOrWhatever(object):
    def __init__(self, scheduler_cls):
        avaialble_schedulers = BaseScheduler.__subclasses__()
        # Iterate over available_schedulers and do whatever setup you wanted here
        self.__scheduler = scheduler_cls()
        self.__scheduler.do_the_thing()

BarBarOrWhatever(Scheduler1)

You said that you wanted the __init__ method to do something with all the available schedulers, so that's what the __sublcasses__ bit is doing. See:
How to find all the subclasses of a class given its name?. Everything else is hopefully fairly straightforward.

ymbirtt
  • 1,481
  • 2
  • 13
  • 24
  • Thanks for the explanation. added more details in the question – tandem Apr 25 '19 at 08:06
  • One question: Why would I need to iterate over `available_schedulers`, if all the functions of `scheduler1` are within the scope of `barbarorwhatever`? Also, what does passing `Scheduler1(BaseScheduler)` do? – tandem Apr 25 '19 at 08:49
  • @tandem You said "The barbar function will get all the parameters from "all" the scheduling algorithms you are going to implement". If you want to do something to all the implemented schedulers, you'll need something like that `__subclasses__` call knocking around. If that isn't actually your requirement, then you can skip that bit. `Scheduler1(BaseScheduler)` is just class inheritance. You should read about it here: https://docs.python.org/3.7/tutorial/classes.html#inheritance – ymbirtt Apr 25 '19 at 09:13