Python中向回调函数传参的语法问题及代码报错排查
你遇到的问题其实是Python回调函数传递的常见误区——你直接执行了回调函数,而不是把函数本身传递给GPIO.add_event_detect,再加上GPIO库会自动给回调函数传入第一个channel参数,这就导致了参数不匹配的报错。
错误原因分析
看你代码里这一行:
GPIO.add_event_detect(4, GPIO.FALLING, callback = Callback(port_nb,dist_user,dist_ip), bouncetime = 1000)
这里Callback(port_nb,dist_user,dist_ip)是直接调用了函数,而不是把函数对象传进去。Python会立刻执行这个函数,但你的Callback定义第一个参数是channel,这时候调用它并没有传入channel,自然就会报参数缺失的错误(参数顺序问题让报错提示指向了最后一个参数dist_ip)。
而且GPIO.add_event_detect要求callback参数是一个可调用的函数对象,当GPIO事件触发时,它会自动把触发的通道号作为第一个参数传给这个函数。
两种解决方案
方案1:用lambda表达式包装回调
这是最简洁的方式,用lambda接收GPIO传入的channel参数,再把它和你的自定义参数一起传给Callback:
GPIO.add_event_detect(4, GPIO.FALLING, callback=lambda ch: Callback(ch, port_nb, dist_user, dist_ip), bouncetime=1000)
方案2:用functools.partial绑定参数
如果你觉得lambda不够直观,可以用functools.partial来预先绑定自定义参数,保留channel参数的位置:
首先导入functools模块:
from functools import partial
然后创建绑定好参数的回调函数:
callback_func = partial(Callback, port_nb=port_nb, dist_user=dist_user, dist_ip=dist_ip) GPIO.add_event_detect(4, GPIO.FALLING, callback=callback_func, bouncetime=1000)
partial会帮你把指定的关键字参数绑定到函数上,当GPIO触发时,自动传入的channel会作为第一个位置参数传递给Callback。
修改后的完整代码(lambda版本)
#!/usr/bin/env python3 import sys,os,time import RPi.GPIO as GPIO import subprocess flag_callback=True def Callback(channel,port_nb,dist_user,dist_ip): global flag_callback flag_callback=False print('Button Pushed. SSH Tunnel open on port: "{}" until reboot.'.format(port_nb)) bashCommand = "nohup ssh -NR {}:localhost:22 {}@{}".format(port_nb,dist_user,dist_ip) subprocess.Popen(bashCommand.split(), stdout=open('/dev/null', 'w'), stderr=open('logfile.log', 'a'), preexec_fn=os.setpgrp ) def main(): if len(sys.argv) > 1: port_nb=sys.argv[1] else: port_nb='2222' if len(sys.argv) > 2: dist_user=sys.argv[2] else: dist_user='martin' if len(sys.argv) > 3: dist_ip = sys.argv[3] else: dist_ip='192.168.11.111' GPIO.setmode(GPIO.BCM) GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_UP) # 修正回调传递方式 GPIO.add_event_detect(4, GPIO.FALLING, callback=lambda ch: Callback(ch, port_nb, dist_user, dist_ip), bouncetime=1000) try: while(flag_callback): time.sleep(1) except KeyboardInterrupt: # 捕获Ctrl+C,友好退出 print("\nExiting...") finally: # 程序结束时清理GPIO资源,避免下次运行报错 GPIO.cleanup() if __name__== "__main__": main()
我还加了KeyboardInterrupt捕获和GPIO.cleanup(),这样程序退出时会清理GPIO资源,避免后续运行出现问题。
内容的提问来源于stack exchange,提问作者Julionabi

