Python全栈开发【面向对象】 |
本节内容:
- 三大编程范式
- 面向对象设计与面向对象编程
- 类和对象
- 静态属性、类方法、静态方法
- 类组合
- 继承
- 多态
- 封装
三大编程范式 |
三大编程范式:
1.面向过程编程
2.函数式编程
3.面向对象编程
面向对象设计与面向对象编程 |
面向对象设计(Object oriented design)OOD:将一类具体事物的数据和动作整合到一起,即面向对象设计
面向对象设计(OOD)不会特别要求面向对象编程语言。事实上,OOD 可以由纯结构化语言来实现(比如 C)。但如果想要构造具备对象性质和特点的数据类型,就需要在程序上作更多的努力。
函数也可以实现面向对象设计
def dog(name,gender,type): def jiao(): print('狗%s叫'%dog['name']) def cs(): print('狗%scs'%dog['type']) def init(name,gender,type): dog1 = { 'name' : name, 'gender' :gender, 'type':type, 'jiao':jiao, 'cs':cs, } return dog1 return init(name,gender,type)
面向对象编程(object-oriented programming)OOP:用定义类+实例/对象的方式去实现面向对象的设计
# 定义类,实例化及数据属性、函数属性的增删改查 class Chinese: '''中国人''' dang = '共产_党' def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender def chi_shuijiao(self): print('中国人吃水饺') def he_cha(self): print('中国人%s喝茶'%self.name) p1 = Chinese('ocean',18,'male') print(p1.dang) #共产_党 print(p1.name) #ocean p1.chi_shuijiao() #中国人吃水饺 p1.he_cha()# 中国人ocean喝茶 #---------------增删改查---------------- #----数据属性 Chinese.dang = '国民_党' #改 print(p1.dang) #国民_党 Chinese.ren ='中国人' #增 print(p1.ren) #中国人 print(Chinese.__dict__)#删 del Chinese.dang print(Chinese.__dict__) print(Chinese.ren) #查 #----------函数属性 def eat_food(self,food): print('%s正在吃%s'%(self.name,food)) Chinese.eat = eat_food #增 p1.eat('满汉全席') #ocean正在吃满汉全席 def test(self): #改 print('test') Chinese.he_cha=test p1.he_cha() #test
一门面向对象的语言不一定会强制你写 OO 方面的程序。例如 C++可以被认为“更好 的 C”;而 Java,则要求万物皆类,此外还规定,一个源文件对应一个类定义。 然而,在 Python 中, 类和 OOP 都不是日常编程所必需的。尽管它从一开始设计就是面向对象的,并且结构上支持 OOP,但 Python 没有限定或要求你在你的应用中写 OO 的代码 用面向对象语言写程序,和一个程序的设计是面向对象的,两者是八杆子打不着的两码事。 纯C写的linux kernel事实上比c++/java之类语言搞出来的大多数项目更加面向对象——只是绝大部分人都自以为自己到处瞎写class的面条代码才是面向对象的正统、而死脑筋的linus搞的泛文件抽象不过是过程式思维搞出来的老古董。
小结
类与对象 |
1.什么叫类:
类是一种数据结构,就好比一个模型,该模型用来表述一类事物(事物即数据和动作的结合体),用它来生产真实的物体(实例)。
2.什么叫对象:
睁开眼,你看到的一切的事物都是一个个的对象,你可以把对象理解为一个具体的事物(事物即数据和动作的结合体)
(铅笔是对象,人是对象,房子是对象,狗是对象,能看到的一切皆对象)
3.类与对象的关系:
对象都是由类产生的,上帝造人,上帝首先有一个造人的模板,这个模板即人的类,然后上帝根据类的定义来生产一个个的人
4.什么叫实例化:
由类生产对象的过程叫实例化,类实例化的结果就是一个对象,或者叫做一个实例(实例=对象)
5、经典类与新式类:
大前提: 1.只有在python2中才分新式类和经典类,python3中统一都是新式类 2.新式类和经典类声明的最大不同在于,所有新式类必须继承至少一个父类 3.所有类甭管是否显式声明父类,都有一个默认继承object父类(讲继承时会讲,先记住) 在python2中的区分 经典类: class 类名: pass 经典类: class 类名(父类): pass 在python3中,上述两种定义方式全都是新式类
类的属性 |
类是用来描述一类事物,类的对象指的是这一类事物中的一个个体
是事物就要有属性,属性分为:
1、数据属性:就是变量
2、函数属性:就是函数,在面向对象里通常称为方法
注意:类和对象均用点来访问自己的属性
# 例1 country = '美国' class Chinese: country = '中国' def __init__(self,name): print('>>>>',country) p1 =Chinese('alex') #>>>> 美国 #只有点(.)在调用的时候才是调用数据属性,否则就是找变量 # 例2 country = '美国' class Chinese: def __init__(self,name): country = '中国' print('>>>>',country) p1 =Chinese('alex') #>>>> 中国 # country在__init__函数内则为局部变量,类内则为数据属性
特殊的类属性
class Chinese: pass print(Chinese.__name__)# 类C的名字(字符串) print(Chinese.__doc__)# 类C的文档字符串 print(Chinese.__base__)# 类C的第一个父类(在讲继承时会讲) print(Chinese.__bases__)# 类C的所有父类构成的元组(在讲继承时会讲) print(Chinese.__dict__)# 类C的属性 print(Chinese.__module__)# 类C定义所在的模块 print(Chinese.__class__)# 实例C对应的类(仅新式类中)
实例化注意点:
注意:在说实例化的时候说过,执行类()会自动返回一值,这个值就是实例,而类()会自动执行__init__,所以一定不要在该函数内定义返回值,会冲突。
静态属性、类方法、静态方法 |
class Room: tag = '皇宫' #类的数据属性 def __init__(self,name,width,lenth,high): self.name = name self.width = width self.lenth = lenth self.high = high @property #静态属性,封装成类似于数据属性 def cal_volume(self): return self.width*self.lenth*self.high #用 return 返回计算体积 @classmethod #类方法,专门给类使用与实例无关,类方法只能访问类相关的属性,不能访问实例属性 def tell_info(cls,s): print('%s%s'%(cls.tag,s)) #访问类属性tag @staticmethod #静态方法,只是名义上的属于类管理,不能使用类变量和实例变量,是类的工具包 def bash_room(a,b): print('%s和%s在洗澡'%(a,b)) r1 = Room('紫禁城',1000,1000,30) print(r1.cal_volume) #静态属性调用时不用加括号() print(r1.name) Room.tell_info('是我家') #类方法用类直接调用 >>>皇宫是我家 Room.bash_room('Ababy','ocean') #Ababy和ocean在洗澡
类组合 |
用途:
1:做关联
2:小的组成大的
class School: def __init__(self,name,addr): self.name=name self.addr=addr def tell_info(self): print('School(%s,%s)' %(self.name,self.addr)) class Course: def __init__(self,name,price,period): self.name=name self.price=price self.period=period self.school=School('清华','海淀') c1=Course('python',15000,'5month') print(c1.name) c1.school.tell_info()
继承 |
1、类的继承
类的继承跟现实生活中的父、子、孙子、重孙子、继承关系一样,父类又称为基类。
python中类的继承分为:单继承和多继承
class ParentClass1: pass class ParentClass2: pass class SubClass(ParentClass1): #单继承 pass class SubClass(ParentClass1,ParentClass2): #多继承 pass
2、什么时候用继承
1).当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
例如:描述一个机器人类,机器人这个大类是由很多互不相关的小类组成,如机械胳膊类、腿类、身体类、电池类
2).当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好
例如
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
吃、喝、拉、撒是猫和狗都具有的功能
使用 继承 的思想,如下实现:
动物:吃、喝、拉、撒
猫:喵喵叫(猫继承动物的功能)
狗:汪汪叫(狗继承动物的功能)
class Animal: def eat(self): print("%s 吃 " %self.name) def drink(self): print ("%s 喝 " %self.name) def shit(self): print ("%s 拉 " %self.name) def pee(self): print ("%s 撒 " %self.name) class Cat(Animal): def __init__(self, name): self.name = name self.breed = '猫' def cry(self): print('喵喵叫') class Dog(Animal): def __init__(self, name): self.name = name self.breed='狗' def cry(self): print('汪汪叫') # ######### 执行 ######### c1 = Cat('小白家的小黑猫') c1.eat() c2 = Cat('小黑的小白猫') c2.drink() d1 = Dog('胖子家的小瘦狗') d1.eat()
猫狗
class Dad: money = 10000 def __init__(self,name): self.name = name def hit_son(self): print('%s正在打儿子'%self.name) class Son(Dad): money = 2000000000 pass print(Son.money) print(Dad.money) s1 = Son('jack') s1.hit_son() # 结果: # # # jack正在打儿子
父子
3、继承同时具有两种含义
- 含义一 继承基类的方法,并且做出自己的改变或者扩展(代码重用)
- 含义二 声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现接口中定义的方法
#_*_coding:utf-8_*_ #一切皆文件 import abc class All_file(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): '子类必须实现读功能' pass @abc.abstractmethod def write(self): '子类必须实现写功能' pass class Txt(All_file): def read(self): print('文本数据的读取方法') def write(self): print('文本数据的写入方法') class Sata(All_file): def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的写入方法') class Process(All_file): def read(self): print('进程数据的读取方法') def write(self): print('进程数据的写入方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #归一化,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read()
接口继承
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
4、继承顺序
class A: def test(self): print('A') class B(A): def test(self): print('B') class C(A): def test(self): print('C') class D(B): def test(self): print('D') class E(C): def test(self): print('E') class F(D,E): def test(self): print('F') f1 = F() f1.test() print(F.__mro__) #新式继承顺序:F-->D-->B-->E-->C-->A
新式广度优化
5、子类中调用父类方法
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法
方法一:父类名.父类方法()
方法二:super()
#_*_coding:utf-8_*_ class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...') class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): # Vehicle.__init__(self,name,speed,load,power) super().__init__(name,speed,load.power) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) # Vehicle.run(self) super().run() line13=Subway('中国地铁','180m/s','1000人/箱','电',10) line13.run()
#_*_coding:utf-8_*_ # 每个类中都继承了且重写了父类的方法 class A: def __init__(self): print('A的构造方法') class B(A): def __init__(self): print('B的构造方法') A.__init__(self) class C(A): def __init__(self): print('C的构造方法') A.__init__(self) class D(B,C): def __init__(self): print('D的构造方法') B.__init__(self) C.__init__(self) pass f1=D() print(D.__mro__) #python2中没有这个属性 #运行结果: # D的构造方法 # B的构造方法 # A的构造方法 # C的构造方法 # A的构造方法 # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) ################################################################################ #每个类中都继承了且重写了父类的方法 class A: def __init__(self): print('A的构造方法') class B(A): def __init__(self): print('B的构造方法') super(B,self).__init__() class C(A): def __init__(self): print('C的构造方法') super(C,self).__init__() class D(B,C): def __init__(self): print('D的构造方法') super(D,self).__init__() f1=D() print(D.__mro__) #python2中没有这个属性 # 运行结果: # D的构造方法 # B的构造方法 # C的构造方法 # A的构造方法 # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
不使用super()乱象
多态 |
什么是多态:
类的继承有两层意义:1.改变 2.扩展
多态就是类的这两层意义的一个具体的实现机制 即,调用不同的类实例化得对象下的相同的方法,实现的过程不一样
python中的标准类型就是多态概念的一个很好的示范如(str.__len__(),list.__len__(),tuple.__len__可以len(str),len(list),len(tuple)),虽然调用的方法一样但实现的过程却是不同的。
class H2O: def __init__(self,name,temperature): self.name=name self.temperature=temperature def turn_ice(self): if self.temperature < 0: print('[%s]温度太低结冰了' %self.name) elif self.temperature > 0 and self.temperature < 100: print('[%s]液化成水' %self.name) elif self.temperature > 100: print('[%s]温度太高变成了水蒸气' %self.name) class Water(H2O): pass class Ice(H2O): pass class Steam(H2O): pass w1=Water('水',25) i1=Ice('冰',-20) s1=Steam('蒸汽',3000) #多态:由不同的类实例化得到的对象,调用同一个方法,执行的逻辑不同。 w1.turn_ice() i1.turn_ice() s1.turn_ice() #调用的方法相同,得到的结果不同 # [水]液化成水 # [冰]温度太低结冰了 # [蒸汽]温度太高变成了水蒸气 #################模拟系统len等方法实现的多态 def func(obj): obj.turn_ice() func(w1) func(i1) func(s1) # [水]液化成水 # [冰]温度太低结冰了 # [蒸汽]温度太高变成了水蒸气
封装 |
第一个层面的封装:类就是麻袋,这本身就是一种封装
第二个层面的封装:类中定义私有的,只在类的内部使用,外部无法访问
python不依赖语言特性去实现第二层面的封装,而是通过遵循一定的数据属性和函数属性的命名约定来达到封的效果
约定一:任何以单下划线开头的名字都应该是内部的,私有的
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,外部强行调用也是可行的但没这么调用的!
约定二:双下划线开头的名字
#_*_coding:utf-8_*_ class People: __star='earth' def __init__(self,id,name,age,salary): self.id=id self.name=name self.__age=age self._salary=salary def _get_id(self): print('我是私有方法啊,我找到的id是[%s]' %self.id) class Korean(People): __star = '火星' pass print(People.__dict__)#__star存到类的属性字典中被重命名为_People__star print(Korean.__dict__) # print(Korean.__star)#报错__star被重命名了 print(Korean._Korean__star) print(Korean._People__star)
第三个层面的封装:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用
python并不严格限制外部对私有属性的访问,龟叔之所以这么设计,我估计一个重要原因就是见多了滥用私有而不断打补丁的人于是干脆把我们的python做成非严格意义的封装,以避免你滥用封装。
面向对象的优点 |
从编程进化论我们得知,面向对象是一种更高等级的结构化编程方式,它的好处就两点
1:通过封装明确了内外,你作为类的缔造者,你是上帝,上帝造物的逻辑你无需知道,上帝想让你知道的你才能知道,这样就明确了划分了等级,物就是调用者,上帝就是物的创造者
2:通过继承+多态在语言层面支持了归一化设计
注意:不用面向对象语言(即不用class),一样可以做归一化(如老掉牙的泛文件概念、游戏行业的一切皆精灵),一样可以封装(通过定义模块和接口),只是用面向对象语言可以直接用语言元素显式声明这些而已;而用了面向对象语言,满篇都是class,并不等于就有了归一化的设计。甚至,因为被这些花哨的东西迷惑,反而更加不知道什么才是设计。
Python中OOP的常用术语 |
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态
多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
多态表明了动态(又名,运行时)绑定的存在,允计重载及运行时类型确定和验证。
举例:
水是一个类
不同温度,水被实例化成了不同的状态:冰,水蒸气,雾(然而很多人就理解到这一步就任务此乃多态,错,fuck!,多态是运行时绑定的存在)
(多态体现在由同一个类实例化出的多个对象,这些对象执行相同的方法时,执行的过程和结果是不一样的)
冰,水蒸气,雾,有一个共同的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的两个过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__