@property装饰器

之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示。

class Person(object):def __init__(self, name, age):self._name = nameself._age = age# 访问器 - getter方法
    @propertydef name(self):return self._name# 访问器 - getter方法
    @propertydef age(self):return self._age# 修改器 - setter方法
    @age.setterdef age(self, age):self._age = agedef play(self):if self._age <= 16:print('%s正在玩飞行棋.' % self._name)else:print('%s正在玩斗地主.' % self._name)def main():person = Person('王大锤', 12)person.play()person.age = 22person.play()# person.name = '白元芳'  # AttributeError: can't set attributeif __name__ == '__main__':main()

静态方法和类方法

之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。

from math import sqrtclass Triangle(object):def __init__(self, a, b, c):self._a = aself._b = bself._c = c@staticmethoddef is_valid(a, b, c):return a + b > c and b + c > a and a + c > bdef perimeter(self):return self._a + self._b + self._cdef area(self):half = self.perimeter() / 2return 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()

类之间的关系

简单的说,类和类之间的关系有三种: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 = nameself._age = age@propertydef name(self):return self._name@propertydef age(self):return self._age@age.setterdef age(self, age):self._age = agedef 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@propertydef grade(self):return self._grade@grade.setterdef grade(self, grade):self._grade = gradedef 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@propertydef title(self):return self._title@title.setterdef title(self, title):self._title = titledef 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, abstractmethodclass Pet(object, metaclass=ABCMeta):"""宠物"""def __init__(self, nickname):self._nickname = nickname@abstractmethoddef make_voice(self):"""发出声音"""passclass 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包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,DogCat两个子类分别对Pet类中的make_voice抽象方法进行了重写并给出了不同的实现版本,当我们在main函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。

案例1:奥特曼打小怪兽

from abc import ABCMeta, abstractmethod
from random import randint, randrangeclass Fighter(object, metaclass=ABCMeta):"""战斗者"""# 通过__slots__魔法限定对象可以绑定的成员变量__slots__ = ('_name', '_hp')def __init__(self, name, hp):"""初始化方法:param name: 名字:param hp: 生命值"""self._name = nameself._hp = hp@propertydef name(self):return self._name@propertydef hp(self):return self._hp@hp.setterdef hp(self, hp):self._hp = hp if hp >= 0 else 0@propertydef alive(self):return self._hp > 0@abstractmethoddef attack(self, other):"""攻击:param other: 被攻击的对象"""passclass Ultraman(Fighter):"""奥特曼"""__slots__ = ('_name', '_hp', '_mp')def __init__(self, name, hp, mp):"""初始化方法:param name: 名字:param hp: 生命值:param mp: 魔法值"""super().__init__(name, hp)self._mp = mpdef 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 -= 50injury = other.hp * 3 // 4injury = injury if injury >= 50 else 50other.hp -= injuryreturn Trueelse:self.attack(other)return Falsedef magic_attack(self, others):"""魔法攻击:param others: 被攻击的群体:return: 使用魔法成功返回True否则返回False"""if self._mp >= 20:self._mp -= 20for temp in others:if temp.alive:temp.hp -= randint(10, 15)return Trueelse:return Falsedef resume(self):"""恢复魔法值"""incr_point = randint(1, 10)self._mp += incr_pointreturn incr_pointdef __str__(self):return '~~~%s奥特曼~~~\n' % self._name + \'生命值: %d\n' % self._hp + \'魔法值: %d\n' % self._mpclass 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._hpdef is_any_alive(monsters):"""判断有没有小怪兽是活着的"""for monster in monsters:if monster.alive > 0:return Truereturn Falsedef select_alive_one(monsters):"""选中一只活着的小怪兽"""monsters_len = len(monsters)while True:index = randrange(monsters_len)monster = monsters[index]if monster.alive > 0:return monsterdef 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 = 1while 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 += 1print('\n========战斗结束!========\n')if u.alive > 0:print('%s奥特曼胜利!' % u.name)else:print('小怪兽胜利!')if __name__ == '__main__':main()

案例2:扑克游戏

import randomclass Card(object):"""一张牌"""def __init__(self, suite, face):self._suite = suiteself._face = face@propertydef face(self):return self._face@propertydef suite(self):return self._suitedef __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@propertydef cards(self):return self._cardsdef shuffle(self):"""洗牌(随机乱序)"""self._current = 0random.shuffle(self._cards)@propertydef next(self):"""发牌"""card = self._cards[self._current]self._current += 1return card@propertydef has_next(self):"""还有没有牌"""return self._current < len(self._cards)class Player(object):"""玩家"""def __init__(self, name):self._name = nameself._cards_on_hand = []@propertydef name(self):return self._name@propertydef cards_on_hand(self):return self._cards_on_handdef 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()

案例3:工资结算系统

