python之路---面向对象编程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python之路---面向对象编程相关的知识,希望对你有一定的参考价值。
一.设计思想的发展
面向机器(代码复杂,效率低,学习成本高,开发周期长)-------->面向过程(扩展性差,不适用多变的需求改变)----------->面向对象(扩展性好,但是可控性差)
二.面向过程和面向对象对比
面向过程:
定义:面向过程的核心是过程(即流水线式设计),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点是:极大的降低了程序的复杂度
缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象:
定义:面向对象的核心是对象(上帝式思维,自上而下)。世界万物皆是对象,我们在使用面向对象的时候,要有上帝造物的思想,从无到有,从上而下来设计。神说,要有光,于是就有了光。我们需要什么对象,即可造成相应的类来生成对象。
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
注意:面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。
三.类和对象
3.1 类和对象的定义
提示:python的class术语与c++有一定区别,与 Modula-3更像。
类:python中一切皆为对象,且python3统一了类与类型的概念,类型就是类,所以,不管你信不信,你已经使用了很长时间的类了。例如list,dict,str等,都是类。
对象:特征与技能的结合体就是一个对象。特征即数据属性,技能即方法属性。
从一组对象中提取相似的部分就是类,类所有对象都具有的特征和技能的结合体,二者称为类的属性
在python中,用变量表示特征,用函数表示技能,因而类是变量与函数的结合体,对象是变量与方法(指向类的函数)的结合体
3.2 类
1.类的声明
‘‘‘ class 类名: ‘类的文档字符串‘ 类体 ‘‘‘ #我们创建一个类 class Data: pass
2.新式类和经典类
注意:经典类和新式类只有在python2中存在,python3全部都是新式类 #经典类 class classic: pass #新式类 class new_class(object): pass print classic.__bases__ #() print new_class.__bases__ #(<type ‘object‘>,) 新式类比经典类多了更多的方法和属性
3.类的作用:实例化和属性引用
属性引用:
类的属性引用,会在类的命名空间里查找,看是否有对应属性,可以用__dict__查看命令空间内容。
class test: name=‘this is test‘ def __init__(self): self.init=‘this is init‘ def run(self): print(‘run‘) print(test.__dict__) ‘‘‘ {‘__module__‘: ‘__main__‘, ‘name‘: ‘this is test‘, ‘__init__‘: <function test.__init__ at 0x0000000001E7B8C8>, ‘run‘: <function test.run at 0x0000000001E7B950>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘test‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘test‘ objects>, ‘__doc__‘: None} ‘‘‘
我们对属性的引用,修改,删除,增加,其实上就是在操作命令空间的内容。例如:
class test: name=‘this is test‘ def __init__(self): pass test.name=‘this is new‘ #改 print(test.__dict__) #命令空间中,name对应的values修改为‘this is new‘ ‘‘‘ {‘__module__‘: ‘__main__‘, ‘name‘: ‘this is new‘, ‘__dict__‘: <attribute ‘__dict__‘ of ‘test‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘test‘ objects>, ‘__doc__‘: None} ‘‘‘ del test.name #删 print(test.__dict__) #命令空间中,name及对应的值被删除 ‘‘‘ {‘__module__‘: ‘__main__‘, ‘__init__‘: <function test.__init__ at 0x000000000221B8C8>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘test‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘test‘ objects>, ‘__doc__‘: None} ‘‘‘ test.new_name=‘this is very new name‘ #增 print(test.__dict__) #命令空间中,增加new_name关键字及对应的值
实例化:
类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
class school_man: def __init__(self,name,sex,course,birth): self.name=name self.sex=sex self.cource=course self.birth=birth s1=school_man(‘test‘,‘male‘,‘python‘,‘2017‘)
一:我们定义的类的属性到底存到哪里了?有两种方式查看 dir(类名):查出的是一个名字列表 类名.__dict__:查出的是一个字典,key为属性名,value为属性值 二:特殊的类属性 类名.__name__# 类的名字(字符串) 类名.__doc__# 类的文档字符串 类名.__base__# 类的第一个父类(在讲继承时会讲) 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类(仅新式类中)
4.对象的作用:属性引用
对象是类的一个具体化的实例。例如:
class test: pass s1=test() print(type(s1)) #<class ‘__main__.test‘> print(isinstance(s1,test)) #True
对象/实例本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的方法,或者叫绑定方法,绑定方法唯一绑定一个对象,同一个类的方法绑定到不同的对象上,属于不同的方法,内存地址都不会一样。
class test: name=‘test‘ def speak(self): print(‘this is spead‘) s1=test() print(id(s1.name)) #31954400 print(id(test.name)) #31954400 内存地址一致,所以对象是共享变量的 。 print(id(s1.speak)) #4539976 print(id(test.speak)) #40548552 对象和类方法不共享,但是对象会绑定类的方法。
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
四.继承,派生和组合
继承:
继承:继承是一种创建新的类的方式。继承解决了代码的重用性问题。(可以表述为。。。是。。。,例如人是动物,猪是动物。)新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
class school_man: def __init__(self,name,sex,course,birth): self.name=name self.sex=sex self.cource=course self.birth=birth def speak(self): print(‘this ia prarent‘) class Student(school_man): pass s1=Student(1,2,3,4,) print(s1.__dict__) #{‘name‘: 1, ‘sex‘: 2, ‘cource‘: 3, ‘birth‘: 4} print(s1.speak) #<bound method school_man.speak of <__main__.Student object at 0x00000000021C9B00>>
我们可以发现,student并没有__init__函数和speak方法,但是他的对象是可以进行初始化和调用speak方法,这是因为继承了school_man。从上面可以看出,继承减少了子类功能重复的代码。但是,如果子类的功能和父类全都一样,又何必再定义一个子类,直接用父类即可。这就是接下来说的,派生。
派生
派生:子类继承了父类的属性,然后衍生出自己的新属性。如果子类衍生的新属性和父类某个名字相同,那么调用给属性,就以子类的为准。
class school_man: def __init__(self,name,sex,course,birth): self.name=name self.sex=sex self.cource=course self.birth=birth def speak(self): print(‘this ia prarent‘) class Student(school_man): def walk(self): #派生新属性处理 print(‘walk‘)
组合
组合:什么有什么:学生有课程,盖伦有装备
class school_man: def __init__(self,name,sex,course,birth): self.name=name self.sex=sex self.cource=course self.birth=birth def speak(self): print(‘this ia prarent‘) class Student(school_man): def __init__(self,name,sex,course,birth,score): school_man.__init__(self,name,sex,course,birth) self.score = score class Teacher(school_man): pass class Course: def __init__(self,cource,price,period): self.course=cource self.price=price self.period=period class Birth: def __init__(self,year,mouth,day): self.year=year self.mouth=mouth self.day=day class Score: def __init__(self,cource,score): self.cource=cource self.score=score coure_obj=Course(‘python‘,‘15888‘,‘5m‘) birth_obj=Birth(‘2000‘,‘5‘,‘2‘) score_obj=Score(‘python‘,‘99‘) t1=Teacher(‘teacher‘,‘male‘,coure_obj,birth_obj) s1=Student(‘student‘,‘male‘,coure_obj,birth_obj,score_obj) print(t1.name) print(t1.cource.course) print(s1.score.score) print(s1.birth.year)
五.接口与归一化设计
接口:接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
在python中,使用了抽象类来实现接口。
class interface: def speak(self): raise AttributeError(‘必须有speak方法‘) def walk(self): raise AttributeError(‘必须有walk方法‘) class people(interface): def walk(self): print(‘walk‘) p1=people() p1.speak() #报错,报错内容为:AttributeError: 必须有speak方法
import abc class interface(metaclass=abc.ABCMeta): @abc.abstractmethod def speak(self): pass class a(interface): def speak(self): print(‘speak‘) def run(self): print(‘run‘) b=a() b.speak() b.run()
在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块:
http://pypi.python.org/pypi/zope.interface
twisted的twisted\\internet\\interface.py里使用zope.interface
文档https://zopeinterface.readthedocs.io/en/latest/
设计模式:https://github.com/faif/python-patterns
六.面向对象的设计思想
很多人在学完了python的class机制之后,遇到一个生产中的问题,还是会懵逼,这其实太正常了,因为任何程序的开发都是先设计后编程,python的class机制只不过是一种编程方式,如果你硬要拿着class去和你的问题死磕,变得更加懵逼都是分分钟的事,在以前,软件的开发相对简单,从任务的分析到编写程序,再到程序的调试,可以由一个人或一个小组去完成。但是随着软件规模的迅速增大,软件任意面临的问题十分复杂,需要考虑的因素太多,在一个软件中所产生的错误和隐藏的错误、未知的错误可能达到惊人的程度,这也不是在设计阶段就完全解决的。
所以软件的开发其实一整套规范,我们所学的只是其中的一小部分,一个完整的开发过程,需要明确每个阶段的任务,在保证一个阶段正确的前提下再进行下一个阶段的工作,称之为软件工程
面向对象的软件工程包括下面几个部:
1.面向对象分析(object oriented analysis ,OOA)
软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。
建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。
2 面向对象设计(object oriented design,OOD)
根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。
首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。
在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述
3 面向对象编程(object oriented programming,OOP)
根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python
4 面向对象测试(object oriented test,OOT)
在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。
面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。
5 面向对象维护(object oriendted soft maintenance,OOSM)
正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。
由于使用了面向对象的方法开发程序,使用程序的维护比较容易。
因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。
在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。
现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。
在一个大型软件开发过程中,OOP只是很小的一个部分。
对于全栈开发的你来说,这五个阶段都有了,对于简单的问题,不必严格按照这个5个阶段进行,往往由程序设计者按照面向对象的方法进行程序设计,包括类的设计和程序的设计
总结为:先分析需求,看需要哪些对象,对象有哪些共同特征,技能,不同特征。再跟进对象的技能和特征来归纳出类。然后跟进规划的类,来使用代码来实现它。对于测试和维护,我们暂且不关心他。
以上是关于python之路---面向对象编程的主要内容,如果未能解决你的问题,请参考以下文章