结对编程小项目实现 Python+PyQt5+OOP
最開始我們兩個人分別寫個人項目時,分別用的是Java和C++,但是在做這個帶UI的升級版后,我堅定地擯棄了之前兩個人所寫的代碼,選擇用Python完全重寫,原因有以下幾點:
1. 之前兩個人寫的都不夠好,在生成算式(尤其是括號匹配等方面)的過程中算法過于繁瑣,而且有缺陷(我的只能最多生成一對括號,他的括號分布是固定搭配中的偽隨機)。
2. 之前兩人采用的算法導致生成算式后并不能很好的進行計算,而結對項目需要生成正確答案。
3. Java下的UI框架Swing過于陳舊,已不是一個相當受開發者歡迎的框架。C++下的Qt由于C++語法過于繁瑣,會帶來諸多不便。
4. Python有自帶的eval函數,該函數能夠進行簡單的四則運算(但不能計算乘方、三角函數等),帶來簡便。
?
于是最后決定使用Python+Qt的搭配,且UI的設計在Qt Designer下進行。
?
① 核心代碼部分
吸取了之前個人項目中因為采用面向過程的思想,結果導致整個算法過于繁瑣冗雜,且牽涉到了相當復雜的字符串處理,無論是代碼的簡潔清晰程度還是程序的運行效率都不夠好。
于是這次打算采用思路更加清晰、可塑性更高的面向對象的思想。
關于算式的生成與計算有兩個類:
?
a. Item類,代表算式中的某一項。
主要包括三個成員:前綴,本體與后綴。
前綴包含三角函數、左括號、平方根、負號
后綴包括平方、右括號
本體則是該一項的數值對應的字符串。
?
包含多個成員函數
構造函數傳入一個boolean值,默認為False。當且僅當其為True時會生成帶有三角函數的項。
? ? ? ?其余的例如插左括號、插右括號等操作就是直接對該個對象的前綴或后綴進行插入,較為簡單,不一一介紹。
具體代碼如下:
1 class Item: 2 def __init__(self, flag=False): # when the flag is true, initialize an trigonometric item 3 random = Random() 4 if flag == True: 5 func = ['sin', 'cos', 'tan'] 6 value = ['0°', '30°', '45°', '60°', '120°', '135°', '150°', '180°', '90°'] 7 choice = func[random.randint(0, 2)] 8 if choice == 'tan': 9 self.curr = choice + value[random.randint(0, 7)] 10 else: 11 self.curr = choice + value[random.randint(0, 8)] 12 else: 13 self.curr = str(random.randint(1, 100)) 14 15 def __str__(self): 16 return self.prev + self.curr + self.next 17 18 19 def add_left_bracket(self): 20 self.prev = '(' + self.prev 21 22 def add_right_bracket(self): 23 self.next += ')' 24 25 def add_square(self): 26 random = Random() 27 length = len(self.next) 28 if length == 0: 29 self.next = '2' 30 return 31 pos = random.randint(0, length - 1) 32 self.next = self.next[0: pos + 1] + '2' + self.next[pos + 1:] 33 34 35 def add_sqrt(self): 36 random = Random() 37 length = len(self.prev) 38 if length == 0: 39 self.prev = '√' 40 return 41 pos = random.randint(0, length - 1) 42 self.prev = self.prev[0: pos + 1] + '√' + self.prev[pos + 1:] 43 44 curr = '' 45 prev = '' 46 next = ''b. Exp類,代表一個算式表達式。
成員變量只有兩個:
一個存有Item的列表,以及一個存儲對應運算符的列表。
之所以將二者分開存儲,也是為了進一步對象化整個過程,否則各項和運算符容易互相雜糅導致必須進行較為復雜的字符串處理操作。
?
成員函數包括以下幾種:
為表達式添加Item的函數
添加括號的函數
添加乘方的函數
處理三角函數的函數
以字符串形式返回表達式的函數
返回運算結果的函數
隨機返回一個運算符的函數(靜態)
處理平方的函數(靜態)
處理平方根的函數(靜態)
具體代碼如下(已省去較為復雜的函數的實現):
class Exp:def __init__(self):passdef append(self, item):self.items.append(item)if len(self.items) != 1: # the first item added self.op.append(Exp.get_op())def add_brackets(self):passdef add_power(self): # add 2 or √passdef __str__(self): # return the str of the expressiontmp = ''for i in range(0, len(self.items)):if i == 0:tmp += self.items[i].__str__()else:tmp += self.op[i - 1] + self.items[i].__str__()return tmpdef handle_func(self):passdef get_answer(self): # return the answerpassitems = []op = []@staticmethoddef get_op():ops = ['+', '-', '*', '/']random = Random()return ops[random.randint(0, 3)]@staticmethoddef handle_square(s): # to compute the squarepass@staticmethoddef handle_sqrt(s): # to compute the square root, similar to the function abovepass整體的思路是
先判斷難度:
若為高中,則生成帶三角函數的各項加入表達式;否則不加。(用Item的構造函數是否傳True來區分)
然后進行添加括號操作,再根據是否是小學題選擇添加乘方運算或者不添加乘方運算。
獲取運算結果時,先處理算式的三角函數(如果有),再處理算式中的乘方(如果有),最后將處理結果(沒有任何三角函數以及乘方運算)的字符串傳給eval函數計算結果。
?
② UI部分
用Qt Designer設計界面。然后設定好各個signal和slot,再編寫各個slot的函數即可。
整體只需要兩個界面,一個是主界面,一個是答題界面
該部分較為簡單,并無特別的技術要求,故不細述。
?
③ 短信API接口部分
采用阿里云,注冊后直接自己編寫一個函數調用DEMO即可。
因為接口需要先安裝阿里云的庫才能用,所以我干脆設定成了每運行一次都安裝一次庫(用os.system函數)。
?
④ 中途遇見的問題
這樣完全靠系統隨機產生的算式,會存在無法運算的情況。例如負數開方,tan90°,或是0作了除數。
從而導致程序直接崩潰,因為無法運算。
解決方案:
1. 為避免生成tan90°,對隨機范圍進行限定:
def __init__(self, flag=False): # when the flag is true, initialize an trigonometric itemrandom = Random()if flag == True:func = ['sin', 'cos', 'tan']value = ['0°', '30°', '45°', '60°', '120°', '135°', '150°', '180°', '90°']choice = func[random.randint(0, 2)]if choice == 'tan':self.curr = choice + value[random.randint(0, 7)]else:self.curr = choice + value[random.randint(0, 8)]else:self.curr = str(random.randint(1, 100))2. 為避免負數開根,在開根號前進行檢查(捕捉異常):
@staticmethoddef handle_sqrt(s): # to compute the square root, similar to the function abovecnt = s.count('√')while cnt != 0:pos = s.find('√')i = pos + 1if s[i].isdigit():j = iwhile j < len(s) - 1 and (s[j + 1].isdigit() or s[j + 1] == '.'):j += 1tmp = ''try:tmp = str(round(math.pow(float(s[i: j + 1]), 0.5), 3))except Exception:print('\tException: negative square root')return ''s = s[: i - 1] + tmp + s[j + 1:]cnt -= 1else:j = iflag = 1while flag != 0 and j < len(s) - 1:j += 1if s[j] == ')':flag -= 1elif s[j] == '(':flag += 1tmp = ''try:tmp = str(round(math.pow(eval(s[i: j + 1]), 0.5), 3))except Exception:print('\tException: negative value or zero division in square root')return ''s = s[: i - 1] + tmp + s[j + 1:]cnt -= 1return s3. 為避免除0,在最后一步計算時也捕捉異常。
def get_answer(self): # return the answer self.handle_func()print('\tafter handling the functions: ' + self.__str__())s = Exp.handle_sqrt(Exp.handle_square(self.__str__()))print('\tafter handling the powers: ' + s)if s != '':res = 0try:res = round(eval(s), 3)except ZeroDivisionError:res = 77777print('\tException: zero division')finally:return reselse:return 77777對于無法計算的算式,請求返回其答案時會返回固定值77777。
?
當軟件生成一個題目時,發現其答案為77777,直接跳過該題,并在控制臺輸出錯誤報告。
if res == 77777:self.curr -= 1print('-----ILLEGAL EXPRESSION, AVOIDED-----')self.set_problem()return?
?
總結:
結對項目歷時數天,因為之前自己就學過Qt,所以UI部分實現難度不大,主要難度還是在于生成算式并計算答案中的算法中,以及申請阿里云的API使用權也是一個較為費神的東西。
通過這次項目,也算是進一步體味到了OOP的重要性與優越性,可以讓很復雜的一個程序結構變得非常清晰易懂,一有錯誤也能立刻找出來。
轉載于:https://www.cnblogs.com/cadenza/p/9751591.html
總結
以上是生活随笔為你收集整理的结对编程小项目实现 Python+PyQt5+OOP的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: css 五种位置
- 下一篇: python D5 字典