类与类之间的关系
千世界, 万物之间皆有规则和规. 我们的类和对象是对千世界中的所有事物进归
类. 那事物之间存在着相对应的关系. 类与类之间也同样如此. 在向对象的世界中. 类与类
中存在以下关系:
1. 依赖关系
2. 关联关系
3. 组合关系
4. 聚合关系
5. 继承关系
6. 实现关系
由于python是门弱类型编程语言. 并且所有的对象之间其实都是多态的关系. 也就是说,
所有的东都可以当做对象来使用. 所以我们在写代码的时候很容形成以上关系. 先. 我
们先看第一种, 也是这些关系中紧密程度最低的一个, 依赖关系.
# 写一个植物大战僵尸 # 1. 植物 # 打僵尸. 僵尸掉血 # 2. 僵尸 # 吃植物. 植物掉血 class Plant: def __init__(self, name, hp, ad): # 200 self.name = name self.hp = hp self.ad = ad def attack(self, js): print("植物攻击僵尸") js.hp -= self.ad print(f"僵尸掉血{self.ad}, 还剩下{js.hp}") class JiangShi: def __init__(self, name, hp, ad): # 1000 800 self.name = name self.hp = hp self.ad = ad def attack(self, zw): print("僵尸咬植物") zw.hp -= self.ad print(f"植物掉血{self.ad}, 还剩{zw.hp}") # 植物 wd = Plant("歪脖子豌豆", 10, 20) # 僵尸 js = JiangShi("铁桶僵尸", 200, 1) wd.attack(js) wd.attack(js) wd.attack(js) wd.attack(js) wd.attack(js) js.attack(wd) js.attack(wd) js.attack(wd) js.attack(wd) # 攻击僵尸 # 僵尸掉血20, 还剩下180 # 植物攻击僵尸 # 僵尸掉血20, 还剩下160 # 植物攻击僵尸 # 僵尸掉血20, 还剩下140 # 植物攻击僵尸 # 僵尸掉血20, 还剩下120 # 植物攻击僵尸 # 僵尸掉血20, 还剩下100 # 僵尸咬植物 # 植物掉血1, 还剩9 # 僵尸咬植物 # 植物掉血1, 还剩8 # 僵尸咬植物 # 植物掉血1, 还剩7 # 僵尸咬植物 # 植物掉血1, 还剩6
我用着你. 但是你不属于我. 这种关系是最弱的.
比如. 公司和雇员之间. 对于正式员, 肯定要签订劳动合同. 还得心伺候着. 但是如果是兼
职. 那无所谓. 需要你就来. 不需要你就可以拜拜. 这的兼职(临时工) 就属于依赖关系.
我用你. 但是你不属于我.
首先, 我们设计一个场景. 还是最初的那个例子. 要把大象装冰箱. 注意. 在这个场景中, 其
实是存在两种事物的. 个是大象, 象负责整个事件的掌控者, 还有个是冰箱, 冰箱负
责被大象操纵.
先, 写出两个类, 个是大象类, 一个是冰箱类.
class Elphant: def __init__(self, name): self.name = name def open(self): ''' 开 return: ''' pass def close(self): ''' 关 :retrrn: ''' pass class Refrigerator: def open_door(self): print("冰箱门被打开了") def close_door(self): print("冰箱门被关上了")
冰箱的功能非常简单, 只要会开门, 关就了. 但是大象就没那么简单. 想想. 大象开
门和关的时候是不是要先找个冰箱啊. 然后呢? 打开冰箱门. 是不是打开刚才找到的那个冰
箱. 然后装己. 最后呢? 关冰箱门, 注意, 关的是刚才那个冰箱吧. 也就是说. 开门和关门
用的是同个冰箱. 并且. 大象有换冰箱的权, 想进那个冰箱就进那个冰箱. 这时, 象类
和冰箱类的关系并没有那么的紧密. 因为大象可以指定任何一个冰箱. 接下来. 我们把代码完
善下.


