面向对象编程
Posted 轻风乘舟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象编程相关的知识,希望对你有一定的参考价值。
面向对象编程
两种编程方式
指令式编程 —> 面向过程(函数)编程 —> 程序比较简单的时候没有任何毛病
编程范式(程序设计的方法论):面向对象编程/函数式编程
类和对象的定义
-
对象:对象是可以接收消息的实体,面向对象编程就是通过给对象发消息达到解决问题的目标。
-
对象 = 数据 + 函数(方法) —> 对象将数据和操作数据的函数从逻辑上变成了一个整体。
- 一切皆为对象
- 对象都有属性和行为
- 每个对象都是独一无二的
- 对象一定属于某个类
-
类(类型): 将一大类对象共同的特征(静态特征和动态特征)抽取出来之后得到的一个抽象概念。
简单的说,类是对象的蓝图(模板),有了类才能够创建出这种类型的对象。
面向对象编程三步骤
面向对象编程
-
-
1.定义类 —>类的命名使用驼峰命名法(每个单词首字母大写)
- 数据抽象:找到和对象相关的静态特征(属性)—> 找名词
- 行为抽象:找到和对象相关的动态特征(方法)—> 找动词
- 2.造对象 具体
- 3.发消息
一、定义类
适应性代码 在实际开发中不会加 print 不然会高耦合
在Python中,可以使用class
关键字加上类名来定义类(类名的命名使用驼峰命名法,即每个单词首字母大写),通过缩进我们可以确定类的代码块,就如同定义函数那样。
self
是代表接收消息的对象
'''
example01
'''
class Student:
"""学生"""
# 数据抽象 (属性)
def __init__(self, name, age): #self 学生对象 后面的是参数
self.name = name #给对象绑定name 数据
self.age = age
# 行为抽象 (方法)
def eat(self): #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}')
def watch_av(self):
if self.age < 18:
print(f'{self.name}未满18岁,只能看《天线宝宝》')
else:
print(f'{self.name}正在观看岛国片')
二、造对象
在前面学的函数与模块知识
此时我们在相同路径下创建新的文件example02
导入example01
'''
example02
'''
from example01 import Student
#第二步 创建对象 ---> 构造器语法 ---> 类名(...,...)
stu1 = Student('王大锤',15)
stu2 = Student('吴某凡',25)
三、发消息
还是在example02中
#第三步:给对象发消息(调用对象的方法)
#Student.study(stu1,'python程序设计')
stu1.study('python程序设计')
stu1.eat()
stu1.watch_av()
stu1.play('斗地主')
stu2.play('选妃')
stu2.watch_av()
案例
import time
# 定义数字时钟类
class Clock(object):
"""数字时钟"""
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)
while True:
# 给时钟对象发消息读取时间
print(clock.show())
# 休眠1秒钟
time.sleep(1)
# 给时钟对象发消息使其走字
clock.run()
静态方法、类方法
我们在类里面写的函数,通常称之为方法,它们基本上都是发给对象的消息
但是有的时候,我们的消息并不想发给对象,而是希望发给这个类(类本身也是一个对象)
在创建对象前给类发消息让它决定是否执行,能执行就创建对象。
- 静态方法 - 发给类的消息 —> @staticmethod —> 装饰器
- 类方法 - 发给类的消息 —> @classmethod —> 装饰器 —> 第一个参数(cls)是接收消息的类
"""
example05 - 定义三角形的类,提供计算周长和面积的方法
Author: 龚凡
Date: 2021/8/5 0005
"""
import math
# 直接在初始化方法下面写判断条件是可以的,但会造成代码冗余执行效率不加
# 如果条件不满足会导致报错难以继续执行,此时我们就需要使用静态方法让类先判断后才决定是否执行
# class Triangle:
# def __init__(self, a, b, c):
# if a + b > c and b + c > a and c + a > b:
# self.a = a
# self.b = b
# self.c = c
# else:
# raise ValueError('无效的边长') # 引发异常
#
# def perimeter(self):
# return self.a + self.b + self.c
#
# def area(self):
# half = self.perimeter() / 2
# return math.sqrt(half * (half - self.a) * (half - self.b) * (half - self.c))
#
class Triangle:
"""三角形"""
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
# @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 perimeter(self):
return self.a + self.b + self.c
def area(self):
half = self.perimeter() / 2
return (half * (half - self.a) * (half - self.b) * (half - self.c)) ** 0.5
if __name__ == '__main__':
# 调用静态方法,判断三条边能否构成三角形
if Triangle.is_valid(3, 4, 5):
t = Triangle(3, 4, 5) #创建对象
print(t.perimeter()) #发消息
print(t.area())
else:
print('无效的边长,无法构造三角形对象')
面向对象解决实际问题
魔术方法
魔术方法(魔法方法)—> 有特殊用途和意义的方法
-
魔术方法(魔法方法)—> 有特殊用途和意义的方法
- init —> 初始化方法,在调用构造器语法创建对象的时候会被自动调用
- str —> 获得对象的字符串表示,在调用print函数输出对象时会被自动调用
-
repr —> 获得对象的字符串表示,把对象放到容器中调用print输出时会自动调用
—> representation - lt —> 在使用 < 运算符比较两个对象大小时会自动调用
如果要限制一个类的对象只能拥有某些属性,可以在类中使用__slots__魔法属性
里面的属性是不可修改的。
class Student:
__slots__ = ('name', 'gender')
"""
example06 - 扑克游戏,四个玩家参与,先洗牌,再把牌发到四个玩家的手上。
~ 牌(Card)
- 属性:花色(suite)、点数(face)
- 行为:显示
~ 扑克(Poker)
- 属性:保存牌的列表
- 行为:洗牌(shuffle)、发牌(deal)
~ 玩家
- 属性:名字(昵称)、保存玩家手牌的列表
- 行为:摸牌(get)、整理(arrange)
魔术方法(魔法方法)---> 有特殊用途和意义的方法
~ __init__ ---> 初始化方法,在调用构造器语法创建对象的时候会被自动调用
~ __str__ ---> 获得对象的字符串表示,在调用print函数输出对象时会被自动调用
~ __repr__ ---> 获得对象的字符串表示,把对象放到容器中调用print输出时会自动调用
---> representation
~ __lt__ ---> 在使用 < 运算符比较两个对象大小时会自动调用
如果一个变量的取值只有有限个选项,可以考虑使用枚举类型。
Python中没有定义枚举类型的语法,但是可以通过继承Enum类来实现枚举类型。
结论1:枚举类型是定义符号常量的最佳选择!!!
结论2:符号常量(有意义的名字)总是优于字面常量!!!
Author:
Date: 2021/8/5
"""
from enum import Enum
# 枚举类型
class Suite(Enum):
SPADE, HEART, CLUB, DIAMOND = range(4)
class Card:
"""牌"""
def __init__(self, suite, face):
self.suite = suite
self.face = face
def __str__(self):
return self.show()
def __repr__(self):
return self.show()
def __lt__(self, other): #两个牌做比较 利于下面整理牌
if self.suite == other.suite:
return self.face < other.face
return self.suite.value < other.suite.value
def show(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]}'
def main():
"""程序入口"""
card1 = Card(Suite.HEART, 1)
card2 = Card(Suite.SPADE, 13)
print(card1, card2)
print(card1 is card2)
card3 = Card(Suite.DIAMOND, 9)
card4 = Card(Suite.CLUB, 11)
print(card3.show(), card4.show())
card1 = card2
print(card1, card2, card3)
# 身份运算符
print(card1 is card2)
print(card1 is card3)
cards = [card1, card2, card3, card4]
print(cards)
if __name__ == '__main__':
main()
"""
example07 - 扑克
Author:
Date: 2021/8/6
"""
import random
from example06 import Card
from example06 import Suite
class Poker:
"""扑克"""
def __init__(self):
self.cards = [Card(suite, face)
for suite in Suite
for face in range(1, 14)]
self.counter = 0
def shuffle(self):
"""洗牌"""
self.counter = 0
random.shuffle(self.cards)
def deal(self) -> Card:
"""发牌"""
card = self.cards[self.counter]
self.counter += 1
return card
def has_more(self) -> bool:
"""是否还有牌"""
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 example07 import Poker
class Player:
"""玩家"""
def __init__(self, nickname):
self.nickname = nickname
self.cards = []
def get_one_card(self, card):
"""摸一张牌"""
self.cards.append(card)
def arrange(self):
"""整理手上的牌"""
self.cards.sort()
def show(self):
"""显示玩家手上的牌"""
print(self.nickname, end=': ')
for card in self.cards:
print(card, end=' ')
print()
def main():
nicknames = ('东邪', '西毒', '南帝', '北丐')
players = [Player(nickname) for nickname in nicknames]
poker = Poker()
poker.shuffle()
# 将牌发到四个玩家的手上
for _ in range(13):
for player in players:
card = poker.deal()
player.get_one_card(card)
# 显示四个玩家手上的牌
for player in players:
player.arrange()
player.show()
if __name__ == '__main__':
main()
面向对象四大支柱
面向对象编程的四大支柱:
-
~ 抽象(abstraction):提取共性(定义类就是一个抽象过程,需要做数据抽象和行为抽象)。
-
~ 封装(encapsulation):把数据和操作数据的函数从逻辑上组装成一个整体(对象)。
—> 隐藏实现细节,暴露简单的调用接口。 -
~ 继承(inheritance):扩展已有的类创建新类,实现对已有类的代码复用。
-
~ 多态(polymorphism):给不同的对象发出同样的消息,不同的对象执行了不同的行为。
—> 方法重写:子类对父类已有的方法,重新给出自己的实现版本
抽象和封装在前面我们已经学习了,现在来学习继承和多态。
继承
继承:对已有的类进行扩展创建出新的类,这个过程就叫继承。
提供继承信息的类叫做父类(超类、基类),得到继承信息的类称为子类(派生类)。
注意 !!
继承是实现代码复用的一种手段,但是千万不要滥用继承。
子类直接从父类继承公共的属性和行为,再添加自己特有的属性和行为,
所以子类一定是比父类更强大的,任何时候都可以用子类对象去替代父类对象。
Python中的继承允许多重继承,一个类可以有一个或多个父类。
如果不是必须使用多重继承的场景下,请尽量使用单一继承。
在下列例子中 我们定义Person为父类类,子类为三个,分别是Student、Teacher、Programmer、它们都有三个共同的行为是 eat 、play、introduce。所以创建父类来装这些行为,这样有利于减少代码。
class Person:
"""人"""
def __init__(self, name, gender):
self.name = name
self.gender = gender
def eat(self):
"""吃饭"""
print(f'{self.name}正在吃饭.')
def play(self, game_name):
"""玩"""
print(f'{self.name}正在玩{game_name}.')
def introduce(self):
"""自我介绍"""
sex = "男" if self.gender else "女"
print(f'我叫{self.name}, 是一个{sex}人.')
class Student(Person):
"""学生"""
def __init__(self, name, gender, grade):
super().__init__(name, gender)
self.grade = grade
def study(self, course_name):
"""学习"""
print(f'{self.name}正在学习{course_name}.')
class Teacher(Person):
"""老师"""
def __init__(self, name, gender, title):
super().__init__(name, gender)
self.title = title
def teach(self, course_name):
"""教课"""
print(f'{self.name}{self.title}正在讲授{course_name}.')
class Programmer(Person):
"""程序员"""
def write_code(self, programming_language):
"""写代码"""
print(f'{self.name}正在用{programming_language}写代码.')
stu = Student('王大锤', True, '五年级')
stu.study('语文')
stu.play('王者荣耀')
stu.eat()
stu.introduce()
teacher = Teacher('王大锤', True, '叫兽')
teacher.eat()
teacher.play('斗地主')
teacher.teach('Python程序设计')
teacher.introduce()
programmer = Programmer('白元芳', True)
programmer.eat()
programmer.play('吃鸡')
programmer.write_code('Python')
多态
~ 多态(polymorphism):给不同的对象发出同样的消息,不同的对象执行了不同的行为。
—> 方法重写:子类对父类已有的方法,重新给出自己的实现版本
在父类Employee中拥有get_salary行为,且三个子类中也有相同的行为但实现过程不同,运行下代码会看出。
子类对父类已有的方法,重新给出自己的实现版本,这个过程叫做方法重写(override)。
在重写方法的过程中,不同的子类可以对父类的同一个方法给出不同的实现版本,那么该方法在运行时就会表现出多态行为。
'''
三类员工:
~ 部门经理:固定月薪,15000元
~ 程序员:计时结算月薪,每小时200元
~ 销售员:底薪+提成,底薪1800元,销售额5%提成
录入员工信息,自动结算月薪
'''
from abc import abstractmethod
class Employee:
def __init__(self, no, name):
self.no = no
self.name = name
@abstractmethod
def get_salary以上是关于面向对象编程的主要内容,如果未能解决你的问题,请参考以下文章