【Python之路Day7】基础篇之面向对象上篇
一. 概述:
面向对象编程(Object Oriented Programming),简称OOP,说的其实是一种程序的设计思想。
回顾下之前几周学习的知识和作业:
1. 面向过程式编程
第一天的作业,三级菜单,现在看来觉得好low啊。。。作业就完全是垒代码,这种编程方式就是面向过程编程。
面向过程式编程期初比较容易让初学者接受,但往往随着功能的增多,代码重用性就变得很不方便了,多个地方要使用同一个功能就得到处复制粘贴,共用性的一个功能要修改,所有引用代码的地方都要修改。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- ''' Author: DBQ Blog: http://www.cnblogs.com/dubq/articles/5474785.html Github: https://github.com/daniel-vv/ops ''' import time #导入time模块,用于在下面退出时,休眠1秒 dic = { #定义一个字典,其中嵌套字典和列表,用于存储三级菜单内容 '华北':{ '北京市':['朝阳区','海淀区','丰台区'], '天津市':['和平区','河西区','河东区',], '山西省':['太原市','运城市','大同市',] }, '东北':{ '黑龙江省':['哈尔滨市',], '吉林省':['吉林市','长春市'], '辽宁省':['沈阳市','大连市','铁岭市'], }, '华东':{ '山东省':['青岛市','济南市','日照市'], '江苏省':['南京市','常州市','苏州市'], '上海市':['虹口区','黄埔区','徐汇区'], }, '华南':{ '广东省':{ '广州市':['','',''], '东莞市':['','',''], '珠海市':['','',''] }, '广西省':{ '南宁市':['','',''], '桂林市':['','',''], '柳州市':['','',''] }, '香港特别行政区':{ '中西区':['','',''], '湾仔区':['','',''], '九龙城区':['','',''], '深水埗区':['','',''] } } } Welcome = '欢迎来到天朝' #定义欢迎信息,用于下方字符串格式化 area_check = ('0','1','2','3') #定义一个元组,用于检测用户输入序列号是否在合法范围内; Flag = False #定义标志位,用于进入循环 while not Flag: #进入循环体 print('%s'% Welcome.center(30,'>')) for id,china in enumerate(dic.keys()): #循环字典keys,并且用enumerate方法列出索引序号,而后打印出来,供用户选择 print(id,china) print('#'*65) UserInput = input('请输入一个您感兴趣的一个区域序号: 退出(q/Q) 返回(b/B): ').strip() #提示用户选择序列号 print('#'*65) Num = UserInput if Num == 'q' or Num == 'Q': #判断用户输入是否是退出? print('欢迎下次再来~,Bye') time.sleep(1) Flag = True #将标志位置为True,用于退出整个循环 #break elif Num == 'b' or Num == 'B': #如果顶级菜单用户输入回退,提示顶级菜单,重新进入循环 print('已经最最顶层菜单了') continue elif Num in area_check: #如果用户输入的是元组内的索引序列的话,进入下一层循环体 Province = list(dic.keys())[int(Num)] #进入循环体之前先将省字典转换为列表,不然无法进行索引 while not Flag: #循环进入第二级菜单: for ID,Provi in enumerate(dic[Province]): print(ID,Provi) print('#'*65) UserInput = input('请输入一个您感兴趣的城市编号: 退出(q/Q) 返回(b/B): ').strip() print('#'*65) Num = UserInput if Num == 'q' or Num == 'Q': print('欢迎下次再来~,Bye') time.sleep(1) Flag = True elif Num == 'b' or Num == 'B': break elif Num in area_check: City = list(dic[Province].keys())[int(Num)] #将用户输入和字典转换为列表 while not Flag: #进入三级菜单 print('---> %s' % City) for ID,city in enumerate(dic[Province][City]): #循环,并使用enumerate打印出菜单和序列号,供用户选择 print(ID,city) print('#'*65) UserInput = input('请输入一个您感兴趣的一个区域序号: 退出(q/Q) 返回(b/B): ').strip() print('#'*65) if UserInput == 'q' or UserInput == 'Q': #输入Q,标志位置为1 Flag = True #break elif UserInput == 'b' or UserInput == 'B': #输入B,break,返回到生一层循环 break elif UserInput == '': #判断用户输入是否为空,如果为空,则提示为空,进入下一次循环 print('输入为空') continue else: print('已经到了最后一层啦,哈哈') if Flag: #如果标志位为真,退出循环体 break
2. 函数式编程
后来,我们学习了函数,再看第三天的作业,编辑Haproxy配置文件的代码,虽然也low,但相对来讲已经好多了,大量使用了函数。这样一来,代码的重用性就变得好多了,多处使用一个功能,封装一个函数即可,需要的时候调用即可。如有功能变动,直接修改函数,其他地方都不用修改,直接调用函数即可。这种编程方式叫做函数式编程。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Author: DBQ(Du Baoqiang) """ Author: DBQ Blog: http://www.cnblogs.com/dubq/articles/5520208.html Github: https://github.com/daniel-vv/ops """ import json import time import os CONF = './conf/haproxy.cfg' #定义几个全局变量--配置文件,在下方引用 CONF_NEW = './conf/haproxy.cfg.new' CONF_BAK = './conf/haproxy.cfg.bak' FLAG = False #标志位,用于下方判断add记录 RENAME = False #定义一个标志位,用于判断下面判断是否告知用户配置文件更改 def get_backend(user_input_num): """ 获取用户输入backed :param num: 接受用户传入的backed实际参数 :return: 返回用户输入backend的一个列表值,而后在main函数里循环打印出来 """ server_info = [] #定义一个空列表,用于下面判断用户输入的backend后,把serverinfo写入到列表中,而后循环打印出 with open(CONF,'r') as f1: if user_input_num in f1.read(): with open(CONF,'r') as f: backend_title = 'backend %s' %user_input_num #定义一个局部变量,用户下面判断在文件中对比有backend的后端服务器 #server_info = [] Flag = False #定义一个标志位,用于下面判断什么时候跳出循环,因为配置文件里不止一个backend for line in f: line = line.strip() #去除空格而后进行下面的判断 if line == backend_title: #如果为真表示匹配到对应的backend,而后将标志位置为true,跳出本次进入下次循环 Flag = True continue if Flag and line.startswith('backend'): #如果标志位为真并且line开头以backend字符开始,表示到了下一个backend位置 Flag = False #将标志位置为False break #跳出循环 #基于标志位判断: if Flag and line: #如果标志位为真并且line为真,追加line下面server信息到列表中 server_info.append(line) else: server_info.append(user_input_num) return server_info #return 用户输入backend列表 def add_backend(user_input_server): ''' 添加backend server函数 :param user_input_server: #接受用户传入的字典信息,针对于keys值和values做进一步处理 :return: 添加成功返回值为True,否则返回默认None,标明添加失败; ''' #使用上下文管理同时打开两个文件,一个用户之前的配置文件从里面读取数据,而后写入新数据到另一个文件中,做备份 #从用户输入的字典中取出各个值,并赋值给变量 backend_title = 'backend %s' % user_input_server['backend'] record_IP = str(user_input_server['record']['server']) record_weight = str(user_input_server['record']['weight']) record_maxconn = str(user_input_server['record']['maxconn']) #server 100.1.7.9 100.1.7.9 weight 20 maxconn 3000 record = 'server' + ' ' + record_IP + ' ' + record_IP + ' ' + 'weight' + ' ' + record_weight + ' ' + 'maxconn' + ' ' + record_maxconn flag = False backend_dict = {} backend_list = [] #server_list = [] global FLAG #声明使用全局变量,备份配置文件之后更改全局变量标志位 if not FLAG: #第一次进来之后,先做个备份,把原有文件备份起来 os.system('cp ./conf/haproxy.cfg ./conf/haproxy.cfg.bak') #备份配置文件 FLAG = True #置标志位为真, 在本程序执行的中途不在备份文件 #写入不修改的内容到新文件中 with open(CONF,'r') as readonly_f , open(CONF_NEW,'w+') as writable_f: for line in readonly_f: if line.strip() == backend_title: #如果匹配到用户输入的backend,置标志位为真 flag = True if flag and line.startswith('backend '): #进一步判断,如果标志位为真,并且line是以backend开头的跳出本次,进入下次循环 continue if not flag or line.startswith('backend'): #如果flag为假,或者line是以backend开头的,匹配到不修改的行,并写入到新文件 flag = False writable_f.write(line) #把line写入到新文件 backend_dict = {} backend_list = [] with open(CONF,'r') as f: if backend_title in f.read(): #判断用户输入的backend是否在配置问价内,如果在的话进入下面的判断; with open(CONF,'r') as readonly_file , open(CONF_NEW,'a+') as writable_file: #再次打开文件,这次是追加模式(写文件) flag = False Count = 0 #添加计数器用于判断,这块好绕啊 add_count = 0 for line1 in readonly_file: if line1.strip() == backend_title: flag = True Count = 1 continue if flag and line1.startswith('backend'): #如果上述两个判断ok,基于标志位来将现有server信息追加到空列表中,而后把用户现在的输入信息追加到列表中; # print(line1) break if Count == 1: #上面判断如果计数器为1,从第二行开始追加server记录到空列表中,并添加一个计数器add_count值为1 backend_list.append(line1.strip()) add_count = 1 if add_count == 1: #如果计数器为2,将原有的backend中的server的记录追加到list中,而后 backend_list.append(record) backend_dict[backend_title] = backend_list #将列表中的记录添加到字典,作为value for i in range(backend_list.count('')): #删除列表中的空元素,在这里卡了有一小会,不然基于list长度判断是否删除backend记录老不对 backend_list.remove('') #删除列表中的空字符 for i in range(len(backend_list)): #循环server的列表长度,用for自增的形式来将列表字典中的元素追加到新配置文件中去 if i == 0: #第一次循环,写入 backend title信息 writable_file.write('\n' + backend_title + '\n') writable_file.write(' '*8 + backend_dict[backend_title][i] + '\n') else: #下面逐个追加server信息到文件中 writable_file.write(' '*8 + backend_dict[backend_title][i] + '\n') else: writable_file.flush() flag = True # return True else: #如果用户输入的backend不在配置文件,那么直接在新文件尾部追加 backend_list.append(record) #先将用户输入的追加到空列表中去 backend_dict[backend_title] = backend_list #追加 # print(backend_list) # print(backend_dict) with open(CONF_NEW,'a+') as f_w: #追加模式打开new配置文件 R = len(backend_list) if R > 1: for i in range(R): if i == 0: f_w.write('\n' + backend_title + '\n') #写入,主要判断用户一次输入几个server后端值,如果是多个的话,也能实现; f_w.write(' '*8 + backend_dict[backend_title][i]) else: f_w.write(' '*8 + backend_dict[backend_title][i] + '\n') else: f_w.flush() flag = True else: f_w.write('\n' + backend_title + '\n') #写入,主要判断用户一次输入几个server后端值,如果是多个的话,也能实现; f_w.write(' '*8 + backend_dict[backend_title][0]) flag = True #print(backend_title) #print(backend_dict.get(backend_title)) if flag: #如果标志位为真, 重命名文件 os.rename('./conf/haproxy.cfg.new','./conf/haproxy.cfg') return True def del_backend(user_input_server): ''' 删除用户输入backend server信息函数 :param user_input_server: 接受用户输入的字典信息 :return: 操作成功返回True, 否则返回默认的None ''' backend_list = [] #定义backend空列表,用于存储用户输入的backend后端的server信息 backend_dict = {} #定义backend空字典,用于存储用户输入的backend信息 backend_title = 'backend %s' % user_input_server['backend'] record_IP = str(user_input_server['record']['server']) record_weight = str(user_input_server['record']['weight']) record_maxconn = str(user_input_server['record']['maxconn']) global FLAG #声明使用全局变量,备份配置文件之后更改全局变量标志位 if not FLAG: #循环中,如果用户第一次选择删除操作的话,也是先备份,本程序这次运行中不再备份,也不再备份中写入任何数据 os.system('cp ./conf/haproxy.cfg ./conf/haproxy.cfg.bak') #备份配置文件 FLAG = True #把用户输入的信息摘出来,然后拼接起来赋值 record = 'server' + ' ' + record_IP + ' ' + record_IP + ' ' + 'weight' + ' ' + record_weight + ' ' + 'maxconn' + ' ' + record_maxconn flag = False Flag = False with open(CONF) as f: if backend_title in f.read(): with open(CONF,'r') as readonly_del_f, open(CONF_NEW,'w+') as writable_del_f: for line in readonly_del_f: if line.strip() == backend_title: #如果匹配到用户输入的backend,置标志位为真 flag = True if flag and line.startswith('backend '): #进一步判断,如果标志位为真,并且line是以backend开头的跳出本次,进入下次循环 continue if not flag or line.startswith('backend'): #如果flag为假,或者line是以backend开头的,匹配到不修改的行,并写入到新文件 flag = False writable_del_f.write(line) #把line(除了用户输入的backend以外的内容)写入到新文件 backend_list = [] backend_dict = {} with open(CONF,'r') as readonly_file , open(CONF_NEW,'a+') as writable_del_f: #再次打开文件,这次是追加模式(写文件) flag = False Count = 0 #添加计数器用于判断,这块好绕啊 add_count = 0 #添加计数器,用于判断用户删除记录后写入文件 for line1 in readonly_file: if line1.strip() == backend_title: flag = True Count = 1 continue if flag and line1.startswith('backend'): #如果上述两个判断ok,基于标志位来将现有server信息追加到空列表中,而后把用户现在的输入信息追加到列表中; break if Count == 1: #上面判断如果计数器为1,从第二行开始追加server记录到空列表中,并添加一个计数器add_count值为1 backend_list.append(line1.strip()) backend_dict[backend_title] = backend_list #将列表中的记录添加到字典,作为value add_count = 1 if add_count == 1 and record in backend_list: #如果计数器为2,判断用户输入的record值是否在列表中,而后进一步操作 record_index = backend_list.index(record) user_input=input('\033[31;1m记录在列表中,是否删除? y/Y\033[0m').strip() if user_input == 'y' or user_input == 'Y': del backend_list[record_index] for i in range(backend_list.count('')): #删除列表中的空元素,在这里卡了有一小会,不然基于list长度判断是否删除backend记录老不对 backend_list.remove('') #删除列表中的空字符 else: #循环体执行完成后判断如果列表为空标明server中没有记录了,然后将计数器置为2 add_count = 2 if len(backend_list) == 0 else 1 #来一下三目运算 ^_^ else: return False #如果用户输入y/Y以外的值的话,就返回False else: if input('\033[31;1m记录不存在,输入任意键继续....\033[0m'):pass add_count = 1 if add_count == 2: #如果计数器为2,提示用户是否要删除 user_input_reuse=input('%s 下面没有任何记录了,是否要删除backend? y/Y' %backend_title).strip() if user_input_reuse == 'y' or user_input_reuse == 'Y': del backend_dict #删除字典 add_count = 3 #删除记录之后计数器为3,用于下面判断,是否改名字 else: writable_del_f.write(backend_title) #写入backend到文件中 add_count = 3 else: #如果标志位不为2,证明列表中还有其他值,因此循环取出,也是先删除下空字符,否则格式好恶心 for i in range(backend_list.count('')): #删除列表中的空元素,在这里卡了有一小会,不然基于list长度判断是否删除backend记录老不对 backend_list.remove('') #删除列表中的空字符 for i in range(len(backend_list)): if i == 0: writable_del_f.write('\n' + backend_title + '\n') #写入,主要判断用户一次输入几个server后端值,如果是多个的话,也能实现; writable_del_f.write(' '*8 + backend_dict[backend_title][i] + '\n') else: writable_del_f.write(' '*8 + backend_dict[backend_title][i] + '\n') else: #循环体正常执行完后,标志位置为3 add_count = 3 if add_count == 3: #而后该文件名 os.rename('./conf/haproxy.cfg.new','./conf/haproxy.cfg') return True def get_help(): ''' 用户帮助函数,只输出一些程序帮助信息 :return: 无返回值,默认None ''' print('\033[31;1mHELP\033[0m'.center(110,'#')) print(''' 1. 查看后端server, 请使用 FQDN 完全合格域名格式,输入您要查找的记录,如: www.dbq168.com 2. 增加后端server,需要输入下面的格式,注意,需要{}里的引号务必是双引号,请严格遵守程序的规范输入; {"backend": "www.oldboy.com","record":{"server": "100.1.7.9","weight": 20,"maxconn": 30}} 3. 删除server, 格式同第二点相同 4. 获取程序帮助 5. 退出程序 请严格按照程序要求格式输入,谢谢您的配合! ''') print('#'*100) if input('\033[32;1m按下任意键继续...\033[0m'):pass def main(): """ 主函数,用于根据用户输入判断,而后传实体参数给各个函数; :return: 暂定没返回值,默认是None """ global RENAME flag = False while not flag: print(""" 1. 查看Haproxy后端Server 2. 添加Haproxy后端Server 3. 删除Haproxy后端Server 4. 获取帮助信息 5. 退出程序 """) user_input_num = input('请输入一个你想要实现的操作序列?').strip() #接受用户传入序列 if user_input_num == '1': user_input_server = input('请输入你要查询的backend配置:').strip() server_info = get_backend(user_input_server) #如果输入主机头不在server_info的列表里,证明get函数有获取到backend的信息,而后进入下面程序,打印用户主机后端服务器信息 if user_input_server not in server_info and server_info: print('你查询的backend [ %s ] 的Server信息如下:' %user_input_server) print('#'*50) for i in range(len(server_info)): #做循环,用于打印多个用户输入的信息 print('\033[34;1m %s \033[0m'%server_info[i]) else: print('#'*50) if input('\033[32;1m按下任意键继续...\033[0m'):pass else: if len(user_input_server) != 0: #如果列表不为空,并且输入不在文件backend中,打印下面的信息,提示用户可以添加主机 print('\033[31;1m您输入的主机名 [ %s ] 不存在,输入序列2添加吧~~\033[0m' %user_input_server) if user_input_num == '2': user_input_server = input("""格式: \033[35;1m{"backend": "test.oldboy.org","record":{"server": "100.1.7.9","weight": 20,"maxconn": 30}}\033[0m 请输入要增加的Server记录:""").strip() if user_input_server: user_input_server = json.loads(user_input_server) #使用json将用户输入的内容转换为本身的类型,也就是字典格式 result = add_backend(user_input_server) #作为实体参数传给add_backend if result: if input('\033[32;1m记录添加成功,按下任意键继续...\033[0m'):pass RENAME = True else: print('记录没有添加成功') if user_input_num == '3': user_input_server = input("""格式: \033[35;1m{"backend": "test.oldboy.org","record":{"server": "100.1.7.9","weight": 20,"maxconn": 30}}\033[0m 请输入要删除的Server记录:""").strip() if user_input_server: user_input_server = json.loads(user_input_server) result = del_backend(user_input_server) if result: if input('\033[31;1m[%s] 删除成功,按下任意键继续...\033[0m'%user_input_server):pass # global RENAME RENAME = True if user_input_num == '4': get_help() if user_input_num == '5' or user_input_num == 'quit': if CONF_BAK and RENAME: edit_flag = input('\033[31;1m原配置文件做了更改,是否保存? (y/Y):\033[0m').strip() if edit_flag == 'y' or edit_flag == 'Y' and not edit_flag: time.sleep(1) print('配置文件修改完成,原文件已备份\033[31;1m[ %s ]\033[0m, 欢迎下次再来, Bye!'%CONF_BAK) break else: print('您放弃了保存...... Bye!') os.rename('./conf/haproxy.cfg.bak','./conf/haproxy.cfg') break else: print('欢迎下次再来, Bye!') break if __name__ == '__main__': main() print()
3. 面向对象编程
而后,到了现在,我们学习了面向对象初级篇,主要是对函数进行分类和封装,从而使开发效率更高,代码逼格更高!
Python这门编程语言比较灵活,既支持面向过程编程,又支持面向对象编程。
面向对象编程里面最重要的两个东西,就是 “类” 和 ”对象“。
类是一个模板,模板里可以包含多个函数,函数里实现各种功能,而函数在类里就叫做 ” 方法“。
对象是根据类(模板)实例化而来,实例化后的对象可以执行类中的定义的各种方法(函数).
类和对象的关系?
类以及类中的方法在内存中只保存一份,而由类实例化而来的每一个对象在内存中都要存一份:
#创建一个类 class Foo: #创建类中的方法(函数) def f1(self): print('hello world') #根据类Foo实例化赋值给obj,而后obj可以调用f1方法 obj = Foo() obj.f1() #执行结果: hello world
看到上面创建的函数,其实没多大卵用,可以发现直接使用一个函数来封装上面的功能,要比通过类再实例化,而后在调用f1方法要简便的多。
也就是说:并不是把所有的代码都面向对象就是逼格高,也不是所有的编程都使用面向对象就牛逼。 so应用场景:
函数式编程的应用场景: 各个函数之间独立并且无共用的数据!
面向对象编程应用场景: 当某些函数具有相同参数时,就可以使用面向对象的方式,将参数值一次性的封装到对象中,以后去对象中取值即可。
二. 面向对象的三大特性:
面向对象的三大特性是: 封装、继承和多台.
1. 封装
所谓封装,就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行隐蔽。简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在使用面向对象的封装特性时,需要:
- 将内容封装到某处
- 从某处调用被封装的内容
封装内容到某处:
self 是一个形式参数, 当执行obj = Foo('daniel', '18') 实例化的时候, self等于obj
当执行obj2 = Foo('Tom', '19')实例化的时候, self等于obj2
而后,内容已经被封装到了obj和obj2中,每个对象都有name和age两个属性;
obj name = daniel age: 18 obj2 name = Tom age = 19
从某处调用被封装的内容
调用被封装内容时的两种情况:
- 通过对象直接调用
- 通过self间接调用
通过对象直接调用:
class Foo: def __init__(self,name,age): self.name = name self.age = age #实例化时候,自动执行__init__构造方法 obj = Foo('daniel','18') print(obj.name,obj.age) obj2 = Foo('Tom','19') print(obj2.name,obj2.age) #执行结果如下: daniel 18 Tom 19
通过self间接调用:
class Foo: def __init__(self,name,age): self.name = name self.age = age def info(self): print(self.name,self.age) #实例化时候,自动执行__init__构造方法 obj = Foo('daniel','18') obj.info() #Python解释器会将obj传值给self参数 obj2 = Foo('Tom','19') obj2.info() #Python解释器会将obj2传值给self参数 #执行结果: daniel 18 Tom 19
所以,对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过直接或者间接self来获取封装的内容。
练习题一:
输出如下信息:
小明,18岁,男,上山去砍柴
小明,18岁,男,开车去东北
小明,18岁,男,最爱大保健
老李,45岁,男,上山去砍柴
老李,45岁,男,开车去东北
老李,45岁,男,最爱大保健
老王, 40岁, 男, 上山去砍柴
老王, 40岁, 男, 开车去东北
老王, 40岁, 男, 最爱大保健
老张...
def kanchai(name,age,gender): print('%s, %s岁, %s, 上山去砍柴'%(name,age,gender)) def dongbei(name,age,gender): print('%s, %s岁, %s, 开车去东北'%(name,age,gender)) def dabaojian(name,age,gender): print('%s, %s岁, %s, 最爱大保健'%(name,age,gender)) xiaoming_kanchai = kanchai('小明','18','男') xiaoming_dongbei = dongbei('小明','18','男') xiaoming_dabaojian = dabaojian('小明','18','男') print(' ') laoli_kanchai = kanchai('老李','45','男') laoli = dongbei('老李','45','男') laoli_dabaojian = dabaojian('老李','45','男') print(' ') laowang_kanchai = kanchai('老王','40','男') laowang = dongbei('老王','40','男') laowang_dabaojian = dabaojian('老王','40','男') #执行代码结果: 小明, 18岁, 男, 上山去砍柴 小明, 18岁, 男, 开车去东北 小明, 18岁, 男, 最爱大保健 老李, 45岁, 男, 上山去砍柴 老李, 45岁, 男, 开车去东北 老李, 45岁, 男, 最爱大保健 老王, 40岁, 男, 上山去砍柴 老王, 40岁, 男, 开车去东北 老王, 40岁, 男, 最爱大保健
class enjor: def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender def kanchai(self): print('%s, %s岁, %s, 上山去砍柴'%(self.name,self.age,self.gender)) def dongbei(self): print('%s, %s岁, %s, 开车去东北'%(self.name,self.age,self.gender)) def dabaojian(self): print('%s, %s岁, %s, 最爱大保健'%(self.name,self.age,self.gender)) xiaoming_obj = enjor('小明','18','男') xiaoming_obj.kanchai() xiaoming_obj.dongbei() xiaoming_obj.dabaojian() print(' ') laoli_obj = enjor('老李','45','男') laoli_obj.kanchai() laoli_obj.dongbei() laoli_obj.dabaojian() print(' ') laowang_obj = enjor('老王','40','男') laowang_obj.kanchai() laowang_obj.dongbei() laowang_obj.dabaojian() ########################### 代码执行结果: 小明, 18岁, 男, 上山去砍柴 小明, 18岁, 男, 开车去东北 小明, 18岁, 男, 最爱大保健 老李, 45岁, 男, 上山去砍柴 老李, 45岁, 男, 开车去东北 老李, 45岁, 男, 最爱大保健 老王, 40岁, 男, 上山去砍柴 老王, 40岁, 男, 开车去东北 老王, 40岁, 男, 最爱大保健
可以看出向上面的例子,使用函数式编程的时候,需要在每次执行函数的时候传入相同的参数,如果参数过多,逼格、代码重用性又不行了。so,面向对象,只需要在创建对象时,将所有需要的参数(共用)的参数封装到对象中,之后再次使用时,间接通过self或者直接的方式去当前对象中取值即可。
练习题二:
练习二:游戏人生程序 1、创建三个游戏人物,分别是: 苍井井,女,18,初始战斗力1000 东尼木木,男,20,初始战斗力1800 波多多,女,19,初始战斗力2500 2、游戏场景,分别: 草丛战斗,消耗200战斗力 自我修炼,增长100战斗力 多人游戏,消耗500战斗力
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Author: DBQ(Du Baoqiang) class LoveActiceMovie: def __init__(self,name,gender,age,zhandouli): ''' 构造函数 :param name: 玩家名字 :param gender: 玩家年龄 :param age: 玩家年龄 :param zhandouli: 玩家战斗力,不知道战斗力英文咋写 :return: ''' self.name = name self.gender = gender self.age = age self.zhandouli = zhandouli def caocongzhandou(self): ''' 草丛战斗 :return: ''' self.zhandouli -= 200 def ziwoxiulian(self): ''' 自我修炼 :return: ''' self.zhandouli += 100 def duorenyouxi(self): ''' 多人游戏 :return: ''' self.zhandouli -= 500 #苍老师 cang_obj = LoveActiceMovie('苍井井','女',18,1000) cang_obj.caocongzhandou() #草丛战斗 cang_obj.duorenyouxi() #多人休息 cang_obj.ziwoxiulian() #自我修炼一次 print('苍井井战斗力还有: %d'%cang_obj.zhandouli) #东尼木木 dongni_obj = LoveActiceMovie('东尼木木','女',20,1800) dongni_obj.caocongzhandou() #草丛战斗 dongni_obj.duorenyouxi() #多人休息 print('东尼木木战斗力还有: %d'%dongni_obj.zhandouli) #波多多 bo_obj = LoveActiceMovie('波多多','女','19',2500) bo_obj.caocongzhandou() #草丛战斗 bo_obj.duorenyouxi() #多人休息 print('波多多战斗力还有: %d'%bo_obj.zhandouli) ######################################### 执行结果: 苍井井战斗力还有: 400 东尼木木战斗力还有: 1100 波多多战斗力还有: 1800
2. 继承
"子继父业", 通过继承可以实现现有类的所有功能和成员变量(当然也需要看访问控制符),并在无需重新编写原来类的情况下对这些功能进行扩展。
实现了以前代码的复用。
引用下武sir的实例:
例如:
喵星人: 喵喵叫 、 吃、喝、拉、撒
汪星人: 汪汪叫 、 吃、喝、拉、撒
如果按照现在所学知识,需要为 喵星人 和汪星人分别实现 它们所有的功能:
class miaoxingren: def jiao(self): print('喵喵叫') def chi(self): print('吃') def he(self): print('喝') def la(self): print('拉') def sa(self): print('撒') class wangxingren: def jiao(self): print('汪汪叫') def chi(self): print('吃') def he(self): print('喝') def la(self): print('拉') def sa(self): print('撒')
可以看到,吃喝拉撒是喵星人和汪星人共有的功能,而叫是他们不同的功能,利用封装原理,我们已经不能满足需求了。但,如果我们使用了继承的思想,把共有的部分用动物的类封装起来,而后不同的功能,再用函数或者类实现:
动物: 吃、喝、拉、撒
喵星人: 喵喵叫
汪星人:汪汪叫
class zoon: def chi(self): print('吃') def he(self): print('喝') def la(self): print('拉') def sa(self): print('撒') class miaoxingren(zoon): def jiao(self): print('喵喵叫') class wangxingren(zoon): def jiao(self): print('汪汪叫') miao_obj = miaoxingren() miao_obj.jiao() miao_obj.chi() miao_obj.he() miao_obj.la() miao_obj.sa() print() wang_obj = wangxingren() wang_obj.jiao() wang_obj.chi() wang_obj.he() wang_obj.la() wang_obj.sa() ##################################### #执行结果: 喵喵叫 吃 喝 拉 撒 汪汪叫 吃 喝 拉 撒
so,对于面向对象的继承来说,其实就是将多个类共有的方法提取出来,放到父类中,子类只需要继承父类的方法即可,不用再一一实现;
父类也叫基类
子类也叫派生类
都是相对来说的
需要注意的是, 如果父类中有的方法,子类中也有,优先级是子类的最高,子类也是相对来讲,比如多级继承的时候,子类就是相对来说的。
如:
class fulei: def fulei_fangfa(self): print('来自父类方法') class zilei(fulei): def fulei_fangfa(self): print('来自子类的方法') erzi = zilei() erzi.fulei_fangfa() #父子类中均有fulei_fangfa()方法,所以当子类实例化对象后,在调用该方法时,以子类优先级高,执行结果: 来自子类的方法
多继承:
Python的类可以继承多个类,如果继承了多个类,寻找方法的方法有两种: 深度优先、广度优先
当类是经典类时,多继承情况下,会按照深度优先方式查找
当类是新式类时,多继承情况下,会按照广度优先方式查找
在Python2.x中分经典类和新式类,但在Python3中全是新式类.
经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。
class D: def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() # 执行bar方法时 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错 # 所以,查找顺序:A --> B --> D --> C # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 a.bar()
class D(object): def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() # 执行bar方法时 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错 # 所以,查找顺序:A --> B --> C --> D # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 a.bar()
经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中没有,则继续去D类中找,如果D类中没有,则继续去C类中找,如果还是未找到,则报错
新式类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中没有,则继续去C类中找,如果C类中没有,则继续去D类中找,如果还是未找到,则报错
注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
3. 多态
当我们定义一个class的时候,实际上是定义了一种数据类型。如下代码,定义了一个MyClass的类,而后实例化之后给对象tom,这里的MyClass也是一个数据类型:
class MyClass: def __init__(self): self.name = 'Tom' tom = MyClass()
和Python自带的dict、list、tuple、str....是一样的。
d1 = dict()
多态,顾名思义就是“多种形态,多重类型”的意思。看下面代码:
class MyClass: def __init__(self,name,age): self.name = name self.age = age def show(self): print("Name: %s\nAge: %s"%(self.name,self.age)) tom = MyClass('Tom',2) jerry = MyClass('Jerry',3.5) tom.show() jerry.show() ################执行结果################## Name: Tom Age: 2 Name: Jerry Age: 3.5
在上述代码中,实例化是接受用传入两个参数,这两个参数可以是任何值,如:
class MyClass: def __init__(self,name,age): self.name = name self.age = age def show(self): print("Name: %s\nAge: %s"%(self.name,self.age)) tom = MyClass(['tom','jerry','sam','eric'],{'k1':'v1'}) #列表、字典都是可以的,这就是一种多态 tom.show() #执行结果: Name: ['tom', 'jerry', 'sam', 'eric'] Age: {'k1': 'v1'}
但
class A: def show(self): print('A') class B(A): pass class C(A): pass #定义一个函数 def func(C): C.show() func(C()) #执行结果: A func(B()) #执行结果: A func(A()) #执行结果: A #这就是多态的好处,由于基类A中有show方法,只要传入的类型是A类或者它的派生类,都可以调用show方法。这就是多态的威力,调用方只管调用,不用管细节。
“开闭”原则:
对扩展开放: 允许新增A的子类;
对修改封闭: 不需要修改依赖A类型的func()等的函数。