对象之间的交互,类与对象的命名空间以及类的组合
一、对象之间的交互
我们创造一个人狗大战
#现在我们已经有一个人类了,通过给人类一些具体的属性我们就可以拿到一个实实在在的人。 #现在我们要再创建一个狗类,狗就不能打人了,只能咬人,所以我们给狗一个bite方法。 有了狗类,我们还要实例化一只实实在在的狗出来。然后人和狗就可以打架了。现在我们就来让他们打一架吧! # 创建一个狗类: class Person: role = 'person' # 静态属性 def __init__(self, name, sex, hp, ad): self.name = name # 对象属性 属性 self.sex = sex self.hp = hp self.ad = ad def attack(self): print('%s发起了一次攻击' % self.name) class Dog: role = 'person' # 静态属性 def __init__(self, name, kind, hp, ad): self.name = name # 对象属性 属性 self.kind = kind self.hp = hp self.ad = ad def bite(self): print('%s咬了人一口' % self.name) alex = Person('a_sb', '不详', 1, 5) boss_jin = Person('金老板', '女', 20, 50) teddy = Dog('笨笨', 'teddy', 50, 10) alex.attack() # 相当于执行Person.attack(alex) boss_jin.attack() # 相当于执行Person.attack(boss_jin) teddy.bite() 执行结果: a_sb发起了一次攻击 金老板发起了一次攻击 笨笨咬了人一口
???那么问题来了,人发起一次攻击,他攻击谁了?
所以我们需要一些交互!(同时完善人狗的攻击)
class Person: role = 'person' # 静态属性 def __init__(self, name, sex, hp, ad): self.name = name # 对象属性 属性 self.sex = sex self.hp = hp self.ad = ad def attack(self,dog): dog.hp -= self.ad print('%s攻击了%s,%s掉了%s点血' % (self.name,dog.name,dog.name,self.ad)) class Dog: role = 'person' # 静态属性 def __init__(self, name, kind, hp, ad): self.name = name # 对象属性 属性 self.kind = kind self.hp = hp self.ad = ad def bite(self,people): #people是变量名,它是一个对象 people.hp -= self.ad #人掉血 print('%s咬了%s一口,%s掉了%s点血' % (self.name,people.name,people.name,self.ad))
#由于people是对象,取name就是people.name alex = Person('a_sb', '不详', 1, 5) boss_jin = Person('金老板', '女', 20, 50) teddy = Dog('笨笨', 'teddy', 50, 10) teddy.bite(alex) #alex是一个对象,把对象传进去了传进去的对象就好比传进去一个字典,将其属性传入 print(alex.hp) #查看alex的血 alex.attack(teddy) print(teddy.hp) #查看teddy的血
总结: 交互就是不同类中的方法可以传参和调用,参数可以是对象(代入得就是它的属性字典)。
二、类命名空间与对象、实例的命名空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:静态属性和动态属性(注意加载类时,会自动执行里面的代码,不需要调用)
1、静态属性就是直接在类中定义的变量(数据属性是共享给所有对象的)静态参数
2、动态属性就是定义在类中的方法(类的动态属性是绑定到所有的对象的) 动态函数
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性,通过初始化函数__init__(各种属性)
查找规律:在obj.name会先从obj的名称空间里找name,找不到则会在类中找(这里再实例的是后,sellf.name会 给obj一个name,当然没有会在类中找),找不到会抛出异常!
#在上面的例子,看下面代码: alex = Person('a_sb', '不详', 1, 5) alex.attack(teddy) 有一个问题 alex是如何找到Person类的? 因为是Person是实例化了alex,那么实例化过程中,发生了什么呢? 查看对象的属性,一定要学会查看属性__dict__ print(alex.__dict__) #结果:{'hp': -9, 'sex': '不详', 'name': 'a_sb', 'ad': 5} 其实例化就是把属性创建一个字典传给实例的名称!
总结:所以类里的方法,在实例化之前,就已经加载到内存中了,类代码执行,会跳过某些步骤,也就是说类是感知不到自己有多少方法,实例化后,只能对象单向去取类中的方法!如下图
捋捋上图的执行顺序:
1.加载类名Person
2.加载类静态属性role='person'
3.加载类初始函数__init__,注意,它不执行代码
4.加载类方法attack,注意,它不执行代码
5.实例化对象alex,执行__init__
6.执行类方法attack
7.打印对象alex的属性
对象的内存空间里 是不是只包含init方法里面创建的所有属性?可以添加属性吗? 加一个age属性试试 alex = Person('a_sb', '不详', 1, 5) alex.attack() alex.age = 18 #此时init没有age #Person实例化alex print(alex.__dict__) 执行输出: a_sb发起了一次攻击 {'ad': 5, 'age': 18, 'name': 'a_sb', 'sex': '不详', 'hp': 1} 答案是可以向里面添加属性,但是只是影响对象的命名空间!
再一次总结:
对象内存空间里: 只存储对象的属性,而不存储方法和静态属性查询对象内存空间,使用__dict__为什么不存储类方法呢?假如类有50方法,都存一份?太浪费空间了。为了节省空间,所以不存储类方法方法和静态属性都存储在类的内存空间中为了节省内存,让多个对象去共享类中的资源当类空间不够时,会自动扩容静态属性和方法是共享的对象属性是独有的对象不能有自己特有的方法。但是注意在加载类时,会自动执行类里面的一些方法:
class Student: print('我执行了') # 直接运行 结果 我执行了
模拟人生的游戏:
里面有一家人,爸爸妈妈出去工作赚钱,女儿和儿子上学,要花钱。
赚的钱,是共享的,大家都可以使用。
class Person: money = 0 def __init__(self,name): self.name = name def work(self): self.money += 1000 print(self.name,'工作,赚了%s快钱' %self.money) father = Person('father') mother = Person('mother') father.work() mother.work() print(Person.money) #查看钱 执行输出: father 工作,赚了1000快钱 mother 工作,赚了1000快钱 #不能改变类的属性 0
为什么,类中的money没有增加?
那怎么解决这个问题哪? 类的属性怎么改变?
class Person: money = 0 def __init__(self,name): self.name = name def work(self): Person.money += 1000 #类属性修改 print(self.name,'工作,赚了%s快钱' %self.money) father = Person('father') mother = Person('mother') father.work() mother.work() print(Person.money) #查看钱 执行输出: father 工作,赚了1000快钱 mother 工作,赚了2000快钱 2000
#总结:对于静态属性的修改,应该使用类名直接修改,就不会出现不一致的情况,因为类的属性是共享的
小作业:
写一个类,完成一个功能,可以统计这个类有几个对象
#写一个类,完成一个功能,可以统计这个类有几个对象 class Foo: count = 0 def __init__(self): Foo.count += 1 #修改类的属性 f1 = Foo() # 如果三个实例化的名字都一样,一样的结果 f2 = Foo() f3 = Foo() print(Foo.count) #执行结果:3 #小测试,看如下代码,最终money会输出多少?2000还是0? class Person: money = [0] def __init__(self,name): self.name = name def work(self): self.money[0] += 1000 #这里调用的类中的money的第一个元素,因为自己没有 print(self.name,'工作,赚了1000快钱') father = Person('father') mother = Person('mother') father.work() mother.work() print(Person.money) #查看钱 执行输出: father 工作,赚了1000快钱 mother 工作,赚了1000快钱 [2000]
上题中为什么是2000?为什么是2000 ?不是说但凡是对象操作类属性,它是没有权利的,它只能存储在自己内存空间里面
第一次类初始化是,money[0] =0。当father实例化之后,money[0]的指针就失效了,变成了1000当mother实例化之后,money[0]的指针就失效了,变成了2000。最终结果为2000!
因为list是可变类型,它有自己独立的内存空间。当实例改变时,它就改变了。所以最后调用时,money的值为2000
#同样的问题? class Person: money = [0] def __init__(self,name): self.name = name def work(self): self.money = [Person.money[0] + 1000] #没有改变列表 print(self.name,'工作,赚了1000快钱') father = Person('father') mother = Person('mother') father.work() mother.work() print(Person.money) #查看钱 执行输出: father 工作,赚了1000快钱 mother 工作,赚了1000快钱 [0]
请注意表达式self.money = [Person.money[0] + 1000]等式先计算右边的,Person.money[0]之间用类名.属性名调用,那么应该是[0 + 1000],最终结果为[1000]。将结果赋值给self.money,它是实例本身调用money,所以修改的值[1000],存放在本实例空间。
总结: 1、不管是类里面还是类外面,统一使用如下方法修改:类名.静态变量名
2、实例查找变量的顺序:
先找自己内存空间->再找到类对象指针->再根据类对象指针找到类->再通过类找对象的内存
3、对象的内存空间里:一 只存储对象属性,二 不存储方法和静态属性。
方法和静态属性都存储在类的内存空间中,为了节省内存,让多个对象去共享类中资源
4、对象属性是独有的,静态属性和方法是共享的,对象使用名字:先找自己内存空间中,再找类的内存空间中的,
类名.静态变量名:对于静态属性的修改,应该使用类名直接修改
class B: l = [0] def __init__(self,name): self.name = name b1 = B('颜海清') b2 = B('孙晶晶') print(B.l) print(b1.l) print(b2.l) b1.l[0] += 1 print(b2.l[0]) b1.l = [123] # 这里相当于在对象内写了个l print(b1.l) print(b2.l) # 运行结果 [0] [0] [0] 1 [123] [1]
总结:
只要是对一个对象.名字直接赋值,那么就是在这个对象的空间内创建了新的属性
只要是对一个可变的数据类型内部的变化,那么仍然是所有的对象和类共享这个改变的成果
所有的静态变量都是用类名来操作,这样修改就能被所有的对象感知到
如果是对于可变数据类型的静态变量 操作的是这个数据内部的内容,也可以使用对象来调用
三,类的加载顺序
1.类内部一个缩进的所有代码都是在py文件从上到下解释的时候就已经被执行了
2.类中的代码永远是从上到下依次执行的
class A: country = 'China' def __init__(self): print('执行我了') print(A.country) def func(self): print('1111111') def func(self): print('2222222') A.country = 'English' a = A() a.func() #运行结果 执行我了 English 2222222
四、面向对象的实例
计算圆的周长 2rπ (r表示半径)
计算圆的面积 r²π
现在有5个圆,半径分别是1,3,5,7,9
请使用面向对象编写出来
提示:π需要用python模块math里面的pi方法
math---数学函数
作用:提供函数完成特殊的数学运算。
pi表示圆周率
from math import pi print(pi)
#输出:3.141592653589793
代码如下:
from math import pi class Circle: def __init__(self, r): self.r = r def cal_area(self): ''' 计算圆面积的方法 :return:返回值是float数据类型的面积 ''' return pi * self.r ** 2 def cal_perimeter(self): ''' 计算圆周长的方法 :return:返回值是float数据类型的周长 ''' return pi * self.r * 2 for i in range(1, 10, 2): #隔一个取一个,得到1,3,5,7,9 c1 = Circle(i) #实例化 print(c1.cal_area()) print(c1.cal_perimeter()) 输出结果: 3.141592653589793 6.283185307179586 28.274333882308138 18.84955592153876 78.53981633974483 31.41592653589793 153.93804002589985 43.982297150257104 254.46900494077323 56.548667764616276
五、类与类的组合
一个类的对象是另一个类对象的属性 如下例 圆形类的对象 是圆环类对象的outer属性的值
这里需要说一说:低耦合 和 紧耦合
低耦合:在一个紧耦合的结构中,对一个类的修改也会导致 对其他一些类的修改。这是要努力避免的,否则,一点小小的改变就可能使整个应用程序发生 改变。另外,要想找到所有需要修改的地方,并一一加以修改,却是一件既困难又费时的事情(且容易出错)。 另一方面,在一个松耦合的系统中,常常可以修改一个类,但同时不会修改其他类,而且 整个程序还可以正常运作。
高内聚:聚合与程序中一个单独的单元所承担的任务的数量和种类相对应有关,它是针对类或方法 这样大小的程序单元而言的理想情况下,一个代码单元应该负责一个聚合的任务(也就是说,一个任务可以被看作是 一个逻辑单元)。一个方法应该实现一个逻辑操作,而一个类应该代表一定类型的实体。聚合 理论背后的要点是重用:如果一个方法或类是只负责一件定义明确的事情,那么就很有可能在 另外不同的上下文环境中使用。遵循这个理论的一个额外的好处是,当程序某部分的代码需要 改变时,在某个代码单元中很可能会找到所有需要改变的相关代码段。
1、学生类 出生日期 入学日期 开学日期 日期的粒度 精确到年月日 #一个日期可不可以是一个类 class Date: def __init__(self,year,month,day): self.year = year self.month = month self.day = day def date(self): return '%s-%s-%s'%(self.year,self.month,self.day) class Student: def __init__(self,name,num,birth,in_shcool,start_day): self.name = name self.num = num self.birth = birth # 组合 self.in_school = in_shcool # 组合 self.start_day = start_day # 组合 d1 = Date(1999,10,27) d2 = Date(2019,1,9) d3 = Date(2019,2,9) feng = Student('冯磊',10086,d1,d2,d3) print(feng.birth.year) print(feng.birth.month) print(feng.birth.day) print(feng.birth.date()) # 1999 10 27 1999-10-27 feng.start_day.month +=1 print(feng.start_day.date()) # 2019-3-9 # 创建个学生类和课程类 class Student: def __init__(self,name,num,course): self.name = name self.num = num self.course = course class Course: def __init__(self,name,price,period): self.name = name self.price = price self.period = period python = Course('python',25000,'6 months') s1 = Student('任世龙',10085,python) s2 = Student('任希同',10084,python) s3 = Student('董鹏',10083,python) # print(s1.__dict__) python.price = 30000 python.period = '7 months' 2,圆环和圆柱类 from math import pi class Circle: def __init__(self,r): self.r = r def area(self): return pi*self.r**2 def perimeter(self): return 2*pi*self.r # 圆环类 class Ring: def __init__(self,r1,r2): c1 = Circle(r1) c2 = Circle(r2) if r1>=r2: self.outer = c1 # 组合 # self.outer.area() # c1.area() self.inner = c2 # 组合 else: self.inner = c1 # 组合 self.outer = c2 # 组合 def area(self): return self.outer.area() -self.inner.area() def perimeter(self): return self.outer.perimeter() + self.inner.perimeter() 圆柱类 class C3d: def __init__(self,r,h): self.c = Circle(r) # 组合 self.h = h def area(self): c_area = self.c.area() rec_area = self.c.perimeter() * self.h return c_area*2 + rec_area def vic(self): c_area = self.c.area() return c_area*self.h
class Person: def __init__(self,name,hp,ad,sex): self.name = name self.hp = hp self.ad = ad self.sex = sex self.money = 2000000.8 self.beg = [] def attack(self,dog): dog.hp -= self.ad print('%s打了%s,%s掉了%s点血'%(self.name,dog.name,dog.name,self.ad)) def buy_weapon(self,weapon): if weapon.price <= self.money: self.money -= weapon.price self.money = round(self.money,2) self.beg.append(weapon) print('购买%s成功,您当前的余额:%s'%(weapon.name,self.money)) else: print('您的余额不足,请充值之后重试') def use_weapon(self): for weap in self.beg: if weap.t == 'weapon': self.weapon = weap # 组合 self.hp += weap.hp self.ad += weap.ad break class Dog: def __init__(self,name,hp,ad,kind): self.name = name self.hp = hp self.ad = ad self.kind = kind def bite(self,person): person.hp -= self.ad print('%s咬了%s,%s掉了%s点血'%(self.name,person.name,person.name,self.ad)) # 回合制的游戏 alex = Person('alex',10,0.5,'待定') hei = Dog('小黑',3000,20,'藏獒') # 装备 # 名字 # 价格 # 属性 # 技能 class weapon: t = 'weapon' def __init__(self,name,price,ad,hp): self.name = name self.price = price self.ad = ad self.hp = hp def throw(self,dog): dog.hp -= 2000 print('%s被搬砖扔中了,掉了2000点血,当前%s的血量为%s'%(dog.name,dog.name,dog.hp)) zhuan = weapon('搬砖',2000000,1000,100) # 买装备 # 人的余额 > 商品的价格 # 绑定装备 alex.buy_weapon(zhuan) print(alex.beg) alex.use_weapon() print(alex.weapon.name) print(alex.__dict__) alex.weapon.throw(hei) #运行结果 购买搬砖成功,您当前的余额:0.8 [<__main__.weapon object at 0x014A3670>] 搬砖 {'name': 'alex', 'hp': 110, 'ad': 1000.5, 'sex': '待定', 'money': 0.8, 'beg': [<__main__.weapon object at 0x014A3670>], 'weapon': <__main__.weapon object at 0x014A3670>} 小黑被搬砖扔中了,掉了2000点血,当前小黑的血量为1000