閱讀目錄
- 一、抽象數據類型
- 二、python的類
- 三、類的定義和使用
- 四、python異常
- 五、類定義實例:學校認識管理系統中的類
- 六、部分課后編程練習
一、抽象數據類型
抽象數據類型(ADT)是計算機領域的一種思想和方法,是一種用於設計和實現程序模塊的有效技術。模塊通過接口來提供功能所需的信息,並不涉及具體實現細節。
1、數據類型和數據構造
python提供很多的數據類型,但是無論提供多少內置類型,在處理復雜問題的時候,程序員都需要根據自己需求把一組數據組織起來,構成一個數據對象,作為一個整體存儲、傳遞和處理。
例子:有理數的表示,當然可以使用兩個變量表示一個有理數。

a1 = 3 b1 = 5 # 兩個有理數之和 def rational_plus(a1, b1, a2, b2): num = a1*b2+b1*a2 den = b1*b2 return num,den a2, b2 = rational_plus(a1, b1, 7, 10)
但是這樣會有嚴重的管理問題:
- 編程者需要時刻記住哪兩個變量記錄的是分子和分母。
- 如果換一個有理數運算,需要頻繁更換變量名 。
- 非常容易出現錯誤。

r1 = (3, 5) r2 = (7,10) def rational_plus(r1, r2): num = r1[0]*r2[1]+r2[0]*r1[1] den = r1[1]*r1[1] return num,den
情況好了一點,這就是數據構造和組織的作用。但是依然存在問題:
- 不能將元組跟有理數區分,如果程序中再出現一種表示其他數據的二元組,那么無法區分出來。
- 有理數的相關操作跟有理數沒有綁定關系。
- 有理數運算需要直接按照位置獲得元素,如果對於復雜數據對象,要記住每個元素的位置是非常繁瑣的。
2、抽象數據類型的概念
其實上面的有理數例子暴露出兩個本質問題
- 數據表示的完全暴露。
- 對象使用和操作對具體表示的依賴性。
要解決這兩個問題就需要ADT(Abstract Data Type)思想。
抽象數據類型提供的操作包括三類:
- 構造操作。
- 解析操作。
- 變動操作。
python的所有內置類型都是一種抽象數據類型。比如str,int等。只不過它提供了漸變的文字量書寫方式,可以看做是簡化的構造操作。
可變對象和不變對象是區別就是是否具有變動操作。即該類對象在創建之后是否允許變化。
python中的不變對象str,tuple,frozenset是不變數據類型,而list,set和dict是可變數據類型。
3、抽象數據類型的描述
采用上圖的描述方法來進行描述。
ADT是一種思想,也是一種技術。
- 圍繞一類數據定義程序模塊
- 模塊的接口的實現分離
- 在需要實現的時候,從所用的編程語言中選擇一套合適的機制,采用合適的技術,實現這種功能,包括數據的表示和操作。
二、python的類
1、有理數類

class Rational: def __init__(self, num, den=1): self.num =num self.den = den def plus(self, another): den = self.den * another.den num = self.num * another.den + self.den * another.num return Rational(den, num)
2、類定義進階
關於python的私有屬性、還有雙下划線。實例方法、靜態方法、類方法。還有魔術方法__add__,__mul__,__floordiv__,__eq__,__lt__,__gt__等的應用。

