Python面向对象
一、初识面向对面
1、面向对象的基本概念
- 在完成某一个需求前,首先确定 职责 —— 要做的事情(方法)
- 根据 职责 确定不同的 对象,在 对象 内部封装不同的 方法(多个)
- 最后完成的代码,就是顺序地让 不同的对象 调用 不同的方法
特点
- 注重 对象和职责,不同的对象承担不同的职责
- 更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路
- 需要在面向过程基础上,再学习一些面向对象的语法
2、类的概念
- 类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用
- 特征 被称为 属性
- 行为 被称为 方法
- 类 就相当于制造飞机时的图纸,是一个 模板,是 负责创建对象的
3、对象的概念
- 对象 是 由类创建出来的一个具体存在,可以直接使用
- 由哪一个类创建出来的对象,就拥有在哪一个类中定义的:
- 属性
- 方法
- 对象 就相当于用 图纸 制造 的飞机
在程序开发中,应该 先有类,再有对象
4、类和对象的关系
- 类是模板,对象 是根据 类 这个模板创建出来的,应该 先有类,再有对象
- 类只有一个,而对象可以有很多个
- 不同的对象 之间 属性 可能会各不相同
- 类 中定义了什么 属性和方法,对象 中就有什么属性和方法,不可能多,也不可能少
5、类的设计
在使用面相对象开发前,应该首先分析需求,确定一下,程序中需要包含哪些类!
在程序开发中,要设计一个类,通常需要满足一下三个要素:
- 类名 这类事物的名字,满足大驼峰命名法
- 属性 这类事物具有什么样的特征
- 方法 这类事物具有什么样的行为
5.1、大驼峰命名法
- 每一个单词的首字母大写
- 单词与单词之间没有下划线
5.2、 类名的确定
名词提炼法 分析 整个业务流程,出现的 名词,通常就是找到的类
5.3、属性和方法的确定
- 对 对象的特征描述,通常可以定义成 属性
- 对象具有的行为(动词),通常可以定义成 方法
提示:需求中没有涉及的属性或者方法在设计类时,不需要考虑
5.4、类的结构
class Human:
"""
此类主要是构建人类
"""
mind = '有思想' # 第一部分:静态属性 属性 静态变量 静态字段
dic = {}
l1 = []
def work(self): # 第二部分:方法 函数 动态属性
print('人类会工作')
class 是关键字与def用法相同,定义一个类。 Human是此类的类名,类名使用驼峰(CamelCase)命名风格,首字母大写,私有类可用一个下划线开头。 类的结构从大方向来说就分为两部分:
- 静态变量
- 动态方法
6、从类名的角度研究类
6.1、类名操作静态属性
查看类中的所有内容:类名.__dict__
方式。
class Human:
mind = '有思想'
dic = {}
l1 = []
def work(self):
print('会工作')
print(Human.__dict__)
print(Human.__dict__['mind'])
Human.__dict__['mind'] = '高智慧'
print(Human.__dict__)
# 通过这种方式只能查询,不能增删改
万能的点.
class Human:
mind = '有思想'
dic = {}
l1 = []
def work(self):
print('会工作')
print(Human.mind)
Human.mind = '高智慧'
print(Human.mind)
del Human.mind
Human.walk = '用脚走'
print(Human.walk)
# 通过万能的点 可以增删改查类中的单个属性
对以上两种做一个总结:如果想查询类中的所有内容,通过 第一种.__dict__
方法,如果只是操作单个属性则用万能的点的方式。
6.2、类名操作动态方法
class Human:
mind = '有思想'
dic = {}
l1 = []
def work(self):
print(self,'会工作')
Human.work('picaj')
Human.__dict__['work']('picaj')
# 可以直接通过human调用work方法,也可以通过dict为类内部方法传递实参
7、 从对象的角度研究类
7.1、对象
对象是从类中出来的,只要是类名加上(),这就是一个实例化过程,这个就会实例化一个对象。
class Human:
mind = '有思想'
dic = {}
l1 = []
def work(self):
print(self,'会工作')
obj = Human()
# 只要实例化对象,它会自动执行__init__方法
print(obj)
其实实例化一个对象总共发生了三件事:
- 在内存中开辟了一个对象空间。
- 自动执行类中的init方法,并将这个对象空间(内存地址)传给了init方法的第一个位置参数self。
- 在init 方法中通过self给对象空间添加属性。
class Human:
mind = '有思想'
work = '用两只腿走'
def __init__(self,name,sex,age,hobby):
self.name = name
self.sex = sex
self.age = age
self.hobby = hobby
obj = Human('picaj','男','18','打篮球')
print(obj.hobby)
7.2、 对象操作对象空间属性
对象查询对象中所有属性。 对象.__dict__
class Human:
mind = '有思想'
language = '实用语言'
def __init__(self,name,sex,age,hobby):
# self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
self.n = name
self.s = sex
self.a = age
self.h = hobby
obj = Human('picaj','男',18,'打篮球')
print(obj.__dict__)
对象操作对象中的单个属性。 万能的点.
class Human:
mind = '有思想'
work = '用两只腿走'
def __init__(self,name,sex,age,hobby):
# self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
self.name = name
self.sex = sex
self.age = age
self.hobby = hobby
obj = Human('picaj','男','18','打篮球')
print(obj.__dict__)
# 修改属性
obj.name = 'PICAJ'
obj.sex = '女'
obj.hobby = '踢足球'
# 增加属性
obj.job = "学生"
# 删除属性
del obj.age
# 查看属性
print(obj.__dict__)
7.3、对象查看类中的属性
class Human:
mind = '有思想'
work = '用两只腿走'
def __init__(self,name,sex,age,hobby):
# self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
self.name = name
self.sex = sex
self.age = age
self.hobby = hobby
obj = Human('picaj','男','18','打篮球')
print(obj.mind)
print(obj.work)
7.4、对象操作类中的方法
class Human:
mind = '有思想'
work = '用两只腿走'
def __init__(self,name,sex,age,hobby):
# self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
self.name = name
self.sex = sex
self.age = age
self.hobby = hobby
def work(self):
print(self)
print(self.name,"会工作")
def eat(self):
print(self)
print(self.name,"会吃饭")
obj = Human('picaj','男','18','打篮球')
obj.work()
obj.eat()
类中的方法一般都是通过对象执行的(除去类方法,静态方法外),并且对象执行这些方法都会自动将对象空间传给方法中的第一个参数self.
self其实就是类中方法(函数)的第一个位置参数,只不过解释器会自动将调用这个函数的对象传给self。所以咱们把类中的方法的第一个参数约定俗成设置成self, 代表这个就是对象。
一个类可以实例化多个对象
8、类的内置函数
序号 | 方法名 | 类型 | 作用 |
---|---|---|---|
01 | __new__ |
方法 | 创建对象时,会被 自动 调用 |
02 | __init__ |
方法 | 对象被初始化时,会被 自动 调用 |
03 | __del__ |
方法 | 对象被从内存中销毁前,会被 自动 调用 |
04 | __str__ |
方法 | 返回对象的描述信息,print 函数输出使用 |
05 | __dir__ |
方法 | 查看对象内的所有属性以及方法 |
二、类的空间问题及关系
1、类的空间问题
1.1、添加对象属性
class A:
def __init__(self,name):
self.name = name
def func(self,sex):
self.sex = sex
在类外部添加
class A:
def __init__(self,name):
self.name = name
def func(self,sex):
self.sex = sex
obj = A('picaj')
obj.age = 18
obj.func("man")
print(obj.__dict__)
类的内部添加
class A:
def __init__(self,name):
self.name = name
def func(self,sex):
self.sex = sex
obj = A('picaj')
obj.func('男')
print(obj.__dict__)
总结:对象的属性不仅可以在*init*里面添加,还可以在类的其他方法或者类的外面添加。
1.2、 添加类的属性
class A:
def __init__(self, name):
self.name = name
def func(self, sex):
self.sex = sex
def func1(self):
A.bbb = self
# 创建两个类实例
obj1 = A("Object 1")
obj2 = A("Object 2")
# 调用 func1() 方法
obj1.func1()
A.func1('123')
# 访问类属性 bbb
print(A.bbb) # 输出: <__main__.A object at 0x...>
# 访问实例属性
print(obj1.name) # 输出: Object 1
print(obj2.name) # 输出: Object 2
print(obj1.bbb)
总结:类的属性不仅可以在类内部添加,还可以在类的外部添加
1.3、对象如何找到类的属性
对象空间
- 产生这个对象空间,并有一个类对象指针
- 执行
__init__
方法,给对象封装属性
对象查找属性的顺序:先从对象空间找 ------> 类空间找 ------> 父类空间找 ------->.....
类名查找属性的顺序:先从本类空间找 -------> 父类空间找--------> ........
上面的顺序都是单向不可逆,类名不可能找到对象的属性。
2、类与类之间的关系
类与类中存在以下关系:
- 依赖关系
- 关联关系
- 组合关系
- 聚合关系
- 实现关系
- 继承关系(类的三大特性之一:继承。)
2.1、依赖关系
例:将大象装进冰箱,需要两个类, ⼀个是⼤象类, ⼀个是冰箱类
class Elphant:
def __init__(self,name):
self.name = name
def open(self):
'''
开门
'''
pass
def close(self):
'''
关门
'''
pass
class Refrigerator:
def open_door(self):
print('冰箱门打开了')
def close_door(self):
print('冰箱门关上了')
将大象类和冰箱类进行依赖
class Elphant:
def __init__(self,name):
self.name = name
def open(self,obj):
print(self.name + '开门')
obj.open_door()
def close(self):
print(self.name + '关门')
class Refrigerator:
def __init__(self,name):
self.name = name
def open_door(self):
print(self.name + '门被打开了')
def close_door(self):
print(self.name+'门被关上了')
elphant = Elphant('小飞象')
refrigerator = Refrigerator('格力冰箱')
elphant.open(refrigerator)
2.2、关联,聚合,组合关系
其实这三个在代码上写法是⼀样的. 但是, 从含义上是不⼀样的.
- 关联关系. 两种事物必须是互相关联的. 但是在某些特殊情况下是可以更改和更换的.
- 聚合关系. 属于关联关系中的⼀种特例. 侧重点是xxx和xxx聚合成xxx. 各⾃有各⾃的声明周期. 比如电脑. 电脑⾥有CPU, 硬盘, 内存等等. 电脑挂了. CPU还是好的. 还是完整的个体
- 组合关系. 属于关联关系中的⼀种特例. 写法上差不多. 组合关系比聚合还要紧密. 比如⼈的⼤脑, ⼼脏, 各个器官. 这些器官组合成⼀个⼈. 这时. ⼈如果挂了. 其他的东⻄也跟着挂了
关联关系
class Boy:
def __init__(self,name, girlfriend=None):
self.name = name
self.girlfriend=girlfriend
def dinner(self):
if self.girlfriend:
print('%s 和 %s 一起共进晚餐' % (self.name, self.girlfriend.name))
else:
print('连女朋友都没有,还有心情吃饭')
class Girl:
def __init__(self, name):
self.name = name
boy = Boy('张三')
boy.dinner()
girl = Girl('如花')
boy2 = Boy('李四',girl)
boy2.dinner()
注意. 此时Boy和Girl两个类之间就是关联关系. 两个类的对象紧密联系着. 其中⼀个没有了. 另⼀个就孤单的不得了. 关联关系, 其实就是 我需要你. 你也属于我
学校和老师之间的关系
class School:
def __init__(self,name,address):
self.name = name
self.address = address
class Teacher:
def __init__(self,name,school):
self.name = name
self.school = school
s1 = School('北京校区','北京')
s2 = School('上海校区','上海')
s3 = School('深圳校区','深圳')
t1 = Teacher('T1',s1)
t2 = Teacher('T2',s2)
t3 = Teacher('T3',s3)
print(t1.school.name)
print(t2.school.name)
print(t3.school.name)
但是学校也是依赖于老师的,所以老师学校应该互相依赖。
class School:
def __init__(self,name,address):
self.name = name
self.address = address
self.teacher_list = []
def append_teacher(self,teacher):
self.teacher_list.append(teacher)
class Teacher:
def __init__(self,name,school):
self.name = name
self.school = school
s1 = School('北京校区','北京')
s2 = School('上海校区','上海')
s3 = School('深圳校区','深圳')
t1 = Teacher('T1',s1)
t2 = Teacher('T2',s2)
t3 = Teacher('T3',s3)
s1.append_teacher(t1.name)
s1.append_teacher(t2.name)
s1.append_teacher(t3.name)
print(s1.teacher_list)
组合:将一个类的对象封装到另一个类的对象的属性中,就叫组合。
例:设计一个游戏,让游戏里面的人物互殴
加上一个武器类,让人使用武器攻击
class Gamerole:
def __init__(self,name,ad,hp,wea=None):
self.name = name
self.ad = ad
self.hp = hp
self.wea = wea
def attack(self,p1):
p1.hp -= self.ad
print('%s攻击%s,%s掉了%s血,还剩%s'%(self.name,p1.name,p1.name,self.ad,p1.hp))
def equip_weapon(self,wea):
self.wea = wea
wea.ad += self.ad
wea.owner_name = self.name
class Weapon:
def __init__(self,name,ad,owner_name = None):
self.name = name
self.owner_name = owner_name
self.ad = ad
def weapon_attack(self,p2):
p2.hp = p2.hp - self.ad
print('%s利用%s攻击了%s,%s还剩%s血'%(self.owner_name,self.name,p2.name,p2.name,p2.hp))
man = Gamerole('人',10,100)
dog = Gamerole('狗',50,100)
stick = Weapon('木棍',40)
man.equip_weapon(stick)
man.wea.weapon_attack(dog)
# 人利用木棍攻击了狗,狗还剩50血
2.3、案例,循环回合制游戏
import time
import random
class Gamerole:
def __init__(self, name, ad, hp, wea=None):
self.name = name
self.ad = ad
self.hp = hp
self.wea = wea
def attack(self, p1):
p1.hp -= self.ad
print('%s攻击%s,%s掉了%s血,还剩%s' % (self.name, p1.name, p1.name, self.ad, p1.hp))
def equip_weapon(self, wea):
self.wea = wea
wea.ad += self.ad
wea.owner_name = self.name
class Weapon:
def __init__(self, name, ad, owner_name=None):
self.name = name
self.owner_name = owner_name
self.ad = ad
def weapon_attack(self, p2):
p2.hp = p2.hp - self.ad
print('%s利用%s攻击了%s,%s还剩%s血' % (self.owner_name, self.name, p2.name, p2.name, p2.hp))
def perform_attack(attacker, target):
if attacker.wea is not None:
attacker.wea.weapon_attack(target)
else:
attacker.attack(target)
shentaishici = Gamerole("神太史慈", 100, 400)
shensunce = Gamerole("神孙策", 100, 500)
jiexusheng = Gamerole("界徐盛", 100, 400)
shenjiangwei = Gamerole("神姜维", 100, 400)
wuguanyu = Gamerole("武关羽", 100, 500)
shenzhangfei = Gamerole("神张飞", 100, 400)
gudingdao = Weapon("古锭刀", 50)
zhangbashemao = Weapon("丈八蛇矛", 50)
qilinggong = Weapon("麒麟弓", 50)
dongwu = [shentaishici, shensunce, jiexusheng]
shuhan = [shenjiangwei, wuguanyu, shenzhangfei]
wuqi = [gudingdao, zhangbashemao, qilinggong]
if __name__ == '__main__':
print("游戏开始加载")
# 打印一个菜单
for i in range(0, 101, 2):
time.sleep(0.1)
char_num = i // 2
per_str = '\r%s%% : %s\n' % (i, '*' * char_num) if i == 100 else '\r%s%% : %s' % (i, '*' * char_num)
print(per_str, end='', flush=True)
info = input("游戏加载完毕,输入任意字符开始!")
# 输出东吴蜀汉阵营里的任务角色
print("东吴阵营".center(20, '*'))
for i in dongwu:
print(i.name.center(20))
print("蜀汉阵营".center(20, '*'))
for i in shuhan:
print(i.name.center(20))
# 从两个阵营中随机选择三位武将获得武器
for j in range(0, 3):
index4 = random.randint(0, 1)
if index4 == 1:
dongwu[j].equip_weapon(wuqi[j])
else:
shuhan[j].equip_weapon(wuqi[j])
while True:
# 判断游戏结束的条件是某一方全部阵亡
if len(dongwu) == 0:
print("蜀汉阵营胜利!!!")
break
if len(shuhan) == 0:
print("东吴阵营胜利!")
break
# 游戏逻辑,每次随机选择一名角色出击
index1 = random.randint(0, len(dongwu) - 1)
index2 = random.randint(0, len(shuhan) - 1)
index3 = random.randint(1, 2)
# 开始攻击
role1 = dongwu[index1]
role2 = shuhan[index2]
# 通过随机数来判断哪个阵营先攻击
if index3 == 1:
print("\n")
time.sleep(1)
perform_attack(role1, role2)
time.sleep(0.5)
perform_attack(role2, role1)
elif index3 == 2:
print("\n")
time.sleep(1)
perform_attack(role2, role1)
time.sleep(0.5)
perform_attack(role1, role2)
# 判断是否有武将阵亡
if role1.hp <= 0:
print("%s阵亡!" % role1.name)
dongwu.remove(role1)
if role2.hp <= 0:
print("%s阵亡!" % role2.name)
shuhan.remove(role2)
三、继承
1、 面向对象的继承
面向对象三大特性
- 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中
- 继承 实现代码的重用,相同的代码不需要重复的编写
- 多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
不用继承创建对象
class Person:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
class Cat:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
class Dog:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
使用继承的方式
class Aniaml(object):
def __init__(self,name, age):
self.name = name
self.age = age
def eat(self):
print(f"{self.name}吃东西..")
class Dog(Aniaml):
pass
xiaotianquan = Dog("哮天犬",5)
xiaotianquan.eat()
继承的概念:子类 拥有 父类 的所有 方法 和 属性
继承的有点也是显而易见的:
- 增加了类的耦合性(耦合性不宜多,宜精)。
- 减少了重复代码。
- 使得代码更加规范化,合理化。
2、继承的分类
上面的那个例子,涉及到的专业术语:
Dog
类是Animal
类的子类,Animal
类是Dog
类的父类,Dog
类从Animal
类继承Dog
类是Animal
类的派生类,Animal
类是Dog
类的基类,Dog
类从Animal
类派生
继承:可以分单继承,多继承。
这里需要补充一下python中类的种类(继承需要):
在python2x版本中存在两种类.:
- ⼀个叫经典类. 在python2.2之前. ⼀直使⽤的是经典类. 在经典类中,如果一个类没有显式地指定基类,它会默认继承自一个根类(object)。但是这个根类上没有写任何东西。
- ⼀个叫新式类. 在python2.2之后出现了新式类. 新式类的特点是基类的根是object类。 python3x版本中只有一种类: python3中使⽤的都是新式类. 如果基类谁都不继承. 那这个类会默认继承 object
3、单继承
3.1、类名,对象执行父类方法
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('吃',self)
class Person(Aniaml):
pass
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
print(Person.type_name)
Person.eat('东西')
print(Person.type_name)
p1 = Person('picaj','男',18)
print(p1.__dict__)
print(p1.type_name)
p1.type_name = '666'
print(p1)
p1.eat()
3.2、执行顺序
class Aniaml(object):
def __init__(self,name, age):
self.name = name
self.age = age
def eat(self):
print(f"{self.name}吃东西..")
class person(Aniaml):
def eat(self):
print('%s 用筷子吃饭' % self.name)
class Dog(Aniaml):
pass
class Cat(Aniaml):
pass
person1 = person('张三',18)
person1.eat()
3.3、同时执行类以及父类方法
方法一:如果想执行父类的eat()方法,这个方法并且子类中夜用,那么就在子类的方法中写上:父类.eat(对象,其他参数)
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('吃东西')
class Person(Aniaml):
def __init__(self,name,sex,age,mind):
Aniaml.__init__(self,name,sex,age)
self.mind = mind
def eat(self):
Aniaml.eat(self)
print('%s 吃饭'%self.name)
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
p1 = Person('picaj','男',18,'想吃东西')
p1.eat()
方法二:利用super,super().func(参数)
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('吃东西')
class Person(Aniaml):
def __init__(self,name,sex,age,mind):
# super(Person,self).__init__(name,sex,age)
super().__init__(name,sex,age)
self.mind = mind
def eat(self):
super().eat()
print('%s 吃饭'%self.name)
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
p1 = Person('picaj','男',18,'想吃东西')
p1.eat()
3.4、方法的重写
- 如果在开发中,父类的方法实现 和 子类的方法实现,完全不同
- 就可以使用 覆盖 的方式,在子类中 重新编写 父类的方法实现
具体的实现方式,就相当于在 子类中 定义了一个 和父类同名的方法并且实现
重写之后,在运行时,只会调用 子类中重写的方法,而不再会调用 父类封装的方法
3.4.1、对父类方法进行扩展
- 如果在开发中,子类的方法实现中包含父类的方法实现
- 父类原本封装的方法实现 是 子类方法的一部分
- 就可以使用扩展的方式
- 在子类中 重写 父类的方法
- 在需要的位置使用
super().父类方法
来调用父类方法的执行 - 代码其他的位置针对子类的需求,编写 子类特有的代码实现
关于 super
- 在
Python
中super
是一个 特殊的类 super()
就是使用super
类创建出来的对象- 最常 使用的场景就是在 重写父类方法时,调用 在父类中封装的方法实现
调用父类方法的另外一种方式(知道)
在
Python 2.x
时,如果需要调用父类的方法,还可以使用以下方式:
父类名.方法(self)
- 这种方式,目前在
Python 3.x
还支持这种方式 - 这种方法 不推荐使用,因为一旦 父类发生变化,方法调用位置的 类名 同样需要修改
提示
- 在开发时,
父类名
和super()
两种方式不要混用 - 如果使用 当前子类名 调用方法,会形成递归调用,出现死循环
3.4.2、父类的 私有属性 和 私有方法
- 子类对象 不能 在自己的方法内部,直接 访问 父类的 私有属性 或 私有方法
- 子类对象 可以通过 父类 的 公有方法 间接 访问到 私有属性 或 私有方法
- 私有属性、方法 是对象的隐私,不对外公开,外界 以及 子类 都不能直接访问
- 私有属性、方法 通常用于做一些内部的事情
B
的对象不能直接访问__num2
属性B
的对象不能在demo
方法内访问__num2
属性B
的对象可以在demo
方法内,调用父类的test
方法- 父类的
test
方法内部,能够访问__num2
属性和__test
方法
class Animal:
def __init__(self,name):
self.__name = name
def __eat(self):
print(self.__name + "Eating...")
def eat2(self):
self.__eat()
class Dog(Animal):
pass
a = Dog('哮天犬')
print(a.name)
a.__eat()
a.eat2()
# AttributeError: 'Dog' object has no attribute 'name'
4、多继承
概念
- 子类 可以拥有 多个父类,并且具有 所有父类 的 属性 和 方法
- 例如:孩子 会继承自己 父亲 和 母亲 的 特性
语法
class 子类名(父类名1, 父类名2...)
pass
问题的提出
- 如果 不同的父类 中存在 同名的方法,子类对象 在调用方法时,会调用 哪一个父类中的方法呢?
提示:开发时,应该尽量避免这种容易产生混淆的情况! —— 如果 父类之间 存在 同名的属性或者方法,应该 尽量避免 使用多继承
class shengxian: # 神仙
def fei(self):
print("神仙会飞")
def eat(self):
print("吃人参果")
class monkey: # 猴
def eat(self):
print("吃桃子")
class songwukong(shengxian,monkey): #孙悟空既是神仙也是猴
def __init__(self):
self.name = "孙悟空"
def eat(self):
print("我是齐天大圣,我不用吃东西")
swk = songwukong()
swk.eat()
4.1、经典类的多继承
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
class E:
pass
class F(D, E):
pass
class G(F, D):
pass
class H:
pass
class Foo(H, G):
pass
在经典类中采⽤的是深度优先,遍历⽅案. 什么是深度优先. 就是⼀条路走到头. 然后再回来. 继续找下⼀个.
类的MRO(method resolution order): Foo-> H -> G -> F -> E -> D -> B -> A -> C.
4.2、新式类的多继承
4.2.1、mro序列
mro是一个有序列表,在类被创建时就计算出来。
通用计算公式为
mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2] )(其中Child继承自Base1, Base2)
如果继承至一个基类:class B(A) 这时B的mro序列为
mro( B ) = mro( B(A) )
= [B] + merge( mro(A) + [A] )
= [B] + merge( [A] + [A] )
= [B,A]
如果继承至多个基类:class B(A1, A2, A3 …) 这时B的mro序列
mro(B) = mro( B(A1, A2, A3 …) )
= [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] )
= ...
计算结果为列表,列表中至少有一个元素即类自己,如上述示例[A1,A2,A3]。merge操作是C3算法的核心。
4.2.2、表头和表尾
表头:列表的第一个元素
表尾:列表中表头以外的元素集合(可以为空)
示例:列表:[A, B, C] 表头是A,表尾是B和C
4.2.3、列表之间的+操作
[A] + [B] = [A, B]
merge操作示例:
如计算merge( [E,O], [C,E,F,O], [C] ) 有三个列表 : ① ② ③
1 merge不为空,取出第一个列表列表①的表头E,进行判断
各个列表的表尾分别是[O], [E,F,O],E在这些表尾的集合中,因而跳过当前当前列表
2 取出列表②的表头C,进行判断
C不在各个列表的集合中,因而将C拿出到merge外,并从所有表头删除
merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] )
3 进行下一次新的merge操作 ......
---------------------
计算mro(A)方式:
mro(A) = mro( A(B,C) )
原式= [A] + merge( mro(B),mro(C),[B,C] )
mro(B) = mro( B(D,E) )
= [B] + merge( mro(D), mro(E), [D,E] ) # 多继承
= [B] + merge( [D,O] , [E,O] , [D,E] ) # 单继承mro(D(O))=[D,O]
= [B,D] + merge( [O] , [E,O] , [E] ) # 拿出并删除D
= [B,D,E] + merge([O] , [O])
= [B,D,E,O]
mro(C) = mro( C(E,F) )
= [C] + merge( mro(E), mro(F), [E,F] )
= [C] + merge( [E,O] , [F,O] , [E,F] )
= [C,E] + merge( [O] , [F,O] , [F] ) # 跳过O,拿出并删除
= [C,E,F] + merge([O] , [O])
= [C,E,F,O]
原式= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C])
= [A,B] + merge( [D,E,O], [C,E,F,O], [C])
= [A,B,D] + merge( [E,O], [C,E,F,O], [C]) # 跳过E
= [A,B,D,C] + merge([E,O], [E,F,O])
= [A,B,D,C,E] + merge([O], [F,O]) # 跳过O
= [A,B,D,C,E,F] + merge([O], [O])
= [A,B,D,C,E,F,O]
python面向对象的三大特性:继承,封装,多态
- 封装: 把很多数据封装到⼀个对象中. 把固定功能的代码封装到⼀个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析. 比如. 你写了⼀个很⽜B的函数. 那这个也可以被称为封装. 在⾯向对象思想中. 是把⼀些看似⽆关紧要的内容组合到⼀起统⼀进⾏存储和使⽤. 这就是封装.
- 继承: ⼦类可以⾃动拥有⽗类中除了私有属性外的其他所有内容. 说⽩了, ⼉⼦可以随便⽤爹的东⻄. 但是朋友们, ⼀定要认清楚⼀个事情. 必须先有爹, 后有⼉⼦. 顺序不能乱, 在python中实现继承非常简单. 在声明类的时候, 在类名后⾯添加⼀个⼩括号,就可以完成继承关系. 那么什么情况可以使⽤继承呢? 单纯的从代码层⾯上来看. 两个类具有相同的功能或者特征的时候. 可以采⽤继承的形式. 提取⼀个⽗类, 这个⽗类中编写着两个类相同的部分. 然后两个类分别取继承这个类就可以了. 这样写的好处是我们可以避免写很多重复的功能和代码. 如果从语义中去分析的话. 会简单很多. 如果语境中出现了x是⼀种y. 这时, y是⼀种泛化的概念. x比y更加具体. 那这时x就是y的⼦类. 比如. 猫是⼀种动物. 猫继承动物. 动物能动. 猫也能动. 这时猫在创建的时候就有了动物的"动"这个属性. 再比如, ⽩骨精是⼀个妖怪. 妖怪天⽣就有⼀个比较不好的功能叫"吃⼈", ⽩骨精⼀出⽣就知道如何"吃⼈". 此时 ⽩骨精继承妖精.
- 多态: 同⼀个对象, 多种形态. 这个在python中其实是很不容易说明⽩的. 因为我们⼀直在⽤. 只是没有具体的说. 比如. 我们创建⼀个变量a = 10 , 我们知道此时a是整数类型. 但是我们可以通过程序让a = "hello", 这时, a⼜变成了字符串类型. 这是我们都知道的. 但是, 我要告诉你的是. 这个就是多态性. 同⼀个变量a可以是多种形态。
四、封装与多态
1、封装
- 封装 是面向对象编程的一大特点
- 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一个抽象的 类 中
- 外界 使用 类 创建 对象,然后 让对象调用方法
- 对象方法的细节 都被 封装 在 类的内部
第一步:将内容封装到某处
class Foo:
def __init__(self,name,age):
self.name = name
self.age = age
obj1 = Foo('picaj',18)
obj2 = Foo('picaj',16)
第二步:从某处调用被封装的内容
class Foo:
def __init__(self,name,age):
self.name = name
self.age = age
def detail(self):
print(self.name)
print(self.age)
obj1 = Foo('picaj',18)
obj2 = Foo('picaj',16)
print(obj1.name)
print(obj2.age)
# 通过对象直接调用被封装的内容
obj1.detail()
obj2.detail()
# 通过self间接调用被封装的内容
2、多态
多态: 不同的 子类对象 调用相同的 父类方法,产生不同的执行结果
- 多态 可以 增加代码的灵活度
- 以 继承 和 重写父类方法 为前提
- 是调用方法的技巧,不会影响到类的内部设计
class human(object):
def work(self):
print("开始工作")
class Designer(human):
def work(self):
print("开始美工")
class IT(human):
def work(self):
print("开始敲代码")
def do_work(human):
human.work()
designer = Designer()
it = IT()
do_work(ps) # 输出: 开始美工
do_work(it) # 输出: 开始敲代码
3、鸭子类型
python中有一句谚语说的好,你看起来像鸭子,那么你就是鸭子。
这句谚语是关于鸭子类型(Duck Typing)的一种表达方式。鸭子类型是一种动态类型的概念,它强调一个对象的特征和行为,而不是其具体的类型或继承关系。
在 Python 中,鸭子类型的概念可以简单地表述为:如果一个对象具有像鸭子一样的特征和行为,那么我们可以认为它是一个鸭子。这意味着我们关注对象是否具备特定的方法和属性,而不关心对象的具体类型。
这种思想在 Python 中经常被使用,特别是在函数参数传递和对象的使用上。如果一个函数接受一个参数,并假设该参数具有某些特定的方法或属性,那么只要传递的对象满足这些要求,它就可以正常工作,无论对象的具体类型是什么。
下面是一个简单的示例来说明鸭子类型的概念:
class Duck:
def quack(self):
print("嘎嘎叫!")
def fly(self):
print("扑哧扑哧的飞!")
class Person:
def quack(self):
print("我喜欢跟鸭子一样嘎嘎叫")
def fly(self):
print("我也喜欢跟鸭子一样飞")
def make_it_quack_and_fly(obj):
obj.quack()
obj.fly()
duck = Duck()
person = Person()
make_it_quack_and_fly(duck)
make_it_quack_and_fly(person)
在上述示例中,我们定义了一个 Duck
类和一个 Person
类,它们都具有 quack
和 fly
方法。然后,我们定义了一个函数 make_it_quack_and_fly
,它接受一个参数 obj
,并调用 obj
的 quack
和 fly
方法。
我们可以看到,无论是 Duck
对象还是 Person
对象,只要它们具有 quack
和 fly
方法,都可以作为参数传递给 make_it_quack_and_fly
函数,并成功执行相应的方法。
这正是鸭子类型的思想:如果一个对象具有像鸭子一样的特征和行为(即具有 quack
和 fly
方法),那么我们可以将其视为鸭子,而无需关心对象的具体类型。
4、类的约束
约束. 其实就是⽗类对⼦类进⾏约束. ⼦类必须要写xxx⽅法. 在python中约束的⽅式和⽅法有两种:
- 提取⽗类. 然后在⽗类中定义好⽅法. 在这个⽅法中什么都不⽤⼲. 就抛⼀个异常就可以了. 这样所有的⼦类都必须重写这个⽅法. 否则. 访问的时候就会报错.
- 使⽤元类来描述⽗类. 在元类中给出⼀个抽象⽅法. 这样⼦类就不得不给出抽象⽅法的具体实现. 也可以起到约束的效果.
5、super()的深入了解
super是严格按照类的继承顺序执行
class A:
def f1(self):
print('in A f1')
def f2(self):
print('in A f2')
class Foo(A):
def f1(self):
super().f2()
print('in A Foo')
obj = Foo()
obj.f1()
class A:
def f1(self):
print('in A')
class Foo(A):
def f1(self):
super().f1()
print('in Foo')
class Bar(A):
def f1(self):
print('in Bar')
class Info(Foo,Bar):
def f1(self):
super().f1()
print('in Info f1')
obj = Info()
obj.f1()
print(Info.mro())
class A:
def f1(self):
print('in A')
class Foo(A):
def f1(self):
super().f1()
print('in Foo')
class Bar(A):
def f1(self):
print('in Bar')
class Info(Foo,Bar):
def f1(self):
super(Foo,self).f1()
print('in Info f1')
obj = Info()
obj.f1()
五、类的成员
1、细分类的组成成员
类大致分两块区域
class A:
name = 'picaj'
# 第一部分:静态字段(静态变量)部分
def __init__(self):
pass
def func(self):
pass
# 第二部分:方法部分
每个区域详细划分
class A:
company_name = 'picaj' # 静态变量(静态字段)
__iphone = '132333xxxx' # 私有静态变量(私有静态字段)
def __init__(self,name,age): #特殊方法
self.name = name #对象属性(普通字段)
self.__age = age # 私有对象属性(私有普通字段)
def func1(self): # 普通方法
pass
def __func(self): #私有方法
print(666)
@classmethod # 类方法
def class_func(cls):
""" 定义类方法,至少有一个cls参数 """
print('类方法')
@staticmethod #静态方法
def static_func():
""" 定义静态方法 ,无默认参数"""
print('静态方法')
@property # 属性
def prop(self):
pass
2、类的私有成员
对于每一个类的成员而言都有两种形式:
- 公有成员,在任何地方都能访问
- 私有成员,只有在类的内部才能方法
私有成员和公有成员的访问限制不同:
静态字段(静态属性)
- 公有静态字段:类可以访问;类内部可以访问;派生类中可以访问
- 私有静态字段:仅类内部可以访问;
总结:
对于这些私有成员来说,他们只能在类的内部使用,不能再类的外部以及派生类中使用.
ps:非要访问私有成员的话,可以通过 对象.*类_*属性名,但是绝对不允许!!!
为什么可以通过.类__私有成员名访问呢?因为类在创建时,如果遇到了私有成员(包括私有静态字段,私有普通字段,私有方法)它会将其保存在内存时自动在前面加上类名.
3、类的其他成员
这里的其他成员主要就是类方法:
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
实例方法
定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);
调用:只能由实例对象调用。
类方法
定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);
调用:实例对象和类对象都可以调用。
静态方法
定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;
调用:实例对象和类对象都可以调用。
双下方法(后面会讲到)
定义:双下方法是特殊方法,他是解释器提供的 由爽下划线加方法名加爽下划线 方法名的具有特殊意义的方法,双下方法主要是python源码程序员使用的,
我们在开发中尽量不要使用双下方法,但是深入研究双下方法,更有益于我们阅读源码。
调用:不同的双下方法有不同的触发方式,就好比盗墓时触发的机关一样,不知不觉就触发了双下方法,例如:init
3.1、类方法
使用装饰器@classmethod。
原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。另外,如果需要继承,也可以定义为类方法。
class Student:
__num = 0
def __init__(self, name, age):
self.name = name
self.age = age
Student.addNum() # 写在__new__方法中比较合适,但是现在还没有学,暂且放到这里
@classmethod
def addNum(cls):
cls.__num += 1
@classmethod
def getNum(cls):
return cls.__num
Student('picaj', 18)
Student('picajiang', 36)
Student('PICAJ', 73)
print(Student.getNum())
3.2、静态方法
使用装饰器@staticmethod。
静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。
import time
class TimeTest(object):
def __init__(self, hour, minute, second):
self.hour = hour
self.minute = minute
self.second = second
@staticmethod
def showTime():
return time.strftime("%H:%M:%S", time.localtime())
print(TimeTest.showTime())
t = TimeTest(2, 10, 10)
nowTime = t.showTime()
print(nowTime)
3.3、 属性
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
class People:
def __init__(self,name,weight,height):
self.name=name
self.weight=weight
self.height=height
@property
def bmi(self):
return self.weight / (self.height**2)
p1=People('picaj',75,1.85)
print(p1.bmi)
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Foo:
@property
def AAA(self):
print('get的时候运行我啊')
@AAA.setter
def AAA(self,value):
print('set的时候运行我啊')
@AAA.deleter
def AAA(self):
print('delete的时候运行我啊')
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
4、isinstace 与 issubclass
isinstance(a,b):判断a是否是b类(或者b类的派生类)实例化的对象
class A:
pass
class B(A):
pass
obj = B()
print(isinstance(obj,B))
print(isinstance(obj,A))
issubclass(a,b): 判断a类是否是b类(或者b的派生类)的派生类
class A:
pass
class B(A):
pass
class C(B):
pass
print(issubclass(B,A))
print(issubclass(C,A))
六、反射与双下方法
1、反射
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
四个可以实现自省的函数
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
对对象的反射
class Foo:
f = '类的静态变量'
def __init__(self,name,age):
self.name=name
self.age=age
def say_hi(self):
print('hi,%s'%self.name)
obj=Foo('picaj',73)
# 检测是否含有某属性
print(hasattr(obj,'name'))
print(hasattr(obj,'say_hi'))
# 获取属性
n=getattr(obj,'name')
print(n)
func=getattr(obj,'say_hi')
func()
print(getattr(obj,'aaaaaaaa','不存在啊')) # 报错
# 设置属性
setattr(obj,'sb',True)
setattr(obj,'show_name',lambda self:self.name+'sb')
print(obj.__dict__)
print(obj.show_name(obj))
# 删除属性
delattr(obj,'age')
delattr(obj,'show_name')
# delattr(obj,'show_name111') # 不存在,则报错
print(obj.__dict__)
对类的反射
class Foo(object):
staticField = "test"
def __init__(self):
self.name = 'picaj'
def func(self):
return 'func'
@staticmethod
def bar():
return 'bar'
print(getattr(Foo, 'staticField'))
print(getattr(Foo, 'func'))
print(getattr(Foo, 'bar'))
例子
class User:
def login(self):
print('欢迎来到登录页面')
def register(self):
print('欢迎来到注册页面')
def save(self):
print('欢迎来到存储页面')
user = User()
while 1:
choose = input('>>>').strip()
if hasattr(user, choose):
func = getattr(user, choose)
func()
else:
print('输入错误。。。。')
2、函数与方法
2.1、通过打印函数(方法)名确定
def func():
pass
print(func)
class A:
def func(self):
pass
print(A.func)
obj = A()
print(obj.func)
2.2、通过types模块验证
from types import FunctionType
from types import MethodType
def func():
pass
class A:
def func(self):
pass
obj = A()
print(isinstance(func,FunctionType))
print(isinstance(A.func,FunctionType))
print(isinstance(obj.func,FunctionType))
print(isinstance(obj.func,MethodType))
2.3、静态方法是函数
from types import FunctionType
from types import MethodType
class A:
def func(self):
pass
@classmethod
def func1(self):
pass
@staticmethod
def func2(self):
pass
obj = A()
# 静态方法其实是函数
print(isinstance(A.func2,FunctionType))
print(isinstance(obj.func2,FunctionType))
2.4、函数与方法的区别
那么,函数和方法除了上述的不同之处,我们还总结了一下几点区别。
- 函数的是显式传递数据的。如我们要指明为len()函数传递一些要处理数据。
- 函数则跟对象无关。
- 方法中的数据则是隐式传递的。
- 方法可以操作类内部的数据。
- 方法跟对象是关联的。如我们在用strip()方法是,是不是都是要通过str对象调用,比如我们有字符串s,然后s.strip()这样调用。是的,strip()方法属于str对象。
我们或许在日常中会口语化称呼函数和方法时不严谨,但是我们心中要知道二者之间的区别。
在其他语言中,如Java中只有方法,C中只有函数,C++么,则取决于是否在类中
3、双下方法
3.1、 __len__
class B:
def __len__(self):
return 666
b = B()
print(len(b)) # len 一个对象就会触发 __len__方法。
class A:
def __init__(self):
self.a = 1
self.b = 2
def __len__(self):
return len(self.__dict__)
a = A()
print(len(a))
3.2、 __hash__
class A:
def __init__(self):
self.a = 1
self.b = 2
def __hash__(self):
return hash(str(self.a)+str(self.b))
a = A()
print(hash(a))
3.3、 __str__
如果一个类中定义了str方法,那么在打印 对象 时,默认输出该方法的返回值。
class A:
def __init__(self):
pass
def __str__(self):
return 'picaj'
a = A()
print(a)
print('%s' % a)
3.4、 __repr__
如果一个类中定义了repr方法,那么在repr(对象) 时,默认输出该方法的返回值。
class A:
def __init__(self):
pass
def __repr__(self):
return 'picaj'
a = A()
print(repr(a))
print('%r'%a)
3.5、 __call__
对象后面加括号,触发执行。
注:构造方法new的执行是由创建对象触发的,即:对象 = 类名() ;而对于 call 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo:
def __init__(self):
print('__init__')
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
3.6、 __eq__
class A:
def __init__(self):
self.a = 1
self.b = 2
def __eq__(self,obj):
if self.a == obj.a and self.b == obj.b:
return True
a = A()
b = A()
print(a == b)
3.7、 __del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
3.8、 __new__
__new__()
方法是在类准备将自身实例化时调用。__new__()
方法始终都是类的静态方法,即使没有被加上静态方法装饰器。- 通常来说,新式类开始实例化时,
__new__()
方法会返回cls(cls指代当前类)的实例,然后该类的__init__()
方法作为构造方法会接收这个实例(即self)作为自己的第一个参数,然后依次传入__new__()
方法中接收的位置参数和命名参数。
class A:
def __init__(self):
self.x = 1
print('in init function')
def __new__(cls, *args, **kwargs):
print('in new function')
return object.__new__(A, *args, **kwargs)
a = A()
print(a.x)
单例模式
class A:
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
obj = object.__new__(cls)
cls.__instance = obj
return cls.__instance
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。 【采用单例模式动机、原因】 对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。 如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。 【单例模式优缺点】 【优点】 一、实例控制 单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。 二、灵活性 因为类控制了实例化过程,所以类可以灵活更改实例化过程。 【缺点】 一、开销 虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。 二、可能的开发混淆 使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。 三、对象生存期 不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用
3.9、 __item__
系列
class Foo:
def __init__(self,name):
self.name=name
def __getitem__(self, item):
print(self.__dict__[item])
def __setitem__(self, key, value):
self.__dict__[key]=value
print('赋值成功')
def __delitem__(self, key):
print('del obj[key]时,我执行')
self.__dict__.pop(key)
def __delattr__(self, item):
print('del obj.key时,我执行')
self.__dict__.pop(item)
f1=Foo('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='mingzi'
print(f1.__dict__)
4、上下文管理器相关
class A:
def __init__(self, text):
self.text = text
def __enter__(self): # 开启上下文管理器对象时触发此方法
self.text = self.text + '您来啦'
return self # 将实例化的对象返回f1
def __exit__(self, exc_type, exc_val, exc_tb): # 执行完上下文管理器对象f1时触发此方法
self.text = self.text + '这就走啦'
with A('大爷') as f1:
print(f1.text)
print(f1.text)
自定义文件管理器
class Diycontextor:
def __init__(self, name, mode):
self.name = name
self.mode = mode
def __enter__(self):
print("Hi enter here!!")
self.filehander = open(self.name, self.mode)
return self.filehander
def __exit__(self,*args):
print("Hi exit here")
self.filehander.close()
with Diycontextor('config', 'r') as f:
for i in f:
print(i.strip())
评论区