上节回顾
面向过程与面向对象
面向过程:核心的就是过程,过程就是解决问题的步骤,就是实现确定先干什么后干什么,面向对象的设计程序就好比是在设计一条流水,是一种机械思维方式。
优点:复杂的问题简单化
缺点:可扩展性差(牵一发而动全身)
用到的一些经典案例:Linux内核,httpd,git
面向对象:核心是对象,要理解对象应该把自己当成上帝。把一切存在的事物都当成对象,不存在的也可以创建出来。对象可以理解为特征(变量)与技能(函数)的结合体。
优点就是可扩展性强。
缺点是无法像面向过程一样准确的知道什么阶段发生了什么事,会有什么结果。
应用场景:与用户层交互多的,版本迭代需求比较多的,公司内部的软件,游戏,互联网软件。
本节内容
身份运算符和值运算符
我们判断两个变量是否相等的话一般就会用到这两种运算符is和==,is的话就是身份运算符,主要判断对象的内存地址是否一致,而==就是值运算符,主要判断变量的值是否一致。
要理解这两种运算符首先要理解一个对象的三个属性:ID、类型、值。
id代表的是他(变量名)在内存中的位置,使用内置方法id来进行查询。
type代表他(指向的变量)的类型。
而值就代表他真正的值。
类与对象
对象就是一个又一个特征和技能的结合体,而类是一系列对象共有的特征或者技能的结合体(可以只有共有特征或者只有共同技能,不需要两个都有)。
在生活中,一般都是先有对象,比如说一个又一个中国人。然后再有从对象中抽象出来特征和方法组成的类,比如说黄皮肤、黑眼睛(特征)、会说中国话(方法)。
而在程序中,我们一般的顺序刚好是倒过来,首先设计出一个抽象的类,然后再通过这个类来实例化出对象来。
创建类的语法:
class class_name:
#注释部分
类体
类的具体用法有两种:
第一种是实例化使用:每个类都有为对象定制自己属性的函数__init__,然后在对象实例化的过程中,也会给对象绑定上方法和独立的内存空间
第二种是属性引用:属性引用主要是引用类的一些变量,一些函数。比如说可以看类目前有多少个实例,可以单独设置一个实例计数器来记录这个数据。
查看类的名称空间中都有哪些属性:使用__dict__方法来查看。
对象:
对象是由类实例出来的产物,他的名称空间中只包含自己的一些数据,而他的方法都是类内部的方法,多个实例调用方法时调用的都是一个方法,只是传入的参数(对象自己)不同而已。
对象可以访问到的属性由:自己本身的数据属性,类的方法,类的数据属性。
小结:定义在类内部的变量是所有对象共有的,ID是全一样。
定义在类内部的函数,是绑定到所有对象的,是给对象来用的,对象用的时候,相当于把自己作为第一个参数传进去了。
类的继承
什么是继承:
继承是一种创建类的方式,通过继承方式创建的类,可以通过继承关系调用父类的东西,通过继承创建的类有一个方法__bases__可以查看自己的父类。
新式类与经典类:
在python2中有新式类和经典类之分,在python3中都是新式类了已经。
在python2中,继承自object的类或者其他新式类的,就是新式类。
新式类和经典类的区别主要是在调用方法的查找顺序上出现了问题,如果是有多个父类,其中的几个父类又是从同一个类继承过来的。比如下面的结构
E
A B C
D (D的父类是A、B、C,A B C的父类是E)
那么如果D是新式类,那么他调属性会从D→A→B→C→E这个顺序去寻找(广度优先)。如果是经典类,会从D→A→E→B→C这个顺序去寻找。(深度优先)
组合:
组合和继承不太一样,组合的意思是一个对象的属性是另外一个对象。我们就可以说是组合,组合也可以解决代码冗余问题,但是组合反映的是一种什么有什么的关系。
抽象类
子类定义新的属性(方法),覆盖掉父类的属性,称为派生。这样在调用的时候,优先使用新定义的属性(方法)。
子类在派生的时候可以重新调用父类的方法,相当于把父类的方法主体加到子类里面了。
派生过程中可以通过以下两种方式来在父类的基础上来继承。
1.直接引用
class person(animal):
def __init__(self,name,age):
animal.__init__(self,name)
self.age = age
2.使用super
class person(animal):
def __init__(self,name,age):
super().__init__()
self.age = age
一般情况下子类都是可派生可不派生的,但是在产品开发的过程中,有的时候要求必须所有的子类都要提供一个统一的对外接口,就用到了抽象类。
假设现在要写很多的文件下面的小类(块文件,可执行文件,目录文件),他们都应该提供两个接口,一个读一个写,最好的实现方法就是让他们都继承自一个抽象类,然后抽象类中定义好read和write,这样他们如果不写着两个方法,实例化都实例化不了。
抽象类的实现方法。
1.导入一个abc的模块。
2.定义类的时候,在类名后面的括号里面加上这个参数metaclass=abc.ABCMeta,然后在需要必须派生的类上面加一个装饰器,@abc.abstractmethod。
隐藏属性(封装)
封装的意义就是为了保护隐私和隔离复杂度,人们不需要关心函数或者类内部的构造,只需要调用对应的接口就好。python上没有真正意义的隐藏,一般都是在属性前面加两个下划线,就算是隐藏了。外面想调用要用 xx._classname_fun的方法来调用。
而且语法的变形只在定义的时候发生变形,如果实例化一个类以后再赋予就不行,p1.__name = ‘xxoo’。
隐藏函数的查看,修改,删除:
1.可以通过函数直接查看、修改、删除。
2.用到一些内置的类做装饰器,来让对这个变量操作的时候对用户来说是透明的。
查看
class people:
__name = “hehe”
@property
def name(self)
return people.__name
property:可以让外界查看某个函数的返回值变成直接查看这个函数,相当于是透明了。
更改:
class people:
__name = “hehe”
@property
def name(self)
return people.__name
@name.setter
def name(self)
return people.__name
@name.deleter
def name(self):
del test.__name
xx.setter:提供一个从外界更改隐藏变量xx的方法,必须前面有可以查看的接口以后才能用这个。
xx.deleter:提供了一个删除deleter的接口,也是前面必须提供了查看的接口以后才能用这个。
其中xx必须和被property修饰的函数一样,而xx.setter修饰的函数也必须要叫xx。
绑定方法与非绑定方法
类中定义的函数可以通过是不是绑定方法来区分,绑定方法的意思就是是谁的绑定方法,在使用的时候默认给他传谁。绑定方法也分为两大类:对象绑定方法,类绑定方法。
三种方法的定义方式:
1.对象绑定方法:在类中定义的函数,如果什么都不加默认是对象绑定方法,
2.类绑定方法:用classmethod装饰器装饰的方法就是类绑定方法。
3.非绑定方法:非绑定方法就是使用staticmethod装饰器装饰的方法。就是一个普通的工具,正常传参数,正常使用。
三种方法在生产中的用途:
1.对象绑定方法:类在实例化的时候想让实例继承的技能都可以通过对象绑定的方法设定。
2.类绑定方法:类绑定方法可以提供一个和__init__不一样的初始化的方式。
具体实现方式:
@classmethod
def from_conf(cls):
xx=f.read
host =xx
port =xxx
return cls(host,port)
3、非绑定方法:
非绑定方法就是一个普通函数,可以理解为是类中的一个工具包,一般用在比如说是要生成一个ID,可以在类的内部定义一个函数,然后初始化对象的时候自动生成一个ID。
生成一个随机ID的方法:
import hashlib
import time
def create_id():
m=hashlib.md5(str(time.clock()).encode(‘utf-8’))
return m.hexdigest()
类的持久化
类的持久化的话最好的办法是使用Pickel实例化到指定文件中,而文件名的话使用随机生成的ID即可。
然后读取的时候直接读取文件下面所有的文件。
代码示例
类面向对象程度的完成流程
整理需求
最开始要根据需求进行调研。
设计
根据面向对象阶段能形成的需求模型,对每一个部分就行具体的设计。首先是类的设计,类的设计可以包含多个层次,么然后以这些类为基础提供程序设计的思路和方法。
一般设计阶段不考虑语言,而是用流程图的方式来描述。
写代码
在写代码的过程中,将设计的结果通过某种语言写到程序里面去。
测试
根据最开始提出的需求来进行测试工作,看看需求可不可以正常的运转。
维护
升级 迭代 修改BUG 都数据维护的过程。
反射与动态导入模块
首先我们知道,每一个对象都有自己的__dict__空间的,我们可以通过这个空间来获取一些对象的内容。
比如
T1 =test("liuyi",18)
print(T1.__dict__[‘name‘])
而我们也可以用反射的方式来获取这个属性。
print(getattr(T1,"name"))
print(getattr(T1,"salary","not exit"))
就是使用反射的hasattr,getattr,setattr,delattr相当于是__dict__进行增删改查。
hasattr(obj,’name’):判断obj对象下面有没有一个叫做name的属性。
setattr(obj,val,value):给对象设置属性。
getattr(obj,p,’not exit’):返回对象中属性p的值,如果没有的话返回not exit
delattr(obj,y):删除obj对象的属性y
实际用途之一:
1.判断模块下面有没有自己想要的方法:
import sys
m= sys.modules[__name__]
m就是当前模块的对象。
动态导入模块:
__str__与__del__
__del__:删除对象的时候会执行的函数。
__str__:直接print对象的时候输出的东西。
常规用法:
del可以作为组合对象的一些绑定关系,或者更改一些类的变量,比如说类的实例计数器。
作业:
组合多对多的关系怎么做?
可以建一个中间的类。
老师-班级。
他实例化的时候输入课程和老师
比如
(python,egon)
(python,alex)
(go,alex)