Python基础之(面向对象初识)
一、初识面向对象
1.1、面向过程的程序的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可
缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身,扩展性较差
1.2、面向对象的程序的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。被上帝造出来的每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法)
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果
面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外、基于面向对象的程序可以使他人更加容易理解你的代码逻辑,从而使团队开发变得更从容
1.3、类与对象
类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体
在现实世界中:先有对象,再有类
在程序中:务必保证先定义类,后产生对象
实例化即是由类产生对象的过程,实例化的结果就是一个类,或者叫一个实例
二、类的基础知识
2.1、创建一个类
''' class 类名: '类的文档字符串' 类体 ''' #与创建一个函数相似 class Person: #创建一个类 pass #python2中分经典类与新式类,python3中都是新式类 #经典类 class Person: pass #新式类 class Person(父类名称): pass #申明一个新式类: class Person(object): pass
2.2、属性方法
class Person: "这是个人的类" role = "person" #数据属性 def type(self): #函数属性(类中的方法) print("yellow race") print(Person.role) #调用属性 Person.type(Person1) #要传一个参数就是self实例自己,一般不这么玩,使用print(Person.type)看到这个类方法
2.3、实例化
实例化:类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
class Person: "这是个人的类" role = "person" #数据属性 def type(self): #函数属性(类中的方法) print("yellow race" p1 = Person() #实例化 p1.role #查看对象属性 p1.type() #调用类方法
#补充:特殊的类属性 类名.__name__# 类的名字(字符串) 类名.__doc__# 类的文档字符串 类名.__base__# 类的第一个父类(在讲继承时会讲) 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类(仅新式类中)
2.4、小结
class 类名: def __init__(self,参数1,参数2): self.对象的属性1 = 参数1 self.对象的属性2 = 参数2 def 方法名(self):pass def 方法名2(self):pass 对象名 = 类名(1,2) #对象就是实例,代表一个具体的东西 #类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法 #括号里传参数,参数不需要传self,其他与init中的形参一一对应 #结果返回一个对象 对象名.对象的属性1 #查看对象的属性,直接用 对象名.属性名 即可 对象名.方法名() #调用类中的方法,直接用 对象名.方法名() 即可
self:在实例化时自动将对象/实例本身传给__init__的第一个参数
2.5、类和实例属性的增删改查
class Person: role = "person" def __init__(self, name, age): self.name = name self.age = age def running(self): print("%s is running" %self.name) def eat_food(self,food): print("%s eat %s" %(self.name,food)) def age(self): print("%s is %s" %(self.name,self.age)) #类属性 print(Person.role) #查看 Person.role = "dog" #修改 Person.job = "doctor" #增加 del Person.job #删除 def test(self): print("test") Person.test = test #增加 #实例属性 p1 = Person("crazy",18) print(p1.__dict__) print(p1.name) #查看 p1.job = "xxx" #增加 print(p1.__dict__) def test(self): print("test") p1.test=test p1.test(p1) #需要增加参数 #实例只有数据属性,其调用的是类的方法,但是实例也可以增加自己的方法(不同的实例去调的类方法相同,节省内存)
2.6、面向对象设计,有兴趣看下面一个例子:
def cl(name,age,job): def jump(cl): print("%s 正在跳" %cl["name"] ) def run(cl): print("%s 今年 %s 做 %s "%(cl["name"],cl["age"],cl["job"])) def init(name,age,job): per = { "name":name, "age":age, "job":job, "run":run, "jump":jump } return per return init(name,age,job) c1 =cl("jojo",18,"xxx") #{'name': 'dave', 'age': 18, 'job': 'xxx', 'run': <function cl.<locals>.run at 0x02EA3930>, 'jump': <function cl.<locals>.jump at 0x02EA38E8>} c1["run"](c1) #jojo 今年 18 做 xxx
三、面向对象三特性(封装、继承和多态)
3.1、继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
class Parent1: #定义父类1 pass class Parent2: #定义父类2 pass class Sub1(Parent1): #单继承 pass class Sub2(Parent1,Parent2): #python支持多继承,用逗号分隔开多个继承的类 pass
#查看继承 print(Sub2.__bases__ ) #(<class '__main__.Parent1'>, <class '__main__.Parent2'>)
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类
3.1.1、继承的重用
在开发过程中,如果我们定义了一个A类,然后又想新建立另外一个B类,但是类B的大部分内容与类A的相同时我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用,来看个例子:
#第一种方式 class Asia: def head(self): pass def body(self): pass def skin(self): print("yellow") class Europe: def head(self): pass def body(self): pass def skin(self): print("white") class African: def head(self): pass def body(self): pass def skin(self): print("black") #第二种方式: class Person: def head(self): pass def body(self): pass class Asia(Person): def skin(self): print("yellow") class Europe(Person): def skin(self): print("white") class African(Person): def skin(self): print("black")
Python的类可以继承多个类,Java和C#中则只能继承一个类,Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和C3算法(广度优先)
- 当类是经典类时,多继承情况下,会按照深度优先方式查找。
- 当类是新式类时,多继承情况下,会按照C3算法去查询
新式类与经典类:
- 只有在python2中才分新式类和经典类,python3中统一都是新式类
- 在python2中,没有显示继承object类的类,以及该类的子类,都是经典类
- 在python2中,声明继承object的类,以及该类的子类,都是新式类
- 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
#python2中 class new(object):pass #新式类 class old:pass #经典类 #python3中都是新式类 class new(object):pass class old:pass
#经典类多继承 class D: def dd(self): print 'D' class C(D): def cc(self): print 'C' class B(D): def bb(self): print 'B' class A(B, C): def aa(self): print 'A' a=A() # 执行aa方法时 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中没有,则继续去D类中找,如果D类中没有,则继续去C类中找,如果还是未找到,则报错 # 所以,查找顺序:A --> B --> D --> C # 在上述查找aa方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找 #新式类多继承 class D: def D(self): print('D') class C(D): def C(self): print ('C') class B(D): def B(self): print ('B') class A(B, C): def A(self): print ('A') print(A.mro()) #查看新式类多继承顺序 #[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>]
经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
新式类:则根据C3算法,具体可以通过类名.mro()方法去查询他的继承顺序。
注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找
3.1.2、接口与抽象类
继承有两种用途:1、继承基类的方法,并且做出自己的改变或者扩展(代码重用) 2、声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,由子类继承接口类,并且实现接口中的功能。(实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合)
#模拟接口 class Osname: def Windows(self): #接口函数 pass def Linux(self): #接口函数 pass class Win(Osname): def Windows(self): print("windows") def Linux(self): print("linx") class Lin(Osname): def Linux(self): print("linx") def Windows(self): print("windows")
在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口,这就用到了抽象类。什么是抽象类?与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
import abc #利用abc模块实现抽象类 class Osname(metaclass=abc.ABCMeta): @abc.abstractmethod #定义抽象方法,无需实现功能 def Windows(self): pass @abc.abstractmethod def Linux(self): pass class Win(Osname): #子类继承抽象类,但是必须定义Windows和Linux方法 def Windows(self): print("windows") def Linux(self): print("linx") class Lin(Osname): def Linux(self): print("linx") def Windows(self): print("windows") class Error(Osname): pass error = Error() #报错没有定义方法,无法实例化
3.1.5、子类中调用父类方法
- 直接父类名.方法名(self,name,addr)
-
super().方法名(name,addr) 或者super(子类名,self).方法名()
#直接父类名.方法名(...) class Parent: def __init__(self,name,job): self.name = name self.job = job def money(self): print("%s have 1元" %self.name) class Son(Parent): def __init__(self,name,job,addr): Parent.__init__(self,name,job) self.addr = addr def xxx(self,dd): Parent.money(self) print(dd) s1 = Son("x","xx","xxx") s1.xxx("2元") #x have 1元 2元 #super().方法名() class Parent: def __init__(self,name,job): self.name = name self.job = job def money(self): print("%s have 1元" %self.name) class Son(Parent): def __init__(self,name,job,addr): super().__init__(name,job) #等同于super(Son,self).__init(name,job) self.addr = addr def xxx(self): super().money() s1 = Son("x","xx","xxx") s1.xxx()
3.1.4、类的组合与对象之间交互
软件重用的重要方式除了继承之外还有另外一种方式,即:组合。组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
class Company: def __init__(self,name,addr): self.name = name self.addr = addr self.contain = Department("技术部") class Department: def __init__(self,name): self.name = name self.function = "打酱油" def makemoney(self): print("1毛钱1分钟") m= Company("xx","地球") m.contain.makemoney() #1毛钱1分钟 print(m.contain.name) #技术部
组合与继承都是有效地利用已有类的资源的重要方式,当类之间有显著不同并且较小的类是较大的类所需要的组件时,用组合比较好
对象之间的交互,先来看个例子:
class Thishero: def __init__(self,name): self.name = name #hero名称 self.attack = 98 #攻击值 self.life = 100 #生命值 def attacko(self,enemy): #enmy为敌人 enemy.life-= self.attack #攻击后敌人的生命剩余值 class Otherhero: def __init__(self,name): self.name = name self.attack = 99 self.life = 100 def attacko(self,enemy): enemy.life-= self.attack c1 = Thishero("关羽") c2 = Otherhero("秦琼") c1.attacko(c2) #关羽战秦琼 print(c2.life) #秦琼剩余生命值
3.2 多态 (不同的对象执行相同的方法,执行的逻辑不同)
多态指的是一类事物有多种形态,比如人类有亚洲人、欧洲人、外星人...文件有文本、执行等等
class Person(metaclass=abc.ABCMeta): @abc.abstractmethod def head(self): pass class Asia(Person): def head(self): print("yellow") class Europe(Person): def head(self): print("white") class African(Person): def head(self): print("black") asia = Asia() europe = Europe() african = African() asia.head() europe.head() african.head() def obj(obj): #我们可以定义一个统一的接口来使用 obj.head()
其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的。或者你说太坑了吧,多态不就是继承嘛!
3.3、封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。所以在使用面向对象的封装特性时,需要:
- 将内容封装到某处
- 从某处调用被封装的内容
class F: def __init__(self, name, age): #构造方法,创建对象时候自动执行 self.name = name self.age = age obj = F('jump', 18) #将jump,18封装到name和age中 print (obj.name) # 直接调用obj对象的name属性 print (obj.age) # 直接调用obj对象的age属性
由此我们可以看出对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象调用封装的内容
3.3.1、封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
class Run: def __init__(self,a,b,c): self.a = a self.__b = b #(隐藏,外部可以通过接口或者直接调用) self._c = c #(只是约定隐藏,外部仍可以调用) def run(self): return (self.a*self.__b*self._c) r = Run(1,2,3) print(r.run()) print(r._c) # print(r._Run__b) #直接调用隐藏 #隐藏属性 (两种方式外部访问) 1、def out(self):(外部调用,接口) return __self.xxx 2、obj._类名__xxx obj._classname__xxx