python基础之面向对象(基础篇)
概述
- 面向过程:根据业务逻辑从上到下写垒代码
- 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可
- 面向对象:对函数进行分类和封装,让开发“更快更好更强...”
在之前已经接触过了面向过程和函数式的编程,还有那么今天我们来学习一种新的编程方式:面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)
创建类和对象
面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现,所以,面向对象编程其实就是对 “类” 和 “对象” 的使用。
类就是一个模板,模板里可以包含多个函数,函数里实现一些功能(把一类事物的相同特征和动作整合到一起就是类)
对象则是根据模板创建的实例,通过实例对象可以执行类中的函数(就是基于类而创建的一个具体的事物(具体存在的))
在这里举一个小例子:
# 创建类 class Foo: def Bar(self): print 'Bar' def Hello(self, name): print 'i am %s' %name # 根据类Foo创建对象obj obj = Foo() obj.Bar() #执行Bar方法 obj.Hello('wupeiqi') #执行Hello方法
面向对象:【创建对象】【通过对象执行方法】
函数编程:【执行函数】
应用的场景不同造成的编程方式也不同,总结:函数式的应用场景 --> 各个函数之间是独立且无共用的数据
这里再对面向对象再进行一下细分:
1、面向对象不是所有情况都适用
2、面向对象编程
a 定义类
class Foo:
def 方法1(self,bb):
pass
obj = Foo(bb)
b 根据类创建对象(创建一个Foo类的实例)
可以使用对象去执行类中的方法
3、self,形式参数,代指执行方法的对象。python内部自动传递
4、类+括号 => 自动执行类中的 __init__方法: 创建了一个对象
在__init__ 有一个特殊名字:构造方法
==》 初始化
__del__ 解释器销毁对象时候自动调用,特殊的名: 析构方法
静态属性,静态方法,类方法,组合
静态属性的作用 将函数属性伪装成数据属性(封装逻辑)
用法 在方法(函数属性)上面加 @property
1 class Desk: 2 def __init__(self,name,width,length,heigh): 3 self.name = name 4 self.width = width 5 self.length = length 6 self.heigh = heigh 7 @property # 静态 将函数属性伪装成数据属性 8 def cal_area(self): 9 x = "立方厘米" 10 return self.length*self.width*self.heigh,x 11 12 d1 = Desk("舒克",20,50,10) 13 print(d1.cal_area) 14 15 (10000, '立方厘米')
静态方法 跟类和实例无关系,名义上归属类管理,但不能使用类变量和实例变量
用法 在方法(函数属性)上面加 @staticmethod
特点: 只能使用静态变量, 所以始终占用同一个内存, 执行效率更高, 但不会被自动回收.
1 class Desk: 2 def __init__(self,name,width,length,heigh): 3 self.name = name 4 self.width = width 5 self.length = length 6 self.heigh = heigh 7 @staticmethod # 用函数修饰符来声明静态方法 8 def cal_area(): 9 print("我跟类和实例没有任何关系哦!") 10 11 12 13 # 静态方法不能调用类和实例的属性
类方法 不需要self参数,自动加cls参数,python底层自动传入类对象。
跟实例没有关系,类自己调用;只能访问类的属性,不能访问实例属性
用法 在方法(函数属性)上面加 @classmethod
1 #@classmethod 类方法 2 class Desk: 3 same = "study" 4 def __init__(self,name,width,length,heigh): 5 self.name = name 6 self.width = width 7 self.length = length 8 self.heigh = heigh 9 @classmethod 10 def cal_area(cls): 11 print(cls) # 接收一个类名,就是类本身,可以访问类的属性,不能访问实例的属性 12 print(cls.same) 13 14 Desk.cal_area() # 类调用方法 此刻不需要传参数,自动传了参数进去
组合 大类包含小类,可以作关联
就是将不同的类混合并加入到其他类中,来 增加类的功能 / 提高代码的重用性 / 易于维护(对类的修改会直接反应到整个应用中) 。我们可以实例化一个更大的对象,同时还可以添加一些实例属性和实例方法的实现来丰富实例对象的功能。
1 class Hand: 2 pass 3 4 class Foot: 5 pass 6 7 class Trunk: 8 pass 9 10 class Head: 11 pass 12 13 class Person: 14 def __init__(self,id_num,name): 15 self.id_num=id_num #身份证号码 16 self.name=name 17 self.hand=Hand() 18 self.foot=Foot() 19 self.trunk=Trunk() 20 self.head=Head() 21 22 p1=Person('111111','alex') #给实例传值 23 print(p1.__dict__) #查看实例的属性字典 24 25 {'foot': <__main__.Foot object at 0x011B4130>, 'trunk': <__main__.Trunk object at 0x011B4170>, 'id_num': '111111', 'hand': <__main__.Hand object at 0x011B40D0>, 'name': 'alex', 'head': <__main__.Head object at 0x011B4190>}
# 组合 class Sea: # def __init__(self,name,country,addr): self.name = name self.country = country self.addr = addr def sea_wave(self): print("一波儿海啸正在来袭") s1 = Sea("东海","哥雅王国","风车村") class Seapoacher: def __init__(self,name,age,gender,ability): self.name = name self.age = age self.gender = gender self.ability = ability self.sea = s1 # 此时关联到上一个类 def atk(self): print("攻击力100") def show_info(self): print("%s %s %s 籍贯:%s %s %s" %(self.name,self.gender,self.age,self.sea.name, self.sea.country,self.sea.addr)) hz = Seapoacher("路飞",17,"男","橡胶") hz.show_info() 路飞 男 17 籍贯:东海 哥雅王国 风车村
========三大特性=========
封装:
使用场景 当同一类型的方法具有相同参数时,直接封装到对象即可。
使用场景 把类当作模板,创建多个对象(对象内封装的数据可以不一样)
1 # 声明定义一个类 2 class onepiece: 3 def __init__(self,name,ability): # 封装数据到里面 4 self.name = name 5 self.ability = ability 6 def long(self): 7 print("%s是拥有%s恶魔果实能力的能力者" %(self.name,self.ability)) 8 # 类实例化,类执行自动执行__init__方法 9 haizei = onepiece("路飞","橡胶") 10 haizei.long() # 根据对象可以调用类下面的方法
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要1、将内容封装到某处 2、从某处调用被封装的内容
第一步:将内容封装到某处
self 是一个形式参数,当执行 obj1 = Foo('wupeiqi', 18 ) 时,self 等于 obj1
当执行 obj2 = Foo('alex', 78 ) 时,self 等于 obj2
所以,内容其实被封装到了对象 obj1 和 obj2 中,每个对象中都有 name 和 age 属性,在内存里类似于下图来保存。
第二步:从某处调用被封装的内容
调用被封装的内容有俩种情况
1、通过对象直接调用被封装的内容
对象.属性名
class Foo: def __init__(self, name, age): self.name = name self.age = age obj1 = Foo('lufei',20) print(obj1.name) # 直接调用obj1对象的name属性 print(obj1.age) # 直接调用obj1对象的age属性 obj2 = Foo('suolong',19) print(obj2.name) # 直接调用obj2对象的name属性 print(obj2.age) # 直接调用obj2对象的age属性
2、通过self间接调用被封装的内容
1 class Foo: 2 3 def __init__(self, name, age): 4 self.name = name 5 self.age = age 6 7 def detail(self): 8 print self.name 9 print self.age 10 11 obj1 = Foo('wupeiqi', 18) 12 obj1.detail() # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18 13 14 obj2 = Foo('alex', 73) 15 obj2.detail() # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78
综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
封装是啥,抛开面向对象,你单去想什么是装,装就是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,什么是封,封就是把麻袋封上口子。
在面向对象中这个麻袋就是你的类或者对象,类或者对象这俩麻袋内部装了数据属性和函数属性,那么对于类和对象来说‘封’的概念从何而来,其实封的概念代表隐藏。
约定:任何以单下划线开头的名字和双下划线开头的名字都应该是内部的,私有的。
# 封装下的 _ 和 __ # _和__表示一种隐藏的约定 python并不会真正阻止访问 class Onepiece: _BOSS = "哥尔 D 罗杰" __FIRST = "白胡子" def __init__(self,name): self.name = name def func(self): pass l = Onepiece("路飞") print(l._BOSS) # __ python会自动进行重新命名的操作 # print(l.__FIRST) # 报错 print(l._Onepiece__FIRST) # 重新命名的形式就是此种 print(Onepiece.__dict__) # 在属性字典中可以查看到
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的。
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,仍然可以用。
第三个层面的封装:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用。(这才是真正的封装。) 理解不深刻
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都没有从根本上限制你的访问。
继承:
继承又分为单继承和多继承
1 class ParentClass1: 2 pass 3 4 class ParentClass2: 5 pass 6 7 class SubClass(ParentClass1): #单继承 8 pass 9 10 class SubClass(ParentClass1,ParentClass2): #多继承 11 pass
#继承 class Dad: '这个是爸爸类' money=10 def __init__(self,name): print('爸爸') self.name=name def hit_son(self): print('%s 正在打儿子' %self.name) '这个是儿子类' class Son(Dad): money = 1000 s1=Son('alex') #继承父类的属性 print(s1.money) print(Dad.money) # print(Dad.__dict__) # print(Son.__dict__)
继承也有俩种含义
1、继承基类的方法,并且做出自己的改变或者扩展(代码重用)
2、声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现接口中定义的方法(又称接口继承)
# 接口继承 import abc """接口类 一般不用实现方法,没必要实例化,一般用来规范子类的 接口类有的方法,再派生类中定义方法,必须要有基类下的方法""" class All_file(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass class Cd(All_file): def read(self): print("cd read") def write(self): print("cd write") class Mem(All_file): def read(self): print("mem read") def write(self): print("mem write") m1 = Mem()
在子类中继承父类的方法,为了避免修改类名的关系,这里用super()更好的解决方式
super() 不用传父类名,也不需要传self的参数
1 # 在子类当中调用父类方法 2 class A: 3 def __init__(self,name): 4 self.name = "-->%s"%name 5 def run(self): 6 print("地裂波动剑-->") 7 class B(A): 8 def __init__(self,name,age): 9 # A.__init__(self,name) 10 # super(B, self).__init__(name) 11 super().__init__(name) # super内不用传参数 上面俩种方式与此相同 12 self.age = age 13 def show_info(self): 14 print(self.name) 15 def run(self): 16 super().run() 17 print("造成9999+伤害") 18 a = A("狂战士") 19 b = B("鬼泣",18) 20 b.run() 21 print(b.name) 22 print(a.name) 23 24 地裂波动剑--> 25 造成9999+伤害 26 -->鬼泣 27 -->狂战士
这里介绍一下继承的顺序
在python3中,基类默认继承 object 所以python中的类都称之为新式类,新式类的顺序
# 继承顺序 # 在python3中都默认继承object 先优先自己,继而自左向右 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("A") class F(D,E): def test(self): print("F") print(F.__mro__) # 继承顺序就是基于mro 定义的顺序 (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
多态:
多态:表明了动态绑定的存在,调用了不同的方法。才能展示出来。反映的是运行时候的状态。
什么是多态?
由不同的类实例化得到的对象,调用同一个方法,执行的逻辑不同。
1 s1='abc' # 示例 2 l=[1,2] 3 print(s1.__len__()) 4 print(l.__len__()) 5 6 3 7 2
1 class H2O: 2 def __init__(self,name,temperature): #名字,温度 3 self.name=name 4 self.temperature=temperature 5 def turn_ice(self): 6 if self.temperature < 0: 7 print('[%s]温度太低结冰了' %self.name) 8 elif self.temperature >0 and self.temperature < 100: 9 print('[%s]液化成水' % self.name) 10 elif self.temperature > 100: 11 print('[%s]温度太高变成了水蒸气' % self.name) 12 13 class Water(H2O): #继承父类(H2O) 14 pass 15 16 class Ice(H2O): 17 pass 18 19 class Steam(H2O): 20 pass 21 22 w1 = Water('水', 25) 23 i1 = Ice('冰', -20) 24 s1 = Steam('蒸汽', 3000) 25 26 def func(obj): 27 obj.turn_ice() 28 29 func(w1) #---->w1.turn_ice() 30 func(i1) #---->i1.turn_ice() 31 func(s1) #---->s1.turn_ice()
类的继承有两层意义:1.改变 2.扩展
1、多态就是类的这两层意义的一个具体的实现机制
2、调用不同的类实例化得到对象下的相同的方法,实现的过程不一样。
3、python中的标准类型就是多态概念的一个很好的示范 。
说完之后,面向对象都有些什么专业化的术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态
多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
多态表明了动态(又名,运行时)绑定的存在,允计重载及运行时类型确定和验证。
举例:
水是一个类
不同温度,水被实例化成了不同的状态:冰,水蒸气,雾(然而很多人就理解到这一步就任务此乃多态,错,fuck!,多态是运行时绑定的存在)
(多态体现在由同一个类实例化出的多个对象,这些对象执行相同的方法时,执行的过程和结果是不一样的)
冰,水蒸气,雾,有一个共同的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的两个过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__