面向对象编程(补充)
注:
设计函数三要素:干什么的,需要接受的信息(参数),返回值是什么
-
私有属性---封装
在实际开发中,对象 的 某些属性或方法 可能只希望 在对象的内部被使用,而 不希望在外部被访问到
定义方式:在 定义属性或方法时,在 属性名或者方法名前 增加 两个下划线__
实际开发中私有属性也不是一层不变的。
通过自定义get,set方法提供私有属性的访问
因为在外部不能调用__age属性,所以通过类的内部创建方法调用__age,在外部再通过调用这个方法来调用这个属性
注意:set需要传参。
用处:用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
调用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) #jack 23 ,直接使用s1.p会自动判断会取值,调用getAge
print(s1.name,s1.getAge()) #jack 23,这个时候set,get方法可以单独使用。
使用property标注提供私有属性的访问。(常用)
注意:有了property后,直接使用t1.age,而不是t1.age()方法了。
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: #还可以在setter方法里增加判断条件
print("年龄输入有误")
else:
self.__age = age
@property
def for_speak(self): #注意2.这个同名函数名可以自定义名称,一般都是默认使用属性名。
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"
print(t1.name,t1.age,t1.for_speak) #herry 38 English
-
将实例用作属性---对象组合(has a 关系)
使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分属性和方法作为一个独立的类提取出来。你可以将大型类拆分成多个协同工作的小类。
实例1:摆放家具
需求
- 房子(House) 有 户型、总面积 和 家具名称列表
- 新房子没有任何的家具
- 家具(HouseItem) 有 名字 和 占地面积,其中
- 席梦思(bed) 占地 4 平米
- 衣柜(chest) 占地 2 平米
- 餐桌(table) 占地 1.5 平米
- 将以上三件 家具 添加 到 房子 中
- 打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表
剩余面积
- 在创建房子对象时,定义一个 剩余面积的属性,初始值和总面积相等
- 当调用 add_item 方法,向房间 添加家具 时,让 剩余面积 -= 家具面积
思考:应该先开发哪一个类?
答案 —— 家具类
- 家具简单
- 房子要使用到家具,
被使用的类
,通常应该先开发
创建家具
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)
# 1\. 创建家具
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌", 1.5)
print(bed)
print(chest)
print(table)
小结
- 创建了一个 家具类,使用到 init 和 str 两个内置方法
- 使用 家具类 创建了 三个家具对象,并且 输出家具信息
创建房间
class House:
def __init__(self,type,area):
self.type = type
self.area = area
self.remain_area = area
#self.house_items保存的是HouseItem类型的对象
self.house_items = []
def __str__(self):
return f"户型:{self.type} 总面积:{self.area}[剩余:{ self.remain_area}]\n家具:{self.house_items}"
def add_item(self,house_item):
self.house_items.append(house_item)
self.remain_area -= house_item.area
bed = HoureItem('席梦思',4)
chest = HoureItem('衣柜',2)
table = HoureItem('餐桌',1.5)
house = House('三室一厅',123)
print(house)
print('-'*70)
house.add_item(bed)
house.add_item(chest)
house.add_item(table)
print(house)
小结
- 主程序只负责创建 房子 对象和 家具 对象
- 让 房子 对象调用 add_item 方法 将家具添加到房子中
- 面积计算、剩余面积、家具列表 等处理都被 封装 到 房子类的内部
实例2:英雄pk怪物
Sprite.py (精灵类,一般游戏开发时把怪物、英雄什么的统一封装到这里)
class Sprite:
def __init__(self,name,blood=100,strength=100):
self.name = name
self.blood = blood
self.strength = strength
def calc_health(self):
return self.blood
def get_damage_point(self):
return 0
def take_damage(self, sprite):
# damage_point = random.randint(sprite.strength - 10, sprite.strength + 10)
damage_point = sprite.get_damage_point()
self.blood -= damage_point
print(f"{self.name} 你被{sprite.name}攻击,受到了{str(damage_point)}点伤害!还剩{str(self.blood)}滴血")
if self.blood > 0:
return False
else:
print(f"{self.name}你被杀死了!胜败乃兵家常事 请重新来过。")
return True
Hero.py
import random
from Sprite import Sprite
class Hero(Sprite):
def __init__(self,name,blood=100,strength=100):
super().__init__(name, blood, strength)
def get_damage_point(self):
damage_point = random.randint(self.strength - 7, self.strength + 12)
return damage_point
Monster.py
import random
from Sprite import Sprite
class Monster(Sprite):
def __init__(self,name,blood=100,strength=100):
super().__init__(name,blood,strength)
def get_damage_point(self):
damage_point = random.randint(self.strength - 9, self.strength + 13)
return damage_point
Game.py
import time
from Monster import Monster
from Hero import Hero
hero = Hero('关羽',150,20)
mon = Monster('吕布',200,15)
while True:
#关羽攻击吕布
isMonDie = mon.take_damage(hero)
if isMonDie:
break
# 吕布攻击关羽
isHeroDie = hero.take_damage(mon)
if isHeroDie:
break
time.sleep(0.3)
-
类属性 类方法 静态方法
类属性
如果在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性。
# 类属性
class people:
name="Tom" #公有的类属性 # 类属性:实例对象和类对象可以同时调用
__age=18 #私有的类属性
def __init__(self,age):
self.age=age # 实例属性
p=people()
print(p.name) #实例对象
print(people.name) #类对象
# print(p.__age) #错误 不能在类外通过实例对象访问私有的类属性
print(people.__age) #错误 不能在类外同过类对象访问私有的类属性
类方法
类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以'cls'作为第一个参数的名字),能够通过实例对象和类对象去访问。
类方法还有一个用途就是可以对类属性进行修改
class people:
country="china"
@classmethod
def getCountry(cls):
return cls.country
p=people()
print(p.getCountry()) #实例对象调用类方法
print(people.getCountry()) #类对象调用类方法
静态方法
需要通过修饰器@staticmethod来进行修饰,静态方法不需要定义参数
class people3:
country="china"
@staticmethod
def getCountry():
return people3.country
p=people3()
print(p.getCountry()) #实例对象调用类方法
print(people3.getCountry()) #类对象调用类方法
-
继承(A is B 关系)
继承的概念:子类 自动拥有(继承) 父类 的所有 方法
和 属性
继承的语法:
class 类名(父类名):
pass
- 子类 继承自 父类,可以直接 享受 父类中已经封装好的方法,不需要再次开发
- 子类 中应该根据 职责,封装 子类特有的 属性和方法
- 当 父类 的方法实现不能满足子类需求时,可以对方法进行 重写(override)
示例:
我们看到Rectangle和Circle有同样的属性color和方法showcolor 我们可以定义一个父类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 Circle(Shape):
def __init__(self, color, r):
super().__init__(color)
# Shape.__init__(self,color) #这样也行,但是不好(考虑父类Shape的名字改变了,怎么办)
self.r = r
def area(self):
return math.pi * self.r * self.r
class Rectangle(Shape):
def __init__(self, color, a, b):
super().__init__(color)
# Shape.__init__(self, color) #这样也行,但是不好(考虑父类Shape的名字改变了,怎么办)
self.a, self.b = a, b
def area(self):
return self.a * self.b
circle = Circle('red', 3.0)
print(circle.area())
circle.show_color()
rectangle = Rectangle('blue', 2.0, 3.0)
print(rectangle.area())
rectangle.show_color()
子类Circle和Rectangle本身并没有定义show_color方法, 从父类Shape继承了show_color方法。子类Circle和Rectangle改写(Override)了父类的area方法,分别提供了自己不同的实现。
一定不要忘记在子类的init方法中调用super()._init_()
-
__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()
-
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()
-
单例模式
单例模式,相当于创建一个共享区,里面对象是唯一的
如果我们创建两个实例:
a1 = A()
a2 = A()
那么id(a1)和id(a2)的值不一样,也就是说python在内容当中创建了两个实例对象,用了两份内存。同样的东西创建了两份
如果想不管创建多少个实例对象,我们都让它的id是一样的。
也就是说,先创建一个实例对象,之后不管创建多少个,返回的永远都是第一个实例对象的内存地址
。可以这样实现:
# 重写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) # super().__new__(cls)就是第一个对象的内存地址
return A._instance
a1 = A()
print(id(a1))
a2 = A()
print(id(a2))
网友评论