3.类与类之间的关系
一. 类与类之间的依赖关系
大千世界, 万物之间皆有规则和规律. 我们的类和对象是对大千世界中的所有事物进行归类. 那事物之间存在着相对应的关系. 类与类之间也同样如此. 在面向对象的世界中. 类与类 中存在以下关系:
1. 依赖关系
2. 关联关系
3. 组合关系
4. 聚合关系
5. 继承关系
6. 实现关系
由于python是一⻔门弱类型编程语言. 并且所有的对象之间其实都是多态的关系. 也就是说, 所有的东西都可以当做对象来使用. 所以我们在写代码的时候很容易形成以上关系. 首先. 我们先看第一种, 也是这些关系中紧密程度最低的一个, 依赖关系.
class Elphant: def __init__(self,name): self.name = name def open(self,ref): print("大象开门了") ref.open_door() def close(self,ref): print("大象关门了") def take(self): print("钻进去") class Refrigerator: def open_door(self): print("冰箱门打开了") def close_door(self): print("冰箱门关了") el = Elphant("生气的大象") r = Refrigerator() el.open(r) el.take() el.close(r)
大象和冰箱之间就是依赖关系. 我用着你. 但是你不属于我. 这种关系是最弱的. 比如. 公司和雇员之间. 对于正式员工, 肯定要签订劳动合同. 还得小心伺候着. 但是如果是兼职. 那无所谓. 需要了你就来. 不需要你就可以拜拜了. 这里的兼职(临时工) 就属于依赖关系. 我用你. 但是你不属于我.
二. 关联关系.组合关系, 聚合关系
其实这三个在代码上写法是一样的. 但是, 从含义上是不一样的.
1. 关联关系. 两种事物必须是互相关联的. 但是在某些特殊情况下是可以更改和更换的.
2. 聚合关系. 属于关联关系中的一种特例. 侧重点是xxx和xxx聚合成xxx. 各自有各自的声明周期. 比如电脑. 电脑里有CPU, 硬盘, 内存等等. 电脑挂了. CPU还是好的. 还是完整的个体
3. 组合关系. 属于关联关系中的一种特例. 写法上差不多. 组合关系比聚合还要紧密. 比 如人的大脑, ⼼心脏, 各个器官. 这些器官组合成一个人. 这时. 人如果挂了. 其他的东西也跟着挂了.
class Boy: def __init__(self,name,girlFriend=None): self.name = name self.girlFriend = girlFriend def have_a_dinner(self): if self.girlFriend: print("%s和%s一起吃饭"%(self.name,self.girlFriend.name)) else: print("单身狗,吃个屁") class Girl: def __init__(self,name): self.name = name b = Boy("alex") b.have_a_dinner() g = Girl("如花") b.girlFriend = g b.have_a_dinner() gg = Girl("李小花") bb = Boy("Wusir",gg) bb.have_a_dinner() bb.girlFriend = None bb.have_a_dinner()
此时Boy和Girl两个类之间就是关联关系. 两个类的对象紧密练习着. 其中一个没有了. 另一个就孤单的不得了. 关联关系, 其实就是 我需要你. 你也属于我. 这就是关联关系. 像这样的关系有很多很多. 比如. 学校和老师之间的关系.
School --- 学校
Teacher--- 老师
老师必然属于一个学校. 换句话说. 每个老师肯定有一个指定的工作机构. 就是学校. 那老师的属性中必然关联着学校
class School: def __init__(self,name,address): self.name = name self.address = address class Teacher: def __init__(self,name,school=None): self.name = name self.school = school s1 = School("老男孩北京校区","美丽沙河") t1 = Teacher("alex",s1) print(t1.school.address)
果然关系反过来,一个老师可以选一个学校任职, 一个学校有多少老师
class School: def __init__(self,name): self.teach_list = [] self.name = name def zhaopin(self,teach): self.teach_list.append(teach) def shangke(self): for t in self.teach_list: t.work() class Teacher: def __init__(self,name): self.name =name def work(self): print(f"{self.name}在上课") lnh = School("老男孩") t1 = Teacher("武sir") t2 = Teacher("太白") t3 = Teacher("哪吒") t4 = Teacher("女神") t5 = Teacher("日天") t6 = Teacher("宝浪") lnh.zhaopin(t1) lnh.zhaopin(t2) lnh.zhaopin(t3) lnh.zhaopin(t4) lnh.zhaopin(t5) lnh.zhaopin(t6) lnh.shangke()
这就是关联关系,我需要你. 你还得属于我. 这种逻辑 就是关联关系. 那注意. 这种关系的紧密程度比上面的依赖关系要紧密的多.
三. 继承关系.
在⾯面向对象的世界中存在着继承关系. 我们现实中也存在着这样的关系. 我们说过. x是⼀一 种y, 那x就可以继承y. 这时理理解层⾯面上的. 如果上升到代码层⾯面. 我们可以这样认为. ⼦子类在不 影响⽗父类的程序运⾏行行的基础上对⽗父类进⾏行行的扩充和扩展. 这⾥里里.我们可以把⽗父类被称为超类或 者基类. ⼦子类被称为派⽣生类. ⾸首先, 类名和对象默认是可以作为字典的key的
class Cat: #父类 => 基类 =>超类 def catch_mouse(self): print("猫可以抓老鼠") class BosiCat(Cat): #子类 =>派生类 pass class Foo: pass print(hash(Foo)) #可哈希 print(hash(Foo()))
注意: 不管方法之 间如何进行调用. 类与类之间是何关系. 默认的self都是访问这个方法的对象.
class Foo: def chi(self,food): print("我爱吃鱼和",food) class Bar: def chi(self,food): print("我爱吃肉和",food) dic = {Foo:"鸡蛋",Bar : "香肠"} for k,v in dic.items(): k().chi(v)
练习题
class Base: def __init__(self,num): self.num=num def func1(self): print(self.num) self.func2() def func2(self): print(111,self.num) class Foo(Base): def func2(self): print(222,self.num) lst = [Base(1),Base(2),Foo(3)] for obj in lst: obj.func1()
结论: self就是你访问⽅的那个对象. 先找⾃己, 然后在找类的.
四. 类中的特殊成员
什么是特殊成员呢? __init_()就是一个特殊的成员. 带双下划线的那一坨. 这些方法在特殊的场景的时候会被自动的执行. 比如,
1. 类名() 会自动执行__init__()
2. 对象() 会自动执行__call__()
3. 对象[key] 会自动执行__getitem__()
4. 对象[key] = value 会自动执行__setitem__()
5. del 对象[key] 会自动执行 __delitem__()
6. 对象+对象 会自动执行 __add__()
7. with 对象 as 变量 会自动执行__enter__ 和__exit__
8. 打印对象的时候 会自动执行 __str__
9. 干掉可哈希 __hash__ == None 对象就不可哈希了.
__init__() # 创建对象的时候初始化操作 __call__() # 对象() __getitem__() # 对象[哈哈] __setitem__() # 对象[哈哈] = 值 __new__() # 创建对象的时候.开辟内存 __enter__() # with 对象 __exit__() #结束with的时候 __hash__() # 可哈希 hash() __str__()#改变对象的字符串显示 __repr__()#在python解释器环境下,会默认显示对象的repr __doc__() #输出文档注释信息,而且注释信息不能被继承 __class__()# 获取类名 __moudle__()# 获取当前导入的模块名
创建对象的真正步骤:
首先, 在执行类名()的时候. 系统会自动先执行__new__()来开辟内存. 此时新开辟出来的内存区域是空的. 紧随其后, 系统自动调用__init__()来完成对象的初始化工作. 按照时间轴来算.
1. 加载类
2. 开辟内存(__new__)
3. 初始化(__init__)
4. 使用对象干xxxxxxxxx
class Foo: def __init__(self): print("我是init,我是老二") def __new__(cls, *args, **kwargs): print("我是new,我是老大") return object.__new__(cls) # 对象() def __call__(self, *args, **kwargs): print("我是对象()") #对象[] def __getitem__(self, item): print("item=",item) print("你执行了__getitem__") return "哈哈" #对象[key] = value def __setitem__(self, key, value): print("key=",key) print("value=",value) #del lst[1] def __delitem__(self, key): print("key=",key) # with 对象: def __enter__(self): print("我是enter") # with 对象:代码执行完后 最后执行这里 def __exit__(self, exc_type, exc_val, exc_tb): print("我叫exit") def __len__(self): print("我的天") return 3 f = Foo() #自动执行__init__() f() print(callable(f)) print(f["李嘉诚"]) f["alex"] = 123 del f["哈哈"] with f: print("哈哈") print(len(f))
面向对象编程的执行流程
1. 加载类 -> 给类创建一个名称空间 -> 主要存放类变量.
2. 创建对象 -> 先找类. -> 根据类来开辟内存 -> 执行类中的__new__() -> 执行__init__() -> 返回对象