Author Topic: a Thonny in my side  (Read 648 times)

0 Members and 1 Guest are viewing this topic.

Offline PerranOakTopic starter

  • Frequent Contributor
  • **
  • Posts: 548
  • Country: gb
a Thonny in my side
« on: April 13, 2022, 09:38:19 am »
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: [Select]
from machine import Pin, Timer
led = 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!
You can release yourself but the only way to go is down!
RJD
 

Offline John B

  • Frequent Contributor
  • **
  • Posts: 802
  • Country: au
Re: a Thonny in my side
« Reply #1 on: April 13, 2022, 09:42:12 am »
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.
 

Offline PerranOakTopic starter

  • Frequent Contributor
  • **
  • Posts: 548
  • Country: gb
Re: a Thonny in my side
« Reply #2 on: April 13, 2022, 09:45:16 am »
Yes that’s all it does. Crazy.
You can release yourself but the only way to go is down!
RJD
 

Online Ian.M

  • Super Contributor
  • ***
  • Posts: 12904
Re: a Thonny in my side
« Reply #3 on: April 13, 2022, 09:50:19 am »
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.
 

Offline PerranOakTopic starter

  • Frequent Contributor
  • **
  • Posts: 548
  • Country: gb
Re: a Thonny in my side
« Reply #4 on: April 13, 2022, 10:23:46 am »
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: [Select]
def tick(timer):                # we will receive the timer object when being called
So, 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.
You can release yourself but the only way to go is down!
RJD
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6357
  • Country: fi
    • My home page and email address
Re: a Thonny in my side
« Reply #5 on: April 13, 2022, 03:58:17 pm »
I don't get why "blink" has to have the parameter "timer" passed to is when "led.toggle()" needs none.
It's the other way around: when the timer calls a callback, it passes the timer that caused the event to the callback function.  That is 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, just often 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 be args; 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 like
Code: [Select]
def myfunction(*args, **kwargs):
    """This function can take any arguments"""
    # args is a list of unnamed arguments,
    # kwargs is a dict of name-value pairs

You 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: [Select]
class Call:

    def __init__(self, func, *args, **kwargs):
        self.func = func
        self.args = args
        self.kwargs = kwargs

    def trigger(self):
        self.func(self, *self.args, **self.kwargs)
The first parameter it passes is the Call object (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: [Select]
if __name__ == '__main__':
    from sys import stderr
    example = 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 the print() built-in Python function as the callback function, it will first print the Call instance, and then "Hello, world!", to standard error.

To use our own callback:
Code: [Select]
if __name__ == '__main__':

    def myfunc(event, message):
        print("Callback called with message '%s'" % message)

    example = Call(myfunc, "Another")
    example.trigger()
which outputs Callback 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, **kwargs notation to get any unnamed parameters in a list and named parameters in a dict:
Code: [Select]
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 output myfunc 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: [Select]
if __name__ == '__main__':

    def myfunc():
        print("Triggered!")

    example = Call(myfunc)
    example.trigger()
instead of getting Triggered! as the output, a full Python interpreter will abort with a TypeError exception, with the error message being typically myfunc() 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 *args and **kwargs instead.

Hope this helps. ;D Sorry for the long-windedness, I can't help it.   :-[
« Last Edit: April 13, 2022, 04:02:47 pm by Nominal Animal »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf