import random
import time
from abc import ABCMeta, abstractmethod
from enum import Enum

if __name__ == '__main__':
    print("面向对象")


class Student:

    def study(self, course_name):
        print(f'学生正在学习{course_name}.')

    def play(self):
        print(f'学生正在玩游戏.')


stu1 = Student()
stu2 = Student()
print(stu1)  # <__main__.Student object at 0x7fd1e82f4f10>
print(stu2)  # <__main__.Student object at 0x7fd1e82f4ee0>
# 对象在内存中的地址（十六进制形式）
print(hex(id(stu1)), hex(id(stu2)))  # 0x7fd1e82f4f10 0x7fd1e82f4ee0
# stu1 和 stu2 保存的是对象的逻辑地址，所以用赋值语句也只是用新对象保存了已有对象的逻辑地址，并不会创建新对象
stu3 = stu1
print(hex(id(stu3)))

# 通过“类.方法”调用方法，第一个参数是接收消息的对象，第二个参数是学习的课程名称
Student.study(stu1, 'Python程序设计')  # 学生正在学习Python程序设计.
# 通过“对象.方法”调用方法，点前面的对象就是接收消息的对象，只需要传入第二个参数
stu1.study('Python程序设计')  # 学生正在学习Python程序设计.

Student.play(stu2)  # 学生正在玩游戏.
stu2.play()  # 学生正在玩游戏.


class Student1:
    """学生"""

    def __init__(self, name, age):
        """初始化方法"""
        self.name = name
        self.age = age

    def study(self, course_name):
        """学习"""
        print(f'{self.name}正在学习{course_name}.')

    def play(self):
        """玩耍"""
        print(f'{self.name}正在玩游戏.')

    # 前后带有两个下划线的称为魔术方法，具有特殊用途
    # __repr__重写了print方法
    def __repr__(self):
        return f'{self.name}: {self.age}'


stu4 = Student1('谛听', 18)
stu4.study('政治')
stu4.play()
print(stu4)


# 定义数字时钟类
class Clock:
    """数字时钟"""

    def __init__(self, hour=0, minute=0, second=0):
        """初始化方法
        :param hour: 时
        :param minute: 分
        :param second: 秒
        """
        self.hour = hour
        self.min = minute
        self.sec = second

    def run(self):
        """走字"""
        self.sec += 1
        if self.sec == 60:
            self.sec = 0
            self.min += 1
            if self.min == 60:
                self.min = 0
                self.hour += 1
                if self.hour == 24:
                    self.hour = 0

    def show(self):
        """显示时间"""
        return f'{self.hour:0>2d}:{self.min:0>2d}:{self.sec:0>2d}'


# 创建时钟对象
clock = Clock(23, 59, 58)
# 给时钟对象发消息读取时间
print(clock.show())
# 给时钟对象发消息使其走字
clock.run()
# 给时钟对象发消息读取时间
print(clock.show())


class Point:
    """定义一个类描述平面上的点，要求提供计算到另一个点距离的方法。"""

    def __init__(self, x=0, y=0):
        """初始化方法
        :param x: 横坐标
        :param y: 纵坐标
        """
        self.x, self.y = x, y

    def distance_to(self, other):
        """计算与另一个点的距离
        :param other: 另一个点
        """
        dx = self.x - other.x
        dy = self.y - other.y
        return (dx * dx + dy * dy) ** 0.5

    def __str__(self):
        return f'({self.x}, {self.y})'


p1 = Point(3, 5)
p2 = Point(6, 9)
print(p1, p2)
print(p1.distance_to(p2))


class Student2:

    def __init__(self, name, age):
        # 私有变量前面加两个下划线，受保护变量前面加一个下划线
        self.__name = name
        self.__age = age

    def study(self, course_name):
        print(f'{self.__name}正在学习{course_name}.')

    # 属性访问器(getter方法) - 获取__name属性
    @property
    def name(self):
        return self.__name

    # 属性修改器(setter方法) - 修改__name属性
    @name.setter
    def name(self, name):
        # 如果name参数不为空就赋值给对象的__name属性
        # 否则将__name属性赋值为'无名氏'，有两种写法
        # self.__name = name if name else '无名氏'
        self.__name = name or '无名氏'

    @property
    def age(self):
        return self.__age


stu5 = Student2('谛听', 18)
stu5.study('Python程序设计')

# Python并没有从语法上严格保证私有属性的私密性，它只是给私有的属性和方法换了一个名字来阻挠对它们的访问
print(stu5._Student2__name, stu5._Student2__age)

print(stu5.name)
print(stu5.age)
stu5.name = ''
print(stu5.name)
stu5.name = '人才'
print(stu5.name)

# 为Student对象动态添加sex属性
stu5.sex = '男'
print(stu5.sex)


