一、面向对象编程:
1.比设计模式更重要的是设计原则:
1)面向对象设计的目标:
·可扩展:新特性很容易添加到现有系统中,基本不影响系统原有功能
·可修改:当修改某一部分代码时,不会影响到其他不相关的部分
·可替代:用具有相同接口的代码去替换系统中某一部分代码时,系统不受影响
2)面向对象设计的SOLID原则:
·单一职责原则:设计出来的每一个类,只有一个引起这个类变化的原因
·开闭原则:对扩展开放,对修改封闭
·替换原则:父类能用的功能,换成子类一样可以用
·接口隔离原则:接口设计要按需供给(类似微服务器设计)
·依赖倒置原则:抽象不依赖于细节,细节应该依赖于抽象(针对接口编程)
3)AI(场景)+python(语言)+OOP(编程模式)
·AI:业务导向不明显,需求变动频率较低,实现和复现频率高
·python:虽然是一门面向对象语言,但与传统的面向对象并不相同
·OOP:使用python时,并不需要深入学习OOP或OOD那些理论
二、OOP in python
1.类的创建、数据绑定
class Model:
__name = 'DNN'
def __init__(self,name):
self.__name = name
def print_name(self):
print(self.__name)
@classmethod #装饰器,表示下面这个函数是类所有的
def print_cls_name(cls):#这里的参数名一般使用cls(同之前的函数都使用self一样)
print(cls.__name)
class CNNModel(Model):
__name = 'CNN'
def __init__(self,name,layer_num):
Model.__init__(self,name)#子类的初始化函数必须显式的包含父类的初始化函数
self.__layer_num = layer_num
def print_layer_num(self):
print(self.__layer_num)
def main():
cnnmodel = Model('CNN') #实例化
rnnmodel = Model('RNN')
print(cnnmodel) #实例化完的对象可以直接print
print(rnnmodel.__name) #用.访问成员,报错,因为是私有成员,不能类外访问
cnnmodel.print_name()
要点:
·类名一般大写,实例化出来的对象,名称一般小写
·类在被定义时,创建了一个局部作用域;类实例化出对象后,创建了新的局部作用域,额就是对象自己的作用域
·类名加上()生成对象,说明类被定义后便可以调用
·本质上讲,类本身是一个对象
·在类定义中,self只带实例化出来的对象
·cnnmodel.print_name()等价于Model.print_name(cnnmodel)
·通过双下划线开头,可以将数据属性私有化,对于方法一样适用(数据在外部不能访问,但是可以通过函数访问)
·python中的私有化是假的,本质上做了一次名称替换,因此实际中也有为了方便调试而使用单下划线的情况,而私有化也全凭自觉了
·使用了@classmethod后的方法虽然可以继承,但是方法里面的cls参数绑定了父类,即使在子类中调用了类方法,但通过cls引用的属性依旧是父类的类属性
三、 Pythonic OOP
1.关于双下划线
1)怎么读?
_name:single underscore name
__name:double underscore name
name:dunder name(dunder来源:double首字母d,underscore取under)
init():dunder init method (function)
2)双下划綫开头和结尾的变量或方法叫什么?
·类别:special;magic;dunder
·实体:attribute,method
3)如何认识python的special method
·special method:method with special name(dunder)
·Why use it?A class can implement certain operation that are invoked by special syntax
·original intention of design:operation overloading
2.Pythonic OOP with Special Method and Attribute
·每一个class在定义的时候如果没有继承,都会隐式继承object这个superclass(超类)
·每一个自定义的class在Python中都是一个type object
class X:
pass
class Y(x):
pass
def main():
x = X()
y = Y()
print(x.__class__.__name__) #X
print(y.__class__.__name__) #Y
print(X.__class__.__name__) #type
print(Y.__class__.__name__) #type
print(x.__class__.__base__.__name__) #object
print(y.__class__.__base__.__name__) #X
print(X.__class__.__base__.__name__) #object
print(Y.__class__.__base__.__name__) #object
3.Attribute Access and Peoperties
Attribute相关的操作一般有:
·create
·read
·Update
·Delete
1)level 1 basic access(default access)
class X:
pass
if name == ‘main’:
X.a = ‘a’
print(X.a)
X.a = ‘aa’
print(X.aa)
del X.a
上述例子说明即使在类中没有绑定数据,外部依然可以绑定(太逆天了)
但是如果实例化的对象中没有这个Attribute,访问时会报错
2)level 2:Property(长得像Attribute的Method) ——推荐使用
Property的设计初衷:
·代码复用
·延迟计算
·更加规范的对象属性访问管理
场景:我要减肥,需要监控BMI指标,但是只能测量体重,每天更新体重,隔几天看一次BMI指数
class X:
def __init__(self,w,h):
self.w = w
self.h = h
self.BMI = w / h ** 2
def main():
x = X(175,1.83)
print(x.BMI) #22.3954
x.w = 74
x.w = 73
x.w = 72
print(x.BMI) #22.3954
注意:上述虽然把w传入了,但是__init__()函数只在初始化的时候执行一次,后续不再执行
改进一:
class X:
def __init__(self,w,h):
self.__w = w
self.__h = h
self.BMI = w / h ** 2
def update_w(self,w):
self.__w = w
self._update_bmi()
def _update_bmi(self):
self.BMI = self.__w / self.__h ** 2
def main():
x = X(75,1.83)
print(x.BMI) #22.3954
x.update_w(74)
x.update_w(73)
x.update_w(72)
print(x.BMI) #21.4996
缺点:
BMI属性依旧可以被外部访问和修改
无论BMI属性是否被访问,每次w更新均更新BMI,造成一定的计算资源浪费
改进二:
class X:
def __init__(self,w,h):
self.w = w
self.h = h
def get_bmi(self):
return self.w / self.h ** 2
def main():
x = X(75,1.83)
print(x.get_bmi())
x.w = 74
x.w = 73
x.w = 72
print(x.get_bmi())
缺点:
·上述代码依然需要更改main函数中的语句
·当w变化频率小于BMI访问次数时,需要进行大量的重复计算
改进三:
class X:
def __init__(self,w,h):
self.__w = w
self.__h = h
self.__bmi = w / h ** 2
def get_w(self):
return self.__w
def set_w(self,value):
if value <= 0 :
raise ValueError('weight below 0 is not possible')
self.__w = value
self.__bmi = self.__w / self.__h ** 2
def get_bmi(self):
return self.__bmi
w = property(get_w,set_w) #***括号里面的参数必须是函数名
BMI = property(get_bmi) #***非常重要
def main():
x = X(75,1.83)
print(x.BMI)
x.w = 74
x.w = 73
x.w = 72
print(x.BMI)
分析:
·通过Property对象显式的控制属性的访问
·仅在w被更改的时候更新BMI,充分避免了重复计算
·很容易的增加了异常处理,对更新属性进行预检验
·完美复用原始调用代码,在调用方不知情的情况完成功能添加
实际使用中并不会这样写,有更加优美的写法
改进四:
class X:
def __init__(self,w,h):
self.__w = w
self.__h = h
self.__bmi = w / h ** 2
@property #只读
def w(self):
return self.__w
@w.setter #可写
def w(self,value):
if value <= 0 :
raise ValueError('weight below 0 is not possible')
self.__w = value
self.__bmi = self.__w / self.__h ** 23
@property #只读
def BMI(self):
return self.__bmi
def main():
x = X(75,1.83)
print(x.BMI)
x.w = 74
x.w = 73
x.w = 72
print(x.BMI)
关于property的用法:
·property(fget=None,fset=None,fdel=None,doc=None)
·使用@property默认实现了可读
·被@property装饰过的method可以通过@method.setter继续装饰单输入参数方法实现可写
3.Cross-Cutting and Duck Typing
·单继承vs多态
-单继承保证了纵向的复用和一致性
-多态保证了跨类型的复用和一致性
·传统OOP vs 鸭子类型
-传统OOP基于类别进行设计,从类别出发逐步扩展
-鸭子类型进考虑功能,从需要满足的功能出发进行设计
·传统的OOP的多态 vs 鸭子类型的多态
-传统OOP中的多态大多基于共同的基类进行设计
-python中的鸭子类型无需考虑继承关系,实现了某个通用的接口就可以完成多态设计(special method)
Cross-Cutting:基于鸭子类型的视角看Decorator与special method
·通过一个类实现一个个的special method,你就让这个类越来越像python的Built-in class
·实现special method是从语言衔接层面为你的class赋能
·实现decorator是从通用的函数功能层面为你的class赋能
·通过multi-Inheritance,利用MixIn的理念,你可以为你的class批量化的赋能