Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing function to a class

I have created a class that can take a function with a set of arguments. I would like to run the passed function every time the event handler signals.

I am attaching my code below which runs when I pass a fun2 which has no arguments but not with fun1. Any suggestions that I can make to the code below work with fun1 and fun2? If I omit the return statement from fun1, I get an error that 'str' object is not callable.

>>> TimerTest.main()
function 1. this function does task1
my function from init from function1
my function in start of runTimerTraceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Program Files (x86)\IronPython 2.7\TimerTest.py", line 57, in main
File "C:\Program Files (x86)\IronPython 2.7\TimerTest.py", line 25, in runTime
r
TypeError: str is not callable



import System  
from System.Timers import (Timer, ElapsedEventArgs)

class timerTest:

    def __init__ (self, interval,autoreset, fun):
        self.Timer = Timer()
        self.Timer.Interval= interval
        self.Timer.AutoReset = autoreset
        self.Timer.Enabled = True
        self.myfunction = fun

    def runTimer(self):

        print 'my function in start of runTimer', self.myfunction ()

        self.Timer.Start()

        def OnTimedEvent (s, e):
                print "The Elapsed event was raised at " , e.SignalTime
                print 'printing myfunction...', self.myfunction()
                self.myfunction()

        self.Timer.Elapsed += OnTimedEvent


    def stopTimer(self):
        self.Timer.Stop()
        self.Timer.Dispose= True


def fun1(a,b):
   print 'function 1. this function does task1'
   return 'from function1'

def fun2():
   print 'Function 2. This function does something'
   print 'Test 1...2...3...'
   return 'From function 2'

def main():
    a = timerTest(1000, True, fun1(10,20))
    a.runTimer()

    b= timerTest(3000,True,fun2)
    b.runTimer()

if __name__ == '__main__':
  main()

I am learning Python and I apologize if my questions are basic.

To change the interval, I stop the timer using a stopTimer method I added to the timerTest class:

def stopTimer(self):
    self.Timer.Stop()

I take the new user input to call the runTimer method which I have revised per Paolo Moretti's suggestions:

def runTimer(self, interval,autoreset,fun,arg1, arg2, etc.):

    self.Timer.Interval= interval
    self.Timer.AutoReset = autoreset
    myfunction = fun
    my_args = args

    self.Timer.Start()

        def OnTimedEvent (s, e):
            print "The Elapsed event was raised at " , e.SignalTime
            myfunction(*my_args)

    self.Timer.Elapsed += OnTimedEvent   

Whenever a command button is pressed, the following method is called:

requestTimer.runTimer((self.intervalnumericUpDown.Value* 1000),True, function, *args) 

I do not understand why stopping the timer and sending the request causes the runTimer method to be executed multiple times and it seems dependent on how many times I change the interval. I have tried a couple of methods: Close and Dispose with no success.

A second question on slightly different subject.

I have been looking at other .NET classes with Timer classes. A second question is on how I would translate the following VB sample code into Python. Is "callback As TimerCallback" equivalent to myfunction(*my_args)?

Public Sub New ( _
  callback As TimerCallback, _
  state As Object, _
  dueTime As Integer, _
  period As Integer _
)  

per .NET documentation: callback Type: System.Threading.TimerCallback A TimerCallback delegate representing a method to be executed. I can partially get the timer event to fire if I define a function with no arguments such as:

def fun2(stateinfo):
    # function code

which works with:

self.Timer = Timer(fun2, self.autoEvent, self.dueTime,self.period)

The function call fails if I replace fun2 with a more generic function call myfunction(*my_args)

like image 480
cjbust Avatar asked Jul 06 '13 20:07

cjbust


People also ask

How do you pass a class function?

Basically in the second case you are invoking a function with additional arguments unpacked from a tuple. So If you want to pass a function with a different signature to a constructor which accepts a TimerCallback delegate you have to create a new function, like @Lasse is suggesting.

Can you pass a function to a class Python?

Functions can be passed around in Python. In fact there are functions built into Python that expect functions to be given as one or more of their arguments so that they can then call them later.

Can we pass class as function arguments?

In C++ we can pass class's objects as arguments and also return them from a function the same way we pass and return other variables.

What is passing a function?

Functions are data, and therefore can be passed around just like other values. This means a function can be passed to another function as an argument. This allows the function being called to use the function argument to carry out its action. This turns out to be extremely useful.


2 Answers

You can also use * syntax for calling a function with an arbitrary argument list:

class TimerTest:
    def __init__(self, interval, autoreset, fun, *args):
        # ...
        self.my_function = fun
        self.my_args = args
        # ...

    def run_timer(self):
        # ...
        def on_timed_event(s, e):
            # ...
            self.my_function(*self.my_args)
            # ...

Usage:

>>> t1 = TimerTest(1000, True, fun1, 10, 20)
>>> t2 = TimerTest(1000, True, fun2)

And check out the PEP8 style guide as well. Python's preferred coding conventions are different than many other common languages.


Question 1

Every time you use the addition assignment operator (+=) you are attaching a new event handler to the event. For example this code:

timer = Timer()

def on_timed_event(s, e):
    print "Hello form my event handler"

timer.Elapsed += on_timed_event
timer.Elapsed += on_timed_event

timer.Start()

will print the "Hello form my event handler"phrase twice.

For more information you can check out the MSDN documentation, in particular Subscribe to and Unsubscribe from Events .

So, you should probably move the event subscription to the __init__ method, and only start the timer in your run_timer method:

def run_timer(self):
     self.Timer.Start()

You could also add a new method (or use a property) for changing the interval:

def set_interval(self, interval):
     self.Timer.Interval = interval

Question 2

You are right about TimerCallback: it's a delegate representing a method to be executed.

For example, this Timer constructor:

public Timer(
    TimerCallback callback
)

is expecting a void function with a single parameter of type Object.

public delegate void TimerCallback(
    Object state
)

When you are invoking a function using the * syntax you are doing something completely different. It's probably easier if I'll show you an example:

def foo(a, b, *args):
    print a
    print b
    print args

>>> foo(1, 2, 3, 4, 5)
1
2
(3, 4, 5)

>>> args = (1, 2, 3)
>>> foo(1, 2, *args)
1
2
(1, 2, 3)

Basically in the second case you are invoking a function with additional arguments unpacked from a tuple.

So If you want to pass a function with a different signature to a constructor which accepts a TimerCallback delegate you have to create a new function, like @Lasse is suggesting.

def my_func(state, a, b):
    pass

You can do this either using the lambda keyword:

t1 = Timer(lambda state: my_func(state, 1, 2))

or by declaring a new function:

def time_proc(state):
     my_func(state, 1, 2)

t2 = Timer(time_proc)
like image 146
Paolo Moretti Avatar answered Oct 17 '22 13:10

Paolo Moretti


If the function takes no parameters, simply pass it without calling it:

b = timerTest(3000, True, fun2)

If it takes parameters, you need to convert it to a function that doesn't take parameters. What you're doing is calling it, and then you pass the result, which in this case is a string. Instead do this:

a = timerTest(1000, True, lambda: fun1(10, 20))
like image 42
Lasse V. Karlsen Avatar answered Oct 17 '22 12:10

Lasse V. Karlsen