10.面向对象、类

编程思维:

  • 指令式编程 → 面向过程(函数)编程
  • 面向对象的编程

当代码量很少的时候,面向过程编程足够解决问题。当代码越来越复杂的时候,一个好的方案是面向对象的编程思维。

对象

  • 对象是可以接收消息的实体,我们只需将指令发给对象,就可以完成我们想要实现的操作
  • 对象由数据和**函数(方法)**组成

将一大类对象的共同特征提取出来,就形成了类这个抽象概念

类是对象的模板,对象是类的实例

类既有静态特征也有动态特征

面向对象的编程

  1. 定义类
    • 数据抽象:找到静态特征, 属性
    • 行为抽象:找到动态特征, 方法
  2. 造对象
  3. 发消息

类的定义

  • 类的名称,每个单词首字母大写
  • init表示初始化,固定用__init__表示
  • 类中的方法,一般中间间隔一个空行
class Student:

    # 数据抽象(属性)
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 行为抽象(方法)
    def eat(self):
        print(f"{self.name}正在吃饭")

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

    def play(self, game_name):
        print(f"{self.name}正在玩{game_name}")

创建对象、给对象发消息

from class_exp import Student

stu1 = Student("Alice", 15)
stu2 = Student("Ash", 16)

stu1.study("English")
stu2.eat()

例子1 泳池

给定泳池半径为r,外面有3米的过道。泳池周长58.5元/平方,围墙造价38.5元/米。

其实是圆的问题,所以定义一个圆的类

import math

class MakeCircle:

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

    def perimeter(self):
        return 2 * math.pi * self.radius

    def area(self):
        return math.pi * self.radius ** 2

if __name__ == '__main__':
    r = float(input("请输入泳池半径:"))
    c1 = MakeCircle(r)
    c2 = MakeCircle(r + 3)

    c2_price = c2.perimeter() * 38.5
    aisle_price = (c2.area() - c1.area()) * 58.5
    print(f"围墙的造价:{c2_price:.2f}元")
    print(f"过道的造价:{aisle_price:.2f}元")

例子2:时钟

创建一个时钟对象(可以显示时、分、秒),可以运作,走字。

import time
import os

class Clock:

    def __init__(self, hour, minute, second):
        self.hour = hour
        self.minute = minute
        self.second = second

    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 f"{self.hour:0>2d}:{self.minute:0>2d}:{self.second:0>2d}"

if __name__ == '__main__':
    clock1 = Clock(12, 30, 55)
    while True:
        os.system("cls")  # 清除屏幕操作需要再terminal运行
        print(clock1.show())
        time.sleep(1)
        clock1.run()

例子3:三角形的类,计算周长和面积

类可以调用自身的方法

静态方法:发给类的消息,大部分时候消息是发给对象的,但有时候我们需要把消息发给类

两种写法

  • @classmethod ,这样写第一个参数需要写cls
  • @static
class Triangle:
    """三角形"""

    def __init__(self, a, b, c):
        if Triangle.is_valid(a, b, c):
            self.a, self.b, self.c = a, b, c
        else:
            raise ValueError("无效的边长")

    # @classmethod
    # def is_valid(cls, a, b, c):
    #     return a+b > c and b+c > a and a+c > b

    @staticmethod
    def is_valid(a, b, c):
        return a+b > c and b+c > a and a+c > b

    def girth(self):
        return self.a + self.b + self.c

    def area(self):
        half = self.girth() / 2
        return (half * (half - self.a) * (half - self.b) * (half - self.c)) ** 0.5

if __name__ == '__main__':
    try:
        t = Triangle(1, 2, 3)
        print(t.girth())
    except ValueError as err:
        print(err)
    finally:
        print("请输入新的边长")

例子4:扑克牌

魔术方法(魔法方法):

  • 初始化方法,创建对象时自动调用, init
  • 获取对象的字符串表示,在用print输出时自动调用 str
  • 获取对象的字符串表示,在把输出对象放到列表中输出时自动调用 repr
