python 面对对象
编程思想
- 面向过程:我们程序员是执行者的角色,实现某个功能,需要自己亲力亲为,设计好实现步骤,按照步骤一一进行实现. 适合比较简单需求的开发
- 面向对象:我们程序员是设计者的角色,实现某个功能,需要找合适的人或者物帮我们来完成,不需要关心具体的实现步骤和实现过程, 但是面向对象其实是对面向过程的封装,找的人或者物都是按照步骤去实现某个功能的. 适合比较复杂需求的开发
两大概念
面向对象编程的2个非常重要的概念:类和对象
对象是面向对象编程的核心,在使用对象的过程中,为了将具有共同特征和行为的一组对象抽象定义,提出了另外一个新的概念——类
1.类概念
类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用, 只要用于模拟现实世界中的一类事物不能具体到某一个
在 Python 中一切接对象,因此类是一个特殊的对象 —— 类对象
在程序运行时,类对象 在内存中只有一份,其中封装了类属性和所有方法
2. 对象概念
对象是由类创建出的一个具体事物,可以直接使用, 由那个类创建就拥有那个类中定义的属性和方法(不会多不会少)
由类对象创建出来的对象也叫做实例对象, 创建的过程叫做实例化
每一个实例对象 都有自己 独立的内存空间,仅保存着各自的实例属性
3.类和对象关系
类和对象的关系:先有类,然后通过类可以创建对象
类其实就是创建对象的一个模板
步骤
封装定义类
- 根据需求通过名词提炼法提炼出类并确定类名
- 抽象出类后, 将特征等描述信息作为属性并将其分类
1. 实例属性: 将属于对象的属性(具有个性化差异的描述信息)作为实例属性(例:个人的姓名,年龄) 定义到__init__
初始化方法中
2. 类属性: 将属于类的属性(具有共性的描述信息)作为类对象(例: 人的正常血糖范围)定义到类行首不在任何方法中
3. 私有属性: 将不想在外界访问的属性定义成私有属性(在属性名前增加两个下划线__
) - 将动作等行为, 作为方法(以函数的形式)定义到类中
- 将查看修改实例属性(包括公有和私有)的方法定义为实例方法
- 将查看修改类属性(包括公有和私有)的方法定义为类方法, 在定义方法的上面增加一个
@classmethod
装饰器 - 将既不修改查看实例属性又不修改查看类属性的方法定义为静态方法, 在定义方法的上面增加一个
@staticmethod
装饰器
创建对象
一个类(除单例类)可以创建多个对象
- 系统自动在内存中为对象分配空间
- 将参数传入
__init__
初始化方法中为实例属性赋值 - 使用或修改属性
- 调用方法
- print(对象名)时从而自动调用
__str__
方法 - 对象被从内存中销毁前,会自动调用
__del__
方法 - 使用
类名.__dict__
方法查看类中所有的类属性和所有方法, 返回为字典 - 使用
对象名.__dict__
方法查看对象中所有的实例属性, 返回为字典 - 使用
对象名.__class__
获取对象对应的类
格式
class 类名(父类1, 父类2, ...):
# 类属性定义在类中且不再__init__方法中
公有类属性名 = 值 # 定义公有类属性方法
__私有类属姓名 = 值 # 定义私有类属性方法
def __init__(self, 参数1, 参数2, ...):
# 实例属性定义在类的__init__方法中, 并且在创建对象时自定调用
self.公有实例属性 = 参数1 # 定义公有实例属性
self.__money私有实例属性 = 参数2 # 定义私有实例属性
def __str__(self):
# 在print(对象名)时自动调用, 若没有__str__方法, print(对象名)仅会返回对象地址
return "必须有返回值, 且必须为字符串"
def __del__(self):
# 在对象销毁时自动调用, 默认没有返回值
print("__del__方法, 对象销毁时自动调用")
# 定义实例方法, 用于查看及修改实例属性(包括公有实例属性和私有实例属性)
def 方法名(self, 参数1, 参数2, ...):
self.公有实例属性 = 参数1 # 修改__init__方法中定义的公有实例属性
self.__私有实例属性 = 参数2 # 修改__init__方法中定义的私有实例属性
print(self.公有实例属性, self.__私有实力属性) # 查看修改后的实例属性
# 定义类方法, 用于查看及修改类属性(包括公有类属性和私有类属性)
@classmethod
def 方法名(cls, 参数1, 参数2, ...):
cls.公有类属性 = 参数1 # 修改类中定义的公有类属性
cls.__私有类属性 = 参数2 # 修改类中定义的私有类属性
print(cls.公有类属性, cls.__私有类属性) # 查看修改后的类属性
# 定义静态方法, 仅提供提示信息, 不涉及任何属性的查看及修改
@staticmethod
def 方法名():
print("提示信息")
对象名1 = 类名(参数1, 参数2, ...) # 传参如__init__方法中为实例属性传参
对象名2 = 类名(参数1, 参数2, ...) # 一个类可以创建多个拥有不同实例属性的对象
对象名.公有实例属性名/对象名.公有实例属性名 = 值 # 可查可改(重新赋值)
对象名.公有类属性 # 仅可查不可改
类名.公有类属性名/类名.公有类属性名 = 值 # 可查可改(重新赋值)
对象名.实例方法名()/类方法名()/静态方法名() # 可以使用对象名对实例方法、类方法、静态方法进行调用
类名.类方法名()/静态方法名() # 类名不可以调用实例方法
print(对象名) # 自动调用__str__方法
# 对象的代码执行结束后, 会被销毁, 销毁前会自动调用__del__方法
hasattr(对象名, '方法或属性名') # 判断对象是否有 指定属性或方法
getattr(对象名, '方法或属性名'[, 默认值]) # 获取指定属性的值,属性不存在返回默认值,无默认值则报错;获取方法的内存地址,可后加()直接执行
setattr(对象名, '属性名') # 给对象的属性赋值,若属性不存在,先创建再赋值;无返回值
# 综合使用:
functiondemo = function_demo() # 实例化一个function_demo对象
res = hasattr(functiondemo, 'addr') # 先判断指定属性是否存在
if res:
addr = getattr(functiondemo, 'addr')
print(addr)
else:
addr = getattr(functiondemo, 'addr', setattr(functiondemo, 'addr', '北京首都'))
# addr = getattr(functiondemo, 'addr', '美国纽约')
print(addr)
注意:
- 定义类时类名符合标识符规则并且满足大驼峰命名法
- 定义类时若其父类不是
object
类(Python 里所有类的最顶级父类)或其父类没有继承object
类, 表示为旧式类 - 存在相同名称的类属性和实例属性的情况下,实例属性优先级更高
属性
一个对象的属性可以是另外一个类创建的对象(老师傅烤红薯需要一个烤炉属性, 烤炉是炉子类创建的对象)
实例属性
定义格式: self.实例属性名 = 值
定义位置: 定义在__init__
方法中
使用方法:
对象名.实例属性名
, 可用在类内外, 并且可查可改(重新赋值)- 在类内部建议使用
self.实例属性名
方式
注意:
- 不能通过
类名.实例属性
的方式使用实例属性, 因为每个对象都保存有自己的实例属性, 都有各自独立的地址, 并非保存在类中 - 在类外也可以使用
对象名.实例属性名 = 值
来增加实例属性, 但是这样增加的实例属性是该对象独有的, 其他对象不会拥有该实例属性, 但不建议如此使用对象的属性应该 封装在类的内部
类属性
定义格式: 类属性名 = 值
定义位置: 类内的首行并且不包含在任何方法中
使用方法:
类名.类属性名
,类内外皆可用, 并且可查可改- 在类方法中也可使用
cls.类属性名
, 并且可查可改 对象名.类属姓名
类内外皆可用, 但仅能查看不能修改; 若强行修改, 不会报错但实则不是修改了类属性而是创建了一个新的实例属性
方法
- 方法的定义格式和之前学习过的函数几乎一样(差别在于第一个参数)
- 在定义类方法时第一个参数最好使用
cls
, 表示当前类 - 在定义内置方法、实例属性和实例方法时第一个参数最好使用
self
表示那个对象调用方法self
就是那个对象
class Hero(): # 没有继承object类, 所以是旧式类 def __init__(self, name, age): self.name = name self.age = age xiao = Hero("小明", 18) 相当于 xiao = Hero(xiao, "小明", 18) ==> xiao.name = "小明" xiao.age = 18
- 在定义类方法时第一个参数最好使用
- 在方法内部,还可以使用其他类创建的对象属性, 调用已经封装好的其他方法
内置方法
以__
开头以__
结尾的方法皆为内置方法
__init__方法
- 自动调用条件: 在创建一个对象时默认被调用,不需要手动调用
- 作用:
__init__
方法用于实例属性的初始化或赋值 - 目的: 让创建对象更加灵活
- 如果在类中没有定义
__init__
方法(也没有继承父类__init__
方法), Python会自动创建,但是不执行任何操作, 即类一定存在__init__
方法但不一定有操作
__str__方法
- 自动调用条件:
print(对象名)
- 作用:
print(对象名)
,默认打印对象的内存地址。如果类定义了__str__
方法,那么就会打印从在这个方法中return
的数据 __str__
方法必须有返回值且为字符串
__del__方法
自动调用条件: 当对象被销毁(对象的引用计数为0或程序执行结束)时,会自动被调用
- 当有变量保存了一个对象的引用时,此对象的引用计数就会加1;当使用del() 删除变量指向的对象时,则会减少对象的引用计数
- 当对象的引用计数为0的时,则对象才会被真正删除(内存被回收)
- 程序执行结束时, 对象也会被销毁
__dict__方法
使用格式:
类名.__cls__
表示查看类中所有的类属性和所有方法, 返回为字典对象名.__dict__
表示查看对象中所有的实例属性, 返回为字典
注意: 返回的字典中包括私有属性及私有方法
__cls__方法
使用格式: 对象名.__cls__
等价于创建该对象的类(例: 大黄是通过狗类创建的, 那么大黄.__cls__
就是狗类)
__mro__方法
使用格式: 类名.__mro__
用来查看类继承顺序
实例方法
定义格式: def 方法名(self, 参数1, ...):
使用条件: 方法中会使用或修改实例属性
使用方法:
对象名.实例方法名()
, 在类内外皆可用, 不过建议在类外使用, 在类内部建议使用self.实例方法名()
方式- 若要通过
类名.实例方法()
的方式间接使用实例属性, 填的第一个参数必须是对象名用来对应self参数
, 不建议使用
注意:
self参数
表示那个对象调用方法self
就是那个对象- 实例方法是所有对象共享的,只占用一份内存空间。类会通过self来判断是哪个对象调用了实例方法
类方法
定义格式:
@classmethod
def 方法名(cls, 参数1, ...):
pass
使用条件: 方法中只会使用或修改类属性
使用方法:
类名.类方法名()
, 在类内外皆可用, 不过建议在类外使用; 在类内部建议使用clf.实例方法名()
方式- 也可以通过
对象名.实例方法()
的方式间接使用类属性, 填的 第一个参数必须是类名或对象名.__cls__
用来对应cls参数
, 不建议使用
注意:
cls参数
, 表示当前类- 需要用修饰器
@classmethod
来标识其为类方法 - 实例方法是所有对象共享的,只占用一份内存空间。类会通过self来判断是哪个对象调用了实例方法
静态方法
定义格式:
@staticmethod
def 方法名():
pass
使用条件: 方法中不使用实例属性和类属性
使用方法:
- 既可以使用
类名.静态方法名()
也可以使用对象名.静态方法名()
在类内外调用静态方发, 不过建议在类外使用; 在类内部最好使用self.静态方法名()
方式
注意:
- 静态方法一般会在实例方法或类方法中调用, 若在调用时传入实例属性或类属性, 那么静态方法也就可以使用实例属性或类属性了
私有属性和方法
- 定义方法: 在类内部定义时在属性名或方法名前面加上两个下划线
__
- 意义: 不能再类外部访问, 仅能在类内部使用, 都不会被子类继承,子类也无法访问
- 作用 : 往往用来处理类的内部事情,不通过对象处理,起到安全作用
- 仅能通过公有方法间接的修改私有属性
拓展:
Python 中,并没有真正意义的私有, 所谓的私有属性和方法不过是在 给属性或方法名做了特殊处理(在名称前面加上 _类名
==> _类名__属性名或方法名
) 使得外界无法访问到.
三大特性
封装
- 将属性和方法封装到类内部,然后通过实例化对象来处理
- 隐藏内部实现细节,只需要和对象及其属性和方法交互就可以
- 为类的属性和方法增加了访问权限控制
继承
定义格式: class 类名(父类名1, 父类名2, ...):
意义: 子类 拥有 父类以及父类的父类(继承传递性) 的所有公有方法和公有属性, 也可以通过 父类 的 公有方法 间接 访问到 私有属性 或 私有方法
作用: 子类中仅封装子类特有的属性和方法即可. 实现代码的重用,相同的代码不需要重复的编写
使用继承需意两点:
- 子类与父类拥有继承关系
- 子类要想调用父类属性或方法, 首先父类中必须拥有该方法, 其次子类中没有该方法
继承分类
分为: 单继承, 多继承, 多层继承三种
子类调用父类属性或方法的三种形式
-
子类对象.父类方法名()
(可能会产生死循环)或self.父类方法名()
最常用
若子类中存在与父类方法名
同名的方法(子类重写父类方法), 则会调用子类中的方法 -
父类对象.父类方法名(self)
不建议使用, 因为一旦父类名发生变化, 方法调用位置的类名同样需要修改 -
supper(子类名, self).父类方法()
表示调用指定子类的下个继承类中的类方法
super()
就是使用 super 类创建出一个指定子类的下个继承类的类对象,仅用于新式类
最常场景:- 子类重写父类方法(
supper().父类方法名()
表示调用当前子类下个继承类中的方法), - 也用于调用父类方法
- 子类重写父类方法(
子类重写父类的方法
重写时: 子类重写的方法名需要与父类的方法名一致
-
覆盖父类的方法
场景: 父类的方法和子类的方法实现效果完全不同
重写方式: 在子类中定义了一个 和父类同名的方法 -
对父类方法进行扩展
场景: 子类的方法中包含父类的方法, 父类原本封装的方法实现是子类方法的一部分
重写方式:- 在需要的位置使用
super().父类方法
来调用继承顺序中下一个类的类方法 - 其他的位置针对子类的需求,编写子类特有的代码实现
- 在需要的位置使用
继承顺序
如果子类和多个父类中有多有同名的属性和方法则需要使用类名.__mro__
决定了属性和方法的使用顺序
- 使用
super()
调用父类方法时,也遵循 mro 类属性的顺序, 并且默认只执行第一个父类的方法(同名方法只执行一次,目前super()不支持执行多个父类的同名方法) - 一般的继承顺序
当前子类, 当前子类继承的父类1, 当前子类继承的父类2, ..., 父类1继承的祖父类1, ..., 父类2继承的祖父类1, ...
提示
- 父类名 和 super() 两种方式不要混用
- 如果使用
子类对象.父类方法名()
调用方法,会形成递归调用,出现死循环 - 开发时,应该尽量避免方法名同名的的情况(除了子类方法重写)
- 如果 父类之间 存在 同名的属性或者方法,应该 尽量避免 使用多继承
多态
意义: 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
例如: 对象(鸭子对象,飞机对象,天鹅对象)调用同一个方法(fly)会出现不同的表现形式或者结果
使用场景: 不同类型的对象想要完成同样的一个操作
注意: 对象必须有指定的方法
# 实现飞的入口函数
def fly(obj): # obj:表示能够飞的对象,可能是飞机对象,鸭子对象,天鹅对象
# 利用多态,使用不同对象飞的功能
obj.fly() # 这里使用了多态
# 定义天鹅类
class Swan(object):
def fly(self):
print("天鹅优雅的飞起来了~")
# 定义鸭子类
class Duck(object):
def fly(self):
print("鸭子笨拙的飞起来~")
# 定义飞机类
class Plane(object):
def fly(self):
print("飞机轰隆隆的飞起来了~")
# 创建天鹅对象
swan = Swan()
fly(swan) # 实现天鹅对象实现飞的功能
# 创建鸭子类对象
duck = Duck()
fly(duck) # 实现鸭子对象实现飞的功能
# 创建飞机类的对象
plane = Plane()
fly(plane) # 实现飞机对象实现飞的功能
单例
作用: 类只能创建出一个也是唯一的对象, 即每一次执行类名()
返回的对象都是一个,内存地址是相同的
例子: 播放器同一时间是能播放一个视频或音乐
设置过程:
- 定义一个 类属性1,初始值是 None,用于记录单例对象的引用
- 再定义一个类属性2,初始值为 False, 用来标记是否执行过初始化动作
- 实现仅创建一次对象引用的功能
- 重写
__new__
方法, 判断类属性1是否为空, - 如果
类属性1 is None
,调用父类方法分配空间 - 将引用对象记录到类属性1中
- 若类属性1非空, 则获取类属性1中保存的的对象引用
- 重写
- 实现 初始化动作 只被 执行一次 的功能
- 在
__init__
方法中,判断类属性2 - 如果为
False
就执行初始化动作 - 然后将 类属性2 设置为
True
- 如果 类对象2位
True
则跳过__init__
方法
- 在
class MusicPlayer(object):
# 记录第一个被创建对象的引用
instance = None
# 记录是否执行过初始化动作
init_flag = False
def __new__(cls, *args, **kwargs):
# 1. 判断类属性是否是空对象
if cls.instance is None:
# 2. 调用父类的方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
# 3. 返回类属性保存的对象引用
return cls.instance
def __init__(self):
if not MusicPlayer.init_flag:
print("初始化音乐播放器")
MusicPlayer.init_flag = True
__new__方法
作用: 使用 类名() 创建对象时,Python 的解释器 首先 会 调用 __new__
方法为对象 分配空间
__new__
是一个 由 object 基类提供的 内置的静态方法,主要作用有两个:
- 在内存中为对象 分配空间
- 返回 对象的引用
Python 的解释器获得对象的 引用 后,将引用作为 第一个参数,传递给 init 方法
重写 new 方法
重写 __new__
方法时: 一定要调用object类的__new__
方法(super().__new__(cls)
)
否则 Python 的解释器 得不到 分配空间 的对象引用, 就不会无法调用对象的初始化方法
注意:__new__
是一个静态方法,在调用时需要 主动传递 cls
参数