Python 语言-第六章面向对象的编程
第六章面向对象的编程
6.1 基本概念
6.1.1 一些名词
-
对象
- 对象是我们要研究的任何事物,无论实体还是还是抽象均是对象。
-
属性
- 将对象的状态和特征用数据值的方式表示出来称为属性。
-
方法
- 如果用程序代码改变对象的状态的操作称为方法。
- 一般来说,在类中定义的函数称为方法,类外定义的函数称为函数
- 因为在类内部定义的函数一般与类对象或实例对象绑定,所以称为方法。
-
类
- 类为对象的模板,是一组相同属性相同操作的对象的抽象
- 定义类时将成员划分为公有成员、私有成员和保护成员,进而实现了类的访问机制
-
消息
- 由某个对象发出,用于请求另一个对象执行某项操作,或回复某些信息
-
封装
-
面向函数的三特征之一
-
将对象的数据(属性)和操作数据的过程(方法)结合起来所构成的单元,其内部信息对外界是隐藏的,外界不能直接访问,只能通过类对外部提供的接口对该对象进行各种操作,这样可以保证程序的安全性。
-
类是实施数据封装的工具
-
对象则是封装的具体实现,是封装的基本单位。
-
-
-
继承
-
面向函数的三特征之一
-
在一个类的基础上定义一个新类,原有的类称为超类、父类或基类,新生成的类称为派生类和子类。
-
子类不仅可以继承父类所有的方法和属性,还可以进行重写、覆盖和添加
-
-
多态
-
面向函数的三特征之一
-
一个名称相同的方法产生不同的动作行为。即不同对象收到相同的消息产生了不同的行为方式。
-
多态可通过覆存在盖和重载来实现。覆盖是是指在子类中重新定义父类的成员方法。重载则是允许多个同名函数,而这些函数的参数列表有所不同
-
6.1.2 面向过程与面向对象的比较
-
面向过程:通过算法分析列出解决问题的操作步骤,将程序划分为若干个功能模块,然后通过函数来是实现这些功能模块。
-
面向对象:将构成问题的事务分解成各个对象,根据对象的属性和操作抽象出类的定义并基于类创造对象,其目的不是为了解决问题,而是为了描述某个事物在整个解决问题的过程中所做的行为
主要区别 | 面向过程 | 面向对象 |
---|---|---|
对数据的操作 | 通过函数(或过程)来描述对数据的操作,又将函数与其操作的数据分隔开 | 将数据和对数据的操作封装在一起,作为一个对象整体来处理 |
中心 | 以功能为中心 | 以数据为中心 |
控制流程 | 由程序预定顺序来决定 | 运行时的各种事件的实际发生来触发 |
基本单元 | 函数 | 对象 |
- 总结:
- 二者是相辅相成,并不是对立的
- 解决复杂问题,通过面向对象方式便于我们从宏观上把握事物之间的复杂关系,方便分析整个系统;具体到微观操作,仍然使用面向过程的方式来处理
6.2 类
6.2.1 类的创建
-
创建类的语法格式:
class 类名: 类体
类名首字母通常采用大写的形式
类体中定义所有的变量成员[2]和函数成员[3]。
-
例如:
class Student: #定义Student类 name="小明" #定义变量成员 age="18" gender="男" def showInfo(): #定义函数成员 print("姓名:",Student.name) #引进变量成员 print("年龄:",Student.age) print("性别:",Student.gender)
-
-
在定义类的时候就创建了一个新的自定义对象,简称类对象,类名指向类对象。
-
访问变量成员,其语法格式如下:
类名.属性名
-
-
类由类属性和成员方法构成
- 成员方法包括内置方法、类方法、实例方法、静态方法等
6.2.2 类属性
-
类属性按能否可以类外部访问分为公有属性和私有属性。
- 定义属性时,以双下划线"__"开头就是私有属性,反之就是公有属性
-
访问公有属性语法格式:
类名.属性名
- 无论是在类的内部还是外部都通过这种方式
-
访问私有属性一般情况语法格式:
类名.属性名
-
私有属性,不允许也不提倡在类的外部访问。如果一定要访问,则必须要一个新的属性名来访问该属性,这个新属性名以一个下划线开头,后面跟类名和属性名。即语法格式:
类名·新属性名
-
例如:
Student._Student__name
-
-
定义类属性示例:
class MyClass: #定义MyClass类 attr1=111 #定义公有属性attr1 __attr2=222 #定义私有属性attr2 atte3=attr1+__attr2 #定义公有属性attr3
6.2.3 内置方法
- 内置方法是类方法和实例方法有且至少需要定义一个参数
- 内置方法是 Python 提供具有特殊作用的方法。在Python中,每定义一个类时,系统会自动添加一些默认的内置方法,通常可用特定的操作触发。不需要显式调用,下面介绍两种常用内置方法。
-
构造方法
-
形式:
def __init__(self): pass
第一个参数为
self
,这个名称只是一种习惯用法,可改但不推荐。self参数用于接受类的当前实例,每当创建类的实例的时候不必要在类名后面的圆括号中写入这个参数,Python会自动将当前实例传入构造方法。
-
构造方法是在创造新对象时,自动被调用的,可以对类的实例对象进行一些初始化操作,比如设置实例属性。
-
如果类中未定义构造方法,系统将执行默认的构造方法。
-
同时构造方法支持重载。
-
示例:
class Car: #定义Car类 def __init__(self,brand,color,lengyh): #定义构造方法 self.brand=brand #定义实例属性 self.color=color self.length=lengyh
-
-
析构方法
-
形式:
def __del__(self): pass
-
析构方法是在对象删除之前自动被调用的,当程序运行结束时,在程序中创建的对象会被删除,此时系统自动调用析构方法。
-
当离开某个作用域时,在该作用域创建的对象会被删除,所以也会被调用一次。
-
析构方法也支持重载。通常用该方法来执行一些释放资源的操作。
-
6.2.4 类方法
-
类方法是类对象本身拥有的成员方法,同常用于对类属性进行修改。
-
要定义类方法,要用装饰器
classmethod
的目标函数,并以类对象作为其第一个参数。语法格式:@classmethod def 函数名(cls,...) 函数体
-
访问类方法的语法格式:
类名.方法名([参数]) 对象名.方法名([参数])
- 参数是除类对象本身之外的参数,不然会报异常。
6.2.5 实例方法
-
实例方法是属于实例对象的成员方法。
-
定义实例方法时,至少要定义一个参数,而且必须以类的实例对象作为第一个参数,按习惯第一个参数名称为self。语法格式:
def 函数名(self,...) 函数体
-
访问示例方法的语法格式:
对象名.方法名(参数)
- 参数是除类对象本身之外的参数,不然也会报异常。
6.2.6 静态方法
-
静态方法不需要定义定义参数
-
静态方法既不属于类对象,也不属于实例对象,只是类中的一个普通的成员函数。
-
静态方法可用带任意参数,也可以不带参数。要定义静态方法,要用装饰器
staticmethod
的目标函数,并以类对象作为其第一个参数。语法格式:@staticmethod def 函数名([参数]) 函数体
-
访问静态方法,可通对类名来访问类属性,但不可以通过实例对象来访问实例属性。
-
在类的外部,可通过类对象或者实例对象来调用静态方法,其语法格式为:
类名.静态方法名([参数]) 对象名.静态方法名([参数])
6.2.7 私有方法
-
默认情况下,在类中定义的各种方法都属于公有方法,可在类的外部调用或者继承这些方法。
-
创建私有方法所不同的是,成员函数名必须以双下划线"__"开头。
-
私有方法只能在类的内部使用
-
调用方式与公有方法类似
6.2.8 类中的内存示意图
-
示例:假如创建了一个 Student 类,类中有一个 类属性,以及实例方法、类方法、静态方法各一个
- 内存示意图:
- 内存示意图:
6.3 对象
6.3.1 对象的创建
-
对象的创建又称为类的实例化
-
创建对象语法格式:
对象名=类名(参数列表)
-
例如:假如有一个 Student 类
stu = Student('张三',20)
- 内存分析图:
- 内存分析图:
-
-
访问对象属性和方法的语法格式:
对象名.属性名 对象名.方法名(参数)
-
例如:假如有一个 Student 类的实例对象 stu1 包含有 eat 这个方法
stu1.eat() Student.eat(stu1)
-
-
作用:有了对象就可以调用类中的内容(方法、数据等)
6.3.2 实例属性
-
实例属性是某个具体的对象所具有的属性。
-
在内外部创建属性的语法格式不同
-
内部:
self.属性名=值
-
外部:
对象名.属性名=值
-
6.3.3 类属性与实例属性的比较
不同点 | 类属性 | 实例属性 | 说明 |
---|---|---|---|
所属对象 | 属于类对象本身 | 属于类的某个实例 | 如果存在同名的类属性和实例属性,则相互独立,互不影响 |
定义方法 | 在类中所有方法之外定义 | 在构造方法或其他实例方法中定义 | |
引用方式 | 类名.属性名 | 对象名.属性名 |
联系 |
---|
类对象和实例对象都有一个__dict__ 属性,通过该属性以获取对象的所有成员属性和成员方法。 |
类对象和实例对象都可通过__class__ 属性来获取所属的类。类对象属于type类,实例对象属于创建该实例时所调用的类。 |
如果要读取的实例属性未存在,但是类中定义了一个同名的类属性,Python会将类属性的值作为实例属性的值,并且创建一个新的实例属性。此后修改实例属性的值时不会对同名的类属性产生影响。 |
6.3.4 动态绑定属性和方法
-
动态绑定属性和方法也就是在创建的对象中添加新属性、函数之类的
-
例如:假如已经有一个 完整的 class 类,然后在类外新定义一个 show 函数,再将新函数,绑定到对象中去
class Student: …… def show(): print('Hello') stu = Student('xm',20) stu.gender = '男' #类中未定义 gender 属性,这个是新添加的。动态绑定性别 stu.show = show #新定义的函数 show,将 show 绑定到 stu 对象上。动态绑定方法 stu.show() #访问新绑定的方法
-
从创建类到调用对象的流程示例:
class Student: #定义Student类 def __init__(self,name,age,gender): #定义构造方法 self.姓名=name #定义实例属性 self.年龄=age self.性别=gender def showInfo(self): #定义实例方法 for attr in self.__dict__.items(): #遍历对象的属性和方法组成的字典 print("{0}:{1}".format(attr[0],attr[1])) if __name__ == '__main__': student1=Student("小明","19","男") #对象实例化 student1.showInfo() #调用实例方法 print("-"*66) student2=Student("小红","18","女") student2.爱好="小明" #添加新的实例属性 student2.showInfo()
姓名:小明 年龄:19 性别:男 ------------------------------------------------------------------ 姓名:小红 年龄:18 性别:女 爱好:小明
6.4 面向对象的三大特征
- 封装:提高程序的安全性
- 继承:提高代码的复用性
- 多态:提高程序的可扩展性
- 面向对象三大特征与语言无关,是面向对象这一概念的特点
6.4.1 封装
-
将数据(属性)和行为(方法)包装在类对象中。在方法内部对属性进行操作,在类对象的外部调用方法
-
优点:无需关系方法内部的具体体现细节,隔离了复杂性
-
在 python 中没有专用的修饰符用于属性的私有,希望私有化属性可以使用双下划线"__"
-
例如:
class Student: def __init__(self,name) self.__name = name
-
-
使用方法和基本概念与类的私有属性类似
-
6.4.2 继承
6.4.2.1 继承的分类
-
单一继承:在python中,是在单个父类的基础上来定义一个新的子类的继承称为单一继承
-
语法格式:
class 子类名(父类名) 类体
子类名表示要创建的子类,该子类要继承的父类必须放在括号内,如果括号内就只有一个父类,则为单一继承
-
-
多重继承
-
python 支持多继承,即由多个父类基础上定义一个新子类
-
语法格式:
class 子类名(父类1,父类2,……) 类体
-
例如:C 继承于 A 和 B
class A: pass class B: pass class C(A,B): pass
-
-
-
如果一个类没有继承任何类。则默认继承 object
-
继承之后需要实现父类的方法,通过父类类名或者
super()
函数来调用父类的方法-
使用
super()
函数语法格式:super().父类方法名(形参)
-
-
继承的代码实现:
class Person(object): def __init__(self,name,age,gender): self.姓名=name self.年龄=age self.性别=gender class Sutudent(Person): #定义子类 def __init__(self,name,age,gender,sid): super().__init__(name,age,gender) #实现父类方法 self.编号 = sid #新增子类方法
-
继承父类后,子类拥有父类全部公有属性和成员方法。
-
除了继承还可扩展父类的功能
- 一种方法是在子类增加新的成员属性和成员方法
- 另一种方式是对父类已有的成员方法进行重定义,从而覆盖父类中同名的方法。也叫做方法重写
6.4.2.2 方法重写
-
子类重写后的方法仍然可以通过
super()
函数来调用父类中被重写的方法 -
例如:重写父类 showInfo 方法,并调用父类 showInfo 方法
class Person: def __init__(self,name,age,gender): self.姓名=name self.年龄=age self.性别=gender def showInfo(self): print("姓名:{0}\t年龄:{1}\t\t性别:{2}".format(self.姓名,self.年龄,self.性别)) class Sutudent(Person): def __init__(self,name,age,gender,sid): super().__init__(name,age,gender) self.编号=sid def showInfo(self): #重写方法 super().showIn() #调用方法 print("编号:{0}".format(self.编号))
6.4.2.3 object 类
-
object 类是所有类的父类,因此所有类都有 object 类的属性和方法
-
使用内置函数
dic()
可以查看指定对象所有的属性-
例如:
class Student: place = '中国' def info(self): pass print(dir(Student))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'info', 'place']
-
-
object 有一个
__str__()
方法,用于返回对一个对象的描述,与内置函数str()
相对应,帮助我们查看对象的信息- 常用于 print 输出
- 所以会经常对
__str__()
方法进行重写
6.4.3 多态
-
简单的说:即使不知道一个变量所引起的对象是什么类型,仍然可以通过该变量调用方法,在运行过程中根据变量所引用对象的类型,动态决定调用哪个对象的方法
- 简单理解比喻:我想要看书,就在书包里拿。我拿到的书不一定知道是什么书,根据我拿到的书来决定怎么看这本书
-
例如:定义了一个父类 Education、继承此父类的两个子类 Student 和 Teacher、一个普通类 Geezer、一个函数 adolesce。我通过 adolesce 函数来查找 拥有 read() 方法的类
class Education(object): def read(self): print('都要读书') class Sutudent(Education): def read(self): print('学生要读书') class Teacher(Education): def read(self): print('老师要读书') class Geezer(object): def read(self): print('老头儿要读书') def adolesce(a): a.read() if __name__ == '__main__': adolesce(Sutudent()) adolesce(Teacher()) adolesce(Geezer())
-
静态语言与动态语言关于多态的区别
- 静态语言实现多态的三个必要条件
- 继承
- 方法重写
- 父类引用指向子类对象
- 动态语言的多态形式如同”鸭子类型“
- 当看见一只鸟,走起来像鸭子、游泳像鸭子,那么这只鸟就可以被称为鸭子
- 在鸭子类型中,不需要关系对象是什么类型,是不是鸭子,只关心对象的行为
- 静态语言举例:Java
- 动态语言举例:Python
- 静态语言实现多态的三个必要条件
6.5 特殊属性和特殊方法
6.5.1 特殊属性
__dict__
属性- 对实例对象获取的是属性的键值对
- 对类对象获取的是方法、属性的键值对
__class__
属性- 对实例对象获取该对象属性的类
__bases__
属性- 对类对象获取所属的若干个父类组成的元组
__base__
属性- 对类对象获取最近的父类
__mro__
属性- 对类对象获取类的层次结构
6.5.2 特殊方法
-
__len__()
方法- 重写
__len__()
方法,让内置函数 len() 的参数是自定义类型
- 重写
-
__add__()
方法- 重写
__add__()
方法,可使自定义对象具备”+“的功能
- 重写
-
__subclasses__()
方法- 获取类的子类
-
__new__()
方法- 用于创建对象
-
__init__()
方法- 对创建的对象进行初始化,通常在这里面声明类的属性
-
此外,还可以使用内置函数
isinstance()
函数:判断一个对象是否属于一个已知的类型- 该函数类似于
type()
- 区别:
type()
不考虑继承关系,不认为子类是一种父类类型isinstance()
会考虑继承关系。
- 该函数类似于