Day09.面向对象进阶

零基础Python学习路线及每个阶段学习目标 零基础Python学习路线及阶段学习目标,首先应该夯实Python核心基础、Web前端编程、Django开发框架、Flask开发框架、爬虫与数据分析等知识,理解机器学习相关的基本概念及系统处理流程。 零基础Python学习路线及阶段学习目标: 阶段一、Python核心基础 1、Python编程基础,语法规则,函数与参数,数据类型,模块与包,文件IO,培养扎实的Python编程基本功,同时对Python核心对象和库的编程有熟练的运用。 2、Python面向对象,核心对象,异常处理,多线程,网络编程,深入理解 阅读详情

面向对象进阶



前言

其实在前面的文章中我们已经了解了面向对象的一些入门知识,也知道了如何去定义类,如何创建对象以及如何给对象发消息。而在实际操作中,为了能够更好的使用面向对象编程思想进行程序开发,我们还需要对Python中的面向对象编程这一重要思想进行更为深入的了解和研究。


一. @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 就说明age这个属性已经修改成功。


if __name__ == '__main__':
    main()

# 法外狂徒正在玩飞行棋.
# 法外狂徒正在玩斗地主.

我们通过使用person.name = "张三"发现直接报错,他说无法设置属性,也就是说,这里没有这个属性嘛,但我们应该看到前面的年龄为啥可以直接修改,就是因为我们用了@age.setter,那这样的话,我们再内部就实现了相关修改,从现象来看就是年龄这个属性时可以修改的,而且再次调用person.play()时,显示的是法外狂徒是在玩斗地主的,这就说明他年龄已经完成修改了,而且也没有报错,一切正常嘛。可见setter的作用就是让一个私有属性或者受保护的属性可以在外界就能修改。

二. __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
    
if __name__ == '__main__':
    main()

# 法外狂徒正在玩斗地主.

同样的,我们发现对于Person 这个对象是没有_is_gay这个属性的。就算你设定person._is_gay=True也没用,这就体现了setter的功能嘛。

三. 静态方法和类方法

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

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()

# 12
# 6.0

我们发现,对于某些中间函数,其实我们再使用的过程中,仅仅是调用了一下,并不在乎他在整个过程中的具体使用情况,也就是说,这个函数基本上是不怎么改变的,在整个计算的过程中,想这种的函数,就比较实用使用静态方法嘛。

和静态方法比较类似,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()

就像这里的now()方法,他在使用的过程中就是查看系统此时的时间,作用也是基本固定,所以使用静态函数就比较好嘛。小白感觉这里最难搞的应该就是run()函数了,虽然看起来简单,但是能不能写出来又是另外一回事。当然了,最后就是写个死循环嘛,让程序一直走就好,就像真的时钟一样,每秒走一下(就是程序的运行间隔时间为1秒,即sleep(1))。

四. 类之间的关系

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

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

