George Boukeas
George Boukeas

Reputation: 343

Conditional execution without having to check repeatedly for a condition

I have a class with code that fits into the following template:

class aClass:

    def __init__(self, switch = False):
        self.switch = switch

    def f(self):
        done = False
        while not done:

            # a dozen lines of code

            if self.switch:
                # a single line of code

            # another dozen lines of code

So the single line of code in the if statement will either never be executed, or it will be executed in all iterations. And this is actually known as soon as the object is initialized.

When self.switch is True, I would like the single line of code to be executed without having to check for self.switch at every single iteration. And when self.switch is False, I would like the single line of code to be ignored, again without having to repeatedly check for self.switch.

I have of course considered writing two versions of f and selecting the appropriate one in __init__ according to the value of the switch, but duplicating all this code except for a single line doesn't feel right.

Can anyone suggest an elegant way to solve this problem? Perhaps a way to generate the appropriate version of the f method at initialization?

Upvotes: 2

Views: 161

Answers (3)

George Boukeas
George Boukeas

Reputation: 343

In a comment to my original question, JohnColeman suggested using exec and provided a link to another relevant question.

That was an excellent suggestion and the solution I was lead to is:

_template_pre = """\
def f(self):
    for i in range(5):
        print("Executing code before the optional segment.")
"""

_template_opt = """\
        print("Executing the optional segment")
"""

_template_post = """\
        print("Executing code after the optional segment.")
"""

class aClass:

    def __init__(self, switch = False):
        if switch:
            fdef = _template_pre + _template_opt + _template_post
        else:
            fdef = _template_pre + _template_post
        exec(fdef, globals(), self.__dict__)
        # bind the function
        self.f = self.f.__get__(self)

You can verify this actually works:

aClass(switch = False).f()
aClass(switch = True).f()

Before jumping to conclusions as to how "pythonic" this is, let me point out that such an approach is employed in a couple of metaclass recipes I have encountered and even in the Python Standard Library (check the implementation of namedtuple, to name one example).

Upvotes: 0

abukaj
abukaj

Reputation: 2712

If the .switch attribute is not supposed to change, try to select the loop body dynamicly in the __init__() method:

def __init__(self, switch=False):
    self.switch = switch
    self.__fBody = self.__fSwitchTrue if switch else self.__fSwitchFalse
def f(self):
    self.__done = False
    while not self.__done:
        self.__fBody()
def __fSwitchTrue(self):
    self.__fBodyStart()
    ... # a single line of code
    self.__fBodyEnd()
def __fSwitchFalse(self):
    self.__fBodyStart()
    self.__fBodyEnd()
def __fBodyStart(self):
    ... # a dozen lines of code
def __fBodyEnd(self):
    ... # another dozen lines of code

Remember to change values used by more than one of the defined methods to attributes (like done is changed to .__done).

Upvotes: 0

jbasko
jbasko

Reputation: 7330

That's a completely valid ask. If not for performance then for readability.

Extract the three pieces of logic (before, inside, and after your condition) in three separate methods and in f() just write two implementations of the big loop:

def first(self):
    pass

def second(self):
    pass

def third(self):
    pass

def f(self):
    if self.switch:
        while ...:
             self.first()
             self.third()
    else:
         while ...:
            self.first()
            self.second()
            self.third()

If you want it more elegant (although it depends on taste), you express the two branches of my f() into two methods first_loop and second_loop and then in __init__ assign self.f = self.first_loop or self.f = self.second_loop depending on the switch:

class SuperUnderperformingAccordingToManyYetReadable(object):
    def __init__(self, switch):
        if self.switch:
            self.f = self._first_loop
        else:
            self.f = self._second_loop

    def _first(self):
        pass

    def _second(self):
        pass

    def _third(self):
        pass

    def _first_loop(self):
        while ...:
             self.first()
             self.third()

    def _second_loop(self):
         while ...:
            self.first()
            self.second()
            self.third()

You may need to do some extra work to manage breaking out of the while loop.

Upvotes: 1

Related Questions