class Rational: @staticmethod def _gcd(m, n): if n == 0: m, n = n, m while m != 0: m, n = n % m, m return n def __init__(self, num, den=1): if not isinstance(num, int) or not isinstance(den, int): raise TypeError if den == 0: raise ZeroDivisionError sign = 1 if num < 0: num, sign = -num, -sign if den < 0: den, sign = -den, -sign g = Rational._gcd(num, den) # call function gcd defined in this class. self._num = sign * (num//g) self._den = den//g def num(self): return self._num def den(self): return self._den def __add__(self, another): # mimic + operator den = self._den * another.den() num = (self._num * another.den() + self._den * another.num()) return Rational(num, den) def __mul__(self, another): # mimic * operator return Rational(self._num * another.num(), self._den * another.den()) def __floordiv__(self, another): # mimic // operator if another.num() == 0: raise ZeroDivisionError return Rational(self._num * another.den(), self._den * another.num()) # ... ... # Other operators can be defined similarly: # -:__sub__, /:__truediv__, %:__mod__, etc. def __eq__(self, another): return self._num * another.den() == self._den * another.num() def __lt__(self, another): return self._num * another.den() < self._den * another.num() # Other comparison operators can be defined similarly: # !=:__ne__, <=:__le__, >:__gt__, >=:__ge__ def __str__(self): return str(self._num) + "/" + str(self._den) def print(self): print(self._num, "/", self._den)
三、類的定義和使用
1、類的基本定義和使用
類定義
class用來定義類。一個類定義確定了一個名稱空間,不會與類外面的名字沖突。注意是在執行一個類定義的時候創建這個名稱空間。這個名稱空間將一直存在,除非明確刪除del。
類里定義的變量和函數稱為這個類的屬性。
類定義可以寫在程序的任何地方,只是如果放在一個函數內部,或者另外一個類的內部。那么有兩點不同:
- 建立一個局部的名稱空間。
- 不能使用import導入。
類對象及其使用
執行一個類定義創建一個類對象,這種對象主要支持兩種操作。
- 屬性訪問。
- 實例化。
2、實例對象:初始化和使用
初始化操作
twothirds = Ration(2,3)初始化的步驟:
- 建立一個對象
- 調用__init__函數給這個對象賦值
- 返回新建的對象
- 把這個對象賦值給變量
實例的數據類型
實例對象的數據訪問,用.的方式。
實例對象是一個獨立的數據體。可以賦值給變量,傳給函數,作為返回值,或者其他實例對象的屬性值等。
方法的定義和使用
除了數據屬性之外,類實例的另一種屬性就是方法。例如:p是類C的實例,m是p的實例方法。方法調用p.m()相當於C.m(p)
但是方法對象和函數對象是不同的,方法對象包含了兩部分:
- 由類中的函數定義生成的函數對象。
- 調用時約束的一個實例對象。在這個方法對象最終執行時,其中的實例對象將被作為函數的第一個實參。也就是self。
3、幾點說明
- 創建類對象之后,可以通過屬性賦值的方式給這個類或者對象添加新屬性。但是注意同名覆蓋的問題。
- 實例方法的內部調用其他實例方法用self.方法
- 方法內部也可以通過global和nonlocal訪問全局和局部空間的變量和函數。
- python的自省機制isinstance。
靜態方法和類方法
@staticmenthod靜態方法
@classmethod類方法
類定義的作用域規則
在類外采用類名.屬性名的方式引用。
在python中類的作用域的局部名字和函數作用域的局部名字不同:
- 在函數中,局部名字的作用域自動延伸到內部嵌套的作用域。比如函數g內部函數f,那么在f里可以直接使用g中定義的變量。
- 而在類中,局部定義的名字並不會自動延伸到嵌套的作用域中。如果需要調用,必須采用類名.屬性名的方式調用。
私有變量
單下划線和雙下划線。
4、繼承
對於子類的方法查找順序經典類是深度優先,新式類是廣度優先。
使用__bases__查看所有父類。
issubclass檢測兩個類是否具有繼承關系。
關於靜態約束和動態約束。

class B: def f(self): self.g() def g(self): print('B.g called') class C(B): def g(self): print('C.g called') x = C() x.f() #打印C.g called
標准函數super()的兩種用法:
- super().m(...)。不帶參數調用父類的方法,這種方法只能出現在方法函數的定義里。
- super(子類名,子類對象).m(....)。這個可以出現在程序的任何地方,並不要求一定出現在類的方法函數里。
四、python異常
1、異常類和自定義異常
python通過關鍵字raise或者解釋器進入異常處理模式。
用try:exception:finally來捕獲異常,進入異常處理模式。所有的異常都是繼承BaseException。如果用戶要自定義異常,選擇一個合適的異常類來繼承。
class RationalError(ValueError): pass
2、異常的傳播和捕捉
假設函數f執行過程中發生異常e。處理流程如下:
- 解釋器立即轉入異常處理模式,設法找到處理e的處理器。
- 先在發生異常的函數體(也就是f)里尋找處理器。如果發生異常的語句在try語句體里,那么順序檢查exception子句,看是否存在能處理e的處理器。
- 如果發生異常try語句的所有處理器都不能處理。那么查看包圍該try的try語句(如果存在)是否有與之匹配的處理器。
- 如果e不能再f函數里處理,f的執行異常終止。
- 如果上面的查找國展找到了與e相匹配的處理器,就執行該exception的代碼,執行完之后,回到正常執行模式,並從try語句之后開始繼續執行。
- 如果最后也沒有找到與之匹配的異常處理器,在交互式模式下,輸出錯誤,等待下一次命令。在自主執行下,該程序立即終止。
3、內置的標准異常類
五、類定義實例:學校認識管理系統中的類