我们可以使用一种叫做UML(统一建模语言)的东西来进行面向对象建模,其中一项重要的工作就是把类和类之间的关系用标准化的图形符号描述出来。关于UML我们在这里不做详细的介绍,有兴趣的读者可以自行阅读《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_tv(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_tv()
    t = Teacher('张三', 16, '大师')
    t.teach('艾欧尼亚玩ADC')
    t.watch_tv()


if __name__ == '__main__':
    main()

# 高一的法外狂徒正在学习数学.
# 法外狂徒只能观看《熊出没》.
# 张三大师正在教艾欧尼亚玩ADC.
# 张三只能观看《熊出没》.

注: 子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(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(abc Abstract Base Classes,即Python的抽象基类)模块的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(20, 30)

    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(15, 20)
            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')  # 规定只能有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()

# 东邪: [♠A, ♠5, ♠6, ♣2, ♣4, ♣6, ♣9, ♥6, ♥8, ♥9, ♥K, ♦A, ♦10]
# 西毒: [♠2, ♣3, ♣8, ♣10, ♣Q, ♥2, ♥5, ♥7, ♥10, ♦2, ♦5, ♦6, ♦9]
# 南帝: [♠3, ♠7, ♠8, ♣5, ♣J, ♥A, ♥3, ♥J, ♦3, ♦4, ♦7, ♦8, ♦K]
# 北丐: [♠4, ♠9, ♠10, ♠J, ♠Q, ♠K, ♣A, ♣7, ♣K, ♥4, ♥Q, ♦J, ♦Q]

案例3:工资结算系统

"""
某公司有三种类型的员工,分别是部门经理、程序员和销售员。
需要设计一个工资结算系统,根据提供的员工信息来计算月薪:
1. 部门经理的月薪是每月固定15000元
2. 程序员的月薪按本月工作时间计算 每小时150元
3. 销售员的月薪是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:  # isinstance 两个对象是否为同一个类
        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()

# 刘备本月工资为: ¥15000.0元
# 请输入诸葛亮本月工作时间: 150
# 诸葛亮本月工资为: ¥22500.0元
# 曹操本月工资为: ¥15000.0元
# 请输入荀彧本月销售额: 2000
# 荀彧本月工资为: ¥1300.0元
# 请输入吕布本月销售额: 22220
# 吕布本月工资为: ¥2311.0元
# 请输入张辽本月工作时间: 1
# 张辽本月工资为: ¥150.0元
# 请输入赵云本月工作时间: 150
# 赵云本月工资为: ¥22500.0元

案例4:21点(Black Jack)扑克游戏

import random
import numpy as np
from sys import exit
 
"""
1、对牌组进行洗牌
2、发牌
    2-1、初始化发牌,发两张
    2-2、中途要拍,发一张
3、计算手牌分数
4、胜负判断
5、是否继续叫牌
6、是否继续下一轮
"""
 
 
"""
初始化变量扑克牌组
"""
playing_cards = {
    "♠A": 1, "♠2": 2, "♠3": 3, "♠4": 4, "♠5": 5, "♠6": 6, "♠7": 7,
    "♠8": 8, "♠9": 9, "♠10": 10, "♠J": 10, "♠Q": 10, "♠K": 10,
    "♥A": 1, "♥2": 2, "♥3": 3, "♥4": 4, "♥5": 5, "♥6": 6, "♥7": 7,
    "♥8": 8, "♥9": 9, "♥10": 10, "♥J": 10, "♥Q": 10, "♥K": 10,
    "♣A": 1, "♣2": 2, "♣3": 3, "♣4": 4, "♣5": 5, "♣6": 6, "♣7": 7,
    "♣8": 8, "♣9": 9, "♣10": 10, "♣J": 10, "♣Q": 10, "♣K": 10,
    "♦A": 1, "♦2": 2, "♦3": 3, "♦4": 4, "♦5": 5, "♦6": 6, "♦7": 7,
    "♦8": 8, "♦9": 9, "♦10": 10, "♦J": 10, "♦Q": 10, "♦K": 10
}
poker_number = 1
poker_names = list(playing_cards.keys())
poker_list = poker_names * poker_number
 
# 回合数
game_round = 1
 
# 用于判断手中排是否有A
A_list = ["♠A", "♥A", "♣A", "♦A"]
 
# 总分情况(玩家: 电脑)
total_score = np.array([0, 0])
 

# 模块化函数
"""
对牌组进行打乱
"""
def random_pokers(rand_poker_list):
    random.shuffle(rand_poker_list)
 
 
"""
发牌,两种函数:
第一个,初始化发牌,一次给2张
第二个,后面叫牌,一次给1张
"""
def get_one_poker(input_poker_list):
    return input_poker_list.pop(random.randint(0, len(input_poker_list)-1))
 
 
 
def init_get_poker(input_poker_list):
    return [input_poker_list.pop(random.randint(0, len(input_poker_list)-1)),
            input_poker_list.pop(random.randint(0, len(input_poker_list)-1))]
 
 
"""
计算手中牌的分数
输入:手牌
返回值:分数
"""
def score_count(hand_poker):
    score = 0
    for i in hand_poker:
        score += playing_cards.get(i)
 
    # 判断有没有A
    for i in hand_poker:
        if i in A_list:
            if score + 10 <= 21:
                score = score + 10
            else:
                break
 
    return score
 
"""
胜负判断函数
输入:玩家和电脑分数
输出:输赢情况,并且返回比分,用于加入总比分
"""
def judge_win_lose(your_score, pc_score):
    if your_score > 21 and pc_score > 21:
        print("平局")
        return np.array([0, 0])
    elif your_score <= 21 and pc_score > 21:
        print("你赢了")
        return np.array([1, 0])
    elif your_score > 21 and pc_score <= 21:
        print("你输了")
        return np.array([0, 1])
    else:
        if your_score > pc_score:
            print("你赢了")
            return np.array([1, 0])
        elif your_score == pc_score:
            print("平局")
            return np.array([0, 0])
        else:
            print("你输了")
            return np.array([0, 1])
 
 
"""
是否要牌
输入:玩家输入Y/N
输出:返回状态
"""
def if_get_poker():
    if_continue = input("是否叫牌(Y/N)>>>>>>:")
    if if_continue.upper() == "Y":
        return True
    elif if_continue.upper() == "N":
        return False
    else:
        print("输入有误,请重新输入")
        if_get_poker()
 
 
"""
询问玩家是否继续
返回值:状态
"""
def continue_or_over(total_your, total_pc):
    if_continue = input("是否继续下一轮(Y/N)>>>>>>:")
    if if_continue.upper() == "Y":
        if len(poker_list) < 15:
            print("不好意思,剩余扑克数过少,只有{}张,游戏结束".format(len(poker_list)))
            print("")
            print("这盘游戏最终比分为:(玩家:电脑)>>>>{}:{}".format(total_your, total_pc))
            if total_your > total_pc:
                print("你最终赢得了胜利")
            elif total_your < total_pc:
                print("你最终输掉了游戏")
            else:
                print("和电脑五五开")
 
            exit(1)
        else:
            return True
    elif if_continue.upper() == "N":
        print("玩家不玩了,游戏结束")
        print("")
        print("这盘游戏最终比分为:(玩家:电脑)>>>>{}:{}".format(total_your, total_pc))
        if total_your > total_pc:
            print("你最终赢得了胜利")
        elif total_your < total_pc:
            print("你最终输掉了游戏")
        else:
            print("和电脑五五开")
        exit(1)
    else:
        print("输入有误,请重新输入")
        continue_or_over()
 
 
"""
处于一局游戏的流程
输入:牌组
返回值:该局比分
"""
def every_round(input_poker_list):
    # 初始化玩家和电脑的手牌
    your_hand_poker = []
    pc_hand_poker = []
 
    # 初始化两张牌
    your_init_poker = init_get_poker(input_poker_list)
    pc_init_poker = init_get_poker(input_poker_list)
 
    print("你的手牌是:{}和{}".format(your_init_poker[0], your_init_poker[1]))
    print("电脑的手牌是:{}和?".format(pc_init_poker[0]))
 
    # 判断有没有21点
    your_score = score_count(your_init_poker)
    pc_score = score_count(pc_init_poker)
 
    # 加入手牌
    your_hand_poker.extend(your_init_poker)
    pc_hand_poker.extend(pc_init_poker)
 
    if your_score == 21 or pc_score == 21:
        print("牌面有21点")
        return judge_win_lose(your_score, pc_score)
    else:
        # 玩家叫牌
        while True:
            if_get_your = if_get_poker()
            if if_get_your == True:
                new_poker = get_one_poker(input_poker_list)
                your_hand_poker.append(new_poker)
                your_score = score_count(your_hand_poker)
 
                print("你当前的手牌为:{}".format(your_hand_poker))
 
                if your_score > 21:
                    print("你的手牌超过21点,你输了")
                    print("此时电脑的手牌为:{}".format(pc_hand_poker))
                    return np.array([0, 1])
                else:
                    continue
            else:
                print("你停止叫牌")
                # 轮到电脑叫牌,要求,叫牌直到大于等于玩家分数
                while your_score > pc_score:
                    new_poker = get_one_poker(input_poker_list)
                    pc_hand_poker.append(new_poker)
                    pc_score = score_count(pc_hand_poker)
                print("此时电脑的手牌是:{}".format(pc_hand_poker))
                return judge_win_lose(your_score, pc_score)
 
 
"""
游戏开始咯
"""
input("按'回车'后,游戏正式开始")
 
while True:
    # 游戏提示
    print("********现在是第{}局游戏********".format(game_round))
 
    # 先洗牌
    random_pokers(poker_list)
 
    # 开始每一局, 记录当前输赢情况
    curr_score = every_round(poker_list)
 
    total_score = np.add(total_score, curr_score)
 
    # 播报当前比分
    print("当前总比分为:(玩家:电脑)>>>>{}:{}".format(total_score[0], total_score[1]))
 
    # print("此时牌组剩余卡数:{}".format(len(poker_list)))
    game_round = game_round + 1
 
    continue_or_over(total_score[0], total_score[1])
    print("")

试了几次之后发现这个21点的扑克游戏感觉有点问题,但是不知道如何解决,实在是太菜了,有一次吐槽自己。


总结

本文主要是学习了面向对象编程思想的进阶形式,实在是太难了,再消化几天吧。一是装饰器的使用,对于这种私有属性(被保护的属性)的属性修改问题(也就是你设定setter 和getter在实现修改);二是魔法方法限定类对象只能绑定某些特定的属性;三是静态方法和类方法之间的使用关系以及静态方法的较为适用的范围;四是类与类之间的关系,这就涉及到了继承和多态嘛。最后的最后就是大佬写的几个小案例,当然了对于现在的我来说算是个大工程了,还是那句话,可能我看得懂,但是写出来还是不可能的,还是多动手敲敲代码吧,没啥花里胡哨的,继续加油。

太难了吧,脑壳疼,溜了遛了。Loading(09/100)。。。

Spark Catalog详解 旁边的实习生说:我想要用spark代码中对hive库中的内部表和外部表进行删除(包括数据),咋感觉网上搜了一圈都找不到解决方案啊,spark这么鸡肋吗?我:你应该静下心来好好把spark基础知识进行全面学习。实习生:难道spark有这功能,而我没有学习过?咋弄啊?我:学习一下Spark Catalog。实习生:啥是Catalog啊?Spark Catalog是Spark SQL中的一个元数据管理组件,它提供了一个集中化的存储和查询表、视图和函数的位置。 阅读详情

相关推荐

飞牛NAS一键部署小雅Emby全家桶:解锁海量影音资源与智能刮削

本文详细介绍了如何在飞牛NAS上通过Docker一键部署小雅Emby全家桶,完美解决传统WebDAV挂载无法刮削元数据的问题。该方案整合了小雅Alist海量影音资源与Emby媒体服务器,通过预下载超百GB元数据包,自动实现海报、简介等信息的完美匹配,打造出精美的个人影音库。部署过程需准备阿里云盘令牌与充足存储空间,并详解了从环境配置、镜像拉取到最终验证的完整步骤及优化技巧。

weixin_28312391的博客 923

Python 学习目标

NR比较好的学习资源推荐

xuzhenzhu2010的专栏 2046

从代码层面分析STM32 标准库和HAL库的差异

相当于每个设置都是独立的函数。HAL库将一段代码通过宏定义的方式封装成了一个函数,每个端口的时钟都单独的定义了一个名称,标准库是一个独立的函数,通过参数传递的方式来设置每个端口的时钟。这两个版本的函数本质上操作的还是寄存器,不过HAL库操作的只是BSRR寄存器,而标准库端口置1的时候操作的是BSRR寄存器,端口清0的时候操作的是BRR寄存器。标准库设置端口模式的时候,是在if语句中判断之后直接设置的,而HAL端口模式设置时,却使用了switch语句去设置,说明在HAL库中,端口模式和标准库中发生了改变。

HXYDJ的博客 2万+

2023年最新Python学习路线及学习目标规划

2023年最新Python学习路线及学习目标规划

xiqng17111342931的博客 1849

kaggle项目:纽约出租车行程时间NYC Taxi Trip Duration

纽约出租车形成时间预测(NYC Taxi Trip Duration)是Kaggle中的一个Playground竞赛。比赛数据集基于2016年纽约市黄色出租车行程记录数据,需要建立一个模型,根据单个行程属性,预测测试集中每次行程的持续时间。Kaggle项目模型XGBoost,评价指标RMSLE。

weixin_42113929的博客 2790

Cursor Pro版保姆级开通教程:绕过7天试用,支付宝一步搞定(附地区报错解决方案)

本文详细介绍了Cursor Pro版的订阅流程,包括支付宝支付步骤和解决区域报错的实用方案。针对开发者常见的网络问题和云服务器配置需求,提供了从本地环境调整到云端部署的完整指南,帮助用户高效解锁Pro版的代码补全、私有代码库等核心功能。

weixin_28710999的博客 2104

2020年最新Python学习路线及学习目标规划 拿走不谢!

在任何的编程语言中,注释都不是被用来给电脑执行的,但是注释在编写代码的过程中是非常有必要的。虽然我们可以在代码中不写任何注释,但是我不推荐这么做,因为注释在我们日后回来阅读之前写的代码将起很大的作用。 注释可以让我们阅读自己的或者别人的代码的时候,不至于一头雾水。毕竟当时灵光一现的点子在现在未必能理解,人都是擅长遗忘的。 在Python中,对注释的支持也很大,我们不仅仅可以在源代码中阅读注释,还可以在终端中打印出来。 Python支持两种注释风格,分别为单行注释和多行注释。 单行注释 单行注释以井号“#”

1596

路径规划 | 图解粒子群(PSO)算法(附ROS C++仿真)

粒子群算法(PSO)的基本思想是模拟鸟群觅食机制,将鸟类的觅食方向分为自我认知部分和社会部分,个体自主探索群体周围空间的同时,也随着群体的主流方向前进,不断逼近最优目标。本文详解PSO算法基本概念,并给出ROS C++实现加深理解

FRIGIDWINTER的博客 2616

Python 面向对象 Time类的定义 python

acm A - 2-2 Time类的定义 python 下面展示一些 内联代码片。 // A code block var foo = 'bar'; class Time: def __init__(self): self.__h = 12 self.__m = 0 self.__s = 0 def setHour(self,h): if h > 12 or h < 0: self.__h

qq_43618642的博客 3709

AI Agent的数据安全与隐私保护措施

AI Agent(人工智能代理)作为一种能够感知环境、做出决策并采取行动的智能实体,在现代社会的各个领域中得到了广泛的应用,如智能客服、自动驾驶、金融风险评估等。随着其应用的不断拓展,AI Agent处理和存储的数据量急剧增加,这些数据包含了大量的个人敏感信息和商业机密。因此,确保AI Agent的数据安全与隐私保护变得至关重要。本文的目的在于全面探讨AI Agent的数据安全与隐私保护措施,涵盖从数据的采集、存储、处理到传输的整个生命周期。

AGI×大数据,开启智能时代的认知跃迁;解码AGI,赋能数据驱动的智能革命。 2866

风控系统中的架构设计原型图分析与实践探讨

本文探讨了风控系统的架构设计原型图在风险管理中的重要性。通过分析多个案例,文章展示了如何利用架构设计原型图提升团队沟通效率,优化系统结构与功能。强调了良好的架构设计不仅有助于系统的可扩展性和可维护性,还能增强对风险的应对能力。最后,呼吁金融行业持续关注技术进步与市场需求变化,以不断提升风控系统的设计与实施效果。

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 6万+

Day09 - 面向对象进阶

Day09 - 面向对象进阶 在前面的章节我们已经了解了面向对象的入门知识,知道了如何定义类,如何创建对象以及如何给对象发消息。为了能够更好的使用面向对象编程思想进行程序开发,我们还需要对Python中的面向对象编程进行更为深入的了解。 @property装饰器 之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我...

Laic Zhang的博客 1401

图解移动机器人中的运动规划(Motion Planning),附几十种规划算法

运动规划是移动机器人的核心理论和工程技术之一,本文作为教程先导,解析了运动规划的两大组件——路径规划和轨迹规划。欢迎感兴趣的同学订阅专栏!

FRIGIDWINTER的博客 4万+

【day09】面向对象编程进阶

1.概述:static是一个静态关键字2.使用:a.修饰一个成员变量:static 数据类型 变量名b.修饰一个方法:修饰符 static 返回值类型 方法名(形参){方法体return 结果3.调用静态成员:类名直接调用(不用new对象)4.静态成员特点:a.静态成员属于类成员,不属于对象成员(非静态的成员属于对象成员)b.静态成员会随着类的加载而加载c.静态成员优先于非静态成员存在在内存中d.凡是根据静态成员所在的类创建出来的对象,都可以共享这个静态成员int age。

程序员波特 394

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

day09_Python进阶——面向对象基础

定义类——人类# 在类里面定义方法print('这是一个人类')# 根据类创建一个对象魔法方法是Python中以双下划线__开头和结尾的特殊方法(如__init__),不需要手动调用,Python会在特定场景下自动触发执行。

YUDAMENGNIUBI的博客 210

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

python_100天——DAY09_面向对象进阶

python_100天——DAY09_面向对象进阶

Xiaowu2048的博客 220

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Python实战教程100天:Day09-面向对象进阶

面向对象进阶 在前面的章节我们已经了解了面向对象的入门知识,知道了如何定义类,如何创建对象以及如何给对象发消息。为了能够更好的使用面向对象编程思想进行程序开发,我们还需要对Python中的面向对象编程进行更为深入的了解。 @property装饰器 之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋...

qfluohao的博客 313

day09_面向对象进阶(2)

用interface关键字定义的类我们称之为接口他和抽象类差不多,是一个比抽象类更加彻底的特殊结构,特殊在哪里呢?接口里面只能写抽象方法和常量public interface 接口名{//成员变量(常量)//成员方法(抽象方法)//这里public static final可以加,可以不加。默认就是加了public static final的public static final String SCHOOL_NAME = "黑马程序员";

barry_jiang的博客 676

Day09 - 面向对象编程进阶

日期:2021年1月26日 今天是学习Python的第九天,东西还是挺多了,开始使用小本本记一些代码和用法,方便记忆和查阅,今天没有练习,看几个案例。 学习用的教材是GitHub上骆昊编写的《Python - 100天从新手到大师》 一、@property装饰器 用一个下划线来提示自己类中的属性是受保护的,但是直接暴露给外界也是有问题的。可以使用@property包装器来包装getter(访问器)和setter(修改器)方法来对属性进行访问,安全且方便。 class Rect: def __.

Paranoid's blog 414

Python 100 -- day 09 面向对象进阶

目录 property–装饰器 __slots__魔法 静态方法、类方法 类之间的关系 继承、多态 综合案例 property–装饰器 通过包装getter(访问器)、setter(修改器)方法,来访问属性,主要是对属性进行操作_属性名,暗示收到保护 property–保镖(一对一),setter、getter方法–中间人(线人),最终目标:_属性名 property(保镖)装饰setter、getter(线人),操作_属性名(最终目标) __slots__魔法 因为python 是一

kennle的博客 371

Python入门教程100天:Day09-面向对象进阶

面向对象进阶在前面的章节我们已经了解了面向对象的入门知识,知道了如何定义类,如何创建对象以及如何给对象发消息。为了能够更好的使用面向对象编程思想进行程序开发,我们还需要对Python中的面向对象编程进行更为深入的了解。@property装饰器之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性...

weixin_34238633的博客 474

奥特曼打小怪兽python代码简单版_Python入门:Day09-面向对象进阶

原标题:Python入门:Day09-面向对象进阶面向对象进阶在前面的章节我们已经了解了面向对象的入门知识,知道了如何定义类,如何创建对象以及如何给对象发消息。为了能够更好的使用面向对象编程思想进行程序开发,我们还需要对Python中的面向对象编程进行更为深入的了解。@property装饰器之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴...

weixin_39522423的博客 1589

Python学习教程(Python学习路线):Day09-面向对象进阶

Python学习教程(Python学习路线):面向对象进阶在前面的章节我们已经了解了面向对象的入门知识,知道了如何定义类,如何创建对象以及如何给对象发消息。为了能够更好的使用面向对象编程思想进行程序开发,我们还需要对Python中的面向对象编程进行更为深入的了解。@property装饰器之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给...

weixin_34372728的博客 156

【搬运自用】Python面向对象进阶 - 100天从新手到大师Day09 (GitHub)

搬运Github热门项目Python从新手到大师Day09内容,仅供自己学习和参考

weixin_57560711的博客 172
上一篇: Day10.图形用户界面和游戏开发
下一篇: Day11.文件的操作与异常处理
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值