a Thonny in my side - Page 1
Forum section: electronics / beginners

author: PerranOak
author_rank: Frequent Contributor
timestamp: April 13, 2022, 09:38:19 am
content: I thought I was reasonable at picking-up new languages but the simplest thing has me stumped!This simple prog is in MicroPython (Raspberry Pi Pico):Code:from machine import Pin, Timerled = Pin(15, Pin.OUT)timer = Timer()def blink(timer):led.toggle()print(timer)timer.init(period=500, mode=Timer.PERIODIC, callback=blink)I get how it works in flashing the LED and the use of the function "blink". However, I don't get why "blink" has to have the parameter "timer" passed to is when "led.toggle()" needs none.I put the "print(timer)" in to see what it was doing; it printed:Timer(mode=PERIODIC, period=268680512, tick_hz=1000000)This is an issue with my understanding of programming rather than of the pico: I just don't get it!

author: John B
author_rank: Frequent Contributor
timestamp: April 13, 2022, 09:42:12 am
content: Does the Pin.function toggle() simply flip the state of the IO pin? If so, it shouldn't need an argument passed to it to do so.

author: PerranOak
author_rank: Frequent Contributor
timestamp: April 13, 2022, 09:45:16 am
content: Yes that’s all it does. Crazy.

author: Ian.M
author_rank: Super Contributor
timestamp: April 13, 2022, 09:50:19 am
content: You need to read up on timer callbacks. I suspect that the timer instance is always passed as a parameter to the callback function, so must be declared as a parameter in the function definition even if it isn't used within the function. In this case, its used, but only to print the timer status for debugging, not to implement the blink functionality.

author: PerranOak
author_rank: Frequent Contributor
timestamp: April 13, 2022, 10:23:46 am
content: Thank you Ian.I looked it up and found an example of timer callback with the following command similar to my “blink”, with comment:Code:def tick(timer): # we will receive the timer object when being calledSo, for some reason, it needs to have this parameter, as you suggested, to work. I guess I’ll consider it simply the syntax for this situation.

author: Nominal Animal
author_rank: Super Contributor
timestamp: April 13, 2022, 03:58:17 pm
content: Quote from: PerranOak on April 13, 2022, 09:38:19 amIt's the other way around: when the timer calls a callback, it passes the timer that caused the event to the callback function.Thatis why the callback function must be able to take a timer parameter.(The reason why the timer function call facility is designed like this, is because it is useful in majority of cases. Whenever you have callback functions, regardless of the programming language, you'll see them receive the description of the event that caused the call, in some form or another. It's not any kind of requirement or law, justoften practical. Best thought of as "common" or "usual".)In Python –– I'm not absolutely sure this is relevant to MicroPython, though! –– the number of parameters a function takes is significant: the interpreter will complain if you pass the wrong number of arguments to a function. The way you specify "any unnamed parameters" is*args(but you can choose any name for the rest-of-the-arguments list, it does not need to beargs; it's just the commonly used name), and "any other named parameters" is**kwargs(ditto).In other words, in Python, if you want to write a function that can be called with any parameters at all, it signature is something likeCode:def myfunction(*args, **kwargs):"""This function can take any arguments"""# args is a list of unnamed arguments,# kwargs is a dict of name-value pairsYou can use the above for the callback functions, but really, it is more effort than just looking up the timer documentation, and what it says about the parameters it passes to the callback function, and then just use that.Wall-of-text and full example time.This is an example of how such callback facilities actually work, and why the function signature matters. First, let's create a class that takes a callback function and the parameters it will take as an initialization parameter:Code:class Call:def __init__(self, func, *args, **kwargs):self.func = funcself.args = argsself.kwargs = kwargsdef trigger(self):self.func(self, *self.args, **self.kwargs)The first parameter it passes is theCallobject (instance) itself that was triggered, that caused the call. Again, it is not included because it is necessary, but because it is often useful. A test program to use the above is simple,Code:if __name__ == '__main__':from sys import stderrexample = Call(print, "Hello, world!", file=stderr)stderr.write("Call instance constructed. Triggering:\n")example.trigger()stderr.write("Done.\n")which shows that because we pass theprint()built-in Python function as the callback function, it will first print theCallinstance, and then "Hello, world!", to standard error.To use our own callback:Code:if __name__ == '__main__':def myfunc(event, message):print("Callback called with message '%s'" % message)example = Call(myfunc, "Another")example.trigger()which outputsCallback called with message 'Another'.Finally, if we do not know the parameters passed, there are multiple different parameter sets, or we just don't care, we can use the*args, **kwargsnotation to get any unnamed parameters in a list and named parameters in a dict:Code:if __name__ == '__main__':def myfunc(*args, **kwargs):print("myfunc received %d unnamed and %d named parameters" % (len(args), len(kwargs)))example = Call(myfunc, "Second", foo="bar")example.trigger()which will outputmyfunc received 2 unnamed and 1 named parameters.Basically, within the function parameter list, the*prefix means "the rest of the unnamed parameters as a list", and**means "the rest of the named parameters as a dict". When used elsewhere, the*prefix means "expand the contents of the list here", and**"expand the contents of the dict here as a list of name=value pairs".Now, if we try to use a no-parameter callback,Code:if __name__ == '__main__':def myfunc():print("Triggered!")example = Call(myfunc)example.trigger()instead of gettingTriggered!as the output, a full Python interpreter will abort with a TypeError exception, with the error message being typicallymyfunc() takes 0 positional arguments but 1 was given.Again, this is because in Python, the number of parameters passed to a function matters. If you don't care about the parameters, you don't claim the function takes no parameters, you collect them in*argsand**kwargsinstead.Hope this helps.Sorry for the long-windedness, I can't help it.