目录:
1、面向对象编程思想
2、面向对象基本概念
3、对象的属性添加与获取
4、魔术方法
5、面向对象的综合案例
6、Python中的封装
7、Python中的继承
本期内容信息量很大,小伙伴们可以先收藏,方便随时回看哦~
传统的面向过程的编程思想总结起来就八个字——自顶向下,逐步细化!
→ 将要实现的功能描述为一个从开始到结束按部就班的连续的“步骤”。
→ 依次逐步完成这些步骤,如果某一个步骤的难度较大,又可以将该步骤再次细化为若干个子步骤,以此类推,一直到结尾并得到我们想要的结果。
就是把要开发的系统分解为若干个步骤,每个步骤就是函数,当所有步骤全部完成以后,则这个系统就开发完毕了!
举个栗子:大家以来传智教育报名学习这件事情,可以分成哪些步骤?开始 → 学员提出报名,提供相关材料 → 学生缴纳学费,获得缴费凭证 → 教师凭借学生缴费凭证进行分配班级 → 班级增加学生信息 → 结束所谓的面向过程,就是将上面分析好了的步骤,依次执行就行了!
思考:上面的整个报名过程,都有哪些动词?
提出、提供、缴纳、获得、分配、增加
有动词就一定有实现这个动作的实体!
所谓的模拟现实世界,就是使计算机的编程语言在解决相关业务逻辑的时候,与真实的业务逻辑的发生保持一致,需要使任何一个动作的发生都存在一个支配给该动作的一个实体(主体),因为在现实世界中,任何一个功能的实现都可以看做是一个一个的实体在发挥其各自的“功能”(能力)并在内部进行协调有序的调用过程!
学生提出报名
学生提供相关资料
学生缴费
机构收费
教师分配教室
班级增加学生信息
于是,在整个过程中,一共有四个实体:学生、机构、教师、班级!在现实中的一个具体的实体,就是计算机编程中的一个对象!
属性就是实体固有的某些特征特性信息,在面向对象的术语中,属性就是以前的变量。
比如:
一个人的属性有:身高、体重、三围、姓名、年龄、学历、电话、籍贯、毕业院校等;
一个手机的属性有:价格、品牌、操作系统、颜色、尺寸等。
功能就是就是实体可以完成的动作,在面向对象的术语中,功能就是封装成了函数或方法。
学生提出报名
学生提供相关资料
教师登记学生信息
学生缴费
机构收费
教师分配教室
班级增加学生信息
以前写代码,首先想到的是需要实现什么功能——调用系统函数,或者自己自定义函数,然后按部就班的执行就行了!
以后写代码,首先想到的是应该由什么样的主体去实现什么样的功能,再把该主体的属性和功能统一的进行封装,最后才去实现各个实体的功能。
注意:面向对象并不是一种技术,而是一种思想,是一种解决问题的最基本的思维方式!
所以,面向对象的核心思想是:不仅仅是简单的将功能进行封装(封装成函数),更是对调用该功能的主体进行封装,实现某个主体拥有多个功能,在使用的过程中,先得到对应的主体,再使用主体去实现相关的功能!
一个面试题:面向过程和面向对象的区别?
① 都可以实现代码重用和模块化编程,面向对象的模块化更深,数据也更封闭和安全。
② 面向对象的思维方式更加贴近现实生活,更容易解决大型的复杂的业务逻辑。
③ 从前期开发的角度来看,面向对象比面向过程要更复杂,但是从维护和扩展的角度来看,面向对象要远比面向过程简单!
④ 面向过程的代码执行效率比面向对象高。
对象,object,现实业务逻辑的一个动作实体就对应着OOP编程中的一个对象!
OOA:面向对象分析
OOD:面向对象涉及
OOP:面向对象编程
所以:
① 对象使用属性(property)保存数据!
② 对象使用方法(method)管理数据!
对象如何产生?又是如何规定对象的属性和方法呢?
答:在Python中,采用类(class)来生产对象,用类来规定对象的属性和方法!也就是说,在Python中,要想得到对象,必须先有类!
为什么要引入类的概念?
答:类本来就是对现实世界的一种模拟,在现实生活中,任何一个实体都有一个类别,类就是具有相同或相似属性和动作的一组实体的集合!所以,在Python中,对象是指现实中的一个具体的实体,而既然现实中的实体都有一个类别,所以,OOP中的对象也都应该有一个类!
一个对象的所有应该具有特征特性信息,都是由其所属的类来决定的,但是每个对象又可以具有不同的特征特性信息,比如,我自己(人类)这个对象,名字叫老王,性别男,会写代码,会教书;另一个对象(人类)可能叫赵薇,性别女,会演戏,会唱歌!
在Python中,我们可以有两种类的定义方式:Python2(经典类)和 Python3(新式类)
这就是一个类,只不过里面什么都没有!其中,类名不区分大小写,遵守一般的标识符的命名规则(以字母、数字和下划线构成,并且不能以数字开头),一般为了和方法名相区分,类名的首字母一般大写!(大驼峰法)
基本语法:
class Person(): # 属性 # 方法(函数) def eat(self): print('我喜欢吃零食') def drink(self): print('我喜欢喝饮料')
类的实例化就是把抽象的事务具体为现实世界中的实体。
类的实例化就是通过类得到对象!
类只是对象的一种规范,类本身基本上什么都做不了,必须利用类得到对象,这个过程就叫作类的实例化!
基本语法:
在其他的编程语言中,类的实例化一般是通过new关键字实例化生成的,但是在Python中,我们不需要new关键字,只需要类名+()括号就代表类的实例。
# 1、定义一个类class Person(): # 定义相关方法 def eat(self): print('我喜欢吃零食') def drink(self): print('我喜欢喝饮料')p1 = Person()p1.eat()p1.drink()
self也是Python内置的关键字之一,其指向了类实例对象本身。
# 1、定义一个类class Person(): # 定义一个方法 def speak(self):print(self)print('Nice to meet you!')# 2、类的实例化(生成对象)p1 = Person()print(p1)p1.speak()p2 = Person()print(p2)p2.speak()
在Python中,任何一个对象都应该由两部分组成:属性 + 方法
属性即是特征,比如:
人的姓名、年龄、身高、体重…都是对象的属性。
车的品牌、型号、颜色、载重量...都是对象的属性。
对象属性既可以在类外面添加和获取,也能在类里面添加和获取。
# 1、定义一个Person类class Person(): pass# 2、实例化Person类,生成p1对象p1 = Person()# 3、为p1对象添加属性p1.name = '老王'p1.age = 18p1.address = '北京市顺义区京顺路99号'
在python中,获取对象属性的方法我们可以通过对象名.属性来获取
# 1、定义一个Person类class Person(): pass# 2、实例化Person类,生成p1对象p1 = Person()# 3、为p1对象添加属性p1.name = 'ooaac'p1.age = 20p1.address = '广东省广州市'# 4、获取p1对象的属性print(f'我的姓名:{p1.name}')print(f'我的年龄:{p1.age}')print(f'我的住址:{p1.address}')
# 定义一个Person类class Person(): def speak(self): print(f'我的名字:{self.name},我的年龄:{self.age},我的住址:{self.address}')# 2、实例化Person类,生成p1对象p1 = Person()# 3、添加属性p1.name = '孙悟空'p1.age = '500'p1.address = '花果山水帘洞'# 4、调用speak方法p1.speak()
遗留一个问题:
目前我们的确可以通过对象.属性的方式设置或获取对象的属性,但是这种设置属性的方式有点繁琐,每次定义一个对象,就必须手工设置属性,在我们面向对象中,对象的属性能不能在实例化对象时,直接进行设置呢?
答:可以,但是需要使用魔术方法
在Python中,__xxx__()的函数叫做魔法方法,指的是具有特殊功能的函数。
(2)__init__()方法(初始化方法或构造方法)
思考:人的姓名、年龄等信息都是与生俱来的属性,可不可以在生产过程中就赋予这些属性呢?
答:可以,使用__init__()方法,其作用:实例化对象时,连带其中的参数,会一并传给__init__()函数自动并执行它。__init__()函数的参数列表会在开头多出一项,它永远指代新建的那个实例对象,Python语法要求这个参数必须要有,名称为self。
# 1、定义一个类class Person(): # 初始化实例对象属性 def __init__(self, name1, age1): # name1跟age1是参数 # 赋予name属性、age属性给实例化对象本身 # self.实例化对象属性 = 参数 self.name = name1 self.age = age1# 2、实例化对象并传入初始化属性值p1 = Person('孙悟空', 500)# 3、调用p1对象自身属性name与ageprint(p1.name)print(p1.age)
① __init__()方法,在创建一个对象是默认被调用,不需要手动调用
② __init__(self)中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去。
当使用print输出对象的时候,默认打印对象的内存地址。如果类定义了__str__()方法,那么就会打印从在这个方法中return的数据。
没有使用__str__()方法的类:
# 1、定义一个类class Car(): # 首先定义一个__init__()方法,用于初始化实例对象属性 def __init__(self, brand, model, color): self.brand = brand self.model = model self.color = color # 定义一个__str__()内置魔术方法,用于输出小汽车的相关信息 def __str__(self): return f'汽车品牌:{self.brand},汽车型号:{self.model},汽车颜色:{self.color}'# 2、实例化对象c1c1 = Car('奔驰', 's600', '黑色')print(c1)
① __str__()这个魔术方法是在类的外部,使用print(对象)时,自动被调用的
② 在类的内部定义__str__()方法时,必须使用return返回一个字符串类型的数据
(4)__del__()方法(删除方法或析构方法)
当删除对象时,python解释器也会默认调用__del__()方法。
class Person(): # 构造函数__init__ def __init__(self, name, age): self.name = name self.age = age # 析构方法__del__ def __del__(self): print(f'{self}对象已经被删除')# 实例化对象p1 = Person('白骨精', 100)# 删除对象del p1
__del__()方法在使用过程中,比较简单,但是其在实际开发中,有何作用呢?
答:主要用于关闭文件操作、关闭数据库连接等等。
提到魔术方法:
① 这个方法在什么情况下被触发
② 这个方法有什么实际的作用
__init__():初始化方法或者称之为“构造函数”,在对象初始化时执行,其主要作用就是在对象初始化时,对对象进行初始化操作(如赋予属性)
__str__():对象字符串方法,当我们在类的外部,使用print方法输出对象时被触发,其主要功能就是对对象进行打印输出操作,要求方法必须使用return返回字符串格式的数据。
__del__():删除方法或者称之为“析构方法”,在对象被del删除时触发,其主要作用就是适用于关闭文件、关闭数据库连接等等。
定义学员信息类,包含姓名、成绩属性,定义成绩打印方法(90分及以上显示优秀,80分及以上显示良好,70分及以上显示中等,60分及以上显示合格,60分以下显示不及格)
# 1、定义学员信息类class Student(): # 2、定义学员对象属性 def __init__(self, name, score): self.name = name self.score = score # 3、定义一个方法,用于打印学员的成绩等级 def print_grade(self): if self.score >= 90: print(f'学员姓名:{self.name}, 学员成绩:{self.score}, 优秀') elif self.score >= 80: print(f'学员姓名:{self.name}, 学员成绩:{self.score}, 良好') elif self.score >= 70: print(f'学员姓名:{self.name}, 学员成绩:{self.score}, 中等') elif self.score >= 60: print(f'学员姓名:{self.name}, 学员成绩:{self.score}, 及格') else: print(f'学员姓名:{self.name}, 学员成绩:{self.score}, 不及格')# 4、实例化对象tom = Student('tom', 90)tom.print_grade()jerry = Student('jerry', 58)jerry.print_grade()
小明体重75.0公斤,小明每次跑步会减掉0.50公斤,小明每次吃东西体重增加1公斤分析:
① 对象:小明
② 属性:姓名、体重
③ 方法:跑步、吃东西
# 1、定义Person类class Person(): # 2、初始化对象属性,name和weight def __init__(self, name, weight): self.name = name self.weight = weight # 3、定义一个__str__方法打印对象的信息 def __str__(self): return f'姓名:{self.name},目前体重:{self.weight}KG' # 4、定义一个run方法代表跑步 def run(self): self.weight -= 0.5 # 5、定义一个eat方法代表吃饭 def eat(self): self.weight += 1# 6、实例化对象xiaoming = Person('小明', 75.0)print(xiaoming)# 7、吃饭xiaoming.eat()print(xiaoming)# 8、减肥跑步xiaoming.run()print(xiaoming)
① 封装性
将属性和方法书写到类的里面的操作即为封装,封装可以为属性和方法添加私有权限。
② 继承性
子类默认继承父类的所有属性和方法,与此同时子类也可以重写父类属性和方法。
③ 多态性
多态是同一类事物具有的多种形态。不同的对象调用同一个接口(方法),表现出不同的状态,称为多态。
在Python代码中,封装有两层含义:
① 把现实世界中的主体中的属性和方法书写到类的里面的操作即为封装
class Person(): # 封装属性 # 封装方法
在面向对象代码中,我们可以把属性和方法分为两大类:公有(属性、方法)、私有(属性、方法)
公有属性和公有方法:无论在类的内部还是在类的外部我们都可以对属性和方法进行操作。
但是有些情况下,我们不希望在类的外部对类内部的属性和方法进行操作。我们就可以把这个属性或方法封装成私有形式。
设置私有属性和私有方法的方式非常简单:在属性名和方法名 前面 加上两个下划线 __ 即可。
基本语法:
class Girl(): def __init__(self, name, age): self.name = name self.__age = 18xiaomei = Girl('小美')print(xiaomei.name)print(xiaomei.__age) # 报错,提示Girl对象没有__age属性
由以上代码运行可知,私有属性不能在类的外部被直接访问。但是出于种种原因,我们想在外部对私有属性进行访问,该如何操作呢?
答:我们可以定义一个统计的访问"接口"(函数),专门用于实现私有属性的访问。
在Python中,一般定义函数名get_xx用来获取私有属性,定义set_xx用来修改私有属性值。
class Girl(): def __init__(self, name): self.name = name self.__age = 18 # 公共方法:提供给外部的访问接口 def get_age(self): # 本人访问:允许直接访问 # 外人访问:加入限制条件 return self.__age # 公共方法:提供给外部的设置接口 def set_age(self, age): self.__age = agegirl = Girl('小美')girl.set_age(19)print(girl.get_age())
私有方法的定义方式与私有属性基本一致,在方法名的前面添加两个下划线__方法名()
① 以面向对象的编程思想进行项目开发
② 封装数据属性:明确的区分内外,控制外部对隐藏的属性的操作行为(过滤掉异常数据)
class People(): def __init__(self, name, age): self.__name = name self.__age = age def tell_info(self): print('Name:<%s> Age:<%s>' % (self.__name, self.__age)) # 对私有属性的访问接口 def set_info(self, name, age): ifnot isinstance(name, str): print('名字必须是字符串类型') return ifnot isinstance(age, int): print('年龄必须是数字类型') return self.__name = name self.__age = agep = People('jack', 38)p.tell_info()p.set_info('jennifer', 18)p.tell_info()p.set_info(123, 35)p.tell_info()
classATM: def __card(self): print('插卡') def __auth(self): print('用户认证') def __input(self): print('输入取款金额') def __print_bill(self): print('打印账单') def __take_money(self): print('取款') # 定义一个对外提供服务的公共方法 def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money()atm = ATM()atm.withdraw()
我们接下来来聊聊Python代码中的“继承”:类是用来描述现实世界中同一组事务的共有特性的抽象模型,但是类也有上下级和范围之分,比如:生物 => 动物 => 哺乳动物 => 灵长型动物 => 人类 => 黄种人
从哲学上说,就是共性与个性之间的关系,比如:白马和马!所以,我们在OOP代码中,也一样要体现出类与类之间的共性与个性关系,这里就需要通过类的继承来体现。简单来说,如果一个类A使用了另一个类B的成员(属性和方法),我们就可以说A类继承了B类,同时这也体现了OOP中代码重用的特性!
假设A类要继承B类中的所有属性和方法(私有属性和私有方法除外)
class B(object): passclass A(B): passa = A()a.B中的所有公共属性a.B中的所有公共方法
Person类与Teacher、Student类之间的继承关系
class Person(object): def eat(self): print('i can eat food!') def speak(self): print('i can speak!')class Teacher(Person): passclass Student(Person): passteacher = Teacher()teacher.eat()teacher.speak()student = Student()student.eat()student.speak()
继承:一个类从另一个已有的类获得其成员的相关特性,就叫作继承!
派生:从一个已有的类产生一个新的类,称为派生!
很显然,继承和派生其实就是从不同的方向来描述的相同的概念而已,本质上是一样的!
父类:也叫作基类,就是指已有被继承的类!
子类:也叫作派生类或扩展类
扩展:在子类中增加一些自己特有的特性,就叫作扩展,没有扩展,继承也就没有意义了!
单继承:一个类只能继承自一个其他的类,不能继承多个类,单继承也是大多数面向对象语言的特性!
多继承:一个类同时继承了多个父类, (C++、Python等语言都支持多继承)
单继承:一个类只能继承自一个其他的类,不能继承多个类。这个类会有具有父类的属性和方法。
基本语法:
# 1、定义一个共性类(父类)class Person(object): pass# 2、定义一个个性类(子类)class Teacher(Person): pass
比如汽车可以分为两种类型(汽油车、电动车)
# 1、定义一个共性类(车类)class Car(object): def run(self): print('i can run')# 2、定义汽油车class GasolineCar(Car): pass# 3、定义电动车class EletricCar(Car): passbwm = GasolineCar()bwm.run()
在Python继承中,如A类继承了B类,B类又继承了C类。则根据继承的传递性,则A类也会自动继承C类中所有属性和方法(公共)
class C(object): def func(self): print('我是c类中的相关方法func')class B(C): passclass A(B): passa = A()a.func()
什么是多继承?
Python语言是少数支持多继承的一门编程语言,所谓的多继承就是允许一个类同时继承自多个类的特性。
基本语法:
class B(object): passclass C(object): passclass A(B, C): passa = A()a.B中的所有属性和方法a.C中的所有属性和方法
汽油车、电动车 => 混合动力汽车(汽车 + 电动)
class GasolineCar(object): def run_with_gasoline(self): print('i can run with gasoline')class EletricCar(object): def run_with_eletric(self): print('i can run with eletric')class HybridCar(GasolineCar, EletricCar): passtesla = HybridCar()tesla.run_with_gasoline()tesla.run_with_eletric()
注意:虽然多继承允许我们同时继承自多个类,但是实际开发中,应尽量避免使用多继承,因为如果两个类中出现了相同的属性和方法就会产生命名冲突。
扩展特性:继承让子类继承父类的所有公共属性和方法,但是如果仅仅是为了继承公共属性和方法,继承就没有实际的意义了,应该是在继承以后,子类应该有一些自己的属性和方法。
什么是重写?
重写也叫作覆盖,就是当子类成员与父类成员名字相同的时候,从父类继承下来的成员会重新定义!
此时,通过子类实例化出来的对象访问相关成员的时候,真正其作用的是子类中定义的成员!
上面单继承例子中 Animal 的子类 Cat和Dog 继承了父类的属性和方法,但是我们狗类Dog 有自己的叫声'汪汪叫',猫类 Cat 有自己的叫声 '喵喵叫' ,这时我们需要对父类的 call() 方法进行重构。如下:
class Animal(object): def eat(self):print('i can eat') def call(self):print('i can call')class Dog(Animal): passclass Cat(Animal): passwangcai = Dog()wangcai.eat()wangcai.call()miaomiao = Cat()miaomiao.eat()miaomiao.call()
Dog、Cat子类重写父类Animal中的call方法:
class Animal(object): def eat(self): print('i can eat') def call(self): print('i can call')class Dog(Animal): # 重写父类的call方法 def call(self): print('i can wang wang wang')class Cat(Animal): # 重写父类的call方法 def call(self): print('i can miao miao miao')wangcai = Dog()wangcai.eat()wangcai.call()miaomiao = Cat()miaomiao.eat()miaomiao.call()
思考:重写父类中的call方法以后,此时父类中的call方法还在不在?
答:还在,只不过是在其子类中找不到了。类方法的调用顺序,当我们在子类中重构父类的方法后,Cat子类的实例先会在自己的类 Cat 中查找该方法,当找不到该方法时才会去父类 Animal 中查找对应的方法。
super():调用父类属性或方法,完整写法:super(当前类名, self).属性或方法(),在Python3以后版本中,调用父类的属性和方法我们只需要使用super().属性或super().方法名()就可以完成调用了
Car汽车类、GasolineCar汽油车、ElectricCar电动车
class Car(object): def __init__(self, brand, model, color): self.brand = brand self.model = model self.color = color def run(self): print('i can run')class GasolineCar(Car): def __init__(self, brand, model, color): super().__init__(brand, model, color) def run(self): print('i can run with gasoline')class ElectricCar(Car): def __init__(self, brand, model, color): super().__init__(brand, model, color) # 电池属性 self.battery = 70 def run(self): print(f'i can run with electric,remain:{self.battery}')bwm = GasolineCar('宝马', 'X5', '白色')bwm.run()tesla = ElectricCar('特斯拉', 'Model S', '红色')tesla.run()
在定义类时,其没有遵循类的命名规则
答:在Python中,类理论上是不区分大小写的。但是要遵循一定的命名规范:首字母必须是字母或下划线,其中可以包含字母、数字和下划线,而且要求其命名方式采用大驼峰。
电动汽车:EletricCar
父类:Father
子类:Son
父类一定要继承object么?Car(object)
答:在Python面向对象代码中,建议在编写父类时,让其自动继承object类。但是其实不写也可以,因为默认情况下,Python中的所有类都继承自object。
打印属性和方法时,都喜欢用print
class Person(): def __init__(self, name): self.name = name def speak(self): print('i can speak')# 创建对象,打印属性和方法p = Person('Tom')print(p.name)p.speak()