"""
某公司有三种类型的员工 分别是部门经理、程序员和销售员
需要设计一个工资结算系统 根据提供的员工信息来计算月薪
部门经理的月薪是每月固定15000元
程序员的月薪按本月工作时间计算 每小时150元
销售员的月薪是1200元的底薪加上销售额5%的提成
"""
from abc import ABCMeta, abstractmethodclass Employee(object, metaclass=ABCMeta):"""员工"""def __init__(self, name):"""初始化方法:param name: 姓名"""self._name = name@propertydef name(self):return self._name@abstractmethoddef get_salary(self):"""获得月薪:return: 月薪"""passclass Manager(Employee):"""部门经理"""def get_salary(self):return 15000.0class Programmer(Employee):"""程序员"""def __init__(self, name, working_hour=0):super().__init__(name)self._working_hour = working_hour@propertydef working_hour(self):return self._working_hour@working_hour.setterdef working_hour(self, working_hour):self._working_hour = working_hour if working_hour > 0 else 0def get_salary(self):return 150.0 * self._working_hourclass Salesman(Employee):"""销售员"""def __init__(self, name, sales=0):super().__init__(name)self._sales = sales@propertydef sales(self):return self._sales@sales.setterdef sales(self, sales):self._sales = sales if sales > 0 else 0def get_salary(self):return 1200.0 + self._sales * 0.05def 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()

转载于:https://www.cnblogs.com/zhichao123/p/11266474.html

python基础_面向对象进阶相关推荐

  1. python基础_格式化输出(%用法和format用法)(转载)

    python基础_格式化输出(%用法和format用法) 目录 %用法 format用法 %用法 1.整数的输出 %o -- oct 八进制 %d -- dec 十进制 %x -- hex 十六进制 ...

  2. python基础之面向对象编程

    python基础之面向对象编程 面向对象编程思想 面向对象是一门编程思想,编程思想仅仅是一门思想,与任何技术无关 核心是对象两字,对象可以理解为特征与技能的结合体 基于该编程思想编写程序,就好比创造世 ...

  3. 5.Python基础之面向对象

    文章目录 Python基础之面向对象 面向对象的三大特性 类和对象的关系 类的组成 类的书写规则 实例化对象 检测类和对象的成员 类和对象成员的操作 类成员操作 对象成员操作 关于self 封装特性 ...

  4. Python成长之路【第九篇】:Python基础之面向对象

    一.三大编程范式 正本清源一:有人说,函数式编程就是用函数编程–>错误1 编程范式即编程的方法论,标识一种编程风格 大家学习了基本的Python语法后,大家就可以写Python代码了,然后每个人 ...

  5. (转)Python成长之路【第九篇】:Python基础之面向对象

    一.三大编程范式 正本清源一:有人说,函数式编程就是用函数编程-->错误1 编程范式即编程的方法论,标识一种编程风格 大家学习了基本的Python语法后,大家就可以写Python代码了,然后每个 ...

  6. Python基础_第3章_Python中的循环结构

    Python基础_第3章_Python中的循环结构 文章目录 Python基础_第3章_Python中的循环结构 Python中的循环结构 一.回顾分支练习题 1.判断是否为一个合法三角形 2.求世界 ...

  7. 第一章 第一节:Python基础_认识Python

    Python基础入门(全套保姆级教程) 第一章 第一节:Python基础_认识Python 1. 什么是编程 通俗易懂,编程就是用代码编写程序,编写程序有很多种办法,像c语言,javaPython语言 ...

  8. python判断题题库大数据技术_智慧树_大数据分析的python基础_搜题公众号

    智慧树_大数据分析的python基础_搜题公众号 更多相关问题 社会公众可以查阅烟草专卖行政主管部门的监督检查记录.() 公民.法人或者其他组织不得利用自动售货机销售烟草制品.() 烟草广告中不得有下 ...

  9. Python基础_第2章_Python运算符与if结构

    Python基础_第2章_Python运算符与if结构 文章目录 Python基础_第2章_Python运算符与if结构 Day02之`Python运算符与if结构` 一.昨日回顾 1.回顾昨天的课程 ...

最新文章

  1. 2022-2028年中国安防视频行业市场前景分析预测报告
  2. Java源码分析:深入探讨Iterator模式
  3. TCP/IP协议栈模型分析
  4. 加仓减仓口诀_做短线必备口诀
  5. Java EE CDI程序化依赖关系消歧示例–注入点检查
  6. 【转】Postman系列一:Postman安装及使用过程中遇到的问题
  7. Visual Studio 2013运行的结果一闪而过
  8. 可能这些是你想要的H5软键盘兼容方案
  9. PHP的continue语句,PHP continue语句
  10. 如何清理 Linux 系统开机启动项?
  11. sql经典题目(1)
  12. 形式语言与自动机 3.正则表达式
  13. 使用百度网盘链接分享学习资料 防止链接失效
  14. hdmi怎么支持2k分辨率_为什么显示器闪瞎眼 HDMI线版本有讲究
  15. 阿里云5天学习感悟与案例分享
  16. WEB网站服务器(一)
  17. 微信小程序开发之——数据存储Storage
  18. Chrome浏览器突然无法打开的解决方法
  19. 【python实现网络爬虫22】唯品会商品信息实战步骤详解
  20. 更改iPhone手机铃声详细教程(简单易学,无需付费软件)!

热门文章

  1. 一种针对超声波测距的滤波处理算法
  2. 【MySQL】如何构建一个完整的MySQL知识体系(MySQL专栏启动)
  3. 机器人领域相关英文期刊
  4. GitHub如何配置SSH Key
  5. php mysql 变量赋值_mysql存储过程中变量的定义赋值操作
  6. Meta AI:让手绘小人动起来
  7. 如何判断一个立即数是否合法
  8. cocos2d-x基本面试题
  9. 上海迪士尼将于今年九月开启达菲主题月
  10. Mac的环境配置的详细步骤