本文是給有一點(diǎn) Python 基礎(chǔ)但還想進(jìn)一步深入的同學(xué),有經(jīng)驗(yàn)的開發(fā)者建議跳過。
前言
在寫這篇案例系列的時(shí)候 junhuanchen 期望能夠引導(dǎo)用戶如何成為專業(yè)的開發(fā)者,不是只會(huì)調(diào)用代碼就好,所以在 MaixPy3 開源項(xiàng)目上期望為你帶來值得學(xué)習(xí)和容易上手的開源項(xiàng)目,所以開篇會(huì)引導(dǎo)用戶學(xué)習(xí)一些長(zhǎng)期有利于編程工作上好的做法和觀念,就先從最簡(jiǎn)單的認(rèn)知項(xiàng)目開始吧。
第一次接觸需要編程的開源硬件項(xiàng)目,要做的第一件事就是先有一個(gè)好的開始,例如運(yùn)行 Hello World 程序,意味著你必須能夠先將這個(gè)事物跑起來才能夠繼續(xù)后續(xù)的內(nèi)容,它可能是硬件、軟件、工具等可編程的載體。
但這里先不強(qiáng)調(diào)立刻開始運(yùn)行程序,而是強(qiáng)調(diào)如何熟悉一個(gè)開源項(xiàng)目。
要先找到它提供的開發(fā)文檔(例如本文),先縱覽全文,站在專業(yè)的角度來看,你需要先關(guān)注它提供了哪些資源,可以在哪里反饋你的問題,這樣就有利于你后續(xù)開發(fā)過程中出現(xiàn)問題后,該如何迅速得到解決,避免自己之后在學(xué)習(xí)和開發(fā)過程中耽誤時(shí)間。

