在使用ros::spin()的情況下,一般來說在初始化時(shí)已經(jīng)設(shè)置好所有消息的回調(diào),并且不需要其他背景程序運(yùn)行。這樣以來,每次消息到達(dá)時(shí)會執(zhí)行用戶的回調(diào)函數(shù)進(jìn)行操作,相當(dāng)于程序是消息事件驅(qū)動(dòng)的;而在使用ros::spinOnce()的情況下,一般來說僅僅使用回調(diào)不足以完成任務(wù),還需要其他輔助程序的執(zhí)行:比如定時(shí)任務(wù)、數(shù)據(jù)處理、用戶界面等。
成都創(chuàng)新互聯(lián)公司專注于石峰企業(yè)網(wǎng)站建設(shè),成都響應(yīng)式網(wǎng)站建設(shè),電子商務(wù)商城網(wǎng)站建設(shè)。石峰網(wǎng)站建設(shè)公司,為石峰等地區(qū)提供建站服務(wù)。全流程按需網(wǎng)站建設(shè),專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務(wù)
關(guān)于消息接收回調(diào)機(jī)制在ROS官網(wǎng)上略有說明 (callbacks and spinning)??傮w來說其原理是這樣的:除了用戶的主程序以外,ROS的socket連接控制進(jìn)程會在后臺接收訂閱的消息,所有接收到的消息并不是立即處理,而是等到spin()或者spinOnce()執(zhí)行時(shí)才集中處理。所以為了保證消息可以正常接收,需要尤其注意spinOnce()函數(shù)的使用 (對于spin()來說則不涉及太多的人為因素)。
I. 對于速度較快的消息,需要注意合理控制消息隊(duì)列及spinOnce()的時(shí)間。例如,如果消息到達(dá)的頻率是100Hz,而spinOnce()的執(zhí)行頻率是10Hz,那么就要至少保證消息隊(duì)列中預(yù)留的大小大于10。
II. 如果對于用戶自己的周期性任務(wù),最好和spinOnce()并列調(diào)用。即使該任務(wù)是周期性的對于數(shù)據(jù)進(jìn)行處理,例如對接收到的IMU數(shù)據(jù)進(jìn)行Kalman濾波,也不建議直接放在回調(diào)函數(shù)中:因?yàn)榇嬖谕ㄐ沤邮盏牟淮_定性,不能保證該回調(diào)執(zhí)行在時(shí)間上的穩(wěn)定性。
// 示例代碼
ros::Rate r(100);
while (ros::ok())
{
libusb_handle_events_timeout(...); // Handle USB events
ros::spinOnce(); // Handle ROS events
r.sleep();
}
III. 最后說明一下將ROS集成到其他程序架構(gòu)時(shí)的情況。有些圖形處理程序會將main()包裹起來,此時(shí)就需要找到一個(gè)合理的位置調(diào)用ros::spinOnce()。比如對于OpenGL來說,其中有一個(gè)方法就是采用設(shè)置定時(shí)器定時(shí)調(diào)用的方法:
// 示例代碼
void timerCb(int value) {
ros::spinOnce();
}
glutTimerFunc(10, timerCb, 0);
glutMainLoop(); // Never returns
在前面已經(jīng)多次提到函數(shù)這個(gè)概念,之所以沒有解釋什么是函數(shù),是因?yàn)槌绦蛑械暮瘮?shù)和數(shù)學(xué)中的函數(shù)差不多,如input()、range()等都是函數(shù),這些都是Python的標(biāo)準(zhǔn)函數(shù),直接使用就可以了。根據(jù)需要,用戶也可以自定義函數(shù)。
12.1 函數(shù)
函數(shù)的結(jié)構(gòu):
def 函數(shù)名(參數(shù)):
函數(shù)體
return 返回值
例如:數(shù)學(xué)中的函數(shù)f(x)=2x+5在Python中可以定義如下:
def f(x):
y=2*x+5
return(y)
如果x取值為3,可以使用如下語句調(diào)用函數(shù):
f(3)
下面給出完整的程序代碼:
def f(x):
y=2*x+5
return(y)
res=f(3)
print(res)
運(yùn)行結(jié)果:11
如上例中的x是函數(shù)f(x)的參數(shù),有時(shí)也被稱為形式參數(shù)(簡稱形參),在函數(shù)被調(diào)用時(shí),x被具體的值3替換y就是函數(shù)的返回值,這個(gè)值3也被稱為實(shí)際參數(shù)(簡稱實(shí)參)。
上例中的y是函數(shù)f(x)的返回值。并不是所有的函數(shù)都有參數(shù)和返回值。如下面的函數(shù):
def func():
print('此為無參數(shù)傳遞、無返回值的函數(shù)')
func()
輸出結(jié)果:此為無參數(shù)傳遞、無返回值的函數(shù)
可以看出,該函數(shù)func()無參數(shù),故調(diào)用時(shí)不用賦給參數(shù)值。
函數(shù)也可以有多個(gè)參數(shù),如f(x,y)=x2+y2,可用Python語言定義如下:
def f(x,y):
z=x**2+y**2
return z
print(f(2,3)) #調(diào)用函數(shù)f(x,y)
輸出結(jié)果:13
也可以通過直接給參數(shù)列表中的參數(shù)賦值的方法,為參數(shù)添加默認(rèn)值,如果用戶賦予參數(shù)值,則按照用戶賦值執(zhí)行,否則使用默認(rèn)值。例如:
def f(x,y=3):
z=x**2+y**2
return z
若調(diào)用時(shí)參數(shù)列表為(2,1),即x賦值為2,y賦值為1:
print(f(2,1))
輸出結(jié)果為:5
若調(diào)用時(shí)參數(shù)列表為(2),即x賦值為2,y賦值省缺,則y使用默認(rèn)值:
print(f(2))
輸出結(jié)果為:13
回調(diào)函數(shù),又稱函數(shù)回調(diào),是將函數(shù)作為另一函數(shù)的參數(shù)。
例如:
def func(fun,m,n):
fun(m,n)
def f_add(m,n):
print('m+n=',m+n)
def f_mult(m,n):
print('m*n=',m*n)
func(f_add,2,3)
func(f_mult,2,3)
輸出結(jié)果:
m+n= 5
m*n= 6
在f_add(m,n)和f_mult(m,n)被定義前,func(fun,m,n)中的fun(m,n)就已經(jīng)調(diào)用了這兩個(gè)函數(shù),即“先調(diào)用后定義”,這也是回調(diào)函數(shù)的特點(diǎn)。
如果無法預(yù)知參數(shù)的個(gè)數(shù),可以在參數(shù)前面加上*號,這種參數(shù)實(shí)際上對應(yīng)元組類型。譬如,參會的人數(shù)事先不能確定,只能根據(jù)與會人員名單輸入:
def func(*names):
print('今天與會人員有:')
for name in names:
print(name)
func('張小兵','陳曉梅','李大海','王長江')
運(yùn)行后,輸出結(jié)果為:
今天與會人員有:
張小兵
陳曉梅
李大海
王長江
參數(shù)為字典類型,需要在參數(shù)前面加上**號。
def func(**kwargs):
for i in kwargs:
print(i,kwargs[i])
func(a='a1',b='b1',c='c1')
輸出結(jié)果為:
a a1
b b1
c c1
一個(gè)有趣的實(shí)例:
def func(x,y,z,*args,**kwargs):
print(x,y,z)
print(args)
print(kwargs)
func('a','b','c','Python','is easy',py='python',j='java',ph='php')
輸出結(jié)果:
a b c # 前三個(gè)實(shí)參賦給前三個(gè)形參
('Python', 'is easy') # *args接收元組數(shù)據(jù)
{'py': 'python', 'j': 'java', 'ph': 'php'} # **kwargs接收字典數(shù)據(jù)
12.2 變量的作用域
變量的作用域即變量的有效范圍,可分為全局變量和局部變量。
局部變量
在函數(shù)中定義的變量就是局部變量,局部變量的作用域僅限于函數(shù)內(nèi)部使用。
全局變量
在主程序中定義的變量就是全局變量,但在函數(shù)中用關(guān)鍵字global修飾的變量也可以當(dāng)做全局變量來使用。
全局變量的作用域是整個(gè)程序,也就是說,全局變量可以在整個(gè)程序中可以訪問。
下面通過實(shí)例去討論:
程序1:
a=1 # a為全局變量
def a_add():
print('a的初值:',a) # 在函數(shù)中讀取a的值
a_add() # 調(diào)用函數(shù)a_add()
a+=1 # 主程序語句,a增加1
print('a現(xiàn)在的值是:',a) # 主程序語句,讀取a的值
運(yùn)行結(jié)果:
a的初值: 1
a現(xiàn)在的值是: 2
這個(gè)結(jié)果和我們想象的一樣,全局變量a既可以在主程序中讀取,也可以在子程序(函數(shù))中讀取。
程序2:
a=1
def a_add():
a+=1
print('a的初值:',a)
a_add()
print('a現(xiàn)在的值是:',a)
運(yùn)行程序1時(shí)出現(xiàn)如下錯(cuò)誤提示:
UnboundLocalError: local variable 'a' referenced before assignment
意思是:局部變量'a'在賦值之前被引用。
從語法上來講,該程序沒有錯(cuò)誤。首先定義了一個(gè)全局變量a并賦值為1,又定義了一個(gè)函數(shù)a_add(),函數(shù)內(nèi)的語句a+=1就是出錯(cuò)的根源,雖然我們的初衷是想讓全局變量a的值增加1,但從錯(cuò)誤提示看,這個(gè)語句中的a并不是全局變量,而是局部變量??磥恚诤瘮?shù)中讀取全局變量的值是沒有問題的(在程序1中已經(jīng)得到了驗(yàn)證),但要在函數(shù)中改變?nèi)肿兞康闹凳遣恍械模ㄔ诔绦?的錯(cuò)誤提示a+=1中的a 是局部變量,而非全局變量)。
怎樣解決這個(gè)問題?
程序3:
a=1
def a_add(x):
x+=1
return x
print('a的初值:',a)
a=a_add(a)
print('a現(xiàn)在的值是:',a)
運(yùn)行結(jié)果:
a的初值: 1
a現(xiàn)在的值是: 2
結(jié)果的確是正確的,但在函數(shù)a_add(x)中沒有調(diào)用變量a(沒有出現(xiàn)變量a)。
程序4:
a=1
def a_add(a):
a+=1
return a
print('a的初值:',a)
a=a_add(a)
print('a現(xiàn)在的值是:',a)
運(yùn)行結(jié)果:
a的初值: 1
a現(xiàn)在的值是: 2
對比程序4和程序3不難發(fā)現(xiàn),其實(shí)程序4只是簡單的把函數(shù)的參數(shù)x變成了a,這個(gè)a的實(shí)質(zhì)和程序3中的x還是一樣的。這進(jìn)一步證實(shí),函數(shù)中的a是局部變量,與主程序的全局變量a有著本質(zhì)的區(qū)別。
程序5:
a=1
def a_add():
global a
a+=1
print('a的初值:',a)
a_add()
print('a現(xiàn)在的值是:',a)
運(yùn)行結(jié)果:
a的初值: 1
a現(xiàn)在的值是: 2
程序5和程序2相比較,僅僅是在函數(shù)中添加了一個(gè)定義“global a”,此時(shí)的局部變量a就可以當(dāng)做全局變量使用,由于它和全局變量a同名,自然也就不用區(qū)分a究竟是全局變量還是局部變量了,在主程序和該函數(shù)內(nèi)都可以訪問、修改變量a的值了。
雖然使用global可使變量使用起來非常方便,但也容易引起混淆,故在使用過程中還是謹(jǐn)慎為好。
12.3 函數(shù)的遞歸與嵌套
遞歸,就是函數(shù)調(diào)用它自身。遞歸必須設(shè)置停止條件,否則函數(shù)將無法終止,形成死循環(huán)。
以計(jì)算階乘為例:
def func(n):
if n==1:
return 1
else:
return n*func(n-1) #func( )調(diào)用func( )
print(func(5))
運(yùn)行結(jié)果為:120
嵌套,指在函數(shù)中調(diào)用另外的函數(shù)。這是程序中常見的一種結(jié)構(gòu),在此不再贅述。
匿名函數(shù)
Python中可以在參數(shù)前加上關(guān)鍵字lambda定義一個(gè)匿名函數(shù),這樣的函數(shù)一般都屬于“一次性”的。
例如:
程序1:這是一個(gè)常規(guī)的函數(shù)定義和調(diào)用。
def f_add(x,y):
return x+y
print(f_add(2,3))
輸出結(jié)果:5
程序2:使用lambda定義匿名函數(shù)。
f_add=lambda x,y:x+y
print(f_add(2,3))
輸出結(jié)果:5
從上面的代碼可以看出,使用lambda僅僅減少了一行代碼。f_add=lambda x,y:x+y中的f_add不是變量名,而是函數(shù)名。程序1和程序2的print( )語句中的參數(shù)都是一樣的——調(diào)用函數(shù)f_add( )。所以,匿名函數(shù)并沒有太多的優(yōu)點(diǎn)。
python回調(diào)函數(shù)的使用方法
在計(jì)算機(jī)程序設(shè)計(jì)中,回調(diào)函數(shù),或簡稱回調(diào)(Callback),是指通過函數(shù)參數(shù)傳遞到其它代碼的,某一塊可執(zhí)行代碼的引用。這一設(shè)計(jì)允許了底層代碼調(diào)用在高層定義的子程序
有兩種類型的回調(diào)函數(shù):
那么,在python中如何實(shí)現(xiàn)回調(diào)函數(shù)呢,看代碼:
代碼如下:
def my_callback(input):
print "function my_callback was called with %s input" % (input,)
def caller(input, func):
func(input)
for i in range(5):
caller(i, my_callback)
我以最簡單的代碼給你演示一下
def sum(a,b):
... print a+b
...
sum(1,2)
3
def callBack(c,sum):
... print c
... sum(5,c)
...
callBack(5,sum)
5
10
sum 就是被回調(diào)的函數(shù)
回調(diào)函數(shù)就是在A函數(shù)執(zhí)行完畢之后 再執(zhí)行B函數(shù) 那么B就是回調(diào)函數(shù)
回調(diào)函數(shù)就是一個(gè)被作為參數(shù)傳遞的函數(shù)。在C語言中,回調(diào)函數(shù)只能使用函數(shù)指針實(shí)現(xiàn),在C++、Python、ECMAScript等更現(xiàn)代的編程語言中還可以使用仿函數(shù)或匿名函數(shù)。
回調(diào)函數(shù)的使用可以大大提升編程的效率,這使得它在現(xiàn)代編程中被非常多地使用。同時(shí),有一些需求必須要使用回調(diào)函數(shù)來實(shí)現(xiàn)。
最著名的回調(diào)函數(shù)調(diào)用有C/C++標(biāo)準(zhǔn)庫stdlib.h/cstdlib中的快速排序函數(shù)qsort和二分查找函數(shù)bsearch中都會要求的一個(gè)與strcmp類似的參數(shù),用于設(shè)置數(shù)據(jù)的比較方法。
意義
因?yàn)榭梢园颜{(diào)用者與被調(diào)用者分開,所以調(diào)用者不關(guān)心誰是被調(diào)用者。它只需知道存在一個(gè)具有特定原型和限制條件的被調(diào)用函數(shù)。簡而言之,回調(diào)函數(shù)就是允許用戶把需要調(diào)用的函數(shù)的指針作為參數(shù)傳遞給一個(gè)函數(shù),以便該函數(shù)在處理相似事件的時(shí)候可以靈活的使用不同的方法。