import datetime class PersonTypeError(TypeError): pass class PersonValueError(ValueError): pass

class Person: _num = 0 def __init__(self, name, sex, birthday, ident): if not (isinstance(name, str) and sex in ("女", "男")): raise PersonValueError(name, sex) try: birth = datetime.date(*birthday) # 生成一個日期對象 except: raise PersonValueError("Wrong date:", birthday) self._name = name self._sex = sex self._birthday = birth self._id = ident Person._num += 1 # 實例計數 def id(self): return self._id def name(self): return self._name def sex(self): return self._sex def birthday(self): return self._birthday def age(self): return (datetime.date.today().year - self._birthday.year) def set_name(self, name): # 修改名字 if not isinstance(name, str): raise PersonValueError("set_name", name) self._name = name def __lt__(self, another): if not isinstance(another, Person): raise PersonTypeError(another) return self._id < another._id @classmethod def num(cls): return cls._num def __str__(self): return " ".join((self._id, self._name, self._sex, str(self._birthday))) def details(self): return ", ".join(("學號: " + self._id, "姓名: " + self._name, "性別: " + self._sex, "出生日期: " + str(self._birthday)))

class Student(Person): _id_num = 0 @classmethod def _id_gen(cls): # 實現學號生成規則 cls._id_num += 1 year = datetime.date.today().year return "1{:04}{:05}".format(year, cls._id_num) def __init__(self, name, sex, birthday, department): Person.__init__(self, name, sex, birthday, Student._id_gen()) self._department = department self._enroll_date = datetime.date.today() self._courses = {} # 一個空字典 def department(self): return self._department def en_year(self): return self._enroll_date.year def set_course(self, course_name): self._courses[course_name] = None def set_score(self, course_name, score): if course_name not in self._courses: raise PersonValueError("No this course selected:", course_name) self._courses[course_name] = score def scores(self): return [(cname, self._courses[cname]) for cname in self._courses] def details(self): return ", ".join((Person.details(self), "入學日期: " + str(self._enroll_date), "院系: " + self._department, "課程記錄: " + str(self.scores()))) # 還可以考慮其他有用的方法

class Staff(Person): _id_num = 0 @classmethod def _id_gen(cls, birthday): # 實現職工號生成規則 cls._id_num += 1 birth_year = datetime.date(*birthday).year return "0{:04}{:05}".format(birth_year, cls._id_num) def __init__(self, name, sex, birthday, entry_date=None): super().__init__(name, sex, birthday, Staff._id_gen(birthday)) if entry_date: try: self._entry_date = datetime.date(*entry_date) except: raise PersonValueError("Wrong date:", entry_date) else: self._entry_date = datetime.date.today() self._salary = 1720 # 默認設為最低工資, 可修改 self._department = "未定" # 需要另行設定 self._position = "未定" # 需要另行設定 def set_salary(self, amount): if not type(amount) is int: raise TypeError self._salary = amount def set_position(self, position): self._position = position def set_department(self, department): self._department = department def details(self): return ", ".join((super().details(), "入職日期: " + str(self._entry_date), "院系: " + self._department, "職位: " + self._position, "工資: " + str(self._salary))) # 還應包括查看salary,設置和查看院系,設置和查看職位的操作等,從略
六、部分課后編程練習

