目录
前言
1. @property装饰器
2. 初识__slots__
3. 静态类方法和类方法
4. 类之间的关系
5. 继承和多态
6. 案例
前言
已经了解了面向对象基础知识,比如如何定义类,如何创建对象和向对象发消息。当然这些在真正的编程里是不够的,我们必须要更好的掌握和使用面向对象编程,那就还得对python中的面向对象编程进行更为深入的了解。
1. @property装饰器
如何完成既要保护类的封装特性,又要让开发者可以使用“对象.属性”的方式操作类的属性。除了使用property()函数,python还提供了@property装饰器,可以直接通过方法名来访问方法,不需要在方法名后添加一对‘()’小括号。在之前的小节里是将属性名以单下划线的开头,来暗示属性是受保护的,不建议外界直接访问。在这里将提供新的方法,使用属性的getter(访问器)和setter(修改器)来进行对应的操作。使用@property来包装getter和seter方法,使得对象的访问很安全。
class Presonal(object):
def __init__(self,name,age): # 初始化对象 绑定属性
self._name = name
self._age = age
# 访问器 - getter方法
@property
def name(self):
return self._name
@property
def age(self):
return self._age
# 修改器 - setter方法
@age.setter
def age(self,age):
self._age = age
def play(self):
if self._age <= 16:
print("%s正在看喜羊羊与灰太狼" % self._name)
else:
print("%s正在**************" % self._name)
def main():
person = Presonal('阿尼玛',13)
person.play()
person.age = 22
person.play()
person.name = 'hahah'
#person.play() :AttributeError: can't set attribute
if __name__ == "__main__":
main()
神奇的事情,作为动态语言python可是将动态发挥到了极致,python允许我们在程序运行时更改绑定的属性和方法。
2. 初识__slots__
动态语言允许在程序运行的时候给对象绑定新的属性和方法,也可以对绑定的属性进行解绑。使用__slots__可以做什么呢?可以限定自定义类型的对象只能绑定一些类型的属性,当然__slots__的限定只对当前类的对象生效,对子类不起作用。
class personal(object):
# 限定personal对象只能绑定_name,_age,_gender属性
__slots__ = ("_name","_age","_gender")
def __init__(self,name,age):
self._name = name
self._age = age
# 访问器 getter方法
@property
def name(self):
return self._name
@property
def age(self):
return self._age
# 修改器 setter方法
@age.setter
def age(self,age):
self._age = age
def play(self):
if self._age <= 18:
print('%s正在玩坦克大战.' % self._name)
else:
print('%s人%s正在*********' % (self._gender,self._name))
def main():
pro1 = personal('小明',15)
pro1.play()
pro1._age = 30
pro1._gender = '男'
pro1.play()
pro1.is_meimei = '咦' # AttributeError: 'personal' object has no attribute 'is_meimei'
if __name__ == "__main__":
main()
执行上面这段代码会出现下面的结果,详见如下图:
3. 静态类方法和类方法
在以前的例子中定义的方法都是对象方法,结合前面的例子就是可以实际管理消息的,但类中的方法并不需要都是对象方法,比如一些判断的方法。比如某无良餐厅门口贴上“某人与狗不得入类。”,这个人不管能不能进首先得判断不能进的标识,其次才是进去吃饭。又比如“三角形”类,通过传入三边来构造三角形,提供计算周长和面积的方法。但是传入的三条边未必能构成三角形,那我们就需要写一个方法来验证三条边是否能构成三角形,这个方法就不是熟悉的对象方法,因为调用这个方法时三角形对象尚未创建出来(还不知道到底能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象。那就需要使用到静态类来解决这类问题。
from math import sqrt
class Triangle(object):
def __init__(self,a,b,c):
self._a = a
self._b = b
self._c = c
@staticmethod
def is_valid(a,b,c):
return a + b > c and a + c > b and b + c > a
def preimeter(self):
return self._a + self._b + self._c
def area(self):
half = self.preimeter() / 2
return sqrt(half * (half - self._a) * (half - self._b) * (half - self._c))
def main():
a,b,c = 4,5,6
# 静态方法和类方法都是通过给类发消息来调用
if Triangle.is_valid(a,b,c):
t = Triangle(a,b,c)
# print(t.preimeter())
# 可以通过给类发消息来调用对象方法,需要传入接收消息的对象作为参数
print(Triangle.preimeter(t))
#print(t.area())
print(Triangle.area(t))
else:
print('无法构成三角形')
if __name__ == "__main__":
main()
类方法和静态方法类似,Python还可以在类中定义方法,类方法的第一个参数约定为cls,它代表的是当前类相关的信息对象(类本身也是一个对象,也有称为类的元数据对象),通过这个参数可以获取和类相关的信息并且可以创建出类的对象。
from time import time, localtime, sleep
import os
class Clock(object):
"""数字时钟"""
def __init__(self,hour=0,minute=0,second=0):
self._hour = hour
self._minute = minute
self._second = second
# 定义类方法
@classmethod
def now(cls):
ctime = localtime(time())
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
"""打印时间"""
return '%02d:%02d:%02d' % (self._hour, self._minute, self._second)
def main():
# 通过类方法创建对象并获取系统时间
clock = Clock.now()
while True:
print(clock.show())
sleep(1)
clock.run()
os.system('cls')
if __name__ == "__main__":
main()
4. 类之间的关系
类和类之间的关系分为三种:is-a、has-a和use-a关系。
- is-a关系也叫继承或者泛化,比如学生和人的关系,学生继承了人的属性。
- has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系属于关联关系。看了前辈们的解释我觉得反而更抽象了,如果是整体和部分的关系,这便是聚合;如果整体进一步负责了部分的生命周期(强关联性),那这种强关联关系就是合成关系。(就问一句这段话到底饶不饶?)
- use-a关系通尝称之为依赖,就是为了完成使命必须要使用什么方法或者工具,(方法)和(参数)的关系最好解释这个关系。
我们可以使用一种叫做UML(统一建模语言)的东西来进行面向对象建模,其中一项重要的工作就是把类和类之间的关系用标准化的图形符号描述出来。关于UML我们在这里不做详细的介绍,有兴趣的读者可以自行阅读《UML面向对象设计基础》一书。
提供两幅插图地址有兴趣请自行点击阅览:
https://github.com/jackfrued/Python-100-Days/blob/master/Day01-15/res/uml-components.png
https://github.com/jackfrued/Python-100-Days/blob/master/Day01-15/res/uml-example.png
那类之间的关系说明可以方便做一些什么事情呢?可以在已有类的基础上来完成某些操作,可以在已有类的基础上创建新得类,主要是方便代码复用。因为现在的很多人都是在想着写一段代码能万古垂青,因为要是真的这个世界上的代码和命令都浓缩为一行该多好呀!基本上不用更新,基本上不用维护。
5. 继承和多态
在已有类的基础上创建新类,将新类的属性和方法从另一个类哪里直接继承下来,从而减少重复代码的编写。被继承者称之为父类,也叫超类或基类。继承者称之为子类,亦叫派生或者衍生类,子类要完全继承父类提供的属性和方法,当然也可以自定义新的属性和方法,所以子类比父类更强大。实际代码中可以用子类去替换一个父类对象,这类现象称之为里氏替换原则。请尝代码:
class personal(object):
"""人"""
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self,age):
self._age = age
def play(self):
print('%s正在愉快的玩耍' % self._name)
def watch_av(self):
if self._age >= 18:
print('%s可以观看两人电影' % self._name)
else:
print('!!!!%s试图观看限制电影0.0' % self._name)
class student(personal):
"""学生"""
# 继承名字和年龄
def __init__(self, name, age, grade):
super().__init__(name,age)
self._grade = grade
@property
def grade(self):
return self._grade
@grade.setter
def grade(self,grade):
self._grade = grade
def study(self,course):
print('%s的%s正在学习%s.' % (self._grade,self._name,course))
class teacher(personal):
"""老师"""
# 继承名字和年龄
def __init__(self, name, age, title):
super().__init__(name,age)
self._title = title
@property
def title(self):
return self._title
@title.setter
def title(self,title):
self._title = title
def teach(self,course):
print('%s%s正在上%s课' % (self._title,self._name,course))
def main():
stu = student('麻辣香锅', 13, '初三')
stu.study('打野')
stu.watch_av()
t = teacher('赵老三', 88, '教授')
t.teach('打麻将')
t.watch_av()
if __name__ == '__main__':
main()
子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一行为在子类中拥有不同的实现版本,当调用这个经过子类实现的方法时,不同的子类对象会表现出不同的行为,这就是多态(poly-morphism),个人觉得也就是把父类的一个方法重新定义子类。代码如下:
from abc import ABCMeta, abstractmethod
class Pet(object,metaclass=ABCMeta):
"""宠物"""
def __init__(self,nikename):
self._nikename = nikename
@abstractmethod
def make_voice(self):
"""声音"""
pass
class Dog(Pet):
"""狗"""
def make_voice(self):
print("%s:汪汪汪......" % self._nikename)
class Cat(Pet):
"""猫"""
def make_voice(self):
print("%s:喵喵喵......" % self._nikename)
def main():
pet = [Dog('二哈'),Cat('狸狸'),Dog('拉布拉多')]
for i in pet:
i.make_voice()
if __name__ == "__main__":
main()
代码实例中,Pet类被处理成一个抽象的类,抽象的类即不能够创建对象的类,此类专们让其他类继承它。python语法并没有提供对抽象类的支持,是利用abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果,如果一个类中存在抽象类的方法则这个类就不能实例化。例子中宠物类里的make_voice抽象类方法进行了重写并给出不同的实现版本,当调用该函数的时候,这个方法就表现为多态行为(我理解应该是扩展方法,但是使用同一个接口)
6. 练习
练习1:奥特曼打小怪兽
from abc import ABCMeta, abstractmethod
from random import randint, randrange
class Fighter(object,metaclass=ABCMeta):
'''战斗人物'''
# 限定Fighter对象只能绑定的_name和_hp属性
__slots__ = ('_name', '_hp')
def __init__(self, name, hp):
"""
初始化方法
:name 名字
:hp 生命值
"""
self._name = name
self._hp = hp
# 访问器 - getter方法
@property
def name(self):
return self._name
@property
def hp(self):
return self._hp
# 修改器 - setter方法
@hp.setter
def hp(self, hp):
self._hp = hp if hp >= 0 else 0
# 访问器 - getter方法
@property
def alive(self):
return self._hp > 0
@abstractmethod # 定义子类属性
def attack(self, other):
"""
攻击
other 其他被攻击的对象
"""
pass
class Ultraman(Fighter):
"""奥特曼
在子类里继承父类的_name和_hp
"""
__slots__ = ('_name', '_hp', '_mp')
def __init__(self, name, hp, mp):
"""
:name 名字
:hp 生命值
:mp 魔法值
"""
super().__init__(name, hp)
self._mp = mp
def attack(self, other):
other.hp -= randint(15,25)
def big_attack(self, other):
"""大招(打掉对方至少50点或四分之三的血)
other: 被攻击的对象
return: 使用成功返回True否则返回False
"""
if self._mp >= 50:
self._mp -= 50
injury = other.hp * 3 // 4
injury = injury if injury >= 50 else 50
other.hp -= injury
return True
else:
self.attack(other)
return False
def magic_attack(self, others):
"""
魔法攻击
:others 被攻击的群体
:return 魔法攻击成功为True否则为False
"""
if self._mp >= 20:
self._mp -= 20
for temp in others:
if temp.alive:
temp.hp -= randint(10,15)
return True
else:
return False
def recovery(self):
"""恢复魔法值"""
incr_point = randint(1,10)
self._mp += incr_point
return incr_point
def __str__(self):
return '~~~%s奥特曼~~~\n' % self._name + \
'生命值:%d\n' % self._hp + \
'魔法值:%d\n' % self._mp
class Monster(Fighter):
"""怪兽"""
__slots__ = ('_name', '_hp')
def attack(self, other):
other.hp -= randint(10,20)
def __str__(self):
return '~~~%s怪兽~~~\n' % self._name + \
'生命值: %d\n' % self._hp
def is_any_alive(Monsters):
"""判断存活的小怪兽"""
for monster in Monsters:
if monster.alive > 0:
return True
return False
def select_alive_one(Monsters):
"""选中活着的怪兽"""
Monsters_len = len(Monsters)
while True:
index = randrange(Monsters_len)
monster = Monsters[index]
if monster.alive > 0:
return monster
def display(urltraman, Monsters):
"""显示奥特曼和怪兽的信息"""
print(urltraman)
for monster in Monsters:
print(monster, end='')
def main():
# 给对象赋值
Ultman = Ultraman('大蛇丸',1000,200)
Monster1 = Monster('苍老师',290)
Monster2 = Monster('波多老师',490)
Monster3 = Monster('泷泽老师',690)
Monster_s = [Monster1,Monster2,Monster3]
Fight_round = 1 # 定义变量记录回合数
while Ultman.alive and is_any_alive(Monster_s):
print('======================第%02d回合======================' % Fight_round)
MonOne = select_alive_one(Monster_s) # 选中一只怪兽
skill = randint(1, 10) # 随机选择技能
if skill <= 6: # 小于6使用普通攻击
print('%s使用普通攻击了%s' % (Ultman.name, MonOne.name))
Ultman.attack(MonOne)
print('%s恢复魔法%d点' % (Ultman.name, Ultman.recovery()))
elif skill <= 9: # 6-9使用魔法攻击(注意mp的值)
if Ultman.magic_attack(Monster_s):
print('%s使用了魔法攻击.' % Ultman.name)
else:
print('%s无法使用魔法攻击,蓝量不足.' % Ultman.name)
else: # 10%的机会使用大招(注意mp的值)
if Ultman.big_attack(MonOne):
print('%s对%s使用大招了,快闪......' % (Ultman.name, MonOne.name))
else:
print('%s只能使用普通攻击攻击%s' % (Ultman.name, MonOne.name))
print('%s的魔法值恢复了%d点.' % (Ultman.name, Ultman.recovery()))
if MonOne.alive > 0: # 怪兽回击奥特曼
print('%s回击了%s.' % (MonOne.name, Ultman.name))
MonOne.attack(Ultman)
display(Ultman, Monster_s)
Fight_round += 1
print('\n##############战斗结束##############\n')
if Ultman.alive > 0:
print('%s奥特曼胜利' % Ultman.name)
else:
print('还存在小怪兽!')
if __name__ == "__main__":
main()
练习2:扑克牌游戏
补充:在下面的练习中会接触到(__repr__、__str__)方法,这是显示属性实现类到字符串的转化。通过重写类的__repr__方法可以输出实例化对象的信息。
import random
class Card(object):
"""一张牌"""
# 初始化 绑定属性
def __init__(self, suite, face):
self._suite = suite
self._face = face
@property
def face(self):
return self._face
@property
def suite(self):
return self._suite
def __str__(self):
if self._face == 1:
face_str = 'A'
elif self._face == 11:
face_str = 'J'
elif self._face == 12:
face_str = 'Q'
elif self._face == 13:
face_str = 'K'
else:
face_str = str(self._face)
return '%s%s' % (self._suite, face_str)
def __repr__(self):
return self.__str__()
class Poker(object):
"""一副牌"""
def __init__(self):
self._cards = [Card(suite, face)
for suite in '♠♥♣♦'
for face in range(1,14)]
self._current = 0
@property
def cards(self):
return self._cards
def shuffle(self): # 将序列随机打乱位置。
"""洗牌"""
self._current = 0
random.shuffle(self._cards)
@property
def next(self):
"""发牌"""
card = self._cards[self._current]
self._current += 1
return card
@property
def has_next(self):
# 还有没有牌
return self._current < len(self._cards)
class Player(object):
"""玩家"""
def __init__(self, name):
self._name = name
self._cards_on_hand = []
@property
def name(self):
return self._name
@property
def cards_on_hand(self):
return self._cards_on_hand
def get_card(self, card):
"""摸牌"""
self._cards_on_hand.append(card)
def arrange(self, card_key):
"""玩家整理手上的牌"""
self._cards_on_hand.sort(key=card_key)
# 排序规则 先根据花色再根据点数
def get_key(card):
return (card.suite, card.face)
def main():
pockerCard = Poker()
pockerCard.shuffle()
players = [Player('东'), Player('南'), Player('西'), Player('北')]
for _ in range(13):
for player in players:
player.get_card(pockerCard.next)
for player in players:
print(player.name + ':', end=' ')
player.arrange(get_key)
print(player.cards_on_hand)
if __name__ == "__main__":
main()
练习3:工资结算
"""
某公司有三种类型的员工 分别是部门经理、程序员和销售员
需要设计一个工资结算系统 根据提供的员工信息来计算月薪
部门经理的月薪是每月固定15000元
程序员的月薪按本月工作时间计算 每小时150元
销售员的月薪是1200元的底薪加上销售额5%的提成
"""
from abc import ABCMeta, abstractmethod
class Employee(object, metaclass=ABCMeta):
"""员工"""
def __init__(self, name):
"""
初始化方法
:param name: 姓名
"""
self._name = name
@property
def name(self):
return self._name
@abstractmethod
def get_salary(self):
"""
获得月薪
:return: 月薪
"""
pass
class Manager(Employee):
"""部门经理"""
def get_salary(self):
return 15000.0
class Programmer(Employee):
"""程序员"""
def __init__(self, name, working_hour=0):
super().__init__(name)
self._working_hour = working_hour
@property
def working_hour(self):
return self._working_hour
@working_hour.setter
def working_hour(self, working_hour):
self._working_hour = working_hour if working_hour > 0 else 0
def get_salary(self):
return 150.0 * self._working_hour
class Salesman(Employee):
"""销售员"""
def __init__(self, name, sales=0):
super().__init__(name)
self._sales = sales
@property
def sales(self):
return self._sales
@sales.setter
def sales(self, sales):
self._sales = sales if sales > 0 else 0
def get_salary(self):
return 1200.0 + self._sales * 0.05
def main():
emps = [
Manager('刘备'), Programmer('诸葛亮'),
Manager('曹操'), Salesman('荀彧'),
Salesman('吕布'), Programmer('张辽'),
Programmer('赵云')
]
for emp in emps:
if isinstance(emp, Programmer):
emp.working_hour = int(input('请输入%s本月工作时间: ' % emp.name))
elif isinstance(emp, Salesman):
emp.sales = float(input('请输入%s本月销售额: ' % emp.name))
# 同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态)
print('%s本月工资为: ¥%s元' %
(emp.name, emp.get_salary()))
if __name__ == '__main__':
main()