class Card:

    def __init__(self, color, num):
        self.color = color
        self.num = num

    def __str__(self):
        """如果不加这个方法,直接打印对象时返回对象在内存中的位置"""
        return self.show()

    def __repr__(self):
        return self.show()

    def __lt__(self, other):
        """用此方法规定小于的定义,否则无法对手牌排序"""
        return self.num < other.num

    def show(self):
        colors = {"S": "♠", "H": "❤", "B": "♦", "M": "♣"}
        nums =["", "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
        return f"{colors[self.color]}{nums[self.num]}"

def main():
    """程序入口"""
    card1 = Card("H", 1)
    card2 = Card("B", 4)
    print(card1, card2)
    cards = [card1, card2]
    print(cards)

if __name__ == '__main__':
    main()

扑克牌的类

import random
from cards import Card

class Poker:

    def __init__(self):
        self.cards = [Card(color, num)
                      for color in "SHBM"
                      for num in range(1, 14)]
        self.counter = 0

    def shuffle(self):
        """洗牌"""
        random.shuffle(self.cards)

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

    def has_more(self):
        return self.counter < len(self.cards)

def main():
    poker = Poker()
    poker.shuffle()
    while poker.has_more():
        print(poker.deal(), end="")

if __name__ == '__main__':
    main()

玩家类

from poker import Poker

class Player:
    """玩家"""
    def __init__(self, nickname):
        self.nick_name = nickname
        self.cards = []

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

    def arrange(self):
        """整理手牌"""
        self.cards.sort()

    def show(self):
        """显示玩家手牌"""
        print(self.nick_name, end=":")
        for card in self.cards:
            print(card, end=" ")
        print()

def main():
    poker = Poker()
    poker.shuffle()
    player1 = Player("Alice")
    player2 = Player("Bob")
    player3 = Player("Chris")
    player4 = Player("Devil")
    players = [player1, player2, player3, player4]
    for _ in range(13):
        for player in players:
            card = poker.deal()
            player.get_card(card)
    for player in players:
        player.arrange()
        player.show()

if __name__ == '__main__':
    main()

枚举类型

如果一个变量的取值只有有限多个选项,可以使用枚举类型。

from enum import Enum

class Color(Enum):
    SPADE, HEART, CLUB, DIAMOND = range(4)

应用1:Black Jack游戏

规则:

  • 2-9按照原来的点数计分,10-K都记作10分;
  • A可以记作1点或11点,在不爆牌(也就是大于21)的情况下记最大值
  1. 开局:庄家给玩家发两张明牌,给自己发一张明牌一张暗牌
  2. 如果玩家的牌是A和 10~K,那么玩家拥有了黑杰克
  3. 如果庄家的明牌是10~K,暗牌为A,应该直接翻开并且拥有黑杰克
  4. 如果庄家的明牌是A,玩家可以考虑是否买保险,保险金额是赌注的一半且不退
  5. 此时,如果庄家的暗牌是10~K,那么翻开此牌,购买保险的玩家获得1倍赌注;如果庄家没有黑杰克,则保持暗牌,玩家继续游戏
  6. 如果玩家是黑杰克,庄家不是,那么玩家赢得1.5倍赌注;若庄家为黑杰克而玩家不是,庄家赢得赌注;若双方都是黑杰克,平局。

接下来开始拿牌:

若玩家开始拿牌,只能选择继续拿牌活停牌。如果点数超过21,那么玩家输;

当玩家停止拿牌后,庄家翻开暗牌,并持续拿牌直至点数不小于17

如果庄家爆牌,玩家赢得1倍赌注;否则比点数大小,大的赢。

from player import Player
from poker import Poker

def count_cards(cards):
    """计算手牌的点数"""
    values = {}
    for i in range(2, 11):
        values[f"{i}"] = i
    values["1"] = 11
    values["11"], values["12"], values["13"] = 10, 10, 10
    total, num_aces = 0, 0
    for card in cards:
        total += values[f"{card.num}"]
        if card.num == 1:
            num_aces += 1
    while total > 21 and num_aces > 0:
        total -= 10
        num_aces -= 1
    return total

def black_jack():
    poker = Poker()  # 生成一副牌
    poker.shuffle()  # 洗牌
    player = Player("player")
    banker = Player("Banker")
    money = int(input("请玩家输入赌注:"))

    # 开局发2张牌
    for _ in range(2):
        card = poker.deal()
        player.get_card(card)
    print(f"玩家的手牌是:{player.cards}")
    print(f"玩家点数合计{count_cards(player.cards)}")

    # 开局庄家的两张牌,其中一张是暗牌
    card = poker.deal()
    banker.get_card(card)
    card = poker.deal()
    banker.black_card = card
    print(f"庄家的明牌是:{banker.cards}")
    print(f"庄家的暗牌暂不显示")

    # 判断是否有黑杰克
    is_player_jack, is_banker_jack = False, False
    if count_cards(player.cards) == 21:
        is_player_jack = True
        print("玩家拥有Black Jack!")
    if count_cards(banker.cards) == 21 and banker.black_card.num == 1:
        is_banker_jack = True
        print("庄家拥有Black Jack !")
    if banker.cards[0].num == 1:
        is_safe_money = input("庄家的明牌是A,玩家如果需要购买保险,请输入y:")
        if is_safe_money == "y" and banker.black_card.num >= 10:
            print(f"庄家的暗牌是{banker.black_card}")
            print(f"游戏结束,玩家购买的保险生效了,获得了{money}")
            return
    if is_banker_jack and is_player_jack :
        print("游戏结束,平局")
        return
    elif is_player_jack and not is_banker_jack:
        print(f"游戏结束,玩家赢了,获得了{money * 1.5:0.f}")
        return
    elif not is_player_jack and is_banker_jack:
        print(f"庄家赢了,获得了{money}")
        return

    # 开局没有决出胜负,玩家是否选择继续拿牌
    while True:
        is_get_card = input(f"当前玩家点数是:{count_cards(player.cards)},如果玩家选择继续拿牌,请输入y:") == "y"
        if is_get_card:
            card = poker.deal()
            player.get_card(card)
            if count_cards(player.cards) > 21:
                print(f"玩家点数是:{count_cards(player.cards)}玩家爆牌,庄家赢了,得到了{money}")
                return
        else:
            break

    # 庄家翻开暗牌,并持续拿牌,直到点数大于等于17
    banker.cards.append(banker.black_card)
    while count_cards(banker.cards) < 17:
        card = poker.deal()
        banker.get_card(card)
    if count_cards(banker.cards) > 21:
        print(f"庄家点数是{count_cards(banker.cards)},庄家爆牌,玩家赢了,得到了{money}")
        return

    # 最终比较点数,谁大谁赢
    if count_cards(player.cards) > count_cards(banker.cards):
        print(f"玩家的点数是:{count_cards(player.cards)}大于庄家:{count_cards(banker.cards)},玩家赢")
    elif count_cards(player.cards) < count_cards(banker.cards):
        print(f"玩家的点数是:{count_cards(player.cards)}小于庄家:{count_cards(banker.cards)},庄家赢")
    else:
        print(f"玩家的点数是:{count_cards(player.cards)}等于庄家:{count_cards(banker.cards)},平局")

if __name__ == '__main__':
    is_continue = True
    while is_continue:
        black_jack()
        is_continue = input("如果继续游戏,请输入y:") == "y"

限制给对象添加新的属性

可以双下划线+slots来限制对象的固有属性,比如student的例子

如下代码会报错,因为无法添加新的属性

class Student:

    __slots__ = ("name", "age")

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

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

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

    def play(self, game_name):
        print(f"{self.name}正在玩{game_name}")

if __name__ == '__main__':
    student1 = Student("Alice", 11)
    print(student1.name)
    student1.birthday = "1991-03"
    print(student1.birthday)

类的继承

可以从父类继承属性和方法,并进行扩展

继承是实现代码复用的一种手段,但是千万不要滥用继承

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

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

class Student(Human):
    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

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

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

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

student1 = Student("alice", 11, 6)
teacher1 = Teacher("Bob", 29, "教授")
student1.study("English")
teacher1.teach("Math")
student1.eat()
teacher1.eat()

Python中的继承允许多重继承,但除非必要,不要轻易使用。

类之间的关系

  • is a 关系 继承:student is human 例如 class Student(Human)
  • has a 关系 关联(普通关联和强关联):student has a PC 例如Student类中有一个属性self.computer = Computer()
  • use a 关系 依赖:一个类作为另一个类的方法的参数或返回值 student.use(PC)

应用2:工资结算系统

三类员工:

  • 部门经理:固定月薪15000元
  • 程序员:计时结算月薪,每小时200元
  • 销售:底薪+提成,底薪1800元,提成销售额5%

录入员工信息,自动结算月薪

不同的子类可以对父类的同一个方法给出不同的实现版本,这叫做多态

class Employee:
    def __init__(self, emp_id, name):
        self.emp_id = emp_id
        self.name = name

    def get_money(self):
        pass

class Manager(Employee):
    def get_money(self):
        return 15000

class Programmer(Employee):
    def __init__(self, emp_id, name):
        super().__init__(emp_id, name)
        self.work_time = 0

    def get_money(self):
        return 200 * self.work_time

class SalesMan(Employee):
    def __init__(self, emp_id, name):
        super().__init__(emp_id, name)
        self.sales = 0

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

def main():
    emps = [
        Programmer("1001", "Alice"),
        Manager("1002", "Bob"),
        SalesMan("1003", "Coil")
    ]
    for emp in emps:
        if type(emp) == Programmer:
            emp.work_time = int(input(f"请输入{emp.name}的工作时间:"))
        elif type(emp) == SalesMan:
            emp.sales = int(input(f"请输入{emp.name}的销售额:"))
        print(f"{emp.name}的工资是{emp.get_money():.0f}")

if __name__ == '__main__':
    main()

面向对象的四大支柱

抽象:提取共性(定义类

封装:把数据和函数组装成一个对象,隐藏实现细节,暴露简单的调用接口

继承:扩展已有的类创建新类,实现代码复用

多态:给不同的对象发相同的消息,不同的执行行为

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值