1.1.类和对象
1.1.1.万物皆对象
分类是人们认识世界的一个很自然的过程,在日常生活中会不自觉地将对象进行分类
对象归类:
- 类是抽象的概念,仅仅是模块,比如说:“人”
- 对象是一个你能够看到、摸得着的具体实体:赵本山、刘德华、赵丽颖
举例:
user1 = 'zhangsan'
print(type(user1))
user2 = 'lisi'
print(type(user2))
输出:
<class 'str'>
<class 'str'>
以上str是类(python中的字符串类型),user1和user2是对象(以前我们叫变量)
研究对象
顾客类型
属性:
姓名--张浩
年龄--20
体重--60kg
行为:
购买商品
收银员类型
属性:
员工号--10001
姓名--王淑华
部门--财务部
行为:
收款
打印账单
1.1.2.对象的特征--属性
属性--对象具有的各种特征。每个对象的每个属性都拥有特定值。例如:顾客张浩和李明的年龄、姓名都一样。
1.1.3.对象的特征--方法(操作,行为)
方法--对象执行的操作(通常会改变属性的值)。顾客对象的方法--购买商品。收银员对象的方法--收款。
对象:用来描述客观事物的一个实体,由一组属性和方法构成
例子:
类型:狗
对象名:doudou
属性:
颜色:白色
方法:
叫,跑,吃
对象同时具有属性和方法两项特性。对象的属性和方法通常被封装在一起,共同体现事物的特性,二者相辅相成,不能分割。
1.1.4.从对象抽象出“类”
抽取出下列对象的共同特性(属性和方法)
类是模子,定义对象将会拥有的特性(属性)和行为(方法)。
再次强调
- 类是抽象的概念,仅仅是模板。比如说:“人”,类定义了人这种类型属性(name,age...)和方法
- 对象是一个你能够看得到、摸得着的具体实体:赵本山、刘德华、赵丽颖,这些具体的人都具有人类型中定义的属性和方法,不同的是他们各自的属性不同。
根据类来创建对象被称为实例化。
1.2.类的定义
1.2.1.定义只包含方法的类
在python中要定义一个只包含方法的类,语法格式如下:
class类名:
def 方法1(self,参数列表):
pass
def 方法2(self,参数列表):
pass
方法的定义格式和之前学习过的函数几乎一样。区别在于第一个参数必须是self,大家暂时先记住,稍后介绍self。
1.2.2.创建对象
当一个类定义完成之后,要使用这个类来创建对象,语法格式如下:
对象变量 = 类名()
第一个面向对象程序
需求:
- 小猫爱吃鱼,小猫要喝水。
分析:
1.定义一个猫类Cat。
2.定义两个方法eat和drink。
class Cat:
"""
这是一个猫类
"""
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫在喝水")
tom = Cat()
tom.drink()
tom.eat()
小猫在喝水
小猫爱吃鱼
使用Cat类再创建一个对象
lazy_cat = Cat()
lazy_cat.eat()
lazy_cat.drink()
1.3.self参数
和我们以往的经验不同,我们在调用对象的方法是不需要传递self参数,这个self参数是系统自动传递到方法里的。我们需要知道这个self到底是什么?
1.3.1.给对象增加属性
在python中,要给对象设置属性,非常的容易,但是不推荐使用。因为:对象属性的封装应该封装在类的内部。要给对象设置属性,只需要在类的外部代码中直接通过,设置一个属性即可。
tom.name = "Tom"
lazy_cat.name = "大懒猫"
1.3.2.理解self到底是什么
class Cat:
"""
这是一个猫类
"""
def eat(self):
print(f"小猫爱吃鱼,我是{self.name},self的地址是{id(self)}")
def drink(self):
print("小猫在喝水")
tom = Cat()
print(f'tom对象的id是{id(tom)}')
tom.name = 'Tom'
tom.eat()
print('-'*60)
lazy_cat = Cat()
print(f'lazy_cat对象的id是{id(lazy_cat)}')
lazy_cat.name = "大懒猫"
lazy_cat.eat()
输出
tom对象的id是2103780003008
小猫爱吃鱼,我是Tom,self的地址是2103780003008
------------------------------------------------------------
lazy_cat对象的id是2103780003176
小猫爱吃鱼,我是大懒猫,self的地址是2103780003176
由输出可见,当我们使用
x对象.x方法()
的时候,python会自动将x对象作为实参传给x方法的self参数。也可以这样记忆,谁点(.)的方法,self就是谁。
类中的每个实例方法的第一个参数都是self
1.4.初始化方法init()
之前代码存在的问题--在类的外部给对象增加属性
- 将安利代码进行调整,先调用方法,再设置属性,观察一下执行效果
tom = Cat()
tom.drink()
tom.eat()
tom.name = "Tom"
print(tom)
- 程序执行报错如下:
AttributeError: 'Cat' object has no attribute 'name'
属性错误:'Cat' 对象没有 'name' 属性
提示
- 在日常开发中,不推荐在类的外部给对象增加属性
- 如果在运行时,没有找到属性,程度会报错
- 对象应该包含有哪些属性,应该封装在类的内部
初始化方法 - 当使用类名()创建对象时,会自动执行以下操作
1.为对象在内存中分配空间--创建空间--创建对象(调用____new____,以后说)
2.为对象的属性设置初始值--初始化方法(调用____init____并且将第一步创建的对象,通过self参数传给____init____) - 这个初始化方法就是____init____方法,____init____是对象的内置方法(有的书也叫魔法方法,特殊方法)
____init____方法是专门用来定义一个类具有哪些属性并且给出这些属性的初始值的方法!
在Cat中增加____init____方法,验证该方法在创建对象时会被自动调用
class Cat:
"""
这是一个猫类
"""
def __init__(self):
print("初始化方法")
Cat()
在初始化方法内部定义属性
- 在____init____方法内部使用self.属性名 = 属性的初始值就可以定义属性
- 定义属性之后,再使用Cat类创建的对象,都会拥有该属性
class Cat:
def __init__(self):
print("这是一个初始化方法")
#定义用Cat类创建的猫对象都有一个name属性
self.name = "Tom"
def eat(self):
print("%s 爱吃鱼" % self.name)
#使用类名()创建对象的时候,会自动调用初始化方法__init__
tom = Cat()
tom.eat()
这是一个初始化方法
Tom 爱吃鱼
改造初始化方法--初始化的同时设置初始值
- 在开发中,如果希望在创建对象的同时,就设置成对象的属性,可以用____init____方法进行改造
1.把希望设置的属性值,定义成____init____方法的参数
2.在方法内部使用,self.属性=形参 接收外部传递的参数
3.在创建对象时,使用 类名(属性1,属性2,...)调用
class Cat:
def __init__(self,name):
print("初始化方法 %s" % name)
self.name = name
...
tom = Cat("Tom")
...
lazy_cat = Cat("大懒猫")
...
初始化方法 Tom
初始化方法 大懒猫
1.5.____str____方法
在python中,使用print输出对象变量,默认情况下,会输出这个变量的类型,以及在内存中的地址(十六进制表示)
class Cat:
def __init__(self, new_name):
self.name = new_name
print("%s 来了" % self.name)
def __str__(self):
return "我是小猫:%s" % self.name
tom = Cat("Tom")
print(tom)
输出
Tom 来了
<__main__.Cat object at 0x0000000002852278>
如果在开发中,希望使用print输出对象变量时,能够打印自定义的内容,就可以利用____str____这个内置方法了
注意:____str____方法必须返回一个字符串
1.6.面向对象VS面向过程
def CarInfo(type,price):
print("the car's type %s,price:%d" % (type,price))
print('函数方式(面向过程)')
CarInfo('passat',250000)
CarInfo('ford',280000)
class Car:
def __init__(self,type,price):
self.type = type
self.price = price
def printCarInfo(self):
print("the car's Info in class:type %s,price:%d" % (self.type,self.price))
print('面向对象')
car1 = Car('passat',250000)
car2 = Car('ford',250000)
car1.printCarInfo()
car2.printCarInfo()
输出
函数方式(面向过程)
the car's type passat,price:250000
the car's type ford,price:280000
面向对象
the car's Info in class:type passat,price:250000
the car's Info in class:type ford,price:250000
类能实现的功能,函数也能实现,那么类的存在还有什么特殊的意义呢?新需求--加一个行驶里程的功能--面向过程实现
def CarInfo(type,price):
print("the car's type %s,price:%d"%(type,price))
def driveDristance(oldDistance,distance):
newDistance = oldDistance + distance
return newDistance
print('函数方式(面向过程)')
CarInfo('passart',250000)
distance = 0
distance = driveDristance(distance,100)
distance = driveDristance(distance,200)
print(f'passat已经行驶了{distance}公里')
输出
函数方式(面向过程)
the car's type passart,price:250000
passat已经行驶了300公里
面向对象实现
class Car:
def __init__(self,type,price):
self.type = type
self.price = price
self.distance = 0 #新车
def printCarInfo(self):
print("the car's Info in class:type %s price:%d"%(self.type,self.price))
def driveDistance(self,distance):
self.distance += distance
print('面向对象')
car1 = Car('passat',250000)
car1.printCarInfo()
car1.driveDistance(100)
car1.driveDistance(200)
print(f'passat已经行驶了{car1.distance}公里')
print(f'passat的价格是{car1.price}元')
输出
面向对象
the car's Info in class:type passat price:250000
passat已经行驶了300公里
passat的价格是250000元
通过对比我们可以发现:
面向对象的实现方式封装性更好,已经行驶的公里数是对象内部的属性,对象自身负责管理,外部调用代码无需管理。我们随时可以调用对象的方法和属性得知对象当前的各种信息。而面向过程的方式而言,外部调用代码会“手忙脚乱”。
再加一个需求:车每行驶1公里,车的价值贬值10元,面向对象的实现方式,只需添加一行代码
def driveDistance(self,distance):
self.distance += distance
self.price -= distance * 10
全部代码
class Car:
def __init__(self,type,price):
self.type = type
self.price = price
self.distance = 0 #新车
def printCarInfo(self):
print("the car's Info in class:type %s price:%d"%(self.type,self.price))
def driveDistance(self,distance):
self.distance += distance
self.price -= distance * 10
print('面向对象')
car1 = Car('passat',250000)
car1.printCarInfo()
car1.driveDistance(100)
car1.driveDistance(200)
print(f'passat已经行驶了{car1.distance}公里')
print(f'passat的价格是{car1.price}元')
输出
面向对象
the car's Info in class:type passat price:250000
passat已经行驶了300公里
passat的价格是247000元
面向过程的方式,请自行实现(比较麻烦),可以拿公司运营作为比喻来说明面向对象开发方式的优点
外部调用代码好比老板,面向过程中函数好比员工,让员工完成一个任务,需要老板不断地干涉,大大影响了老板的工作效率。面向对象中对象好比员工,让员工完成一个任务,老板只要下命令即可,员工可以独当一面,大大节省了老板的时间。
还有一种说法:
世界上本没有类,代码写多了,也就有了类
面向对象开发方式是晚于面向过程方式出现的,几十年前,面向对象还没有出现以前,很多软件系统地代码量就已经达到几十上百万行,科学家为了组织这些代码,将各种关系密切的数据,和相关的方法分门别类,放到一切管理,就形成了面向对象的开发思想和编程语言语法。
1.7.私有属性--封装
在实际开发中,对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问到
定义方式:
在定义属性或方法时,在属性名或者方法名前增加两个下划线__实际开发中私有属性也不是一成不变的。所以要给私有属性提供外部能够操作的方法。
1.7.1.通过自定义get,set方法提供私有属性的访问
class Person:
def __init__(self,name,age):
self.name = name
self.__age = age
#定义对私有属性的get方法,获取私有属性
def getAge(self):
return self.__age
#定义对私有属性的重新赋值的set方法,重置私有属性
def setAge(self,age):
self.__age = age
person1 = Person("tom",19)
person1.setAge(20)
print(person1.name,person1.getAge())
tom 20
1.7.2.调用property方法提供私有属性的访问
class Student:
def __init__(self,name,age):
self.name = name
self.__age = age
#定义对私有属性的get方法,获取私有属性
def getAge(self):
return self.__age
#定义对私有属性的重新赋值的set方法,重置私有属性
def setAge(self,age):
self.__age = age
p = property(getAge,setAge) #注意里面getAge,setAge不能带()
s1 = Student('Jack',22)
s1.p = 23 #如果使用=,则会判断为赋值,调用setAge方法
print(s1.name,s1.p)
print(s1.name,s1.getAge())
Jack 23
Jack 23
1.7.3.使用property标注提供私有属性的访问
class Teacher:
def __init__(self,name,age,speak):
self.name = name
self.__age = age
self.__speak = speak
@property #注意1::@proterty下面默认跟的是get方法,如果设置成set会报错
def age(self):
return self.__age
@age.setter #注意2:这里是使用的上面函数名.setter,不是property.setter
def age(self,age):
if age > 150 and age <= 0:
print("年龄输入有误")
else:
self.__age = age
@property
def for_speak(self): #这个同名函数名可以自定义名称,一般都是默认使用属性名
return self.__speak
@for_speak.setter
def for_speak(self,speak):
self.__speak = speak
t1 = Teacher("herry",45,"Chinese")
t1.age = 38 #注意4:有了property后,直接使用t1.age,而不是t1.age()方法了
t1.for_speak = "English"
herry 38 English
1.8.将实例用作属性--对象组合
使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分属性和方法作为一个独立的类提取出来,你可以将大型类拆分成多个协同工作的小类。
1.8.1.实例1摆放家具
需求
1.房子(House)有户型、总面积和家具名称列表
- 新房子没有任何的家具
2.家具(HouseItem)有名字和占地面积,其中 - 席梦思(bed)占地4平米
- 衣柜(chest)占地2平米
- 餐桌(table)占地1.5平米
3.将以上三件家具添加到房子中
4.打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表
剩余面积
1.在创建房子对象时,定义一个剩余面积的属性,初始值和总面积相等
2.当调用add_item方法,向房间添加面具时,让剩余面积 -= 家具面积
思考:应该先开发哪一个类?
答案--家具类
1.家具简单
2.房子要使用到家具,被使用的类,通常应该先开发
创建家具
class HouseItem:
def __init__(self,name,area):
"""
:param name: 家具名字
:param area: 占地面积
"""
self.name = name
self.area = area
def __str__(self):
return "[%s] 占地面积 %.2f" % (self.name,self.area)
bed = HouseItem('席梦思',4)
chest = HouseItem('衣柜',2)
table = HouseItem('餐桌',1.5)
print(bed)
print(chest)
print(table)
小结
1.创建了一个家具类,使用到____init____和____str____两个内置方法
2.使用家具类创建了三个家具对象,并且输出家具信息
创建房间
class House:
def __init__(self,house_type,area):
"""
:param house_type: 户型
:param area: 总面积
"""
self.house_type = house_type
self.area = area
self.free_area = area
self.item_list = []
def __str__(self):
return("户型:%s\n总面积:%.2f[剩余:%.2f]\n家具:%s") %(self.house_type,self.area,self.free_area,self.item_list)
def add_item(self,item):
print("要添加 %s" % item)
#创建房子对象
my_hone = House("两室一厅",60)
my_hone.add_item(bed)
my_hone.add_item(chest)
my_hone.add_item(table)
print(my_hone)
小结:
1.创建了一个房子类,使用到____init____和____str____两个内置方法
2.准备了一个add_item方法添加家具
3.使用房子类创建了一个房子对象
4.让房子对象调用了三次add_item方法,将三件家具以实参传递到add_item内部
添加家具
需求:
- 1>判断家具的面积是否超过剩余面积,如果超过,提示不能添加这件家具
- 2>将家具的名称追加到家具名称列表中
- 3>用房子的剩余面积-家具面积
def add_item(self,item):
print("要添加 %s" % item)
if item.area > self.free_area:
print("%s 的面积太大,不能添加到房子中" % item.name)
return
self.item_list.append(item.name)
self.free_area -= item.area
小结
- 主程序只负责创建房子对象和家具对象
- 让房子对象调用add_item方法将家具添加到房子中
- 面积计算、剩余面积、几句列表等处理都被封装到房子类的内部
1.8.2.实例2 英雄PK怪物
hero.py
from random import randint
class Hero:
def __init__(self,name,flood,strength):
self.name = name
self.flood = flood
self.strength = strength
def calc_health(self):
return self.flood
def take_damage(self,monster):
dmage = randint(monster.strength - 5,monster.strength + 5)
self.flood -= dmage
print(f"{self.name}被{monster.name}攻击,受到了{str(dmage)}点伤害,还剩{str(self.flood)}点血")
if self.calc_health() <= 0:
print(f"{self.name}被杀死了,重新游戏吧~")
return True
else:
return False
monster.py
from random import randint
class Monster:
def __init__(self,name,flood,strength):
self.name = name
self.flood = flood
self.strength = strength
def calc_health(self):
return self.flood
def take_damage(self,hero):
dmage = randint(hero.strength - 5,hero.strength + 5)
self.flood -= dmage
print(f"{self.name}被{hero.name}攻击,受到了{str(dmage)}点伤害,还剩{str(self.flood)}点血")
if self.calc_health() <= 0:
print(f"{self.name}被杀死了,重新游戏吧~")
return True
else:
return False
fight.py
from hero import Hero
from monster import Monster
hero = Hero("水水",100,10)
monster = Monster("某小傻子",60,20)
while True:
monster_died = monster.take_damage(hero)
if monster_died:
break
hero_died = hero.take_damage(monster)
if hero_died:
break
某次输出
某小傻子被水水攻击,受到了11点伤害,还剩49点血
水水被某小傻子攻击,受到了19点伤害,还剩81点血
某小傻子被水水攻击,受到了14点伤害,还剩35点血
水水被某小傻子攻击,受到了24点伤害,还剩57点血
某小傻子被水水攻击,受到了15点伤害,还剩20点血
水水被某小傻子攻击,受到了20点伤害,还剩37点血
某小傻子被水水攻击,受到了7点伤害,还剩13点血
水水被某小傻子攻击,受到了16点伤害,还剩21点血
某小傻子被水水攻击,受到了15点伤害,还剩-2点血
某小傻子被杀死了,重新游戏吧~
1.9.类属性 类方法 静态方法
1.9.1.类对象,实例对象
类对象:类名
实例对象:类创建的对象
类属性就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本,这个和C++、Java中类的静态成员变量有点类似。对于共有的属性,在类外可以通过类对象和实例对象访问
#类属性
class people:
name = "Tom"
__age = 18
p = people()
print(p.name) #实例对象
print(people.name) #类对象
print(p.__age) #错误,不能在类外通过实例对象访问私有的类属性
print(people.__age) #错误,不能在类外通过类对象访问私有的类属性
实例属性
class people:
name = "Tom"
p = people()
p.age = 18
print(p.name)
print(p.age) #实例属性是实例对象特有的,类对象不能拥有
print(people.name)
# print(people.age) #错误,实例属性,不能通过类对象调用
我们经常将实例属性放在构造方法中
class people:
name = "tom"
def __init__(self,age):
self.age = age
p = people(18)
print(p.name)
print(p.age) #实例属性是实例对象特有的,类对象不能拥有
print(people.name)
# print(people.age) #错误,实例属性,不能通过类对象调用
类属性和实例属性混合
class people:
name = "tom"
def __init__(self,age):
self.age = age
p = people(18) #实例对象
p.sex = "男" #实例属性
print(p.name)
print(p.age) #实例属性是实例对象特有的,类对象不能拥有
print(p.sex)
print(people.name)
# print(people.age) #错误,实例属性,不能通过类对象调用
# print(people.sex) #错误,实例属性,不能通过类对象调用
如果在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且如果通过实例对象引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性
class Animal:
name = "Panda"
print(Animal.name) #类对象引用类属性
p = Animal()
print(p.name)
p.name = "dog"
print(p.name)
print(Animal.name)
del p.name
print(p.name)
1.9.2.类属性的应用场景
比如,我们有一个班级类,创建班级对象的时候,需要按序号指定班级名称,我们就需要知道当前已经创建了多少个班级对象,这个数量可以设计成类属性
ass NeuEduClass:
class_num = 0
def __init__(self):
self.class_num = f'东软睿道Python{NeuEduClass.class_num + 1}班'
NeuEduClass.class_num += 1
classList = [NeuEduClass() for i in range(10)]
for c in classList:
print(c.class_num)
输出
东软睿道Python1班
东软睿道Python2班
东软睿道Python3班
东软睿道Python4班
东软睿道Python5班
东软睿道Python6班
东软睿道Python7班
东软睿道Python8班
东软睿道Python9班
东软睿道Python10班
1.9.3.类方法
类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以‘cls’作为第一个参数的名字),能够通过实例对象和类对象去访问。
class people:
country = "China"
@classmethod
def getCountry(cls):
return cls.country
p = people()
print(p.getCountry()) #实例对象调用类方法
print(people.getCountry()) #类对象调用类方法
类方法还有一个用途就是可以对类属性进行修改:
class people:
country = "China"
@classmethod
def getCountry(cls):
return cls.country
@classmethod
def setCounrty(cls,country):
cls.country=country
p = people()
print(p.getCountry()) #实例对象调用类方法
print(people.getCountry()) #类对象调用类方法
p.setCounrty("Japan")
print(p.getCountry())
print(people.getCountry())
1.9.4.静态方法
需要通过修饰器@staticmethod来修饰,静态方法不需要定义参数
class people3:
country = "China"
@staticmethod
def getCountry():
return people3.country
p = people3()
print(p.getCountry()) #实例对象调用类方法
print(people3.getCountry()) #类对象调用类方法
1.10.继承
继承的概念:子类自动用用(继承)父类的方法和属性1)继承的语法
class 类名(父类名):
pass
- 子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
- 子类中应该根据职责,封装子类特有的属性和方法
- 当父类的方法实现不能满足子类需求时,可以对方法进行重写
举一个常见的例子。Circle和Rectangle,不同的图形,面积计算方法不同
import math
class Circle:
def __init__(self,color,r):
self.r = r
self.color = color
def area(self):
return math.pi * (self.r**2)
def show_color(self):
print(self.color)
class Rectangle:
def __init__(self,color,a,b):
self.color = color
self.a,self.b = a,b
def area(self):
return self.a * self.b
def show_color(self):
print(self.color)
circle = Circle('red',3)
print(circle.area())
circle.show_color()
rectangle = Rectangle('blue',2,3)
print(rectangle.area())
rectangle.show_color()
输出
28.274333882308138
red
6
blue
我们看到Rectangle和Circle有同样的属性color和方法show_color,我们可以定义一个父类shape,将Rectangle和Circle通用的部分提取到Shape类中,然后在子类的init方法中,通过调用super()____init____(color),把color传给父类的__init()
import math
class Shape:
def __init__(self,color):
self.color = color
def area(self):
return None
def show_color(self):
print(self.color)
class Cirle(Shape):
def __init__(self,color,r):
super().__init__(color)
self.r = r
def area(self):
return math.pi * (self.r ** 2)
class Rectangle(Shape):
def __init__(self,color,a,b):
super().__init__(color)
self.a,self.b = a,b
def area(self):
return self.a * self.b
circle = Cirle('red',3)
print(circle.area())
circle.show_color()
rectangle = Rectangle('blue',2,3)
print(rectangle.area())
rectangle.show_color()
输出
28.274333882308138
red
6
blue
子类Circle和Rectangle本身并没有定义show_color方法,从父类Shape继承了show_color方法。子类Circle和Rectangle改写(Override)了父类的area方法,分别提供了字节不同的实现。
注释掉Circle类中init方法中super().____init____()这行代码:
class Circle(Shape):
def __init__(self, color, r):
# super().__init__(color)
# Shape.__init__(self,color) #这样也行,但是不好(考虑父类Shape的名字改变了,怎么办)
self.r = r
再次运行代码,报错:
File "C:/Users/Administrator/PycharmProjects/untitled16/shape.py", line 12, in show_color
print(self.color)
AttributeError: 'Circle' object has no attribute 'color'
错误输出指定的出错位置在:print(self.color),说Circle类型对象没有color属性
class Shape:
def show_color(self):
print(self.color)
问题原因是,Circle类型重写了父类Shape的____init____()方法,导致父类Shape的____init()方法没有被调用到,所以一定不要忘记了在子类的init方法中调用super().____init____()
1.10.1.重构英雄PK怪物
我们发现Hero类和Monster类中有很多代码是重复的,我们把这些重复的代码提取到一个父类Sprite(精灵)中。
sprite.py
from random import randint
class Sprite:
def __init__(self,flood,strength):
self.flood = flood
self.strength = strength
def calc_health(self):
return self.flood
def take_damage(self,attack_sprite):
damage = randint(attack_sprite.strength - 5,attack_sprite.strength + 5)
self.flood -= damage
print(f"{self.name}被{attack_sprite.name}攻击,受到了{str(damage)}点伤害!还剩{str(self.flood)}点血")
if self.calc_health() <= 0:
print(f"{self.name}被杀死了!请重新游戏~")
return True
else:
return False
hero.py
from sprite import Sprite
class Hero(Sprite):
def __init__(self,name,flood,strength):
self.name = name
super().__init__(flood,strength)
monster.py
from sprite import Sprite
class Monster(Sprite):
def __init__(self,name,flood,strength):
self.name = name
super().__init__(flood,strength)
fight.py
from hero import Hero
from monster import Monster
monster = Monster("阿崔",60,20)
hero = Hero("水水",100,10)
while True:
monster_died = monster.take_damage(hero)
if monster_died:
break
hero_died = hero.take_damage(monster)
if hero_died:
break
输出
阿崔被水水攻击,受到了9点伤害!还剩51点血
水水被阿崔攻击,受到了18点伤害!还剩82点血
阿崔被水水攻击,受到了13点伤害!还剩38点血
水水被阿崔攻击,受到了22点伤害!还剩60点血
阿崔被水水攻击,受到了11点伤害!还剩27点血
水水被阿崔攻击,受到了17点伤害!还剩43点血
阿崔被水水攻击,受到了10点伤害!还剩17点血
水水被阿崔攻击,受到了19点伤害!还剩24点血
阿崔被水水攻击,受到了11点伤害!还剩6点血
水水被阿崔攻击,受到了16点伤害!还剩8点血
阿崔被水水攻击,受到了7点伤害!还剩-1点血
阿崔被杀死了!请重新游戏~
进一步改造:假设我们想修改伤害值damage的大小,不在Sprite类里统一处理,而由各个具体的精灵(Hero或Monster)自己来决定,代码可以改造如下:在Sprite类中添加get_damage_value方法,不提供实现
from random import randint
class Sprite:
def __init__(self,flood,strength):
self.flood = flood
self.strength = strength
def calc_health(self):
return self.flood
def get_damage_value(self):
pass
def take_damage(self,attack_sprite):
damage = randint(attack_sprite.strength - 5,attack_sprite.strength + 5)
self.flood -= damage
print(f"{self.name}被{attack_sprite.name}攻击,受到了{str(damage)}点伤害!还剩{str(self.flood)}点血")
if self.calc_health() <= 0:
print(f"{self.name}被杀死了!请重新游戏~")
return True
else:
return False
hero.py提供自己get_damage_value方法的实现
from sprite import Sprite
from random import randint
class Hero(Sprite):
def __init__(self,name,flood,strength):
self.name = name
super().__init__(flood,strength)
def get_damage_value(self):
return randint(self.strength - 3,self.strength + 3)
monster.py提供自己get_damage_value方法的实现
from sprite import Sprite
from random import randint
class Monster(Sprite):
def __init__(self,name,flood,strength):
self.name = name
super().__init__(flood,strength)
def get_damage_value(self):
return randint(self.strength - 7,self.strength + 7)
输出
阿崔被水水攻击,受到了14点伤害!还剩46点血
水水被阿崔攻击,受到了24点伤害!还剩76点血
阿崔被水水攻击,受到了15点伤害!还剩31点血
水水被阿崔攻击,受到了15点伤害!还剩61点血
阿崔被水水攻击,受到了5点伤害!还剩26点血
水水被阿崔攻击,受到了19点伤害!还剩42点血
阿崔被水水攻击,受到了7点伤害!还剩19点血
水水被阿崔攻击,受到了23点伤害!还剩19点血
阿崔被水水攻击,受到了7点伤害!还剩12点血
水水被阿崔攻击,受到了15点伤害!还剩4点血
阿崔被水水攻击,受到了15点伤害!还剩-3点血
阿崔被杀死了!请重新游戏~
1.11.____new____方法
python中定义的类在创建实例对象的时候,会自动执行init()方法,但是在执行Init()方法之前,会执行new()方法。
____new____()的作用主要有两个
1.在内存中为对象分配空间
2.返回对象的引用(即对象的内存地址)
python解释器在获得引用的时候会将其传递给init()方法中的self。
class A:
def __new__(cls, *args, **kwargs):
print('__new__')
return super().__new__(cls) #这里一定要返回,否则__init__()方法不会被执行
def __init__(self): #这里的self就是new方法中的return返回值
print('__init__')
a = A()
输出结果
__new__
__init__
我们一定要在____new____方法中最后调用
class A:
def __new__(cls, *args, **kwargs):
print('__new__')
return super().__new__(cls) #这里一定要返回,否则__init__()方法不会被执行
def __init__(self): #这里的self就是new方法中的return返回值
print('__init__')
a = A()
输出结果
__new__
__init__
我们一定要在____new____方法中最后调用
retrun super().____new____(cls)
否则init方法不会被调用
class A:
def __new__(cls, *args, **kwargs):
print('__new__')
# return super().__new__(cls) #这里一定要返回,否则__init__()方法不会被执行
def __init__(self): #这里的self就是new方法中的return返回值
print('__init__')
a = A()
输出
__new__
像以前一样,我们不写____new____方法试试
class A:
def __new__(cls, *args, **kwargs):
print('__new__')
# return super().__new__(cls) #这里一定要返回,否则__init__()方法不会被执行
def __init__(self): #这里的self就是new方法中的return返回值
print('__init__')
a = A()
输出
__new__
1.12.object---所有python类型的父类
在python中,所有类型有个隐式的父类(object),上面的代码相当于
class A(object):
# def __new__(cls, *args, **kwargs):
# print('__new__')
# return super().__new__(cls) #这里一定要返回,否则__init__()方法不会被执行
def __init__(self): #这里的self就是new方法中的return返回值
print('__init__')
a = A()
当我们的类中没有定义____new____方法时,创建对象时,python会先调用父类(object)的____new____方法,在内存中创建一个实例,然后传递给我们的init方法。如果我们的自定义类型定义了____new____方法,就会覆盖父类(object)的____new____方法(父类的____new____方法不会被自动调用),所以我们要显式调用父类(object)的____new____方法,在内存中创建一个实例。
那么new有什么作用呢?我们可以改写它,举个栗子,就比如说单例模式。
1.13.单例模式
如果我们创建两个实例
a1 = A()
a2 = A()
那么id(a1)和id(a2)的值不一样,也就是说python在内容当中创建了两个实例对象,用了两份内存。同样的东西创建了两份
如果想不管创建多少个实例对象,我们都让它的id是一样的。
也就是说,先创建一个实例对象,之后不管创建多少个,返回的永远都是第一个实例对象的内存地址。可以这样实现:
# class A(object):
# # def __new__(cls, *args, **kwargs):
# # print('__new__')
# # return super().__new__(cls) #这里一定要返回,否则__init__()方法不会被执行
# def __init__(self): #这里的self就是new方法中的return返回值
# print('__init__')
#
# a = A()
#重写new方法很固定,返回值必须是这个
#这样就避免了创建多份
#创建第一个实例的时候,_instance是None,那么会分配空间创建实例。
#此时的类属性已经被修改,_instance不再为None
#那么当之后实例属性被创建的时候,由于_instance不为None.
#则返回第一个实例对象的应用,即内存地址。
#这样就应用了单例模式。
class A:
_instance = None
def __new__(cls, *args, **kwargs):
if A._instance == None:
A._instance = super().__new__(cls)
return A._instance
a1 = A()
print(id(a1))
a2 = A()
print(id(a2))
1.14.函数参数注解
你写好了一个函数,然后想为这个函数的参数增加一些额外的信息(每个参数的类型),这样的话其他调用者就能清楚地知道这个函数应该怎么使用。
解决方案
使用函数参数注解是一个很好的办法,它能提示程序员应该怎样正确使用这个函数。例如,下面有一个被注解了的函数:
def add(x:int,y:int) -> int:
return x + y
python解释器不会对这些注解添加任何的语义,它们不会被类型检查,运行时没有加注解之前的效果也没有任何差距。然而,对于那些阅读源码的人来讲就很有帮助啦。第三方工具和框架可能会对这些注解添加语义。同时它们也会出现在文档中。
help(add)
Help on function add in module __main__:
add(x:int,y:int) -> int
尽管你可以使用任意类型的对象给函数添加注解(例如数字,字符串,对象实例等等),不过通常来讲使用类或者字符串会比较好点。
函数注解只存储在函数的annotations属性中。例如:
add.__annotations__
{'y':<class 'int'>,'return':<class 'int'>, 'x':<class 'int'>}
尽管注释的使用方法可能有很多种,但是它们的主要用途还是文档。因为python并没有类型声明,通常来讲仅仅通过阅读源码很难知道应该传递什么样的参数给这个参数。这时候使用注释就能给程序员更多的提示,让他们可以正确的使用函数。
函数参数注解还给我们带来的好处有使得pycharm这样的IDE可以自动提示该类型对象拥有的方法。
class A:
def desomething(self):
print('hello')
def foo(a:A):
a.desomething()
a = A()
foo(a)
在foo函数的定义中,如果我们没有指定a:A,当我们输入a.时,IDE提示不出来dosomething方法。
1.15.扑克牌模拟
任务描述:
1.定义一个单张扑克类(考虑需要哪些属性),定义一个一副扑克牌类,该类包含一个单张扑克对象的数组(不考虑大小王)。实现一个模拟扑克发牌洗牌的算法;
2.电脑随机发出5张牌,判断是以下哪种牌型?(提示:利用Map,List,Set等各种集合的特性可以简化判断)
参考代码
import random
colorLst = ['红桃', '方片', '黑桃', '草花']
class Card:
def __init__(self, color, value):
self.value = value
self.color = color
def __str__(self):
strvalue = str(self.value)
if self.value == 11:
strvalue = 'J'
elif self.value == 12:
strvalue = 'Q'
elif self.value == 13:
strvalue = 'K'
elif self.value == 1:
strvalue = 'A'
return self.color + str(strvalue)
class Poke:
def __init__(self):
self.cards = []
for color in colorLst:
for value in range(1, 14):
card = Card(color, value)
self.cards.append(card)
def output(self):
index = 1
for card in self.cards:
print(card, end='\t')
if index % 13 == 0:
print()
index += 1
poke = Poke()
poke.output()
random.shuffle(poke.cards)
print('洗牌之后')
poke.output()
hands = poke.cards[:5]
print('分到的一手牌是')
for card in hands:
print(card, end='\t')
print('\n牌型是:', end='')
bSameColor = False
bShunZi = False
cardColors = [card.color for card in hands]
cardValues = [card.value for card in hands]
cardValuesSet = set(cardValues)
cardColorsSet = set(cardColors)
if len(cardColorsSet) == 1:
bSameColor = True
cardValuesSorted = sorted(cardValues)
if cardValuesSorted[4] - cardValuesSorted[0] == 4 and len(cardValuesSet) == 5:
bShunZi = True
if bSameColor and bShunZi:
print('同花顺')
exit(0)
if bSameColor:
print('同花')
exit(0)
if bShunZi:
print('顺子')
exit(0)
if len(cardValuesSet) == 4:
print('一对')
if len(cardValuesSet) == 5:
print('杂牌')
if len(cardValuesSet) == 2:
if cardValues.count(cardValues[0]) == 1 or cardValues.count(cardValues[0]) == 4:
print('四带一')
else:
print('三带二')
bisThreeOneOne = False
if len(cardValuesSet) == 3:
for value in cardValues:
if cardValues.count(value) == 3:
bisThreeOneOne = True
break
if bisThreeOneOne == False:
print('221')
else:
print('311')
输出
红桃A 红桃2 红桃3 红桃4 红桃5 红桃6 红桃7 红桃8 红桃9 红桃10 红桃J 红桃Q 红桃K
方片A 方片2 方片3 方片4 方片5 方片6 方片7 方片8 方片9 方片10 方片J 方片Q 方片K
黑桃A 黑桃2 黑桃3 黑桃4 黑桃5 黑桃6 黑桃7 黑桃8 黑桃9 黑桃10 黑桃J 黑桃Q 黑桃K
草花A 草花2 草花3 草花4 草花5 草花6 草花7 草花8 草花9 草花10 草花J 草花Q 草花K
洗牌之后
黑桃J 红桃3 红桃Q 红桃9 草花7 黑桃A 方片Q 草花9 红桃5 红桃2 方片A 方片5 方片8
方片K 方片6 红桃8 草花Q 草花4 草花J 黑桃4 黑桃10 草花2 黑桃Q 红桃6 方片J 方片10
红桃J 黑桃5 方片3 草花6 黑桃6 红桃A 黑桃7 方片7 方片9 红桃K 红桃10 草花8 方片4
黑桃9 黑桃K 红桃7 黑桃3 草花10 草花K 黑桃8 红桃4 草花3 草花A 草花5 方片2 黑桃2
分到的一手牌是
黑桃J 红桃3 红桃Q 红桃9 草花7
牌型是:杂牌