Day9 面向对象进阶
@property装饰器
之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示。
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
# 访问器 - getter方法
@property
def name(self):
return self._name
# 访问器 - getter方法
@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 = Person('王大锤', 12)
person.play()
person.age = 22
person.play()
# person.name = '白元芳' # AttributeError: can't set attribute
if __name__ == '__main__':
main()
__slots__魔法
我们讲到这里,不知道大家是否已经意识到,Python是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用。
class Person(object):
# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')
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):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('王大锤', 22)
person.play()
person._gender = '男'
# AttributeError: 'Person' object has no attribute '_is_gay'
# person._is_gay = True
静态方法和类方法
之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。
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 b + c > a and a + c > b
def perimeter(self):
return self._a + self._b + self._c
def area(self):
half = self.perimeter() / 2
return sqrt(half * (half - self._a) *
(half - self._b) * (half - self._c))
def main():
a, b, c = 3, 4, 5
# 静态方法和类方法都是通过给类发消息来调用的
if Triangle.is_valid(a, b, c):
t = Triangle(a, b, c)
print(t.perimeter())
# 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
# print(Triangle.perimeter(t))
print(t.area())
# print(Triangle.area(t))
else:
print('无法构成三角形.')
if __name__ == '__main__':
main()
和静态方法比较类似,Python还可以在类中定义类方法,类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象,代码如下所示。
from time import time, localtime, sleep
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()
if __name__ == '__main__':
main()
类之间的关系
简单的说,类和类之间的关系有三种:is-a、has-a和use-a关系。
is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。
我们可以使用一种叫做UML(统一建模语言)的东西来进行面向对象建模,其中一项重要的工作就是把类和类之间的关系用标准化的图形符号描述出来。关于UML我们在这里不做详细的介绍,有兴趣的读者可以自行阅读《UML面向对象设计基础》一书。
利用类之间的这些关系,我们可以在已有类的基础上来完成某些操作,也可以在已有类的基础上创建新的类,这些都是实现代码复用的重要手段。复用现有的代码不仅可以减少开发的工作量,也有利于代码的管理和维护,这是我们在日常工作中都会使用到的技术手段。
继承和多态
刚才我们提到了,可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为里氏替换原则。下面我们先看一个继承的例子。
class Person(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只能观看《熊出没》.' % self._name)
class Student(Person):
"""学生"""
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(Person):
"""老师"""
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._name, self._title, course))
def main():
stu = Student('王大锤', 15, '初三')
stu.study('数学')
stu.watch_av()
t = Teacher('骆昊', 38, '老叫兽')
t.teach('Python程序设计')
t.watch_av()
if __name__ == '__main__':
main()
子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。
from abc import ABCMeta, abstractmethod
class Pet(object, metaclass=ABCMeta):
"""宠物"""
def __init__(self, nickname):
self._nickname = nickname
@abstractmethod
def make_voice(self):
"""发出声音"""
pass
class Dog(Pet):
"""狗"""
def make_voice(self):
print('%s: 汪汪汪...' % self._nickname)
class Cat(Pet):
"""猫"""
def make_voice(self):
print('%s: 喵...喵...' % self._nickname)
def main():
pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
for pet in pets:
pet.make_voice()
if __name__ == '__main__':
main()
在上面的代码中,我们将Pet类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,Dog和Cat两个子类分别对Pet类中的make_voice抽象方法进行了重写并给出了不同的实现版本,当我们在main函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。
综合案例
案例1:奥特曼打小怪兽
from abc import ABCMeta, abstractmethod
from random import randint, randrange
class Fighter(object, metaclass=ABCMeta):
"""战斗者"""
# 通过__slots__魔法限定对象可以绑定的成员变量
__slots__ = ('_name', '_hp')
def __init__(self, name, hp):
"""初始化方法
:param name: 名字
:param hp: 生命值
"""
self._name = name
self._hp = hp
@property
def name(self):
return self._name
@property
def hp(self):
return self._hp
@hp.setter
def hp(self, hp):
self._hp = hp if hp >= 0 else 0
@property
def alive(self):
return self._hp > 0
@abstractmethod
def attack(self, other):
"""攻击
:param other: 被攻击的对象
"""
pass
class Ultraman(Fighter):
"""奥特曼"""
__slots__ = ('_name', '_hp', '_mp')
def __init__(self, name, hp, mp):
"""初始化方法
:param name: 名字
:param hp: 生命值
:param mp: 魔法值
"""
super().__init__(name, hp)
self._mp = mp
def attack(self, other):
other.hp -= randint(15, 25)
def huge_attack(self, other):
"""究极必杀技(打掉对方至少50点或四分之三的血)
:param 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):
"""魔法攻击
:param 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 resume(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_info(ultraman, monsters):
"""显示奥特曼和小怪兽的信息"""
print(ultraman)
for monster in monsters:
print(monster, end='')
def main():
u = Ultraman('骆昊', 1000, 120)
m1 = Monster('狄仁杰', 250)
m2 = Monster('白元芳', 500)
m3 = Monster('王大锤', 750)
ms = [m1, m2, m3]
fight_round = 1
while u.alive and is_any_alive(ms):
print('========第%02d回合========' % fight_round)
m = select_alive_one(ms) # 选中一只小怪兽
skill = randint(1, 10) # 通过随机数选择使用哪种技能
if skill <= 6: # 60%的概率使用普通攻击
print('%s使用普通攻击打了%s.' % (u.name, m.name))
u.attack(m)
print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
elif skill <= 9: # 30%的概率使用魔法攻击(可能因魔法值不足而失败)
if u.magic_attack(ms):
print('%s使用了魔法攻击.' % u.name)
else:
print('%s使用魔法失败.' % u.name)
else: # 10%的概率使用究极必杀技(如果魔法值不足则使用普通攻击)
if u.huge_attack(m):
print('%s使用究极必杀技虐了%s.' % (u.name, m.name))
else:
print('%s使用普通攻击打了%s.' % (u.name, m.name))
print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
if m.alive > 0: # 如果选中的小怪兽没有死就回击奥特曼
print('%s回击了%s.' % (m.name, u.name))
m.attack(u)
display_info(u, ms) # 每个回合结束后显示奥特曼和小怪兽的信息
fight_round += 1
print('\n========战斗结束!========\n')
if u.alive > 0:
print('%s奥特曼胜利!' % u.name)
else:
print('小怪兽胜利!')
if __name__ == '__main__':
main()
案例2:扑克游戏
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(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():
p = Poker()
p.shuffle()
players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
for _ in range(13):
for player in players:
player.get(p.next)
for player in players:
print(player.name + ':', end=' ')
player.arrange(get_key)
print(player.cards_on_hand)
if __name__ == '__main__':
main()
说明: 大家可以自己尝试在上面代码的基础上写一个简单的扑克游戏,例如21点(Black Jack),游戏的规则可以自己在网上找一找。
案例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__':
Day9 面向对象进阶的更多相关文章
- Python全栈开发【面向对象进阶】
Python全栈开发[面向对象进阶] 本节内容: isinstance(obj,cls)和issubclass(sub,super) 反射 __setattr__,__delattr__,__geta ...
- day26、面向对象进阶:多态、封装、反射
一.多态 什么是多态: 类的继承有两层意义:1.改变 2.扩展 多态就是类的这两层意义的一个具体的实现机. 即:调用不同类实例化的对象,下的相同的方法,实现的过程不一样 python中的标准类型就是多 ...
- Python开发【第七篇】:面向对象 和 python面向对象进阶篇(下)
Python开发[第七篇]:面向对象 详见:<Python之路[第五篇]:面向对象及相关> python 面向对象(进阶篇) 上一篇<Python 面向对象(初级篇)> ...
- python基础——面向对象进阶下
python基础--面向对象进阶下 1 __setitem__,__getitem,__delitem__ 把对象操作属性模拟成字典的格式 想对比__getattr__(), __setattr__( ...
- python基础——面向对象进阶
python基础--面向对象进阶 1.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 ...
- 周末班:Python基础之面向对象进阶
面向对象进阶 类型判断 issubclass 首先,我们先看issubclass() 这个内置函数可以帮我们判断x类是否是y类型的子类. class Base: pass class Foo(Base ...
- Python面向对象进阶和socket网络编程-day08
写在前面 上课第八天,打卡: 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __i ...
- 铁乐学python_day24_面向对象进阶1_内置方法
铁乐学python_day24_面向对象进阶1_内置方法 题外话1: 学习方法[wwwh] what where why how 是什么,用在哪里,为什么,怎么用 学习到一个新知识点的时候,多问问上面 ...
- 铁乐学python_day23_面向对象进阶1_反射
铁乐学python_day23_面向对象进阶1_反射 以下内容大部分摘自博客http://www.cnblogs.com/Eva-J/ isinstance()和issubclass() 两者的返回值 ...
随机推荐
- [bzoj1010][HNOI2008]玩具装箱toy_斜率优化dp
玩具装箱toy bzoj-1010 HNOI-2008 题目大意:P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京.他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一 ...
- MySQL终端(Terminal)命令基本操作(转)
注意:MySQL数据库命令不区分大小写.但在MAC的终端,如果你想使用tab自动补全命令,那么你就必须使用大写,这样MAC的终端才会帮你补全命令,否则你按N遍tab都不会有响应. 1.数据库(data ...
- weblogic 10 无密码启动
首先确定你的domain目录 [c21rms@c21wls10 RMS4]$ pwd/opt/psa/rel/weblogic/RMS4 其次找到下面这个文件夹 servers/AdminServer ...
- JAVA模拟登录实例
近期在做公司一个web项目.要求在我们的系统上,可以显示其它站点上的数据. 刚開始接到这个任务时,还在想.简单的非常.直接用UrlConection直接进入该网页,然后获取该网页的html,取到想要的 ...
- 初识ASP.NET---一般处理程序
问题来源: 今天在敲一个小的demo,利用Jquery实现级联下拉框,敲的过程中发现不管怎么和源代码对比都无法显示想要的功能. 这才想着原来是没有写后台代码,询问一清同学的时候,他告诉我能够利用ASP ...
- COCOS2D 学习笔记
cc.Node.scheduleUpdate:function () * schedules the "update" method. ...
- [WebGL入门]二十三,反射光的光照效果
注:文章译自http://wgld.org/,原作者杉本雅広(doxas).文章中假设有我的额外说明.我会加上[lufy:],另外,鄙人webgl研究还不够深入.一些专业词语.假设翻译有误.欢迎大家指 ...
- C# ListBox 左移、右移、上移、下移
C# ListBox 左移.右移.上移.下移 2012-11-17 22:53:45| 分类: 技术研讨 | 标签:listbox |字号 订阅 /// <summary> ...
- 【BZOJ 2565】 最长双回文串
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=2565 [算法] Manacher [代码] #include<bits/std ...
- python3用list实现栈
工作中遇到的需求,****代表标签数据别的信息: D01 ******** 1 ******** D01 ******** 2 ******** D01 ******** 3 ******** D01 ...