ch6-定制数据对象(打包代码和数据)
为了看出数据属于哪个选手,教练向各个选手的数据文件中添加了标识数据:选手全名,出生日期,计时数据。
例如:sarah文件的数据更新为:
Sarah Sweeney,2002-6-17,2:58,2.58,2:39,2-25,2-55,2:54,2.18,2:55,2:55,2:22,2-21,2.22
接下来主要考虑Sarah的数据,从教练的原始数据抽取并处理Sarah的3个最快时间。
运行结果确实很好,但是你必须指定并创建Sarah的3个变量,以便标识哪个名字、出生日期和计时数据与Sarah关联。如果再增加代码来处理剩余的3个选手James、Julie和Mikey的数据,这就需要长达12个变量。如果人数增加,变量增加的更多。。。
1、使用字典关联数据
字典:将数据值与键关联。例如:键-值:name-Sarah
字典是一个内置的数据结构(内置于python中),允许将数据与键而不是数字字典关联。这样可以使内存中的数据与实际数据的结构保持一致。
实际上在其他编程语言中“字典”被称为“映射”、“散列”、“关联数组”等。
>>> #字典的具体使用 >>> cleese={} # 使用大括号创建空字典 >>> p= SyntaxError: invalid syntax >>> >>> #字典的具体使用 >>> aa={} # 使用大括号创建空字典 >>> bb=dict() #使用一个工厂函数创建 >>> type(aa) <class 'dict'> >>> type(bb) <class 'dict'> >>> #向如上的两个字典中分别增加一些数据。 >>> #注意:这里会自行表示出数据的具体结构,因为每个字典都有一个Name和Occupation列表。还要注意bb字典是一次性同时创建的(而不是分步增加数据): >>> aa['Name']='John aa' >>> aa['Occupation']=['actor','comedian','writer','film producer'] >>> bb={'Name': 'Michael bb', 'Occupation': ['comedian','actor','writer','tv']} >>> #数据与键关联后(在这里,键是字符串),可以使用类似列表的记法访问单个数据项 >>> bb['Name'] #使用键作为索引来访问数据 'Michael bb' >>> aa['Occupation'][-1] #使用数字访问存储在一个特定字典键位置上的列表项。可以认为是“索引串链”,从右向左读作:“......与Occupation关联的列表的最后一项......” 'film producer' >>> #与列表一样,字典可以动态扩展来存储额外的键/值对。下面增加一些有关出生地的数据: >>> bb['出生地']='中国,北京' >>> aa['出生地']='中国,河南,洛阳' >>> #与列表不同,字典不会维持插入的顺序,重点是它会维护关联关系,而不是顺序 >>> bb {'Name': 'Michael bb', 'Occupation': ['comedian', 'actor', 'writer', 'tv'], '出生地': '中国,北京'} >>> aa {'Name': 'John aa', 'Occupation': ['actor', 'comedian', 'writer', 'film producer'], '出生地': '中国,河南,洛阳'} >>> #aa中Occupation的顺序与插入顺序不同,这没关系,不是问题。
下面采用字典来重新保存和处理Sarah的数据:
代码审查和优化:
1、一次性完成字典的创建。
2、把字典创建代码移到get_coach_data()函数中,返回一个字典而不是列表。
3、把确定各个选手的3个最快时间的代码移到get_coach_data()函数中。
4、调整主代码中的函数调用,调用这个新版本的get_coach_data()函数来支持新的操作模式。
把代码和数据放在一起是对的。
尽量将函数和函数所要处理的数据相关联,也就是说,函数将特定于这些数据,而不是一般化的通用函数。
2、将代码及其数据打包在类中
类可以用来将代码与代码处理的数据相关联。这么做的好处:
- 使用类有助于降低复杂性。
- 降低复杂性意味着bug更少。
- bug更少意味着代码更可维护。
在面向对象的世界里,你的代码通常称为类的方法(method),数据称为类的属性(attribute)。实例化的数据对象通常称为实例(instance)。
使用class定义类:
class Athlete: def __init__(self): #初始化各个对象的代码 #....
定义一个类时,实际上是在定义一个定制工厂函数,然后可以使用这个工厂函数创建实例。
每个定义的类都有一个特殊的方法,名为__init__(),可以通过这个方法控制如何初始化对象。
类中的方法同样使用def来定义。
创建对象实例:
a=Athlete() #只需将对类名的调用赋至各个变量 b=Athlete() c=Athlete() d=Athlete()
python 在处理a=Athlete()创建实例的代码时,它把工厂函数调用转换为以下调用,明确了类、方法(自动设置为__init__())和所处理的对象实例:
Athlete().__init__(a)
可以看出,对象实例的目标标识符a赋至self参数,self参数可以帮助标识要处理哪个对象实例的数据。
注意:类代码设计为在所有对象实例间共享:方法共享,而属性不共享。
类中所有方法的第一个参数都是self。
class Athlete: #以下注意使用“self”标识调用对象实例 def __init__(self,value=0): self.thing=value #“初始化”代码将所提供的一个值赋至一个名为“self.thing”的类属性 def how_big(self): return(len(self.thing))
所以self很重要!!!!
你写的代码: python执行的代码:
d=Athlete("Andy") -> Athlete.__init__(d, "Andy")
d.how_big() -> Athlete.how_big(d)
Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:38:48) [MSC v.1900 32 bit (Intel)] on win32 Type "copyright", "credits" or "license()" for more information. >>> #创建一个名为Athlete的很小的类 >>> class Athlete : def __init__(self, a_name, a_dob=None, a_times=[]): self.name=a_name self.dob=a_dob self.times=a_times >>> #创建两个唯一的对象实例 >>> ss=Athlete('ss', '2002-1-1', ['2:58', '2.58', '1.56']) >>> jj=Athlete('jj')>>> type(ss) <class '__main__.Athlete'> >>> type(jj) <class '__main__.Athlete'> >>> #尽管ss和jj都是选手且都有Athlete类的工厂函数创建,但他们存储在不同的内存地址上 >>> ss <__main__.Athlete object at 0x021DB030> >>> jj <__main__.Athlete object at 0x021C4F90> >>> #使用点记法来访问与各个对象实例关联的属性 >>> ss.name 'ss' >>> ss.dob '2002-1-1' >>> ss.times ['2:58', '2.58', '1.56'] >>> jj.name 'jj' >>> jj.dob >>> jj.times [] >>>
采用定义类和定义新方法来重新实现:
def sanitize(time_string): if '-' in time_string: splitter = '-' elif ':' in time_string: splitter = ':' else: return(time_string) (mins, secs) = time_string.split(splitter) return(mins + '.' + secs) class Athlete: #定义类 def __init__(self, a_name, a_dob=None, a_times=[]): self.name = a_name self.dob = a_dob self.times = a_times def top3(self): return(sorted(set([sanitize(t) for t in self.times]))[0:3]) def get_coach_data(filename): try: with open(filename) as f: data = f.readline() templ = data.strip().split(',') return(Athlete(templ.pop(0), templ.pop(0), templ)) #删除字典创建代码,替换为类Athlete对象创建代码 except IOError as ioerr: print('File error: ' + str(ioerr)) return(None) james = get_coach_data('james2.txt') julie = get_coach_data('julie2.txt') mikey = get_coach_data('mikey2.txt') sarah = get_coach_data('sarah2.txt') print(james.name + "'s fastest times are: " + str(james.top3())) #使用点记法访问 print(julie.name + "'s fastest times are: " + str(julie.top3())) print(mikey.name + "'s fastest times are: " + str(mikey.top3())) print(sarah.name + "'s fastest times are: " + str(sarah.top3()))
更多功能=更多方法!
只需增加方法将你需要的新功能封装在类中;
向类中增加2个方法:第一个add_time()将一个额外的计时值追加到选手的计时数据。第二个方法add_times()会用一个或多个计时值(提供一个列表)来扩展一个选手的计时数据;
已经将代码和数据打包在一起,并且创建了一个定制类,由这个类可以创建共享相同行为的对象。
通过把选手代码和数据封装在一个定制类中,这就创建了一个更可维护的软件。
是的,Athlete类确实很像列表,但是与Python列表的区别只是Athlete类包含name和dob对象的属性。
3、继承Python内置的list
class还允许通过继承现有的其他类来创建一个类,通过继承创建的称为子类,子类会免费享有父类所有的功能;
抛弃之前的Athlete类,从内置的list类继承其大量的功能。
Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:38:48) [MSC v.1900 32 bit (Intel)] on win32 Type "copyright", "credits" or "license()" for more information. >>> ===== RESTART: D:\workspace\headfirstpython\chapter6\page200\page196.py ===== ['1.31'] ['1.21', '1.31', '2.22'] >>> #如何继承Python的内置list类 >>> #首先派生内置list类创建一个定制列表,它还有一个名为name的属性 >>> class NamedList(list): def __init__(self,a_name): list.__init__([ ]) #初始化所派生的类,然后把参数赋至属性 self.name=a_name >>> >>> jy=NamedList >>> jy=NamedList('john paul jones')#创建一个新的‘NameList’对象实例 SyntaxError: unexpected indent >>> jy=NamedList('john paul jones')#创建一个新的‘NameList’对象实例 >>> type(jy) <class '__main__.NamedList'> >>> dir(jy) #查看jy能够提供的内容 ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'name', 'pop', 'remove', 'reverse', 'sort'] >>> #jy可以做列表可以做的所有事情,还可以在‘name’属性中存储数据 >>> #下面使用内置list提供的方法为‘NameList’增加数据 >>> jy.append('bass player') >>> jy.extend(['cc','aa','mm']) >>> jy ['bass player', 'cc', 'aa', 'mm'] >>> jy,name Traceback (most recent call last): File "<pyshell#18>", line 1, in <module> jy,name NameError: name 'name' is not defined >>> jy.name 'john paul jones' >>> #因jy是一个列表,完全可以对它做列表处理 >>> for attr in jy: print ( jy.name+"is a "+attr +"." ) john paul jonesis a bass player. john paul jonesis a cc. john paul jonesis a aa. john paul jonesis a mm. >>> >>>
def sanitize(time_string): if '-' in time_string: splitter = '-' elif ':' in time_string: splitter = ':' else: return(time_string) (mins, secs) = time_string.split(splitter) return(mins + '.' + secs) class AthleteList(list): def __init__(self, a_name, a_dob=None, a_times=[]): list.__init__([]) self.name = a_name self.dob = a_dob self.extend(a_times) ## def top3(self): return(sorted(set([sanitize(t) for t in self]))[0:3]) ## def get_coach_data(filename): try: with open(filename) as f: data = f.readline() templ = data.strip().split(',') return(AthleteList(templ.pop(0), templ.pop(0), templ)) ## except IOError as ioerr: print('File error: ' + str(ioerr)) return(None) james = get_coach_data('james2.txt') julie = get_coach_data('julie2.txt') mikey = get_coach_data('mikey2.txt') sarah = get_coach_data('sarah2.txt') print(james.name + "'s fastest times are: " + str(james.top3())) print(julie.name + "'s fastest times are: " + str(julie.top3())) print(mikey.name + "'s fastest times are: " + str(mikey.top3())) print(sarah.name + "'s fastest times are: " + str(sarah.top3()))
Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:38:48) [MSC v.1900 32 bit (Intel)] on win32 Type "copyright", "credits" or "license()" for more information. >>> ===== RESTART: D:\workspace\headfirstpython\chapter6\page207\page210.py ===== James Lee's fastest times are: ['2.01', '2.16', '2.22'] Julie Jones's fastest times are: ['2.11', '2.23', '2.59'] Mikey McManus's fastest times are: ['2.22', '2.31', '2.38'] Sarah Sweeney's fastest times are: ['2.18', '2.21', '2.22'] >>>
通过将类建立在内置的功能之上,你充分利用了Python数据结构的强大功能,同时还提供了具体应用需要的定制解决方案。
这是一个更可维护的解决方案。