抽象數據類型和python類


閱讀目錄

  • 一、抽象數據類型
  • 二、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)
ADT版本的有理數

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)
ADT進階版本

三、類的定義和使用

1、類的基本定義和使用

類定義

class用來定義類。一個類定義確定了一個名稱空間,不會與類外面的名字沖突。注意是在執行一個類定義的時候創建這個名稱空間。這個名稱空間將一直存在,除非明確刪除del。

類里定義的變量和函數稱為這個類的屬性。

類定義可以寫在程序的任何地方,只是如果放在一個函數內部,或者另外一個類的內部。那么有兩點不同:

  •  建立一個局部的名稱空間。
  •  不能使用import導入。

類對象及其使用

執行一個類定義創建一個類對象,這種對象主要支持兩種操作。

  • 屬性訪問。
  • 實例化。

2、實例對象:初始化和使用

初始化操作

twothirds = Ration(2,3)初始化的步驟:

  1. 建立一個對象
  2. 調用__init__函數給這個對象賦值
  3. 返回新建的對象
  4. 把這個對象賦值給變量

實例的數據類型

實例對象的數據訪問,用.的方式。

實例對象是一個獨立的數據體。可以賦值給變量,傳給函數,作為返回值,或者其他實例對象的屬性值等。

方法的定義和使用

除了數據屬性之外,類實例的另一種屬性就是方法。例如: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
python采用動態約束

標准函數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)
第二題

 


免責聲明!

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



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