Passing function to a class
Asked Answered
L

2

7

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)

Lympho answered 6/7, 2013 at 20:50 Comment(0)
B
6

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)
Berri answered 6/7, 2013 at 21:40 Comment(8)
He's using IronPython, hence the .NET-style naming of the libraries.Voigt
@Voigt The OP style is different, for example in .NET classes are declared in CapitalizedWords like in Python. Anyway, following the PEP8 style guide is just a suggestion :)Berri
I am importing this code into another module. I start with an initial value of the interval and after running for awhile I stop the run_timer method and change interval in t1. My function gets executed twice. What do I need to do to run my function once every time the interval changes?Lympho
@user2556944 Can you post some code showing your problem? You can edit your question, if you want. How are you changing the interval?Berri
@PaoloMoretti I added more detail which hopefully will explain my issue. Thanks for the reference to the style guide. I will take a look.Lympho
@CarlosBustamante I've updated my answer and retagged your question for better visibility to the ironpython community :)Berri
Thank You for your comments they have been very helpful. Another use case I would like to consider is performing a sequence of tasks with some delay in between. Is there a recommended approach to do this? Any suggestions keywords, link to reading material is greatly appreciated.Lympho
@CarlosBustamante Do you mean something like daily tasks? In Python you can use something like Celery, but I'm not sure if IronPython is supported. In .NET you can create a Windows Service and use Quartz.NET to run jobs based on a configured schedule. I hope it helps.Berri
M
6

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))
Moeller answered 6/7, 2013 at 20:55 Comment(0)
B
6

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)
Berri answered 6/7, 2013 at 21:40 Comment(8)
He's using IronPython, hence the .NET-style naming of the libraries.Voigt
@Voigt The OP style is different, for example in .NET classes are declared in CapitalizedWords like in Python. Anyway, following the PEP8 style guide is just a suggestion :)Berri
I am importing this code into another module. I start with an initial value of the interval and after running for awhile I stop the run_timer method and change interval in t1. My function gets executed twice. What do I need to do to run my function once every time the interval changes?Lympho
@user2556944 Can you post some code showing your problem? You can edit your question, if you want. How are you changing the interval?Berri
@PaoloMoretti I added more detail which hopefully will explain my issue. Thanks for the reference to the style guide. I will take a look.Lympho
@CarlosBustamante I've updated my answer and retagged your question for better visibility to the ironpython community :)Berri
Thank You for your comments they have been very helpful. Another use case I would like to consider is performing a sequence of tasks with some delay in between. Is there a recommended approach to do this? Any suggestions keywords, link to reading material is greatly appreciated.Lympho
@CarlosBustamante Do you mean something like daily tasks? In Python you can use something like Celery, but I'm not sure if IronPython is supported. In .NET you can create a Windows Service and use Quartz.NET to run jobs based on a configured schedule. I hope it helps.Berri

© 2022 - 2024 — McMap. All rights reserved.