初学Python——面向对象编程
一、面向对象 or 面向过程?
编程范式:
编程是 程序 员 用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程 , 一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务的方式有很多种不同的方式, 对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。 不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路, 大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。 两种最重要的编程范式分别是面向过程编程和面向对象编程。
面向过程:
就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。具体来讲就是:一个大的程序可以按照功能分成若干个文件,每个文件除主函数外封装若干个函数,每个函数理论上是最小的功能单元。
面向对象:
面向对象编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。面向对象适合处理复杂的任务,便于不断地迭代和维护。
无论用什么形式来编程,都要记住:
1.写重复代码是非常不好的低级行为
2.已经写好的代码需要经常变更
二、面向对象介绍
“世间万物皆为对象;世间万物皆可分类”。在这里需要理解对象和类的概念。
只要是对象,就一定属于某种类;只要是对象,就一定有属性。
类(class):一个类是对一类拥有相同属性的对象的抽象,在类中定义了这些对象都具备的相同的属性和方法(功能)。类相当于模子。
对象(object):具体的事物,比如一个人,一棵树等。
- 三个特性:封装、继承、多态
封装:在类中对数据的赋值、内部调用对外部是不可见的(不可访问),这使类和对象变成了一个胶囊或容器,里面包含着数据和方法。
继承:一个类可以派生出子类,在父类中的属性方法会自动被子类继承。
多态:一句话描述:“一个接口,多种实现”。一个父类派生出多个子类,且每个子类在继承了同样的方法名的同时对父类方法做了不同的实现,这是同一种事物的多种形态。对不同的对象发送相同的消息会产生不同的行为。
我们将在下面慢慢体会这三个特性。
三、实例化
把一个类变成具体对象的过程叫实例化。如何实例化呢?这需要我们来了解一下Python中类的组成。
先上一段代码
1 class Role(object): 2 n=123 # 类变量 3 m=[1,2,3,4,5] 4 def __init__(self, name, role, weapon, life_value=100, money=15000): 5 "构造函数" 6 '''在实例化时做一些初始化工作''' 7 self.name = name #在实例化过程中,相当于r1.name=name 8 self.role = role # 实例变量(静态属性) ,作用域:实例对象中 9 self.weapon = weapon 10 self.life_value = life_value 11 self.money = money 12 13 def __del__(self): 14 "析构函数" 15 print("alreadly death") 16 17 def shot(self): # 类的方法(功能)(动态属性) 18 print("shooting...") 19 20 def got_shot(self): 21 print("ah...,I got shot...") 22 23 def buy_gun(self, gun_name): 24 print("just bought %s" % gun_name) 25 26 27 r1 = Role('Alex', 'police',' AK47') # 实例化过程 28 r2 = Role('Jack', 'terrorist', 'B22') 29 r1.buy_gun("Alex") 30 r1.got_shot() # 在对象调用方法时,虽然人并没有传入r1,但是解释器自动帮你写上了r1 31 # 相当于 r1 = Role.got_shor(r1)
1.属性和方法
属性,也就是封装在类里的变量。方法,就是封装在类里具有一定功能的函数。
例如,name,role,weapon等都是属性。shot,got_shot,buy_gun等都是方法。
2.构造函数和析构函数
- __init__被称为构造函数或构造方法。它的作用是:当类实例化时,为对象开辟一片内存,做一些初始化工作。
例如,在创建对象时,需要确定一些属性的初值,当然你也可以做一些任何你想做的事。
- __del__被称为析构函数。它的作用是在销毁对象的时候,做一些收尾的工作。这些工作不需要你去额外调用函数,它会在销毁的时候自动触发。
3.类变量和实例变量
在实例化过程后,对象中产生了相应的属性值,但是并没有相应的方法,方法在类里。对象调用方法时,是从类里调用的。那么类该怎么区分到底哪个是哪个对象在调用这个方法呢?
self,相当于身份识别。在类中,self所代表的就是实例本身
- 实例变量
r1.name="lwz" print(r1.name) # 属性值已改 r2.name="lilan" print(r2.name) r1.bullt_prove=" new bianliang" # 类中不存在的属性,可以在对象中定义并使用 print(r1.bullt_prove) print("r1的武器:",r1.weapon) del r1.weapon # 删除之后已经不存在了 输出: lwz lilan new bianliang r1的武器: AK47
- 类变量
在类中的变量叫做类变量,生成对象后在对象内叫做实例变量。但有一种变量,在构造函数的外部,我们也把它叫做类变量。例子中n和m都是类变量。
需要知道的是,对象使用变量时,先从对象内存中找,如果不存在,则从类变量里找。
类变量的访问:通过对象访问类变量时,访问的是类中的内存中的变量
print(Role.n) # 实例化之前可以调用类变量 print(r1.name,r1.n) # 实例化之后也可以调用类变量 print(r2.name,r2.n) 输出: 123 lwz 123 lilan 123
当通过对象修改类变量时,实际上修改的不是类变量,而是此刻在对象中新生成了一个同名变量(思考一下)
r1.n="改类变量" # 看似在实例r1中修改类变量 print("实例r1变量",r1.n) # 实际上修改和访问的是r1中的实例变量 print("实例r2变量",r2.n) # 访问的是类变量 print("类变量",Role.n) # 类变量不会改变 print() Role.n="ABC" # 真正的修改类变量 print("类变量",Role.n) # 此时类变量已修改 print("实例r1变量",r1.n) # n在r1中已变成实例变量,不受影响 print("实例r2变量",r2.n) # r2访问修改后的类变量 print("修改前Role.m",Role.m) r1.m.append("from r1") r2.m.append("from r2") print("修改后r1",r1.m) print("修改后r2",r2.m) print("修改后Role.m",Role.m) # 发现无论操作谁,所有的值总是保持一致 输出: 实例r1变量 改类变量 实例r2变量 123 类变量 123 类变量 ABC 实例r1变量 改类变量 实例r2变量 ABC 修改前Role.m [1, 2, 3, 4, 5] 修改后r1 [1, 2, 3, 4, 5, 'from r1', 'from r2'] 修改后r2 [1, 2, 3, 4, 5, 'from r1', 'from r2'] 修改后Role.m [1, 2, 3, 4, 5, 'from r1', 'from r2'] alreadly death alreadly death
类变量和实例变量总结:
1.简单数据类型:通过对象进行修改,会产生一个实例变量在对象内存中,与类变量无关了
2.复杂数据类型(列表等):在对象中进行修改,不会在对象内产生变量,修改的就是类里的变量。所有对象共用类里面的类变量。
这个例子体现了面向对象的第一个基本特性,封装,其实就是使用构造方法将内容封装到某个具体对象中,然后通过对象直接或者self间接获取被封装的内容
四、类的继承(新式类和经典类)
继承分单继承和多继承
1 # class People: 经典类 2 class People(object): # 新式类,比经典类在底层做了一些升级,对我们的影响主要体现在继承上 3 4 def __init__(self,name,age,height): 5 self.name = name 6 self.age = age 7 self.height = height 8 9 def eat(self): 10 print("{0} is eating".format(self.name)) 11 12 def talk(self): 13 print("{0} can speak".format(self.name)) 14 15 def sleep(self): 16 print("{0} is sleeping".format(self.name)) 17 18 19 '''Python支持多继承,子类可以继承多个父类 20 ''' 21 class Relation(object): 22 def makefriend(self,obj): 23 print("{0} is making friends with {1}".format(self.name,obj.name)) 24 25 26 class man(People,Relation): 27 "Man类继承自People类" 28 '''如果需要在子类中添加属性,需要重构init函数,父类原有的属性必须要加在重构的init函数中''' 29 '''在生成对象的时候,构造函数首先在man类的init函数中生成属性,然后根据super语句再去调用父类的init''' 30 def __init__(self,name,age,height,money): 31 People.__init__(self,name,age,height) # 调用父类原有的属性 经典类的写法 32 #super(man, self).__init__(name, age, height) # 调用父类中属性的另一种方法 新式类的特有写法 33 '''推荐用第二种方法,省去了People名称的修改,便于修改和维护''' 34 self.money = money # 新添加的属性 35 print("{0}一出生就有{1}元".format(self.name,self.money)) 36 37 def piao(self): 38 print("woo~!@#¥%…&*") 39 40 def sleep(self): 41 super().sleep() # 如果没有这一行,调用时会覆盖父类 42 # 如果有这一行,调用时先执行父类方法后执行子类方法,等于重构了父类方法 43 print("new sleeping") 44 45 46 class woman(People,Relation): 47 "子类" 48 '''如果子类中没有构造函数,需要直接继承父类的属性和方法''' 49 '''实例化过程中,子类的继承顺序是从左到右的 People --> Relation ''' 50 def bring(self): 51 print("{0} is born a baby".format(self.name)) 52 53 54 print("man:") 55 m1 = man("Alex",20,170,150) # 子类的实例化过程也要属性值 56 print(m1.age) 57 m1.eat() # 使用父类方法 58 m1.piao() # 使用子类方法 59 m1.sleep() 60 61 print("woman:") 62 w1 = woman("Jack",24,160) 63 m1.makefriend(w1) # 运行成功 64 w1.bring() 65 66 '''一个类不能调用其它类的方法''' 67 68 ''' 69 类的多继承顺序: 70 man、woman在继承时,依照继承顺序,只继承People的属性,不会继承Relation。 71 ''' 72 输出: 73 man: 74 Alex一出生就有150元 75 20 76 Alex is eating 77 woo~!@#¥%…&* 78 Alex is sleeping 79 new sleeping 80 woman: 81 Alex is making friends with Jack 82 Jack is born a baby
关于构造方法和普通方法的重构,以上代码的注释已经非常清楚了。
但是请注意!虽然Python支持多继承,但是在实际使用中,尽量不要使用多继承。本人在Python3.6.5中测试新式类和经典类的三代多继承,尝试摸清多继承的套路,后又与同伴交流了两代多继承,发现了在不同继承条件下的继承策略不同的bug,甚是混乱,没再深究。
五、新式类和经典类
(仅当娱乐!由于发现Python多继承的bug,没再深究,所以这里仅放出我的测试代码,仅供参考,回头可以再写一个两代继承的代码,你会发现这里总结出来的结论会被推翻)
1 ''' 2 经典类 和 新式类 在多继承情况下的继承策略 3 ''' 4 '''先看经典类:广度优先 ''' 5 class A(): 6 def __init__(self): 7 print("A") 8 def a(self): 9 print("A中的a方法") 10 class E(): 11 def __init__(self): 12 print("E") 13 def a(self): 14 print("E中的a方法") 15 16 class B(E): 17 pass 18 #def __init__(self): 19 # print("B") 20 class C(A): 21 pass 22 #def __init__(self): 23 # print("C") 24 class L(): 25 def __init__(self): 26 print("L") 27 def a(self): 28 print("L中的a属性") 29 30 class D(B,C): 31 pass 32 def __init__(self): 33 L.__init__(self) 34 print("D") 35 36 print("经典类") 37 d = D() # 运行结果显示,D在没有构造函数的情况下只继承了B的属性 38 d.a() 39 # 如果B没有构造函数,继承C的 40 # D继承了E。 41 ''' 42 分析: 43 A -> C(无构造) -> D 44 E -> B(无构造) -> D D(B,C) 45 按照以上的继承顺序,已知是广度优先,那么在父类都没有构造函数的情况下,该继承哪个祖父类呢? 46 运行结果为:D继承E 47 ''' 48 '''结论: 经典类中,多继承方式为广度优先。 49 子类在没有构造函数时首先继承父类,从左到右的优先级继承其中一个, 50 如果父类都没有构造函数,则从祖父类去继承,左边父类的祖父类优先级更高。''' 51 52 53 '''再看新式类:深度优先 ''' 54 55 class X(object): 56 def __init__(self): 57 print("X") 58 def a(self): 59 print("X中的方法a") 60 class Y(object): 61 def __init__(self): 62 print("Y") 63 64 class Z(X): 65 pass 66 #def __init__(self): 67 # print("Z") 68 class Q(Y): 69 def __init__(self): 70 print("Q") 71 def a(self): 72 print("Q中的方法a") 73 74 class P(object): 75 def __init__(self): 76 print("P") 77 def a(self): 78 print("P中的a方法") 79 class T(Z,Q): 80 pass 81 #def __init__(self): 82 # print("T") 83 84 print("新式类") 85 t = T() 86 t.a() 87 ''' 88 分析: 89 X -> Z(无构造) -> T 90 Y -> Q(有构造) -> T T(Z,Q) 91 按照以上的继承顺序,如果是广度优先,则T继承Q,如果是深度优先,则T继承X 92 运行结果为:T继承X 93 即:新式类为深度优先 94 ''' 输出: 经典类 L D E中的a方法 新式类 X X中的方法a
六、多态
多态就是:通过统一的接口来实现对所有对象相应方法的调用。
有两种方法实现多态:父类内部和类的外部
1.父类内部(内部接口实现):
class Animal(object): def __init__(self, name): # Constructor of the class self.name = name def talk(self): # Abstract method, defined by convention only raise NotImplementedError("Subclass must implement abstract method") '''父类内部定义函数(接口),实现多态''' @staticmethod # 可有可无 def animal_talk(obj): obj.talk() class Cat(Animal): def talk(self): print('%s: 喵喵喵!' % self.name) class Dog(Animal): def talk(self): print('%s: 汪!汪!汪!' % self.name) c1 = Cat("小猫") d1 = Dog("小狗") 输出: 内部接口实现多态: 小猫: 喵喵喵! 小狗: 汪!汪!汪!
2.类的外部(外部接口实现):
定义一个函数:
def func(obj): # 一个接口,多种形态 obj.talk() print("外部接口实现多态:") func(c1) # 所有的狗和猫都调用这一个接口 func(d1) 输出: 外部接口实现多态: 小猫: 喵喵喵! 小狗: 汪!汪!汪!