此时, 我们说, 象和冰箱之间就是依赖关系. 我用着你. 但是你不属于我. 这种关系是最弱的.
比如. 公司和雇员之间. 对于正式员工, 肯定要签订劳动合同. 还得心伺候着. 但是如果是兼
职. 那无所谓. 需要你就来. 不需要你就可以拜拜了. 这的兼职(临时工) 就属于依赖关
我用你. 但是你不属于我.
关联关系 .组合关系, 聚合关系
其实这三个在代码上写法是一样的. 但是, 从含义上是不一样的.
1. 关联关系. 两种事物必须是互相关联的. 但是在某些特殊情况下是可以改和换的.
2. 聚合关系. 属于关联关系中的一种特. 侧重点是xxx和xxx聚合成xxx. 各有各自的
生命周期. 比如电脑. 电脑有CPU, 硬盘, 内存等等. 电脑挂. CPU还是好的. 还是
完整的个体
3. 组合关系. 属于关联关系中的一种特. 写法上差不多. 组合关系比聚合还要紧密. 比如的脑, 脏, 各个器官. 这些器官组合成个人. 这时. 人如果挂. 其他的东也跟着挂了
首先我们看关联关系:
这个最简单. 也是最常用的种关系. 比如. 大家都有男女朋友. 男人
关联着女朋友. 女人关联着男朋友. 这种关系可以是互相的, 也可以是单的.
class Boy: def __init__(self, name, girlFriend=None): # 在初始化的时候可以给一个对象的属性设置成另一个类的对象 self.girlFriend = girlFriend # 一个男孩有一个女朋友 def chi(self): if self.girlFriend: print(f"带着他的女朋友{self.girlFriend.name}去吃饭") else: print("单身狗, 吃什么吃? 滚去学习.") def movie(self): if self.girlFriend: print(f"带着他的女朋友{self.girlFriend.name}去看电影") else: print("单身狗, 看什么看? 滚去学习.") class Girl: def __init__(self, name): self.name = name b = Boy("宝浪") g = Girl("孙艺珍") b.chi() # alex给包浪介绍了一个女朋. 孙艺珍 b.girlFriend = g b.chi() g2 = Girl("梁咏琪") b.girlFriend = g2 # 换了个女朋友 b.chi() # 单身狗, 吃什么吃? 滚去学习. # 带着他的女朋友孙艺珍去吃饭 # 带着他的女朋友梁咏琪去吃饭
注意. 此时Boy和Girl两个类之间就是关联关系. 两个类的对象紧密练习着. 其中一个没有
. 另一个就孤单的不得. 关联关系, 其实就是 我需要你. 你也属于我. 这就是关联关系. 像
这样的关系有很多很多. 比如. 学校和老师之间的关系.
School --- 学校
Teacher--- 老师
老师必然属于个学校. 换句话说. 每个老师肯定有个指定的工作机构. 就是学校. 那老师
的属性中必然关联着学校.
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() # 武sir在上课 # 太白在上课 # 哪吒在上课 # 女神在上课 # 日天在上课 # 宝浪在上课
好. 这就是关联关系. 当我们在逻辑上出现. 我需要你. 你还得属于我. 这种逻辑 就是关
联关系. 那注意. 这种关系的紧密程度比上面的依赖关系要紧密的多. 为么呢? 想想吧
至于组合关系和聚合关系. 其实代码上的差别不大. 都是把另一个类的对象作为这个类的
属性来传递和保存. 只是在含义上会有些许的不同而已.
继承关系
在向对象的世界中存在着继承关系. 我们现实中也存在着这样的关系. 我们说过. x是一
种y, 那x就可以继承y. 这时解层面上的. 如果上升到代码层面. 我们可以这样认为. 子类在不
影响父类的程序运的基础上对父类进的扩充和扩展. 这.我们可以把父类被称为超类或
者基类. 子类被称为派生类
首先, 类名和对象默认是可以作为字典的key的


class Foo: pass print(hash(Foo)) # 可哈希 ##-9223371886657599443 print(hash(Foo())) ##150199209592 # 我们写好的类和创建的对象默认都是可哈希的 # 去掉可哈希 class Foo: __hash__ = None # 当前类的对象不可哈希 print(hash(Foo)) # 可哈希 # -9223371886657599384 # print(hash(Foo())) # TypeError: unhashable type: 'Foo'不可哈希 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) # 我爱吃鱼和 鸡蛋 # 我爱吃肉和 香肠
类名和对象默认是可以作为字典的key的
接下来. 我们来继续研究继承上的相关内容. 在本节中主要研究一下self. 记住. 不管方法之
间如何进调用. 类与类之间是何关系. 默认的self都是访问这个法的对象.
class Base: def __init__(self, num): self.num = num def func1(self): print(self.num) class Foo(Base): pass obj = Foo(123) obj.func1() #123
class Base: def __init__(self, num): self.num = num def func1(self): print(self.num) class Foo(Base): def func1(self): print("Foo. func1", self.num) obj = Foo(123) obj.func1() # # Foo. func1 123 #
class Base: def __init__(self, num): self.num = num def func1(self): print(self.num) self.func2() def func2(self): print("Base.func2") class Foo(Base): def func2(self): print("Foo.func2") obj = Foo(123) obj.func1() ##123
总结. self在访问方法的顺序: 永远先找自己的. 己的找不到再找类的.
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.func2() ##111 1 ##111 2 ##222 3
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() # 1 # 111 1 # 2 # 111 2 # 3 # 222 3
结论: self就是你访问方法的那个对象. 先找己, 然后在找父类的.
类中的关系: 依赖关系是最轻的. 最重的是继承关系. 关联关系是比较微妙的.
self到底是谁?
self:谁调用的就是谁. 类型是根据调用方的对象来进行变换的
super:表示的是父类
类中的特殊成员
什么是特殊成员呢? __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()
创建对象的真正步骤:
首先, 在执类名()的时候. 系统会自动先执__new__()来开辟内存. 此时新开辟出来的内
存区域是空的. 紧随其后, 系统动调__init__()来完成对象的初始化工作. 按照时间轴来算.
1. 加载类
2. 开辟内存(__new__)
3. 初始化(__init__)
4. 使用对象干xxxxxxxxx
类似的操作还有很多很多. 我们不需要完全刻意的去把所有的特殊成员全都记住. 实战中也
用不到那么多. 用到查就是.
面向对象编程的执行流程 ->
1. 加载类 -> 给类创建一个名称空间 -> 主要存放类变量.
2. 创建对象 -> 先找类. -> 根据类来开辟内存 ->
执行类中的__new__() -> 执行__init__() -> 返回对象
class Foo: def __init__(self): # 初始化操作 print("我是init, 我是老二") print("初始化操作. 在创建对象的时候自动调用这个方法") 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() # 调用-> __call__() print(callable(f)) # 对象() print(f["李嘉诚"]) # 自动调用__getitem__() f['jay'] = "林俊杰" del f['哈哈'] with f: print("我是哈哈哈哈") with open() : lst = ["孙艺珍", "李金珠", "井柏然"] lst[2] # =>自动的调用__getitem__() def func(): pass func = 3 print(callable(func)) # 判断xxx是否是可调用的 f.__init__() # 第一次这么写. 以后别这么写 lst = [1,2,3,4] it = iter(lst) print(it.__next__()) print(next(it)) # __next__() 面试之前翻一番 写出15个特殊成员, 并给出具体作用




