编程思维:
- 指令式编程 → 面向过程(函数)编程
- 面向对象的编程
当代码量很少的时候,面向过程编程足够解决问题。当代码越来越复杂的时候,一个好的方案是面向对象的编程思维。
对象
- 对象是可以接收消息的实体,我们只需将指令发给对象,就可以完成我们想要实现的操作
- 对象由数据和**函数(方法)**组成
类
将一大类对象的共同特征提取出来,就形成了类这个抽象概念
类是对象的模板,对象是类的实例
类既有静态特征也有动态特征
面向对象的编程
- 定义类
- 数据抽象:找到静态特征, 属性
- 行为抽象:找到动态特征, 方法
- 造对象
- 发消息
类的定义
- 类的名称,每个单词首字母大写
- 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)的情况下记最大值
- 开局:庄家给玩家发两张明牌,给自己发一张明牌一张暗牌
- 如果玩家的牌是A和 10~K,那么玩家拥有了黑杰克
- 如果庄家的明牌是10~K,暗牌为A,应该直接翻开并且拥有黑杰克
- 如果庄家的明牌是A,玩家可以考虑是否买保险,保险金额是赌注的一半且不退
- 此时,如果庄家的暗牌是10~K,那么翻开此牌,购买保险的玩家获得1倍赌注;如果庄家没有黑杰克,则保持暗牌,玩家继续游戏
- 如果玩家是黑杰克,庄家不是,那么玩家赢得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()
面向对象的四大支柱
抽象:提取共性(定义类
封装:把数据和函数组装成一个对象,隐藏实现细节,暴露简单的调用接口
继承:扩展已有的类创建新类,实现代码复用
多态:给不同的对象发相同的消息,不同的执行行为