Python-100-Days 第九天 面向对象进阶

项目地址
https://github.com/jackfrued/Python-100-Days
Day01~15 - Python语言基础

Day16~20 - Python语言进阶

Day21~30 - Web前端入门

Day31~35 - 玩转Linux操作系统

Day36~40 - 数据库基础和进阶

Day41~55 - 实战Django

Day56~60 - 实战Flask

Day61~65 - 实战Tornado

Day66~75 - 爬虫开发

Day76~90 - 数据处理和机器学习

Day91~100 - 团队项目开发

Python-100-Days 第九天 面向对象进阶

  • 面向对象进阶
  • @property装饰器
  • __slots__魔法
  • 静态方法和类方法
  • 类之间的关系
  • 继承和多态
  • 综合案例
    • 案例1:奥特曼打小怪兽。
    • 案例2:扑克游戏。
    • 案例3:工资结算系统。
  • 小结

面向对象进阶

在前面的章节我们已经了解了面向对象的入门知识,知道了如何定义类,如何创建对象以及如何给对象发消息。为了能够更好的使用面向对象编程思想进行程序开发,我们还需要对Python中的面向对象编程进行更为深入的了解。

@property装饰器

之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。

我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示。

class Person(object):

    def __init__(self, name, age):
        self._name = name
        self._age = age

    # 访问器 - getter方法
    @property           # 利用装饰器 包装访问器getter方法  使得姓名可以访问
    def name(self):
        return self._name

    # 访问器 - getter方法
    @property           # 利用装饰器 包装访问器getter方法  使得年龄可以访问
    def age(self):
        return self._age

    # 修改器 - setter方法
    @age.setter         # 利用装饰器 包装修改器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()

在这里插入图片描述
修改后,利用修饰器修饰name的 修改器 - setter方法 使得姓名可以被修改

class Person(object):

    def __init__(self, name, age):
        self._name = name
        self._age = age

    # 访问器 - getter方法
    @property           # 利用装饰器 包装访问器getter方法  使得姓名可以访问
    def name(self):
        return self._name

    # 访问器 - getter方法
    @property           # 利用装饰器 包装访问器getter方法  使得年龄可以访问
    def age(self):
        return self._age

    # 修改器 - setter方法
    @age.setter         # 利用装饰器 包装修改器setter‘方法  使得年龄可以修改
    def age(self, age):
        self._age = age

    # 修改器 - setter方法
    @name.setter         # 利用装饰器 包装修改器setter‘方法  使得姓名可以修改
    def name(self, name):
        self._name = name

    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   # 不可以修改姓名
    person.play()


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 = '男'   # 性别用__slot__进行了限定,person可以修改_gender属性
    person._is_gay = True  # AttributeError: 'Person' object has no attribute '_is_gay'

if __name__ == '__main__':
    main()

在这里插入图片描述

静态方法和类方法

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

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  # 返回值为bool值

    def perimeter(self):   # 定义perimeter函数,求三角形的周长
        return self._a + self._b + self._c

    def area(self):        # 定义area函数 求三角形的面积
        half = self.perimeter() / 2     # 定义half变量  half的值为海伦公式中的周长的一半   p=(a+b+c)/2   周长的一半
        return sqrt(half * (half - self._a) * (half - self._b) * (half - self._c))   # S=sqrt[p(p-a)(p-b)(p-c)]


