python开发基础----面向对象优点及其他

面向对象是一种更高等级的结构化编程方式,它的好处:

1、通过封装明确了内外

2、通过继承+多态在语言层面支持了归一化设计

注意:不用面向对象语言(即class),一样可以做归一化(如linux的泛文件概念),一样可以封装(通过定义模块和接口)只是面向对象语言可以直接用语言元素显示声明这些而已,而用了面向对象语言,满篇都是class,并不等于就有了归一化设计。

 

python中关于OOP的常用术语

抽象/实现

抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。

对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。 

封装/接口

封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。

注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”

真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明

(注意:对外透明的意思是外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)

合成

合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。

派生/继承/继承结构

派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。

泛化/特化

基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。

多态与多态性

多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气

多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。

冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样

自省/反射

自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__

 

反射的概念由smith在1982年首次提出的,主要是指程序可以访问、检测、和修改它本身状态或行为的一种能力(自省),这一概念的提出很快引发了计算机科学领域关于应用反射的研究,它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得成绩

 

四个可以实现自省的函数

下面的方法适用于类和对象

hasattr(object,name) #判断对象或类里到底有没有 name 这个属性 ,相当于 object.name in class.__dict__

getattr(object,name,default=None)

setattr(object,name,value)

delattr(object,name)

class Vehicle:
    def __init__(self,name,speed,load,power):
        self.name = name
        self.speed = speed
        self.load = load
        self.power = power

    def run(self):
        print("开动了")

v1 = Vehicle('sad',1212,1212,'asd')
print(hasattr(v1,"name"))  #----> True
print(hasattr(v1,"sada")) # ----> False
print(getattr(v1,"run")) #<bound method Vehicle.run of <__main__.Vehicle object at 0x00000000028E93C8>> 函数地址
getattr(v1,"run")()  #输出   开动了  相当于执行run函数
# getattr(v1,"dsadas") #报错,如果没有这个属性,而且没有传入default默认值,就会报错
print(getattr(v1,"dasdsad","没有这个函数")) #输出没有这个函数

setattr(v1,'age',12)
print(v1.__dict__) #给v1添加一个age的数据属性,值为12

setattr(v1,'run1',lambda self:self.age + 10)
print(v1.__dict__) #给v1添加一个run1的函数属性,
print(v1.run1(v1))

delattr(v1,"run1") #删除run1这个函数属性
print(v1.__dict__)

结果:

True
False
<bound method Vehicle.run of <__main__.Vehicle object at 0x0000000001E898D0>>
开动了
没有这个函数
{'name': 'sad', 'speed': 1212, 'load': 1212, 'power': 'asd', 'age': 12}
{'name': 'sad', 'speed': 1212, 'load': 1212, 'power': 'asd', 'age': 12, 'run1': <function <lambda> at 0x00000000001D1E18>}
22
{'name': 'sad', 'speed': 1212, 'load': 1212, 'power': 'asd', 'age': 12}

为什么要用反射?

反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种“后期绑定”,就是你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能

 

动态导入模块

__import__(str(模块名)) 

m = __import__('m1.t')  #导入的只取最顶层,这里也就是m1,后面的t模块不会导入

importlib.import_module('m1.s16') #这个会导入整个字符串里面的模块,这里是m1目录下面的s16模块

import importlib

m = importlib.import_module('m1.s16')
print(m)
m.test()

结果
<module 'm1.s16' from 'D:\\Users\\Administrator\\PycharmProjects\\python\\day10\\m1\\s16.py'>
test

 

类的内置双下划线attr属性

__getattr__(self,item)     当对象调用一个不存在的属性是会自动触发这个函数

__delattr__(self,item)     当删除对象属性的时候会触发这个函数

__setattr__(self,key,value)  设置对象属性的时候会执行这个函数,如self.f = 1 这个就会触发,注意,如果重写了这个函数,必须要在函数里面用最底层的操作给属性赋值: self.__dict__[key] = value ,否则用self.key = value来设置,又会重新调用自己,形成没有结束的递归

 

class test:
    def __init__(self,name):
        self.name = name

    def __getattr__(self, item):
        print("%s 这个属性不存在" % item)

    def __setattr__(self, key, value):
        if type(value) is str:
            print("开始设置  ",key,value )
            # self.key = value #这样会报错
            self.__dict__[key] = value #这里如果不写,那么所有的属性操作(赋值、修改)等、都不会执行,查询print(__dict__)会是一个空字典
        else:
            print("必须是字符串类型")
        # print("执行setattr  ",key,value )

    def __delattr__(self, item):
        print('执行delattr' ,item)
        self.__dict__.pop(item) #这里如果不写,那么删除操作都不会真正删除,查询print(__dict__)还会是在字典里面

t1 = test('alex')
t1.age
t1.age = "19"
print(t1.__dict__)
del t1.age
print(t1.__dict__)


结果:
开始设置   name alex
age 这个属性不存在
开始设置   age 19
{'name': 'alex', 'age': '19'}
执行delattr age
{'name': 'alex'}

 

二次加工标准类型(包装)

包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们学过的继承/派生知识(其他的标准类型可以通过下面的方式进行二次加工)

class List(list):
    def append(self, object):
        if type(object) is str:
            super().append(object)
        else:
            print("只能添加字符")

    def show_midlle(self):
        return self[len(self)//2]




l = List('hello worldw')
print(l)
l.append('s')
print(l)
print(l.show_midlle())
l.append(1)
print(l)

结果:
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 'w']
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 'w', 's']
w
只能添加字符
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 'w', 's']

 

授权:授权是包装的一个特性,包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能,其他的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性

#授权给Open函数文件操作,同时重写write函数,给写进去的字符串前面都加上时间,相当于给write新添加了格式功能
import
time class Open: def __init__(self,filename,modu='r',encode='utf-8'): self.file = open(filename, modu,encoding=encode) def __getattr__(self, item): return getattr(self.file,item) def write(self,string): self.file.write('%s %s \n'% (time.asctime(),string)) f = Open('s18.py','a+') # print(f.file) # print(f.read()) f.write('sd') f.seek(0) print(f.read())

 

 

面向对象的软件开发

很多人在学完了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提出的思路,用面向对象语言编写出程序既可。

posted @ 2019-09-19 10:55  Mr-谢  阅读(568)  评论(0编辑  收藏  举报