Python面向对象详解
⛄面向对象基本概念
面向过程和面向对象,是两种不同的编程方式。
👀面向过程
- 把完成某一个需求的所有步骤,从头到尾逐步实现;
- 根据开发需求,将某些功能独立的代码封装成一个又一个函数;
- 最后完成的代码,就是顺序地调用不同的函数。
特点:
- 注重步骤与过程,不注重职责分工;
- 如果需求复杂,代码会变得很复杂;
- 开发复杂项目,没有固定的套路,开发难度很大!
👀面向对象
相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法。
- 在完成某一个需求前,首先确定职责——要做的事情(方法);
- 根据职责确定不同的对象,在对象内部封装不同的方法(多个);
- 最后完成的代码,就是顺序地让不同的对象调用不同的方法。
特点
- 注重对象和职责,不同的对象承担不同的职责;
- 更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路;
- 需要在面向过程基础上,再学习一些面向对象的语法。
⛄类和对象
👀类和对象的概念
类和对象是面向对象编程的两个核心概念。
(1)类
类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用。
特征被称为属性;
行为被称为方法;
类就相当于制造飞机时的图纸,是一个模板,是负责创建对象的
(2)对象
对象是由类创建出来的一个具体存在,可以直接使用。
由哪一个类创建出来的对象,就拥有在哪一个类中定义的:
属性
方法
对象就相当于用图纸制造的飞机。在程序开发中,应该先有类,再有对象。
(3)类和对象的关系
- 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象。
- 类只有一个,而对象可以有很多个
- 不同的对象之间属性可能会各不相同
- 类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少。
👀类的设计
在程序开发中,要设计一个类,通常需要满足一下三个要素:
- 类名这类事物的名字,满足大驼峰命名法;
- 属性这类事物具有什么样的特征;
- 方法这类事物具有什么样的行为。
大驼峰命名法:HelloWorld,每一个单词的首字母大写;单词与单词之间没有下划线。
(1)类名的确定
名词提炼法分析整个业务流程,出现的名词,通常就是找到的类。
(2)属性和方法的确定
- 对象的特征描述,通常可以定义成属性;
- 对象具有的行为(动词),通常可以定义成方法。
提示:需求中没有涉及的属性或者方法在设计类时,不需要考虑。
⛄面向对象基础语法
👀dir()内置函数
在Python
中对象几乎是无所不在的,我们之前学习的变量、数据、函数都是对象。
在Python
中可以使用以下两个方法验证:
- 在标识符/数据后输入一个
.
,然后按下TAB
键,ipython
会提示该对象能够调用的方法列表。 - 使用内置函数
dir()
传入标识符/数据,可以查看对象内的所有属性及方法。
提示:
__方法名__
格式的方法是Python
提供的内置方法/属性。
__new__|方法|创建对象时,会被自动调用 |
👀定义简单的类
面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了!!
(1)定义只包含方法的类
# 类名的命名格式符合大驼峰命名法 |
方法的定义格式和之前学习过的函数几乎一样;区别在于第一个参数必须是self
。
(2)创建对象
当一个类定义完成之后,要使用这个类来创建对象:
对象变量 = 类名()
👀self
的使用
在Python
中,给对象设置属性,只需要在类的外部的代码中直接通过.
设置一个属性即可(这种方式虽然简单,但是不推荐使用)。
- 在类封装的方法内部,
self
就表示当前调用方法的对象自己; - 由哪一个对象调用的方法,方法内的
self
就是哪一个对象的引用; - 调用方法时,程序员不需要传递
self
参数; - 在方法内部,可以通过
self.
访问对象的属性,可以通过self.
调用其它对象方法;
在类的外部,通过
变量名.
访问对象的属性和方法;在类封装的方法中,通过
self.
访问对象的属性和方法。
👀__init__
方法
在日常开发中,不推荐在类的外部给对象增加属性;如果在运行时,没有找到属性,程序会报错;对象应该包含有哪些属性,应该封装在类的内部。
当使用
类名()
创建对象时,会自动执行以下操作:为对象在内存中分配空间——创建对象
为对象的属性设置初始值——初始化方法(
__init__
)
这个初始化方法就是
__init__
方法,__init__
是对象的内置方法
__init__
方法是专门用来定义一个类具有哪些属性的方法!在__init__
方法内部使用self.属性名 = 属性的初始值
就可以定义属性。
在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对__init__
方法进行改造:
- 把希望设置的属性值,定义成
__init__
方法的参数 - 在方法内部使用
se1f.属性 = 形参
接收外部传递的参数 - 在创建对象时,使用
类名(属性1,属性2..…)
调用
class People(): |
👀__del__
和__str__
方法
(1)__del__
方法
在
Python
中当使用
类名()
创建对象时,为对象分配完空间后,自动调用__init__
方法;当一个对象被从内存中销毁前,自动调用
__del__
方法。
应用场景
__init__
改造初始化方法,可以让创建对象更加灵活;__del__
如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__
方法。
生命周期
一个对象从调用
类名()
创建,生命周期开始;一个对象的
__del__
方法一旦被调用,生命周期结束;在对象的生命周期内,可以访问对象属性,或者让对象调用方法。
class People(): |
(2)__str__
方法
- 在
Python
中,使用print
输出对象变量,默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示); - 如果在开发中,希望使用
print
输出对象变量
时,能够打印自定义的内容,就可以利用__str__
这个内置方法了。
注意:__str__
方法必须返回一个字符串
class People(): |
⛄面向对象三个基本特征
面向对象三大特性:
封装,根据职责将属性和方法封装到一个抽象的类中。
- 定义类的准则
继承,实现代码的重用,相同的代码不需要重复的编写。
设计类的技巧
子类针对自己特有的需求,编写特定的代码
多态,不同的子类对象调用相同的父类方法,产生不同的执行结果。
多态可以增加代码的灵活度
以继承和重写父类方法为前提
是调用方法的技巧,不会影响到类的内部设计
⛄封装
👀封装概述
- 封装是面向对象编程的一大特点;
- 面向对象编程的第一步,将属性和方法封装到一个抽象的类中;
- 外界使用类创建对象,然后让对象调用方法;
- 对象方法的细节都被封装在类的内部;
class Gun: |
👀is与==区别
(1)身份运算符
身份运算符用于比较两个对象的内存地址是否一致,是否是对同一个对象的引用。在Python
中针对None
比较时,建议使用is
判断。
运算符 | 描述 | 实例 |
---|---|---|
is |
is 是判断两个标识符是不是引用同一对象 |
x is y ,类似于id(x) == id(y) |
is not |
is not 是判断两个标识符是不是引用不同对象 |
x is not y ,类似于id(a) != id(y) |
(2)is
与==
区别
is
用于判断两个变量引用对象是否为同一个;
==
用于判断引用变量的值是否相等。
a = [1,2,3] |
👀私有属性和私有方法
在实际开发中,对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问。
私有属性就是对象不希望公开的属性;
私有方法就是对象不希望公开的方法;
在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或者方法。
私有属性/方法,在外界不能够被直接访问;
在对象的方法内部,是可以访问对象的私有属性/方法的。
伪私有属性和私有方法(不建议使用),Python中,并没有真正意义的私有。
在给属性、方法命名时,实际是对名称做了一些特殊处理,使得外界无法访问到;
处理方式:在名称前面加上
_类名
,即_类名__名称
。
class People: |
👀单例设计模式
设计模式
设计模式是前人工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对某一特定问题的成熟的解决方案;
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
单例设计模式
目的–让类创建的对象,在系统中只有唯一的一个实例;
每一次执行
类名()
返回的对象,内存地址是相同的。
单例设计模式的应用场景
音乐播放对象
回收站对象
打印机对象
(1)__new__
方法
使用
类名()
创建对象时,Python
的解释器首先会调用__new__
方法为对象分配空间;__new__
是一个由object
基类提供的内置的静态方法,主要作用有两个:在内存中为对象分配空间
返回对象的引用
Python
的解释器获得对象的引用后,将引用作为第一个参数,传递给__init__
方法。
重写__new__
方法的代码非常固定:
- 重写
__new__
方法一定要return super().__new__(cls)
- 否则
Python
的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法 - 注意:
__new__
是一个静态方法,在调用时需要主动传递cls
参数
class MusicPlayer(object): |
(2)Python
中的单例
单例——让类创建的对象,在系统中只有唯一的一个实例
- 定义一个类属性,初始值是
None
,用于记录单例对象的引用; - 重写
__new__
方法; - 如果类属性
is None
,调用父类方法分配空间,并在类属性中记录结果; - 返回类属性中记录的对象引用。
# 单例设计模式实现 |
(3)存在的问题
问题所在
在每次使用
类名()
创建对象时,Python
的解释器都会自动调用两个方法。__new__
分配空间__init__
对象初始化
在上一小节对
__new__
方法改造之后,每次都会得到第一次被创建对象的引用。但是:初始化方法还会被再次调用。
需求:让初始化动作只被执行一次。
解决办法
- 定义一个类属性
init_flag
标记是否执行过初始化动作,初始值为False
; - 在
__init__
方法中,判断init_flag
,如果为False
就执行初始化动作; - 然后将
init_flag
设置为True
; - 这样,再次自动调用
__init__
方法时,初始化动作就不会被再次执行了。
class MPlayers(): |
⛄继承
👀继承概述
(1)继承的概念:子类拥有父类的所有方法和属性。
(2)继承的语法
class 类名(父类名): |
子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发;
子类中应该根据职责,封装子类特有的属性和方法。
子类 父类
派生类 基类
(3)继承的传递性
- C类是B类继承,B类又从A类继承;
- 那么C类就具有B类和A类的所有属性和方法;
- 子类拥有父类以及父类的父类中封装的所有属性和方法。
👀方法重写
(1)方法的重写
子类拥有父类的所有方法和属性。
子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
(2)应用场景
当父类的方法实现不能满足子类需求时,可以对方法进行重写(override
)
重写父类方法有两种情况:①覆盖父类的方法;②对父类方法进行扩展。
①覆盖父类的方法
- 如果在开发中,父类的方法实现和子类的方法实现,完全不同;
- 就可以使用覆盖的方式,在子类中重新编写父类的方法实现;
【具体的实现方式】,就相当于在子类中定义了一个和父类同名的方法并且实现。
重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法
②对父类方法进行扩展
如果在开发中,子类的方法实现中包含父类的方法实现,父类原本封装的方法实现是子类方法的一部分。
使用扩展的方式:
- 在子类中重写父类的方法;
- 在需要的位置使用
super().父类方法
来调用父类方法的执行; - 代码其他的位置针对子类的需求,编写子类特有的代码实现。
(3)super
- 在
Python
中super
是一个特殊的类; super()
就是使用super
类创建出来的对象;- 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现。
(4)调用父类方法的另外一种方式(知道)
在Python 2.x
时,如果需要调用父类的方法,还可以使用以下方式:
父类名.方法(self) |
- 这种方式,目前在Python 3.x还支持这种方式
- 这种方法不推荐使用,因为一旦父类发生变化,方法调用位置的类名同样需要修改
提示:
在开发时,父类名和
super()
两种方式不要混用;如果使用当前子类名调用方法,会形成递归调用,出现死循环。
class People: |
👀父类的私有属性和私有方法
父类的私有属性和私有方法
- 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法;
- 子类对象可以通过父类的公有方法间接访问私有属性或私有方法。
私有属性、方法是对象的隐私,不对外公开,外界以及子类都不能直接访问;
私有属性、方法通常用于做一些内部的事情。
class People: |
👀多继承
子类可以拥有多个父类,并且具有所有父类的属性和方法。
例如:孩子会继承父亲和母亲的特性
# 语法 |
多继承的使用注意事项
如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪一个父类中的方法。开发时,应该尽量避免这种容易产生混淆的情况!如果父类之间存在同名的属性或方法,应该尽量避免使用多继承。
python
中的MRO
—-方法搜索顺序(了解)
python
中针对类提供了一个内置属性__mro__
可以查看方法的搜索顺序。MRO
是method resolution order
,主要用于在多继承时判断方法、属性的调用路径。
print(*.__mro__) |
- 在搜索方法时,是按照
__mro__
的输出结果从左至右的顺序查找的; - 如果在当前类中找到方法,就直接执行,不再搜索;
- 如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索;
- 如果找到最后一个类,还没有找到方法,程序报错。
👀新式类与旧式(经典)类
object
是Python
为所有对象提供的基类,提供有一些内置的属性和方法,可以使用dir
函数查看。
- 新式类:以
object
为基类的类,推荐使用; - 经典类:不以
object
为基类的类,不推荐使用; - 在
Python 3.x
中定义类时,如果没有指定父类,会默认使用object
作为该类的基类–Python 3.x
中定义的类都是新式类; - 在
Python 2.x
中定义类时,如果没有指定父类,则不会以object
作为基类。
新式类和经典类在多继承时–会影响到方法的搜索顺序
为了保证编写的代码能够同时在Python 2.x
和Python 3.x
运行!今后在定义类时,如果没有父类,建议统一继承自object
。
# 语法 |
⛄多态
多态,不同的子类对象调用相同的父类方法,产生不同的执行结果。
多态可以增加代码的灵活度
以继承和重写父类方法为前提
是调用方法的技巧,不会影响到类的内部设计
class Dog: |
⛄类属性和类方法
👀类的结构
(1)实例
使用类名()
创建对象(分配空间、对象初始化)后,内存中就有了一个对象的实实在在的存在–实例。
- 创建出来的对象叫做类的实例;
- 创建对象的动作叫做实例化;
- 对象的属性叫做实例属性;
- 对象调用的方法叫做实例方法。
在程序执行时:
对象各自拥有自己的实例属性;
调用对象方法,可以通过
self.
访问自己的属性
调用自己的方法
结论
- 每一个对象都有自己独立的内存空间,保存各自不同的属性;
- 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部。
(2)类是一个特殊的对象
Python
中一切皆对象:
class AAA
:定义的类属于类对象obj1=AAA()
属于实例对象
在程序运行时,类同样会被加载到内存;
在
Python
中,类是一个特殊的对象–类对象;在程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例;
除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法:
类属性
类方法
通过
类名.
的方式可以访问类的属性或者调用类的方法。类名.类属性
类名.方法名()
👀类属性和实例属性
(1)概念和使用
- 类属性就是在类对象中定义的属性;
- 通常用来记录与这个类相关的特征;
- 类属性不会用于记录具体对象的特征。
(2)属性的获取机制
在Python
中属性的获取存在一个向上查找机制。因此,要访问类属性有两种方式:
类名.类属性
对象.类属性
(不推荐),遵循向上查找机制(先在对象内部查找,没有就会向上查找类属性)
注意:如果使用【对象.类属性=值赋值语句】,只会给对象添加一个属性,而不会影响到类属性的值。
class Tool(object): |
👀类方法和静态方法
(1)类方法
类属性就是针对类对象定义的属性
- 使用赋值语句在
class
关键字下方可以定义类属性; - 类属性用于记录与这个类相关的特征。
类方法就是针对类对象定义的方法
- 在类方法内部可以直接访问类属性或者调用其他的类方法。
# 语法 |
类方法需要用修饰器
@classmethod
来标识,告诉解释器这是一个类方法。类方法的第一个参数应该是
cls
由哪一个类调用的方法,方法内的
cls
就是哪一个类的引用;这个参数和实例方法的第一个参数是
self
类似;提示:使用其他名称也可以,不过习惯使用
cls
。
通过
类名.
调用类方法,调用方法时,不需要传递cls
参数。在方法内部
可以通过
c1s.
访问类的属性也可以通过
c1s.
调用其他的类方法
class Tool(object): |
在类方法内部,可以直接使用
cls
访问类属性或者调用类方法。
(2)静态方法
在开发时,如果需要在类中封装一个方法,这个方法:
既不需要访问实例属性或者调用实例方法
也不需要访问类属性或者调用类方法
这个时候,可以把这个方法封装成一个静态方法语法如下。
# 语法 |
静态方法需要用修饰器
@staticmethod
来标识,告诉解释器这是一个静态方法。通过
类名.
调用静态方法,不需要创建对象。
class Game: |
小结
- 实例方法–方法内部需要访问实例属性
- 实例方法–方法内部可以使用
类名.
访问类属性- 类方法–方法内部只需要访问类属性,或类方法
- 静态方法–方法内部,不需要访问实例属性和类属性
如果方法内部即需要访问实例属性,又需要访问类属性,应该定义成什么方法?(实例方法)
笔者不才,请多交流!!!
参考文献:黑马程序员《Python
入门教程完整版》