def main():
    a, b, c = 3, 4, 5  # 定义三边长
    # 静态方法和类方法都是通过给类发消息来调用的
    if Triangle.is_valid(a, b, c):  # 判断能否构成三角形
        t = Triangle(a, b, c)   # 实例化类Triangle  为t
        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):   # 在类中定义类方法
        """
        类方法的第一个参数约定名为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()   # # 通过类方法创建对象并获取系统时间
    #  clock = Clock(23, 59, 58)  之前传入参数的格式
    while True:
        print(clock.show())
        sleep(1)
        clock.run()


if __name__ == '__main__':
    main()

Python-100-Days 第九天 面向对象进阶_第1张图片

类之间的关系

简单的说,类和类之间的关系有三种:is-a、has-a和use-a关系。

  1. is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
  2. has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
  3. use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。

我们可以使用一种叫做UML(统一建模语言)的东西来进行面向对象建模,其中一项重要的工作就是把类和类之间的关系用标准化的图形符号描述出来。

关于UML我们在这里不做详细的介绍,有兴趣的读者可以自行阅读《UML面向对象设计基础》一书。

利用类之间的这些关系,我们可以在已有类的基础上来完成某些操作,也可以在已有类的基础上创建新的类,这些都是实现代码复用的重要手段。复用现有的代码不仅可以减少开发的工作量,也有利于代码的管理和维护,这是我们在日常工作中都会使用到的技术手段。

继承和多态

刚才我们提到了,可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。
提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。
子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为里氏替换原则。下面我们先看一个继承的例子。

class Person(object):
    """人"""

    def __init__(self, name, age):  # 父类 初始化 有两个属性 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):   # 子类   继承自 person类
    """学生"""

    def __init__(self, name, age, grade):
        super().__init__(name, age)   # super() 函数是用于调用父类(超类)的一个方法。
        self._grade = grade    # 初始化年级

    @property
    def grade(self):    # 定义grade函数 返回学生的年级
        return self._grade

    @grade.setter   # 定义grade函数的修改器  使得学生的年级可以被修改
    def grade(self, grade):
        self._grade = grade

    def study(self, course):   # 学生类  学习study函数
        print('%s的%s正在学习%s.' % (self._grade, self._name, course))


class Teacher(Person):    # 子类  继承自person类
    """老师"""

    def __init__(self, name, age, title):
        super().__init__(name, age)
        self._title = title

    @property
    def title(self):         # 访问器  教师头衔
        return self._title

    @title.setter             # 头衔  利用修改器setter 使得教师的头衔可以被修改
    def title(self, title):
        self._title = title

    def teach(self, course):   # 教师类 教学teach函数
        print('%s%s正在讲%s.' % (self._name, self._title, course))


def main():
    stu = Student('王大锤', 15, '初三')  # 定义学生stu  传入参数
    stu.study('数学')
    stu.watch_av()
    t = Teacher('骆昊', 38, '砖家')     # 定义教师t  差传入参数
    t.teach('Python程序设计')
    t.watch_av()


if __name__ == '__main__':
    main()

Python-100-Days 第九天 面向对象进阶_第2张图片
子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写**(override)。

通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。

from abc import ABCMeta, abstractmethod


class Pet(object, metaclass=ABCMeta):  # 定义宠物类 通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果
    """宠物"""

    def __init__(self, nickname):  # 初始化 参数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('大黄')]  # 定义一个pets列表  存储不同的宠物名称
    for pet in pets:   # 循环
        pet.make_voice()  # 每个子类中的宠物 调用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):   # 父类 基类 战斗者 通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果*
    """战斗者"""

    __slots__ = ('_name', '_hp')    # 通过__slots__魔法 限定对象可以绑定的成员变量

    def __init__(self, name, hp):  # 初始化 战斗者需要有名字和生命值两个属性
        """初始化方法

        :param name: 名字
        :param hp: 生命值   hp=hit point 生命值
        """
        self._name = name
        self._hp = hp

    @property   # 访问器
    def name(self):
        return self._name

    @property   # 访问器
    def hp(self):
        return self._hp

    @hp.setter   # hp生命值的修改器
    def hp(self, hp):
        self._hp = hp if hp >= 0 else 0  # 如果hp<0 把0赋值给hp

    @property    # 访问器
    def alive(self):   # 是否存活函数alive
        return self._hp > 0  # 生命值大于0   bool值

    @abstractmethod  # 抽象方法   Python的abc提供了@abstractmethod装饰器实现抽象方法
    def attack(self, other):  # 抽象方法表示基类的一个方法,没有实现,所以基类不能实例化,子类实现了该抽象方法才能被实例化。
        """攻击

        :param other: 被攻击的对象
        """
        pass


class Ultraman(Fighter):  # 继承父类战斗者的一个子类奥特曼
    """奥特曼"""

    __slots__ = ('_name', '_hp', '_mp')  # 通过__slots__魔法 限定对象可以绑定的成员变量

    def __init__(self, name, hp, mp):
        """初始化方法

        :param name: 名字
        :param hp: 生命值  hp=hit point 生命值
        :param mp: 魔法值  mp=mana point 魔法值
        """
        super().__init__(name, hp)  # super() 函数是用于调用父类(超类)的一个方法。
        self._mp = mp

    def attack(self, other):   # other被攻击的对象
        other.hp -= randint(15, 25)   # other被攻击的对象的生命值随机减少15至25点

    def huge_attack(self, other):
        """究极必杀技(打掉对方至少50点或四分之三的血)

        :param other: 被攻击的对象

        :return: 使用成功返回True否则返回False
        """
        if self._mp >= 50:  # 如果血量大于等于50
            self._mp -= 50   # 血量减少50
            injury = other.hp * 3 // 4   # injury损伤 变量存储被攻击对象的3/4的血量
            injury = injury if injury >= 50 else 50   # 如果损伤的3/4血量  小于50,就把50赋值给injury
            other.hp -= injury  # 被攻击对象血量减少injury
            return True     # 使用究极必杀技成功
        else:
            self.attack(other)  # 使用普通攻击
            return False    # 使用究极必杀技失败

    def magic_attack(self, others):
        """魔法攻击

        :param others: 被攻击的群体

        :return: 使用魔法成功返回True否则返回False
        """
        if self._mp >= 20:   # 魔法值大于20时可以使用
            self._mp -= 20   # 魔法值减少20
            for temp in others:
                if temp.alive:   # 如果小怪兽存活
                    temp.hp -= randint(10, 15)  # 小怪兽的血量随机减少10~15点
            return True
        else:
            return False

    def resume(self):   # 定义resume函数  恢复魔法值函数
        """恢复魔法值"""
        incr_point = randint(1, 10)  # 法力值回复值的范围利用 randint函数生成 在1~10之间
        self._mp += incr_point  # mp魔法值 增加(恢复)
        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')  # 通过__slots__魔法 限定对象可以绑定的成员变量

    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  # 返回True  代表小怪兽还有存活的
    return False  # 返回True  代表小怪兽没有存活的


def select_alive_one(monsters):
    """选中一只活着的小怪兽"""
    monsters_len = len(monsters)
    while True:   # 死循环
        index = randrange(monsters_len)
        monster = monsters[index]
        if monster.alive > 0:  # 如果小怪兽还活着  bool值   True
            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]  # 定义ms列表 存储三个小怪兽
    fight_round = 1  # 战斗的第一回合
    while u.alive and is_any_alive(ms):  # 判断 如果奥特曼或者并且有任意一个小怪兽还活着 就继续循环
        print('========第%02d回合========' % fight_round)
        m = select_alive_one(ms)  # 选中一只小怪兽 小怪兽的名字为m
        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)  # 每个回合结束后显示奥特曼和小怪兽的信息 输出u和ms
        fight_round += 1  # 战斗回合数+1
    print('\n========战斗结束!========\n')  # 循环结束 输出战斗结束
    if u.alive > 0:  # 如果u的生命值大于0  奥特曼还存活
        print('%s奥特曼胜利!' % u.name)
    else:
        print('小怪兽胜利!')


if __name__ == '__main__':
    main()

Python-100-Days 第九天 面向对象进阶_第3张图片

案例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):   # 函数重载 重写str方法 格式化输出
        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__()  # __repr__() 方法是类的实例化对象用来做“自我介绍”的方法  重写后 为自定义的自我描述信息


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   # 计数器 每发一张牌 计数器+1
        return card   # 发牌函数返回值为当前card的信息

    @property
    def has_next(self):
        """还有没有牌"""
        return self._current < len(self._cards)  # 返回计数器和全部牌进行比较后的bool值  如果还有牌 返回True 否则返回False


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()  # 实例化Poker类 为p
    p.shuffle()  # p进行洗牌
    players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]  # 玩家姓名
    for _ in range(13):  # 循环
        for player in players:  # 玩家循环 进行发牌
            player.get(p.next)  # 玩家摸牌 摸得是p发的牌
    for player in players:  # 循环
        print(player.name + ':', end=' ')  # 输出玩家姓名
        player.arrange(get_key)  # 整理手牌 根据花色和点数进行排序 调用玩家类中的arrange函数
        print(player.cards_on_hand)  # 输出玩家手里的牌


if __name__ == '__main__':
    main()

Python-100-Days 第九天 面向对象进阶_第4张图片
通常情况下,直接输出某个实例化对象,本意往往是想了解该对象的基本信息,例如该对象有哪些属性,它们的值各是多少等等。但默认情况下,我们得到的信息只会是“类名+object at+内存地址”,对我们了解该实例化对象帮助不大。
类似于

<main.CLanguage object at 0x000001A7275221D0>

那么,有没有可能自定义输出实例化对象时的信息呢?答案是肯定,通过重写类的 repr() 方法即可。事实上,当我们输出某个实例化对象时,其调用的就是该对象的 repr() 方法,输出的是该方法的返回值。

案例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):  # 获得月薪直接为15000.0
        return 15000.0


class Programmer(Employee):  # 派生类  程序员类
    """程序员"""

    def __init__(self, name, working_hour=0):  # 新增属性 工作时长 缺省值为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  # 如果工作时长大于0 返回工作时长数否则返回工作时长为0

    def get_salary(self):
        return 150.0 * self._working_hour  # 获得薪水为 150.0乘以工作时长  按照工时计工资


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  # 基本工资1200+销售额乘以0.05  基本工资加提成计工资


def main():
    emps = [
        Manager('刘备'), Programmer('诸葛亮'), Programmer('赵云'), Salesman('关羽'),
        Manager('曹操'), Programmer('荀彧'), Programmer('张辽'), Salesman('吕布'),
    ]                      # 员工列表
    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()

Python-100-Days 第九天 面向对象进阶_第5张图片

同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态)

小结

属性 - 类属性 / 实例属性 / @property装饰器 / 属性访问器 (getter)/ 属性修改器(setter) / 属性删除器 / 使用__slots__
类中的方法 - 实例方法 / 类方法 / 静态方法
运算符重载 - add / sub / or /getitem / setitem / len / repr / gt / lt / le / ge / eq / ne / contains
类(的对象)之间的关系 - 关联 has-a / 继承 is-a/ 依赖 use-a
继承和多态 - 什么是继承 / 继承的语法 / 调用父类方法 / 方法重写 / 类型判定 / 多重继承 / 菱形继承(钻石继承)和C3算法

你可能感兴趣的:(100天学python)