class Time: @staticmethod def s_to_time(seconds): return (seconds//3600) % 24, (seconds%3600) // 60, (seconds % 3600) % 60 def __init__(self, hours, minutes=0, seconds=0): if not (isinstance(hours, int) or isinstance(minutes, int) or isinstance(seconds, int)): raise TypeError if hours>24 or minutes >60 or seconds >60: raise ValueError self._hours = hours self._minutes = minutes self._seconds = seconds def hours(self): return self._hours def minutes(self): return self._minutes def seconds(self): return self._seconds def __add__(self, other): if not isinstance(other, Time): raise TypeError total_s = (self._hours + other.hours()) * 3600 + (self._minutes + other.minutes()) * 60 + self._seconds + other.seconds() h, m, s = Time.s_to_time(total_s) return Time(h, m, s) def __sub__(self, other): if not isinstance(other, Time): raise TypeError if self < other: raise ValueError total_s = self._hours * 3600 + self._minutes * 60 + self._seconds - other.hours() * 3600 - other.minutes() * 60 - other.seconds() h, m, s = Time.s_to_time(total_s) return Time(h, m, s) def __eq__(self, other): if not isinstance(other, Time): raise TypeError return self._hours == other.hours() and self._minutes == other.minutes() and self._seconds == other.seconds() def __lt__(self, other): if not isinstance(other, Time): raise TypeError return self._hours < other.hours() or (self._hours == other.hours() and self._minutes < other.minutes()) or ( self._hours == other.hours() and self._minutes == other.minutes() and self._seconds < other.seconds() ) def __str__(self): return str(self._hours)+'時'+str(self._minutes)+'分'+str(self._seconds)+'秒'

''' 2. 請定義一個類,實現正文中描述的Date抽象數據類型。 ''' class Date: dan = (1, 3, 5, 7, 8, 10, 12) shuang = (4, 6, 9, 11) @staticmethod def month_day(year, month): if month in Date.dan: month_day = 31 elif month in Date.shuang: month_day = 30 elif (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0) or (year % 3200 == 0 and year % 172800 == 0)and month == 2: month_day = 29 else: month_day = 28 return month_day def __init__(self, year, month=1, day=1): self._year = year self._month = month self._day = day self._leap_year = 0 if (self._year % 4 == 0 and self._year % 100 != 0) or (self._year % 400 == 0) or (self._year % 3200 == 0 and self._year % 172800 == 0): self._leap_year = 1 def year(self): return self._year def month(self): return self._month def day(self): return self._day def __sub__(self, other): if self._year < other.year(): raise ValueError('date1 < date1, not sub') oday = 0 print('oday1',oday,other.month()) for i in range(1,other.month()): oday += Date.month_day(self._year, i) oday += other.day() sday = 0 for j in range(other.year(), self._year): if (j % 4 == 0 and j % 100 != 0) or (j % 400 == 0) or (j % 3200 == 0 and j % 172800 == 0): sday += 366 else: sday += 365 for m in range(1,self._month): sday += Date.month_day(self._year, m) sday += self._day return sday - oday def after_day(self, n): month_day = Date.month_day(self._year, self._month) if self._day + n <= month_day : return Date(self._year, self._month, self._day+n) else: n -= (month_day - self._day) month = self._month + 1 year = self._year if month == 13: month = 1 year += 1 while n > Date.month_day(year, month): if month == 13: month = 1 year += 1 for i in range(month, 13): month_day = Date.month_day(year, month) if n < month_day: break n -= month_day month += 1 day = n return Date(year, month, day) def num_date(self, n): return self.after_day(n-1) def adjust(self, n): if n >=0 : return self.after_day(n) else: month_day = Date.month_day(self._year, self._month) if self._day + n > 0: return Date(self._year, self._month, self._day + n) else: n += self._day month = self._month - 1 year = self._year if month == 0: month = 12 year -= 1 while n + Date.month_day(year, month) <= 0: if month == 0: month = 12 year -= 1 for i in range(month, 0, -1): month_day = Date.month_day(year, month) if n + month_day > 0: break n += month_day month -= 1 day = month_day + n return Date(year, month, day) def __str__(self): return str(self._year) + '/' + str(self._month) + '/' + str(self._day)