class Student3:
    # 该魔术方法 __slots__ 定义 Student3 类只能有 name、age 两个属性，动态增加属性将引发异常
    __slots__ = ('name', 'age')

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


stu6 = Student3('谛听', 18)


# AttributeError: 'Student' object has no attribute 'sex'
# stu6.sex = '男'


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

    # 类方法
    @classmethod
    def is_valid2(cls, 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):
        """计算面积"""
        p = self.perimeter() / 2
        return (p * (p - self.a) * (p - self.b) * (p - self.c)) ** 0.5


t = Triangle(3, 4, 5)
print(Triangle.is_valid(3, 4, 5))
print(Triangle.is_valid2(3, 4, 5))
print(Triangle.perimeter(t))


class Person:
    """人类"""

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

    def eat(self):
        print(f'{self.name}正在吃饭.')

    def sleep(self):
        print(f'{self.name}正在睡觉.')


class Student(Person):
    """学生类，继承自Person"""

    def __init__(self, name, age):
        # super(Student, self).__init__(name, age)
        super().__init__(name, age)

    def study(self, course_name):
        print(f'{self.name}正在学习{course_name}.')


class Teacher(Person):
    """老师类，继承自Person"""

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

    def teach(self, course_name):
        print(f'{self.name}{self.title}正在讲授{course_name}.')


stu1 = Student('白元芳', 21)
stu2 = Student('狄仁杰', 22)
teacher = Teacher('武则天', 35, '副教授')
stu1.eat()
stu2.sleep()
teacher.teach('Python程序设计')
stu1.study('Python程序设计')


class Suite(Enum):
    """花色(枚举)"""
    SPADE, HEART, CLUB, DIAMOND = range(4)


class Card:
    """牌"""

    def __init__(self, suite, face):
        self.suite = suite
        self.face = face

    def __repr__(self):
        suites = '♠♥♣♦'
        faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
        # 根据牌的花色和点数取到对应的字符
        return f'{suites[self.suite.value]}{faces[self.face]}'

    # __lt__ less than <
    # __gt__ gather than >
    # __le__ less equals <=
    # __ge__ gather equals >=
    # __eq__ equals ==
    # __ne__ not equals !=
    def __lt__(self, other):
        # 花色相同比较点数的大小
        if self.suite == other.suite:
            return self.face < other.face
        # 花色不同比较花色对应的值
        return self.suite.value < other.suite.value


card1 = Card(Suite.SPADE, 5)
card2 = Card(Suite.HEART, 13)
print(card1, card2)  # ♠5 ♥K


class Poker:
    """扑克"""

    def __init__(self):
        # 通过列表的生成式语法创建一个装52张牌的列表
        self.cards = [Card(suite, face) for suite in Suite
                      for face in range(1, 14)]
        # current属性表示发牌的位置
        self.current = 0

    def shuffle(self):
        """洗牌"""
        self.current = 0
        # 通过random模块的shuffle函数实现列表的随机乱序
        random.shuffle(self.cards)

    def deal(self):
        """发牌"""
        card = self.cards[self.current]
        self.current += 1
        return card

    @property
    def has_next(self):
        """还有没有牌可以发"""
        return self.current < len(self.cards)


poker = Poker()
poker.shuffle()
print(poker.cards)
print(poker.deal())


class Player:
    """玩家"""

    def __init__(self, name):
        self.name = name
        self.cards = []

    def get_one(self, card):
        """摸牌"""
        self.cards.append(card)

    def arrange(self):
        self.cards.sort()


poker = Poker()
poker.shuffle()
players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
for _ in range(13):
    for player in players:
        player.get_one(poker.deal())
for player in players:
    player.arrange()
    print(f'{player.name}: ', end='')
    print(player.cards)


class Employee(metaclass=ABCMeta):
    """员工，定义为抽象类，使用元类中ABCMeta来定义抽象类"""

    def __init__(self, name):
        self.name = name

    # 定义抽象方法，提供给实现类实现
    @abstractmethod
    def get_salary(self):
        """结算月薪"""
        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

    def get_salary(self):
        return 200 * self.working_hour


class Salesman(Employee):
    """销售员"""

    def __init__(self, name, sales=0):
        super().__init__(name)
        self.sales = sales

    def get_salary(self):
        return 1800 + self.sales * 0.05


employees = [
    Manager('刘备'), Programmer('诸葛亮'), Manager('曹操'),
    Programmer('荀彧'), Salesman('吕布'), Programmer('张辽'),
]
for emp in employees:
    # isinstance 判断是否继承
    if isinstance(emp, Programmer):
        emp.working_hour = random.randint(50, 80)
    elif isinstance(emp, Salesman):
        emp.sales = float(random.randrange(50, 90, 3))
    print(f'{emp.name}本月工资为: ￥{emp.get_salary():.2f}元')