有哪些資源是值得關(guān)注的?
- 學(xué)會(huì)搜索?。。。?!
- 找到它的開源項(xiàng)目(如:github.com/sipeed),獲取它所提供的一系列源碼。
- 找到它提供的用戶手冊(cè)、應(yīng)用案例、數(shù)據(jù)手冊(cè)等等一系列開發(fā)所需要的文檔。
- 找到它的開發(fā)、編譯、燒錄、量產(chǎn)等一系列配套工具鏈,為后續(xù)軟件開發(fā)活動(dòng)中做準(zhǔn)備。
- 找到它的公開交流的環(huán)境,如 bbs、github、twitter、facebook、qq、wechat 等社交平臺(tái)。
現(xiàn)在你可以放心的編程了,但你還需要遵守一些在開源軟件上的規(guī)則,認(rèn)知到開源協(xié)議的存在,不要隨意地做出侵犯他人軟件的行為,哪怕沒有法律責(zé)任的問題。
在開源軟件的世界里,鼓勵(lì)人們自由參與和貢獻(xiàn)代碼,而不是鼓勵(lì)如何免費(fèi)白嫖,自由不等于免費(fèi),免費(fèi)不等于服務(wù),將軟件源碼公開是為了讓用戶更好更具有針對(duì)性的提交和反饋項(xiàng)目中存在的問題,不是為了更好服務(wù)你,請(qǐng)不要以服務(wù)自己的產(chǎn)品為中心。
請(qǐng)尊重所有在開源環(huán)境里工作的朋友們,尊重他們(或是未來的你)的勞動(dòng)成果。
最后在開源的世界里,學(xué)會(huì)技術(shù),學(xué)會(huì)成長(zhǎng),學(xué)會(huì)參與項(xiàng)目,學(xué)會(huì)分享成果!
Hello World
關(guān)于本機(jī)怎樣安裝運(yùn)行 Python 的基礎(chǔ)知識(shí),建議從其他網(wǎng)站教程得知。
說了這么多,不如先來運(yùn)行一段 Python3 代碼吧。
print("hello world")
點(diǎn)擊下方的 run 按鈕即可運(yùn)行,如果有條件就在本機(jī)運(yùn)行測(cè)試。
<div align="center" >
<iframe src="https://tool.lu/coderunner/embed/aEj.html" style="width:90%; height:320px;" frameborder="0" mozallowfullscreen webkitallowfullscreen allowfullscreen></iframe>
</div>
在線 Python 編程 runoob-python google-colab 備用地址。
但這樣的代碼是不夠的,稍微認(rèn)真一點(diǎn)寫。
# encoding: utf-8
def unit_test():
'''
this is unit_test
'''
print("hello world")
raise Exception('unit_test')
if __name__ == "__main__":
try:
unit_test()
except Exception as e:
import sys, traceback
exc_type, exc_value, exc_obj = sys.exc_info()
traceback.print_tb(exc_obj)
print('have a error:', e)
運(yùn)行結(jié)果:
PS C:\Users\dls\Documents\GitHub\MaixPy3> & C:/Users/dls/anaconda3/python.exe c:/Users/dls/Documents/GitHub/MaixPy3/test.py
hello world
File "c:/Users/dls/Documents/GitHub/MaixPy3/test.py", line 12, in <module>
unit_test()
File "c:/Users/dls/Documents/GitHub/MaixPy3/test.py", line 8, in unit_test
raise Exception('unit_test')
have a error: unit_test
代碼瞬間就變得復(fù)雜了起來?其實(shí)不然,這么寫必然有它的用意,那這么寫都考慮到了哪些情況呢?
注意字符編碼和代碼縮進(jìn)格式
初學(xué)者經(jīng)常會(huì)出現(xiàn)縮進(jìn)不對(duì)齊的語法問題,代碼的語法出現(xiàn)問題過于基礎(chǔ)就不詳談,檢查代碼的小技巧就是 CTAL + A 全選代碼,按 TAB 鍵右縮進(jìn),再配合 SHIFT + TAB 左縮進(jìn)來發(fā)現(xiàn)哪段代碼存在問題。
首行的 # encoding: utf-8 是為了避免在代碼中存在中文或其他語言的字符編碼導(dǎo)致的運(yùn)行出錯(cuò)的問題。
在 python3 的字符串類型中 str 與 bytes 是一對(duì)歡喜冤家,例如 print(b'123') 打印出來的是 b'123' ,而實(shí)際上就是 '123' 的 bytes 字符串,前綴 b 只是為了和 str 區(qū)分,因?yàn)橛猛静煌诓煌慕涌趯?duì)數(shù)據(jù)類型的需求不對(duì),例如傳遞 str 字符串時(shí)候是不允許輸入 '\xFF' (0xFF) 字符的(會(huì)在轉(zhuǎn)換過程中丟失),但 bytes 可以存儲(chǔ)和表達(dá)。
給代碼加入單元測(cè)試和異常捕獲
想要寫出一套穩(wěn)定可用的代碼,需要圍繞接口可重入可測(cè)試的設(shè)計(jì)來編寫封裝,任何人寫的代碼都可能存在缺陷,在不能確定是哪里產(chǎn)生的問題之前,要能夠恢復(fù)現(xiàn)場(chǎng)也要能夠定位具體位置,以求問題能夠最快得到反饋。
所以在代碼功能還沒寫之前,先把測(cè)試和異常的模板寫好,再開始寫功能,邊寫邊測(cè),確保最終交付的軟件代碼就算出問題也可以隨時(shí)被測(cè)試(定位)出來。
def unit_test():
'''
this is unit_test
'''
print("hello world")
if __name__ == "__main__":
unit_test()
這樣的代碼可以保證任何人在任何時(shí)候運(yùn)行該代碼的時(shí)候都可以復(fù)現(xiàn)當(dāng)時(shí)寫下的場(chǎng)合所做的內(nèi)容,然后 if __name__ == "__main__": 意味著該代碼被其他模塊包含的時(shí)候,不會(huì)在 import 該 Python 模塊(可取名成 hello )模塊時(shí)調(diào)用,而是根據(jù)自己的代碼需要執(zhí)行相應(yīng)的單元測(cè)試進(jìn)行測(cè)試。
import hello
hello.unit_test() # print("hello world")
接著加入異常機(jī)制(try: except Exception as e:)保護(hù)代碼段,表示該段代碼出錯(cuò)的時(shí)候,能夠不停下代碼繼續(xù)運(yùn)行,像硬件資源訪問的代碼常常會(huì)發(fā)生超時(shí)、找不到、無響應(yīng)的錯(cuò)誤狀態(tài),這種情況下,一個(gè)跑起來的系統(tǒng)程序通常不需要停下來,出錯(cuò)了也可以繼續(xù)運(yùn)行下一件事,然后把當(dāng)時(shí)的錯(cuò)誤記錄下來,通過 print 或 logging 日志模塊記錄下來,拿著錯(cuò)誤結(jié)果(日志)反饋給開發(fā)者,這樣開發(fā)者就可以分析、定位和解決問題,這其中也包括你自己。
try:
raise Exception('unit_test')
except Exception as e:
import sys, traceback
exc_type, exc_value, exc_obj = sys.exc_info()
traceback.print_tb(exc_obj)
print('have a error:', e)
單元測(cè)試是每個(gè)程序都盡可能保持的基本原則,雖然人會(huì)偷懶,但最起碼的代碼格式還是要有的。
注:traceback 可以抓取最后一次運(yùn)行出現(xiàn)的錯(cuò)誤而不停止運(yùn)行,但該模塊不存在 MicroPython(MaixPy) 中,它有類似的替代方法。
封裝代碼接口成通用模塊的方法
世上本沒有路,走的人多了,也便成了路。
這里說的路實(shí)際上就是一種封裝和參考,它意味著你寫的代碼成為一種事實(shí)上的通用操作。
在 Python 上有很多封裝參考,主要是為了形成抽象的函數(shù)模塊。
所以出現(xiàn)了一些經(jīng)典的編程思想,如面向過程、面向?qū)ο?、面向切面、面向函?shù)等編程方法,哪一種更好就不比較和討論了。
這里就簡(jiǎn)單敘述一下這些編程方法的逐漸發(fā)展與變化的過程,可以如何做出選擇。
面向過程
用面向過程的思維寫代碼,強(qiáng)調(diào)的是這份代碼做的這件事需要分幾步完成,例如最開始寫代碼都是這樣的。
one = 1
two = 2
three = one + two
print(three)
這是用人類直覺的過程來寫代碼,后來意識(shí)到可以這樣寫成通用功能,這是最初的代碼封裝成某個(gè)函數(shù)。
def sum(num1, num2):
return num1 + num2
one, two = 1, 2
print(sum(one, two)) # 1 + 2 = 3
于是你多寫了個(gè)類似的乘法操作。
def mul(num1, num2):
return num1 * num2
one, two = 1, 2
print(mul(one, two)) # 1 * 2 = 2
這時(shí)的代碼是按照每一個(gè)代碼操作流程來描述功能的。
面向?qū)ο?/h3>
面向?qū)ο笫窍鄬?duì)于面向過程來講的,把相關(guān)的數(shù)據(jù)和方法組織為一個(gè)整體來看待,從更高的層次來進(jìn)行系統(tǒng)建模,更貼近事物的自然運(yùn)行模式,一切事物皆對(duì)象,通過面向?qū)ο蟮姆绞剑瑢F(xiàn)實(shí)世界的事物抽象成對(duì)象,現(xiàn)實(shí)世界中的關(guān)系抽象成類、繼承,幫助人們實(shí)現(xiàn)對(duì)現(xiàn)實(shí)世界的抽象與數(shù)字建模。
在看了一些面向?qū)ο蟮拿枋龊螅銜?huì)意識(shí)到上節(jié)面向過程的函數(shù)操作可能很通用,應(yīng)該不只適用于一種變量類型,所以可以通過面向?qū)ο螅╟lass)的方法來封裝它,于是可以試著這樣寫。
class object:
def sum(self, a, b):
return a + b
def mul(self, a, b):
return a * b
obj = object()
print(obj.sum(1, 2)) # 1 + 2 = 3
print(obj.mul(1, 2)) # 1 * 2 = 2
這樣會(huì)意識(shí)到似乎還不只是數(shù)字能用,感覺字符串也能用。
class object:
def sum(self, a, b):
return a + b
def mul(self, a, b):
return a * b
obj = object()
print(obj.sum('1', '2')) # 1 + 2 = 3
print(obj.mul('1', '2')) # 1 * 2 = 2
但這么寫會(huì)出問題的,字符串相加的時(shí)候可以,但相乘的時(shí)候會(huì)報(bào)錯(cuò)誤,因?yàn)槭亲址@個(gè)類型的變量是不能相乘的。
12
Traceback (most recent call last):
File "c:/Users/dls/Documents/GitHub/MaixPy3/test.py", line 8, in <module>
print(obj.mul('1', '2')) # 1 * 2 = 2
File "c:/Users/dls/Documents/GitHub/MaixPy3/test.py", line 5, in mul
return a * b
TypeError: can't multiply sequence by non-int of type 'str'
顯然這樣寫代碼就不合理了,但這時(shí)運(yùn)用的面向?qū)ο蟮乃枷胧强尚械?,只是?shí)現(xiàn)的方式不夠好而已,所以這時(shí)候應(yīng)該重新設(shè)計(jì)對(duì)象類結(jié)構(gòu),例如可以寫成下面的類結(jié)構(gòu)。
class obj:
def __init__(self, value):
self.value = value
def __add__(self, obj):
return self.value + obj
def __mul__(self, obj):
return self.value * obj
print(obj(1) + 2) # 3
print(obj(1) * 2) # 2
其中 __add__ 和 __mul__ 是可重載運(yùn)算符函數(shù),意味著這個(gè)類實(shí)例化的對(duì)象在做 + 和 * 運(yùn)算操作的時(shí)候,會(huì)調(diào)用類(class)重載函數(shù),接著可以提升可以運(yùn)算的對(duì)象類型,進(jìn)一步繼承對(duì)象拓展功能(class number(obj):)和訪問超類的函數(shù)(super().__add__(obj)),其中 if type(obj) is __class__: 用于判斷傳入的參數(shù)對(duì)象是否可以進(jìn)一步處理。
class number(obj):
def __add__(self, obj):
if type(obj) is __class__:
return self.value + obj.value
return super().__add__(obj)
def __mul__(self, obj):
if type(obj) is __class__:
return self.value * obj.value
return super().__mul__(obj)
print(number(1) + 2)
print(number(1) * 2)
print(number(1) + number(2))
print(number(1) * number(2))
這時(shí)候會(huì)發(fā)現(xiàn)可以進(jìn)一步改寫成字符串?dāng)?shù)值運(yùn)算。
class str_number(obj):
def __init__(self, value):
self.value = int(value)
def __add__(self, obj):
if type(obj) is __class__:
return str(self.value + int(obj.value))
return str(super().__add__(int(obj)))
def __mul__(self, obj):
if type(obj) is __class__:
return str(self.value * int(obj.value))
return str(super().__mul__(int(obj)))
print(str_number('1') + '2')
print(str_number('1') * '2')
print(str_number('1') + str_number('2'))
print(str_number('1') * str_number('2'))
現(xiàn)在就可以解決了最初的同類操作適用不同的數(shù)據(jù)類型,把最初的一段操作通用到數(shù)值和字符串了,可以受此啟發(fā),它不僅僅只是加法或乘法,還有可能是其他操作,關(guān)于面向?qū)ο蟮膬?nèi)容就說到這里,感興趣的可以查閱相關(guān)資料深入學(xué)習(xí),本節(jié)只講述可以怎樣使用面向?qū)ο蟮乃季S寫代碼,而不是單純把 Class 當(dāng) Struct 來使用。
像最初寫的代碼,如果不通過對(duì)象繼承分解函數(shù),最終將會(huì)形成一個(gè)巨大的 Struct 結(jié)構(gòu)。
面向切面
現(xiàn)在到了選擇更多編程思維方式了,關(guān)于面向切面編程方法的場(chǎng)景是這樣提出的,有一些函數(shù),它在產(chǎn)品調(diào)試的時(shí)候會(huì)需要,但在產(chǎn)品上線的時(shí)候是不需要的,那這樣的函數(shù)應(yīng)該如何實(shí)現(xiàn)比較好?接下來不妨直接看代碼,以日志輸出的代碼為例來說說面向切面,介紹一下如何使用裝飾器進(jìn)行編程的方法。
def log(param):
# simple
if callable(param):
def wrapper(*args, **kw):
print('%s function()' % (param.__name__,))
param(*args, **kw)
return wrapper
# complex
def decorator(func):
import functools
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (param, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
def now():
print("2019")
@log
def now1():
print("2020")
@log("Is this year?")
def now2():
print("2021")
now()
now1()
now2()
運(yùn)行結(jié)果:
PS C:\Users\dls\Documents\GitHub\MaixPy3> & C:/Users/dls/anaconda3/python.exe c:/Users/dls/Documents/GitHub/MaixPy3/test.py
2019
now1 function()
2020
Is this year? now2():
2021
PS C:\Users\dls\Documents\GitHub\MaixPy3>
對(duì)于產(chǎn)品上線時(shí)不需要的函數(shù),注釋掉就可以了,更進(jìn)一步還可以重新設(shè)計(jì)某些函數(shù)滿足于某些條件后再運(yùn)行。
- 在執(zhí)行某段操作前,先打印當(dāng)前的系統(tǒng)狀態(tài)記錄下來,確保出錯(cuò)時(shí)可以追溯到出錯(cuò)的地方。
- 在發(fā)送網(wǎng)絡(luò)數(shù)據(jù)前,要先檢查網(wǎng)絡(luò)通路是否存在,網(wǎng)卡是否還在工作。
- 在運(yùn)行操作前,先檢查內(nèi)存夠不夠,是否需要釋放內(nèi)存再繼續(xù)操作。
可以看到,當(dāng)想要不改變某些現(xiàn)成庫(kù)代碼的條件下拓展系統(tǒng)的功能,就不免需要面向切面的設(shè)計(jì)方法。
注意!面向切面提出的是編程思想,實(shí)現(xiàn)的方法不一定是裝飾函數(shù),可以是回調(diào)函數(shù),也可以是重載函數(shù)。
面向函數(shù)
關(guān)于面向函數(shù)的場(chǎng)景是由于有些問題是被數(shù)學(xué)公式提出的,所以對(duì)于一些數(shù)學(xué)問題,并不一定要按過程化的思維來寫,如實(shí)現(xiàn)階乘函數(shù)(factorial),它的功能就是返回一個(gè)數(shù)的階乘,即1*2*3*...*該數(shù)。
def fact(n):
if n == 3:
return 3*2*1
if n == 2:
return 2*1
if n == 1:
return 1
print(fact(3))
print(fact(2))
print(fact(1))
不難看出用最初的面向過程來寫是寫不下去的,不可能去定義所有的可能性,所以要找出規(guī)律,可以通過遞歸的方式實(shí)現(xiàn)。
def fact(n):
return 1 if n == 1 else n * fact(n - 1)
print(fact(1))
print(fact(5))
print(fact(100))
這樣功能就完整了,簡(jiǎn)單來說函數(shù)式編程是讓編程思維追求程序中存在的公式。
試試快速迭代的敏捷開發(fā)?
現(xiàn)代開源軟件在經(jīng)歷了產(chǎn)測(cè)、內(nèi)測(cè)、公測(cè)等環(huán)節(jié)后,直至更新到用戶的手里,從前到后的過程通常在一周內(nèi)就可以完成,所以在設(shè)計(jì)程序接口的時(shí)候,可以接受當(dāng)下接口設(shè)計(jì)的不完美,等到未來有一個(gè)更好的替代功能接口的時(shí)候,就可以將其迭代替換下來,這意味著可以不用設(shè)計(jì)好整體的軟件系統(tǒng)再開始工作,而是邊做邊改進(jìn),這套理論適用于初期需要頻繁更新業(yè)務(wù)邏輯的開源軟件。
這里簡(jiǎn)單引用一段小故事來說明這個(gè)現(xiàn)象。
快速迭代,不是說一定要產(chǎn)品做好了,才能上線,半成品也能上線。
在沒有上線之前,你怎么知道哪好那不好。所以半成品也是可以出門的,一定不要吝惜在家,丑媳婦才需要盡早見公婆。盡早的讓用戶去評(píng)判你的想法,你的設(shè)計(jì)是否可以贏得用戶的喜愛。快速發(fā)出,緊盯用戶反饋。百度完成了第一版的搜索引擎,也是讓用戶去做的選擇。用百度 CEO 李彥宏(Robin)的話來說“你怎么知道如何把這個(gè)產(chǎn)品設(shè)計(jì)成最好的呢?只有讓用戶盡快去用它。既然大家對(duì)這版產(chǎn)品有信心,在基本的產(chǎn)品功能上我們有競(jìng)爭(zhēng)優(yōu)勢(shì),就應(yīng)該抓住時(shí)機(jī)盡快將產(chǎn)品推向市場(chǎng),真正完善它的人將是用戶。他們會(huì)告訴你喜歡哪里不喜歡哪里,知道了他們的想法,我們就迅速改,改了一百次之后,肯定就是一個(gè)非常好的產(chǎn)品了?!?/p>
準(zhǔn)備一個(gè)好的開始
看到這里的你,可能會(huì)困惑,可能會(huì)看不懂,會(huì)覺得很復(fù)雜,這是認(rèn)知上的偏差,實(shí)際上本文所講述的都是編程思想上的基礎(chǔ),如果想專業(yè)起來,不認(rèn)真是不行的。
不妨自己動(dòng)手試試看吧。