抽象数据类型和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)
第二题

 

posted @ 2019-09-06 13:10  walle_zhao  阅读(1270)  评论(0编辑  收藏  举报