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