1.初识面向对象
一. 面向对象和面向过程(重点理解)
1. 面向过程:
一切以事物的流程为核心. 核心是"过程"二字, 过程是指解决问题的步骤, 即, 先干什么, 后干什么. 基于该思想编写程序就好比在编写一套流水线. 是一种机械式的编程思维
优点: 负责的问题流程化, 编写相对简单
缺点: 可扩展性差
2. 面向对象:
一切以对象为中心.
什么是对象? 不好解释. 先解释解释什么是车? 有轱辘, 有方向盘, 有发动机, 会跑的 是车. 好. 在解释一个. 什么是人. 有名字, 年龄, 爱好, 会唱歌跳舞思考的是人. 我们给这两个东西下了一个简单的定义. 总结: 具有相同属性和动作的结合体叫对象. 面向对象思维, 要自己建立对象. 自己建立场景. 你是就是面向对象世界中的上帝. 你想让车干嘛就干嘛. 你想让 人干嘛人就能干嘛.
优点: 可扩展性强
缺点: 编程的复杂度高于面向过程
对比: 说. 要把大象装冰箱, 总共分几步? 三步. 第一步. 打开冰箱门, 第二部. 装大象, 第三部. 关冰箱门. 这是一个典型的面向过程的思维方式. 来我们如果换成面向对象呢? 很简单. 想办法造一个会钻冰箱的大象就可以了了. 然后命令大象. 进冰箱. 大象就乖的进冰箱了. 这就是面向对象思维. 我们面向的不再是事物发展的流程, ⽽而是操纵某一个事物的个体. 具体的某 一个事物.
二. 面向对象如何编写
说了这么多. 面向对象的程序如何编写呢? 想想在我们的世界中. 我们如何造一辆车? 先由设计师来设计图纸. 设计师在图纸上勾勒出车应该是xx样的. 应该有什么. 以及这台车的功能等等. 然后交给工厂进行代工. 根据设计师设计的图纸去创造车. 程序也一样. 我们需要先设计⼀个图纸. 在图纸上把我要创建的对象进行描述. 然后交给工人去创建对象.
在这里, 我们画图纸的过程需要我们写类, 我们用类来描述一个对象. 类的语法很简单
class 类名: pass
哦这就创建出类了,假设我们创建一个车
class Car: #首字母要大写 pass
车有了. 我们的车至少得有个颜色, 车牌, 排量等信息啊. 不同的车, 有不同的颜色, 车牌, 排量等.
c.color = "红色" #对象.属性 c.pai = "京A88888" c.pailiang = "555L" print(c.color) #此车红色 print(c.pai) print(c.pailiang)
我们使用"对象.特征"可以给对象设置属性信息, 接下来, 再造一辆车, 并给车设置相关的属性信息
c2 = Car() c2.color = "white" c2.pai = "京A6666" c2.pailiang = "2.0T" print(c2.color) #此车红色 print(c2.pai) print(c2.pailiang)
我们发现, 这两量车是完全不同的两量车. 但是.拥有相同的属性和信息. 是不是有点儿冗余了? 怎么办呢? 想想. 我们把车的信息如果写在类里是不是会更好呢? 而且. 我的车在创建的时候这些信息应该已经是设计好了的 不应该是后天设计的. 好了, 我们知道需求了, 在创建对象的时候能给对象设置一些初始化的属性信息. 在python中我们可以是用__init__(self) 函数给对象进行初始化操作. 这个函数(方法)被称为构造函数(方法).
class Car: def __init__(self,color,pai,pailiang): self.color = color self.pai = pai self.pailiang = pailiang c1 = Car("红色","京A66666","555L") c2 = Car("蓝色","京B88888","1.6T") print(c1.color) print(c2.color)
通过打印, 我们发现. 这两个对象依然像原来那样可以完成属性的设置. 属性设置完了. 接下来. 车不光有这些信息啊. 车还会跑呢. 跑是一个动作. 所以我们要把跑写成一个函数. 但是在面向对象编程中. 我们不应该叫函数了, 改成叫方法. 只不过这个方法写起来比正常的方法多一个参数self. 仅此而已.
class Car: #__init__是一个特殊的方法,初始化方法(构造方法) #在创建对象的时候会自动调用__init__() #self就是创建出来的对象 def __init__(self,color,pai,pailiang): self.color = color self.pai = pai self.pailiang = pailiang #在类中写的函数->方法 #self当前类的对象 def pao(self): print("%s我的车能跑"%self.color) def jump(self): print("you jump,I jump") c1 = Car("红色","京A66666","555L") c2 = Car("蓝色","京B88888","1.6T") print(c1.color) print(c2.color) c1.pao() c2.pao() c1.jump()
总结: 类与对象的关系: 类是对事物的总结. 抽象的概念. 类用来描述对象. 对象是类的实例化的结果. 对象能执行哪些方法. 都由类来决定. 类中定义了什么. 对象就拥有什么
练习题:
1. 创建一个武松. 武松可以打老虎, 杀嫂子, 替天行道
2. 用面向对象的思维来模拟LOL里的盖伦上阵杀敌.
3. 编写和尚类. 自己自由发挥和尚有哪些属性和方法.
4. 用面向对象的思维来完成用户登录.
第一题 class Person: def __init__(self,waihao,name,address): self.waihao = waihao self.name = name self.address = address def dalaohu(self): print("%s%s%s打老虎"%(self.waihao,self.name,self.address)) def shasaozi(self): print("这个人喜欢嫂子") def titianxingdao(self): print("替天行道") ws = Person("行者","武松","景阳县") ws.dalaohu() ws.shasaozi() ws.titianxingdao() 第二题 class Hero: def __init__(self,waihao,name,story): self.waihao = waihao self.name = name self.story = story def q(self): print("拿着宝剑嗷嗷跑") def w(self): print("护盾") def e(self): print("电风扇") def r(self): print("大宝剑") gw = Hero("德玛西亚之力","盖伦","500年前盖伦拿一个破斧子捅破玉皇大帝的尿壶") gw.q() gw.w() gw.e() gw.r() 第三题 class HeShang: def __init__(self,fh,name,miao): self.fahao = fh self.name = name self.miao = miao def nianjing(self): print("和尚会念经") def huayuan(self): print("和尚会化缘") lzs = HeShang("花和尚","鲁智深","梁山水庙") lzs.nianjing() lzs.huayuan() 第四题 class User: def __init__(self,username,password): self.username = username self.password = password def login(self): uname = input("请输入你的用户名") upwd = input("请输入你的密码") if uname == self.username and upwd == self.password: print("登录成功") else: print("登录失败") u = User("alex","123") u.login()
三. 面向对象和面向过程大PK
那么面向对象和面向过程到底哪个好? 具体问题. 具体分析. 没有绝对的好和不好. 这一点 要格外注意. 来. 我们来完成之前的装大象的程序:
1. 面向过程:
# 非函数版 print("打开冰箱") print("装大象") print("关冰箱") #函数版 def open_door(): print("打开冰箱") def zhuang(): print("装大象") def close_door(): print("关冰箱") open_door() zhuang() close_door()
2.面向对象:
class Elephant: def open_door(self): print("打开冰箱") def zhuang(self): print("装大象") def close_door(self): print("关冰箱") dx = Elephant() dx.open_door() dx.zhuang()
发现了吧, 面向对象简直麻烦到爆. 别着急. 接着看下一个案例. 小猪佩奇攻击超人. 说. 有一个小猪, 名叫佩奇, 今年40岁了. 会使用嘴巴嘟必杀技. 他不光大战超人, 还会大战蝙蝠侠, 蜘蛛侠
1. 面向过程:
def fight_superman(name,jineng): print("%s使用%s攻击超人"%(name,jineng)) def fight_batman(name,jineng): print("%s使用%s攻击蝙蝠侠"%(name,jineng)) def fight_spiderman(name,jineng): print("%s使用%s攻击超人"%(name,jineng)) fight_superman("小猪佩奇","嘴巴嘟嘟") fight_batman("小猪佩奇","嘴巴嘟嘟") fight_spiderman("小猪佩奇","嘴巴嘟嘟")
2.面向对象
class Pig: def __init__(self,name,jineng): self.name = name self.jineng = jineng def fight_superman(self): print("%s使用%s攻击超人" % (self.name, self.jineng)) def fight_batman(self): print("%s使用%s攻击蝙蝠侠" % (self.name, self.jineng)) def fight_spiderman(self): print("%s使用%s攻击超人" % (self.name, self.jineng)) peiqi = Pig("小猪佩奇", "嘴巴嘟嘟") peiqi.fight_superman() peiqi.fight_batman() peiqi.fight_spiderman()
感觉到一点儿眉目了吧. 在这个案例中, 明显面向对象的思想更加清晰一些. 代码也更容 易编写一些. 所以⽤哪种编程思想不是绝对的. 得根据需求和需要来完成.
四. 面向对象的三大特征
面向对象三大特征: 封装, 继承, 多态. 只要是面向对象编程语言. 都有这三个特征.
1. 封装:
把很多数据封装到一个对象中. 把固定功能的代码封装到一个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析. 比如. 你写了一个很牛B 的函数. 那这个也可以被称为封装. 在面向对象思想中. 是把一些看似无关紧要的内容 组合到一起统一进行存储和使用. 这就是封装.
2. 继承:
子类可以自动拥有父类中除了私有属性外的其他所有内容. 说白了, 儿子可以 随便用爹的东西. 但是朋友们, 一定要认清楚一个事情. 必须先有爹, 后有儿子. 顺序不能乱, 在python中实现继承非常简单. 在声明类的时候, 在类名后面添加一个小括号, 就可以完成继承关系. 那么什么情况可以使用继承呢? 单纯的从代码层面上来看. 两 个类具有相同的功能或者特征的时候. 可以采用继承的形式. 提取一个父类, 这个父类中编写着两个类相同的部分. 然后两个类分别取继承这个类就可以了. 这样写的好处 是我们可以避免写很多重复的功能和代码. 如果从语义中去分析的话.会简单很多. 如果语境中出现了x是一种y. 这时, y是一种泛化的概念. x比y更更加具体. 那这时x就是y的子类. 比如. 猫是一种动物. 猫继承动物. 动物能动. 猫也能动. 这时猫在创建的时候就 有了动物的"动"这个属性. 再比如, 白骨精是一个妖怪. 妖怪天生就有一个比较不好 的功能叫"吃人", 白骨精一出生就知道如何"吃人". 此时 白骨精继承妖精. 话不多说. 上代码.
class Yao: def chi(self): print("我是妖怪天生吃人") class BaiGuJing(Yao): #白骨精继承了妖怪 pass bgj = BaiGuJing() bgj.chi()
在python中, 一个类可以同时继承多个父类. 说白了, 现在一个儿子可能会有多个爹了. 既然是有这么多个爹, 总得有远有近. 比如. 有一个这样的⽜B的人物, 叫锅不美. 就有很多个爹嘛
class QinDie: def chi(self): print("亲爹给你好吃的") def play(self): print("亲爹会陪你玩") class GanDie: def qian(self): print("干爹给你钱") def paly(self): print("亲的陪你玩") class GuNiang(QinDie,GanDie): pass bumei = GuNiang() bumei.chi()#亲爹给你好吃的 bumei.qian()#干爹给你钱 bumei.play()#亲爹会陪你玩
3. 多态:
同一个对象, 多种形态. 这个在python中其实是很不容易说明白的. 因为我们一 直在用. 只是没有具体的说. 比如. 我们创建一个变量a = 10 , 我们知道此时a是整数类型. 但是我们可以通过程序让a = "alex", 这时, a⼜又变成了字符串类型. 这是我们都知道的. 但是, 我要告诉你的是. 这个就是多态性. 同一个变量a可以是多种形态. 可能这样的程序和说法你还get不到具体什么是多态. 接下来. 我们来看一个程序. 北京动物园饲养员alex一天的工作. 从早上开始喂养猪, 中午喂哈士奇, 晚上还得喂阿拉斯加. 来我们用代码实现这样的代码:
class Animal: def chi(self): print("动物就知道吃") class Pig(Animal): def chi(self): print("猪在吃") class Haski(Animal): def chi(self): print("哈士奇在吃") class Alasika(Animal): def chi(self): print("阿拉斯加在吃") class SiYAngYuan: def yanng_animal(self,ani): ani.chi() zhu = Pig() erha = Haski() ala = Alasika() alex = SiYAngYuan() alex.yanng_animal(zhu) alex.yanng_animal(erha) alex.yanng_animal(ala)
多态的好处:程序具有超高的可扩展性,面向对象思想的核心与灵魂,python自带多态
5.什么是面向对象设计
编程范式
编辑即写程序或写代码,具体是指程序员特定的语法+数据结构+算法编写代码,目的是告诉计算机如何执行任务。
面向过程设计
核心是“过程“,过程指解决问题的步骤,面向过程程序设计就是一种机械式的思维方式。
优点:复杂问题流程化,简单化。
缺点:随着程序的增大,维护难度会越来越大。
面向对象设计
核心是“对象”,世间万物皆对象,不存在的开以制造出来,与面向过程机械式思维方式形成鲜明的对比,面向对象更加注重现实世界而非流程的模拟,是一种“上帝式”的思维方式。
优点:解决面向过程可扩展性的问题。
缺点:编辑的复杂度远高于面向过程
类与对象
类的概念:类是一系列对象相似的特征与技能的结合体。
现实生活中:肯定是先有对象,再有类。
在程序中:务必保证先定义类,再定义对象。
class Student: # 类 def learn(self): # 方法 print("learning...") def eat(self): print("eating...") obj = Student()#实例化 obj.learn()
obj.eat()
print(Student.__dict__)#查看类的变量与函数名
__init__方法
该方法是在对象产生之后才会执行,只为对象进行初始化操作,可以是任意代码,但一定不能有返回值。
class Chinese: county = 'China' def __init__(self,name,sex,age): self.name = name self.sex = sex self.age = age def eat(self): print('%s is eating'%self.name)#新增的self.name p1 = Chinese('egon',18,'male')#先调用类产生的空对象p1,然后调用Chinese.__init__(p1,'egon',18,'male') p2 = Chinese('alex',38,'fmale') p3 = Chinese('wpq',48,'fmale') # print(p1.county)#China # print(p2.county) # print(p3.county)
Chinese.eat(p1)#同与p1.eat() Chinese.eat(p2)
Chinese.eat(p3)
p1.eat()#egon is eating p2.eat() p3.eat()
实例化
类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
class Person: #定义一个人类 role = 'person' #人的角色属性都是人 def __init__(self,name): self.name = name # 每一个角色都有自己的昵称; def walk(self): #人都可以走路,也就是有一个走路方法 print("person is walking...") print(Person.role) #查看人的role属性 print(Person.walk) #引用人的走路方法,注意,这里不是在调用
self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给他起个别的名字,但是正常人都不会这么做。
一:我们定义的类的属性到底存到哪里了?有两种方式查看
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值
二:特殊的类属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
类属性的补充
对象是关于类而实际存在的一个例子,即实例
对象/实例只有一种作用:属性引用
对象之间的交互
from math import pi class Circle: ''' 定义了一个圆形类; 提供计算面积(area)和周长(perimeter)的方法 ''' def __init__(self,radius): self.radius = radius def area(self): return pi * self.radius * self.radius def perimeter(self): return 2 * pi *self.radius circle = Circle(10) #实例化一个圆 area1 = circle.area() #计算圆面积 per1 = circle.perimeter() #计算圆周长 print(area1,per1) #打印圆面积和周长 一个简单的例子帮你理解面向对象
6.类命名空间与对象、实例的命名空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:静态属性和动态属性
- 静态属性就是直接在类中定义的变量
- 动态属性就是定义在类中的方法
而类的动态属性是绑定到所有对象的
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
7.继承与抽象(先抽象再继承)
继承
是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
派生
class Animal: ''' 人和狗都是动物,所以创造一个Animal基类 ''' def __init__(self, name, aggressivity, life_value): self.name = name # 人和狗都有自己的昵称; self.aggressivity = aggressivity # 人和狗都有自己的攻击力; self.life_value = life_value # 人和狗都有自己的生命值; def eat(self): print('%s is eating'%self.name) class Dog(Animal): ''' 狗类,继承Animal类 ''' def bite(self, people): ''' 派生:狗有咬人的技能 :param people: ''' people.life_value -= self.aggressivity class Person(Animal): ''' 人类,继承Animal ''' def attack(self, dog): ''' 派生:人有攻击的技能 :param dog: ''' dog.life_value -= self.aggressivity egg = Person('egon',10,1000) ha2 = Dog('二愣子',50,1000) print(ha2.life_value) print(egg.attack(ha2)) print(ha2.life_value)
在python3中,子类执行父类的方法也可以直接用super方法.
8.面向对象常用术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__