結對編程小項目實現 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 = ['', '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 = '²'
30             return
31         pos = random.randint(0, length - 1)
32         self.next = self.next[0: pos + 1] + '²' + 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):
        pass

    def 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):
        pass

    def add_power(self):  # add ² or √
        pass

    def __str__(self):  # return the str of the expression
        tmp = ''
        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 tmp

    def handle_func(self):
        pass



    def get_answer(self):  # return the answer
        pass

    items = []
    op = []

    @staticmethod
    def get_op():
        ops = ['+', '-', '*', '/']
        random = Random()
        return ops[random.randint(0, 3)]

    @staticmethod
    def handle_square(s):  # to compute the square
        pass

    @staticmethod
    def handle_sqrt(s):  # to compute the square root, similar to the function above
        pass

整體的思路是

先判斷難度:

若為高中,則生成帶三角函數的各項加入表達式;否則不加。(用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 item
        random = Random()
        if flag == True:
            func = ['sin', 'cos', 'tan']
            value = ['', '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. 為避免負數開根,在開根號前進行檢查(捕捉異常):

  @staticmethod
    def handle_sqrt(s):  # to compute the square root, similar to the function above
        cnt = s.count('')
        while cnt != 0:
            pos = s.find('')
            i = pos + 1
            if s[i].isdigit():
                j = i
                while j < len(s) - 1 and (s[j + 1].isdigit() or s[j + 1] == '.'):
                    j += 1
                tmp = ''
                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 -= 1
            else:
                j = i
                flag = 1
                while flag != 0 and j < len(s) - 1:
                    j += 1
                    if s[j] == ')':
                        flag -= 1
                    elif s[j] == '(':
                        flag += 1
                tmp = ''
                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 -= 1
        return s

3. 為避免除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 = 0
            try:
                res = round(eval(s), 3)
            except ZeroDivisionError:
                res = 77777
                print('\tException: zero division')
            finally:
                return res
        else:
            return 77777

對於無法計算的算式,請求返回其答案時會返回固定值77777。

 

當軟件生成一個題目時,發現其答案為77777,直接跳過該題,並在控制台輸出錯誤報告。

if res == 77777:
            self.curr -= 1
            print('-----ILLEGAL EXPRESSION, AVOIDED-----')
            self.set_problem()
            return

 

 

總結:

結對項目歷時數天,因為之前自己就學過Qt,所以UI部分實現難度不大,主要難度還是在於生成算式並計算答案中的算法中,以及申請阿里雲的API使用權也是一個較為費神的東西。

通過這次項目,也算是進一步體味到了OOP的重要性與優越性,可以讓很復雜的一個程序結構變得非常清晰易懂,一有錯誤也能立刻找出來。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM