初始面向对象

发布时间:2024年01月06日

引入

面向过程思维

什么是面向过程?

  1. 面向过程就是按步骤一步一步的执行,从头一直执行到尾。

  2. 面向过程的核心就是过程二字。过程指的是解决问题的步骤,就好比设计一条流水线,是一种机械式的思维方式,但是这种机械式的思维方式也有一定的优点与缺点。

优点:将复杂的问题流程化,进而再简单化。

缺点:扩展性很差。

面向对对象思维

面向对象与面向过程就是完全不同的两种编程思维,面向对象可以将整个任务封装成一个大的类,在这个类里面详细的分解每个步骤,我们只需要执行类就可以完成相应的任务。

在软件开发过程中,面向对象编程是一种解决软件复用的设计和编程方法。

类的概念

类是一个比较抽象的东西,类是封装对象的属性和行为的载体,反过来说具有相同属性和行为的一类实体被称为类。

img

对象的概念

对象,是一个抽象概念,英文称作“Object”,表示任意存在的事物,世间万物皆对象。

通常将对象划分为两个部分,即静态部分与动态部分。

对象同样也是由属性和方法构成的,比如说学生类,而学生类再具体到某某学生的时候,这个学生就是一个对象。

静态部分

静态部分被称为“属性”

人类举例:属性包括姓名、性别、年龄等。

动态部分

动态部分指的是对象的行为,即对象执行的动作,一般定义到类的函数里面(类里面的函数也称之为方法)

人类举例:打篮球、游泳、跑步、吃饭、喝水、睡觉等。

举例说明

汽车属于一个类,而某辆红色的 mini 轿车就是汽车类中的一个对象。
水果属于一个类,而火龙果、苹果等都属于水果类中的一个对象。
动物属于一个类,而调皮的狗狗、可爱的猫咪都属于动物中的一个对象。

类:汽车、水果、动物。
对象:mini 轿车、火龙果、苹果、狗狗、猫咪。

定义类

ClassName:用于指定类名,一般使用大写字母开头,如果类名中包括两个单词,第二个单词的首字母也大写,这种命名方法也称为“驼峰式命名法”,这是惯例。当然,也可根据自己的习惯命名,但是一般推荐按照惯例来命名。

class ClassName:
    '''类的帮助信息''' # 类文档字符串    
    statement # 类体
    
class ClassName():
    pass

定义类是否要添加括号

在 Python 中,定义类时,可以选择在类名后面加括号或不加括号。这取决于你是否要继承自其他类。

如果你的类不需要继承自其他类,那么在类名后面加括号是可选的,你可以选择省略它们。例如:

class MyClass:
    pass

上述代码定义了一个名为 MyClass 的类,它没有继承自其他类。

如果你的类需要继承自其他类,那么在类名后面加括号是必要的,括号中指定你要继承的类。例如:

class MyClass(BaseClass):
    pass

上述代码定义了一个名为 MyClass 的类,它继承自 BaseClass 类。

实例化对象

类名加括号就可以调用类,调用类的目的是创建对象。对象的创建又称类的实例化,即实例出一个对象。

创建对象的格式如下:

对象名1 = 类名()
对象名2 = 类名()
对象名3 = 类名()

我们通过上述格式创建出来的对象是实例化对象。 
注:只要是属于这个类创建出来的对象,那么这个对象就可以调用类中的所有方法

在Python中,类名后面加上括号通常表示实例化一个类。例如,Hero()表示实例化Hero类,而Hero().类方法则表示调用实例化后的对象的类方法。

这种语法用于创建类的实例并调用其方法,实例化类是创建类的对象,使其具有类的属性和方法。

因此,Hero().类方法表示调用实例化后的对象的类方法。

class Hero:
    name = "小满"
    age = 3

    def eat(self):
        print(f"{self.name}开始吃东西啦!")
        
Hero().name   # '小满'
Hero().eat()  # 小满开始吃东西啦!
xm = Hero()

xm.name   # 小满'
xm.eat()  # 小满开始吃东西啦!
# 注意,Hero().name 的方式创建了一个新的 Hero 实例对象,与 xm 不同。
# 因此,Hero().name 和 xm.name 的值都是 "小满",但它们指向的是不同的对象。
class Hero:
    name = "小满"

    def hi(self):
        print(f"你好!我是{Hero.name}")  # 使用类名访问类属性
        print(f"你好!我是{xm.name}")  # 使用实例对象访问类属性
        print(f"你好!我是{self.name}")  # 使用 self 访问实例对象的属性

xm = Hero()
xm.hi()

通过vars获取属性和属性值的字典

class Hero:
    pass

xm = Hero()
xm.name = "小满"
xm.age = 3

vars(xm)  # {'name': '小满', 'age': 3}

关于self参数

谁调用的我self就是谁

self 这个参数,可以修改它的名称,但是一般不建议修改,因为当我们在类中编写方法时 Python 解释器会自动将 self 参数打印出来。

一言以蔽之:

self 是一个特殊的参数名,用于表示类的实例对象自身。它允许你在类的方法中访问和操作实例对象的属性和方法,因为在定义类时不知道具体的实例是谁,就用 self 代替这个实例。

实例对象的属性

定义对象的属性方式有两种:

1. 一种是在类外面定义(用的比较少)
2. 直接在类里面定义
class Hero:
    pass

xm = Hero()
xm.name = "小满"
xm.age = 3

print(xm.name)  # 小满
print(xm.age)   # 3

类属性

类属性咱们可以理解为是类对象的属性,但是类属性可以被这个类所创建的所有实例对象共同拥有。

实例属性只属于实例对象,而类属性就类似于公共的属性,只要是这个类所创建的实例对象,那么大家都可以去访问,去使用这个类属性。

什么情况下需要设置类属性

假设,同一个类我们现在需要创建很多个实例对象,但是这些实例对象中都拥有相同的属性,那么我们就可以将这个相同的属性单独写成类属性,这样可以减少我们的代码量,同时也可以节约电脑的内存,提高程序的效率

类对象

因为 Python 万物皆对象,所以我们在定义一个类时,这个类也是一个对象,我们将其称为类对象

# 我们可以将目前所创建出来的两个对象当做两名学生eva与amigo,同时他俩是属于 '精英班' 的学生。 
# 而我们创建的类属性 money = 1000,可以理解为 '精英班' 的班费,那么这个班费是本班的学生共同拥有的。
# 所以如果我们说这 1000 元班费是属于某某学生的,那么就显得很不合理了。 
class Student:

    # 类属性 (数据属性)
    money = 1000
	
    # 函数属性
    def __init__(self):
        # 实例属性 grade 班级
        self.grade = "精英班"

eva = Student()
amigo = Student()
# 实例对象访问类属性值
print(eva.money)    # 1000
print(amigo.money)  # 1000
# 通过类对象访问类属性:类对象.类属性名
print(Student.money)  # 1000

修改类属性

类的外面修改

# 一样通过 类对象.类属性名 去修改
# 我们通过类对象.类属性名的方式可以访问到这个属性的值,同时我们也可以通过这种方式来为其重新赋一个值,这就是修改类属性的方式。 
Student.money = 800

print(Student.money)  # 800
print(eva.money)      # 800
print(amigo.money)    # 800

类的里面修改

class Student:

    # 类属性
    money = 1000

    def __init__(self):
        # 实例属性 grade 班级
        self.grade = "精英班"

    def change(self):
        print("买零食".center(30, '-'))
        print("买奖品".center(30, '-'))

        # 内部修改类属性
        Student.money = 0
        print(f"一共花费800元,剩余班费{Student.money}元")

eva = Student()

# 在外部修改类属性
Student.money = 800
# 通过实例调用实例方法
eva.change()
-------------买零食--------------
-------------买奖品--------------
一共花费800元,剩余班费0元

属性方法命名

单下划线、双下划线、头尾双下划三种分别是:

  • _foo(单下划线): 表示被保护的(protected)类型的变量,只能本身与子类访问,不能用于 from module import *
  • __foo(双下划线): 私有类型(private) 变量, 只允许这个类本身访问
  • __foo__(头尾双下划):特殊方法,一般是系统内置的通用属性和方法名,如 __init__()
  • foo_(单后置下划线,单右下划线):用于避免与关键词冲突,也用于初始化参数中表示不需要用户传入的变量,通常会定义初始值,如 love_ = None

注:以上属性(变量)和方法(函数)均适用。

访问属性和方法

通过类访问

# 方式1:类.__dict__ 得到的是一个字典
class Hero:
    name = "小满"
    age = 3

    def eat(self):
        print(f"{self.name}开始吃东西啦!")

# 得到字典键值对的格式,__dict__字典存放的是共有的属性
Hero.__dict__ 

# 可以根据字典取值去获得对应的值
Hero.__dict__['name']  # '小满'
Hero.__dict__['age']   # 3
Hero.__dict__['eat']   # <function __main__.Hero.eat(self)> 方法的内存地址

# 方式2(推荐):可以通过`类名.方法名()`或者`对象名().方法名()`,运行属性同理。
Hero.name  # '小满'
Hero.age   # 3
Hero.eat   # <function __main__.Hero.eat(self)>

# 如果要行方法,直接添加上括号即可

通过实例访问

class Hero:
    name = "小满"
    age = 3

    def eat(self):
        print(f"{self.name}开始吃东西啦!")


hero = Hero()
hero.name  # '小满'
hero.age   # 3
hero.eat   # <bound method Hero.eat of <__main__.Hero object at 0x0000029A40E01ED0>>

实例化对象&对象属性

class Hero:
    name = "小满"
    age = 3

    def eat(self):
        print(f"{self.name}开始吃东西啦!")


# 这种方式刚实例化,每个对象是没有自己特有的属性,多一打印的结果是空的。
hero = Hero()     
# 对象的.__dict__中存放的是对象的私有属性,不是类中大家共有的。
# 所以拿到的结果是一个空字典
hero.__dict__  # {}
vars(hero)  # {}

# 可以通过 实例名.__dict__['key'] = value 或者 实例名.key = value 去添加私有属性
# 不过想想都很繁琐

hero.__dict__['name'] = "小满"
hero.__dict__  # {'name': '小满'}

hero.age = 3
vars(hero)  # {'name': '小满', 'age': 3}

实例(对象)属性的查找顺序

先从自己本身开始找 从xx.__dict__开始找

找不到再从类.__dict__

实例化对象自己 —> 类 —> 父类 都找不到就报错

创建实例属性

__init__方法

在类里面定义对象的属性,__init__()初始化魔法方法

class Hero:
    def __init__(self):
        self.name = "小满"
        self.age = 3

xm = Hero()

print(xm.name)  # 小满
print(xm.age)   # 3
# 通过 __init__ 魔法方法为对象创建的属性叫做——实例属性。 
# self:表示调用初始化的对象本身,谁调用了这个方法 self 就代表哪个对象。
# name:name 是一个形参,用来接收创建对象时传数的数据,形参名可以根据情况修改。
# age:age 是一个形参,用来接收创建对象时传数的数据,形参名可以根据情况修改。
class Hero:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
xm = Hero("小满", 3)

print(xm.name)  # 小满
print(xm.age)   # 3
# 在创建实例方法时,也可以和创建函数时一样为参数设置默认值。但是被设置了默认值的参数必须位于所有参数的最后(即最右侧)。
class Hero:

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

xm = Hero("小满")

print(xm.name)  # 小满
print(xm.age)   # 3

类不能访问实例属性

实例属性只能通过实例访问,如果通过类去访问实例属性,会报错。

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


xm = Hero("小满", 3)
print(Hero.name) 
# AttributeError: type object 'Hero' has no attribute 'name'

尝试返回__init__

class Hero:

    def __init__(self):
        return True
        

hero = Hero()  # TypeError: __init__() should return None, not 'bool'

__str__魔法方法

我们通常在该方法中定义对当前类的实例对象的描述信息。然后我们可以通过print(实例对象名)的方式来将这个对象的描述信息进行打印。

class Hero:

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

    def __str__(self):
        return f"你好!我的名字叫做{self.name}, 我今年{self.age}岁了"

xm = Hero("小满", 3)
print(xm)  # 你好!我的名字叫做小满, 我今年3岁了

dq = Hero("大乔", 4)
print(dq)  # 你好!我的名字叫做大乔, 我今年4岁了

总结__init__方法

1、会在调用类时自动触发执行,用来为对象初始化自己独有的数据
2、__init__内应该存放是为对象初始化属性的功能,但是可以存放任意其他代码,想要在类调用时就立刻执行的代码都可以放到该方法内
3、__init__方法必须返回None,默认不写return语句

常用魔法方法

魔法方法是指 Python 内部已经包含的,被双下划线所包围的方法,这些方法在进行特定的操作时会被自动调用,它们是 Python 面向对象下智慧的结晶。

魔法方法名说明
__init__(self[...])初始化方法,初始化类的时候被调用
__del__(self)析构方法,当实例化对象被彻底销毁时被调用(实例化对象的所有指针被销毁时调用)
__call__(self[,args...])允许一个类的实例像函数一样被调用:x(a,b)调用x.__call__(a,b)
__len__(self)定义被当成len()调用时的行为
__repr__(self)定义被当成repr()调用时的行为
__bytes__(self)定义被当成bytes()调用时的行为
__hash__(self)定义被当成hash()调用时的行为
__bool__(self)定义被当成bool()调用时的行为,应该返回TrueFalse
__format__(self,format_apec)定义被format()调用时的行为
__new__(cls[...])1.实例化对象时第一个被调用的方法
2. 其参数直接传递给__init__方法处理
3. 我们一般不会重写该方法
__doc__查看类的简介
__dict__查看类的属性,是一个字典
__module__类定义所在的模块
__name__类名

小练习

ATM程序

class ATM:
	def __init__(self):
		self.user_dict = {}  # 用于存储用户信息的字典
		self.name = ''  # 当前登录用户的用户名

	def fetch_input(self):
		username = input("输入账号:").strip()  # 获取用户输入的账号
		password = input("输入密码:").strip()  # 获取用户输入的密码
		return username, password

	def register(self):
		print("开始注册".center(40, "-"))  # 打印注册提示信息
		username, password = self.fetch_input()  # 获取用户输入的账号和密码
		print(f'恭喜!用户【{username}】注册成功。')  # 打印注册成功信息
		data = {"username":username, "password":password, "balance":1000}  # 创建用户信息字典
		self.user_dict.update({username: data})  # 将用户信息字典添加到用户字典中

	def login(self):
		print("开始登录".center(40, '-'))  # 打印登录提示信息
		username, password = self.fetch_input()  # 获取用户输入的账号和密码
		if username not in self.user_dict:
			print(f"用户名【{username}】不存在,请先注册。")  # 打印用户名不存在的错误信息
		else:
			if password == self.user_dict[username]['password']:
				print(f"用户【{username}】登录成功,进入取钱功能。")  # 打印登录成功信息
				self.name = username  # 将当前登录用户的用户名设置为类属性
				self.withdraw_money()  # 调用取款方法
			else:
				print(f"密码不对,请尝试重新登录。")  # 打印密码错误的提示信息
				self.login()  # 重新调用登录方法

	def withdraw_money(self):
		print("开始取款".center(40, '-'))  # 打印取款提示信息
		try:
			real_balance = self.user_dict[self.name]['balance']  # 获取当前用户的余额
			balance = input(f"当前余额为{real_balance}元,请输入取款的金额:").strip()  # 获取用户输入的取款金额
			assert balance.isdigit() and float(balance) <= real_balance, "非法输入"  # 断言取款金额合法性
		except Exception as e:
			print(e)  # 打印异常信息
		else:
			self.user_dict[self.name]['balance'] -= float(balance)  # 更新用户余额
			print(f"恭喜!用户【{self.name}】取款{balance}元成功!余额{self.user_dict[self.name]['balance']}元。")  # 打印取款成功信息
		finally:
			print("-" * 44)  # 打印分隔线
			print("程序已结束,感谢使用!")  # 打印结束提示信息
		
	def run(self):
		self.register()  # 调用注册方法
		self.login()  # 调用登录方法

if __name__ == '__main__':
	atm = ATM()  # 创建ATM对象
	atm.run()  # 调用run方法,开始执行ATM程序
------------------开始注册------------------
输入账号: eva
输入密码: 112233
恭喜!用户【eva】注册成功。
------------------开始登录------------------
输入账号: eva
输入密码: 112233
用户【eva】登录成功,进入取钱功能。
------------------开始取款------------------
当前余额为1000元,请输入取款的金额: 293
恭喜!用户【eva】取款293元成功!余额707.0元。
--------------------------------------------
程序已结束,感谢使用!

大家一起烤地瓜

1、地瓜有自己的状态,默认是生的,地瓜可以进行烧烤

2、地瓜有自己烧烤的总时间,由每次烧烤的时间累加得出

3、地瓜烧烤时,需要提供本次烧烤的时间

4、地瓜烧烤时,地瓜状态随着烧烤总时间的变化而改变:

[0, 3) 生的、[3, 6) 半生不熟、[6, 8) 熟了、[8 、糊了

class SweetPotato:

    def __init__(self):
        self.state = "生的"
        self.cook_time = 0

    def cakePotato(self, cooking_time):
        self.cook_time += cooking_time
        if 0 <= self.cook_time < 3:
            self.state = "生的"
        elif self.cook_time < 6:
            self.state = "半生不熟"
        elif self.cook_time < 8:
            self.state = "熟了"
        else:
            self.state = "糊了"
    def __str__(self):
        return f"烤地瓜结束,耗费时间{self.cook_time}小时,地瓜:{self.state}"


sweet_potatos = [SweetPotato() for _ in range(4)]
cooking_times = [2.1, 4.1, 6.8, 12]

for potato, cooking_time in zip(sweet_potatos, cooking_times):
    potato.cakePotato(cooking_time)
    print(potato)

"""
运行结果
烤地瓜结束,耗费时间2.1小时,地瓜:生的
烤地瓜结束,耗费时间4.1小时,地瓜:半生不熟
烤地瓜结束,耗费时间6.8小时,地瓜:熟了
烤地瓜结束,耗费时间12小时,地瓜:糊了
"""

塑料姐妹花

题目要求:用面向对象编程的方法让两个角色决斗,分出胜负。

小乔(xq)
姓名(name):小乔
生命值(HP):100
攻击力(ATK,attack):25

小满(xm)
姓名(name):小满
生命值(HP):150
攻击力(ATK,attack):15

决斗说明:
决斗采取 回合制,由小乔先发动攻击;
防御时有 20% 几率防御成功,完全闪避攻击,免受伤害。
from random import randint

class Player:
    def __init__(self, name, HP, ATK):
        self.name = name
        self.HP = HP
        self.ATK = ATK
        print("已成功登记信息")
        self.showInfo()
        print('--------------------------------')

    # 展示信息
    def showInfo(self):
        print(f"name: {self.name}\thp: {self.HP}\tatk: {self.ATK}")

    # 发动攻击
    def attack(self, target):
        # 这里的target实际上就是实例对象
        print(f"【{self.name}】向【{target.name}】发动了攻击。")
        target.defend(self.ATK)
    
    # 躲避攻击
    def defend(self, damage):
        if randint(1, 100) <= 20:
            print(f"【{self.name}】成功防御了攻击。")
        else:
            print(f"【{self.name}】防御攻击失败,受到了【{damage}】点伤害。")
            self.HP -= damage

# 创建两个选手对象
xq = Player("小乔", 100, 25)
xm = Player("小满", 150, 15)

while xq.HP >= 0 and xm.HP >= 0:
    # 小乔攻击小满
    xq.attack(xm)
    if xm.HP <= 0:
        print(f"{xm.name}死了")
    print('--------------------------------')
    # 小满攻击小乔
    xm.attack(xq)
    if xq.HP <= 0:
        print(f"{xq.name}死了")
    # 打印当前选手信息
    xq.showInfo()
    xm.showInfo()
    print('--------------------------------')
结果太长  加载失败 ^_^

绑定方法(也成为动态方法)

在Python中,绑定方法是一个特殊的对象,它是一个函数对象,它在调用时会将实例对象作为第一个参数传递给该函数。通过这种方式,绑定方法可以访问实例对象的属性和方法,以及调用实例方法。

要创建绑定方法,首先需要定义一个类,然后在类中定义一个方法。在定义方法时,需要将第一个参数命名为self,这个参数用来接收实例对象。然后,可以在方法中访问实例对象的属性和方法。

class Hero:

    def say(self):
        print("你好!我叫小满~ 欢迎来到我的博客 ^_^")

xm = Hero()
xm.say()  # 你好!我叫小满~ 欢迎来到我的博客 ^_^

类方法

类方法与普通的实例方法不同,类方法需要通过类对象调用。

普通的实例方法参数是 self,而类方法的参数是cls

在 Python 中,@classmethod 是一个装饰器,用于表示一个方法是类方法,而不是实例方法。类方法不接收实例作为参数,而是接收类作为参数。这意味着你在调用类方法时,不需要创建类的实例,而是直接使用类来调用方法。

如果需要定义类方法的话,需要使用装饰器@classmethod来进行装饰,而且第一个参数必须是当前类对象,该参数一般约定命名为cls

# 类方法,用@classmethod 来进行修饰
@classmethod
def get_country(cls):
    pass
class Hero:

    name = '小满'
    
    @classmethod
    def kill(cls):
        print(f"{cls.name}正在大杀四方..")


Hero.kill()  # 小满正在大杀四方..
class Student:
    money = 1000  # 类属性,表示班费的初始金额

    @classmethod
    def change_money(cls):  # cls 代表调用这个方法的类对象本身
        cls.money = 800  # 通过cls.类属性名 = 值的方式,修改类属性 money 的值为 800
        return cls.money  # 将类属性的值进行返回

result = Student.change_money()  # 调用类方法 change_money
print(result)  # 打印修改后的类属性 money 的值 800

注意:类方法可以被类对象和实例对象调用,而实例方法只能被实例对象调用

静态方法(也称为非绑定方法)

定义静态方法与定义类方法相似,不过是代码细节上有一定区别。

定义静态方法,同样也需要装饰器来进行装饰,而这个装饰器就是@staticmethod

经过@staticmethod装饰的函数没有 selfcls 这两个参数。

一般情况下静态方法主要用来存放逻辑性的代码,静态方法内部的操作一般不会涉及到类中的属性和实例方法的操作。

import time


class Student:
    # 通过@staticmethod装饰器定义一个静态方法
    @staticmethod
    def showTime():
        # 格式化输入当前时间
        return time.strftime("%x %X %p")

# 通过类属性访问静态方法
print(Student.showTime())  # 01/02/24 19:37:59 PM

# 通过实例对象访问静态方法
eva = Student()
now = eva.showTime()  # 01/02/24 19:37:59 PM
print(now)

image-20240102193957840

call

__call__ 可以让实例对象像函数那样可被执行,callable(eva) 默认是不能被执行的,我们重写 call 。

使用call前

class Student:

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


# 实例化
eva = Student('eva', 2)

callable(eva)  # False

eva()  # TypeError: 'Student' object is not callable

使用call后

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

    def __call__(self):
        self.age += 1
        print('我能执行了~~~')
    
# 实例化
eva = Student('eva', 2)

callable(eva)  # True

eva()  # 我能执行了~~~

eva.age  # 3

property(属性)

python中有两种方式实现装饰器。第一种是函数装饰器,第二种是类装饰器。比如python3中的property就是一个类装饰器,将来绑定给对象的方法伪造成一个数据属性。可以通过@property(装饰器)将一个方法转换为属性,从而实现用于计算的属性。将方法转换为属性后,可以直接通过方法名来访问方法,而不需要再添加一对小括号“()”,这样可以让代码更加简洁。

应用场景1:将函数属性伪装成数据属性

下面代码是在jupyterlab运行的

注意:使用@property必须return

class Rect:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    @property
    def area(self):
        """
        计算矩形的面积

        Returns:
            int: 矩形的面积
        """
        return self.width * self.height

rect = Rect(4, 8)
print(f"面积为:{rect.area}")  # 面积为:32

type(rect.area)  # int

场景2:统一数据属性的查、改、删操作

在Python中,默认情况下,创建的类属性或者实例是可以在类体外进行修改的,如果想要限制其不能在类体外修改,可以将其设置为私有的,但设置为私有后,在类体外也不能直接通过实例名+属性名获取它的值。如果想要创建一个可以读取但不能修改的属性,那么可以使用@property实现只读属性。

class TvShow:

    def __init__(self, show):
        self.__show = show

    @property
    def show(self):
        return self.__show

tv_show = TvShow("正在播放《七大罪:天空的囚徒》")
print(tv_show.show)  # 正在播放《七大罪:天空的囚徒》

# 尝试对 “属性” 进行修改
tv_show.show = "正在播放《狐妖小红娘》"  # AttributeError: can't set attribute 'show'
class TvShow:

    films:list = ["天气之子", "萤火之森", "铃牙之旅"]
    
    def __init__(self, show):
        self.__show = show

    # 将方法转换为属性
    #  @property.getter 默认就是getter
    @property  
    def show(self):
        # 返回私有属性的值
        return self.__show  

    # 设置setter方法,让属性可以修改
    # show这个名称实际上就是上面的方法名称
    @show.setter  
    def show(self, value):
        # 判断值是否在列表中
        if value in self.films:
            self.__show = f"您选择了《{value}》稍后将播放"

        else:
            self.__show = f"您点播的电影《{value}》不存在"
        
    @show.deleter
    def show(self):
        print("无法删除哦~~~~")


# 创建类的实例
tv_show = TvShow("铃牙之旅")
# 获取属性值
print(f"正在播放《{tv_show.show}》")  # 正在播放《铃牙之旅》
# 修改属性值
tv_show.show = "一人之下"  # 您点播的电影《一人之下》不存在
# 再次获取属性值
print(tv_show.show)
# 尝试删除属性
del tv_show.show  # 无法删除哦~~~~
当show 遇到查询时,触发被property装饰的函数的执行
当show 遇到赋值操作,即 = 时触发被property.setter装饰的函数的执行
当show 遇到删除操作,即 del  时触发property.deleter装饰的函数的执行

上述使用property的流程如下:

1.将想要伪装的系列方法命名成一个相同的名字
2.在查看功能上加上property语法糖
3.在修改和删除功能上加名字.setter和 .deleter语法糖
# 方案2
class TvShow:
    # 类属性
    films: list = ["天气之子", "萤火之森", "铃牙之旅"]
    
    def __init__(self, show):
        # 实例属性
        self.__show = show

    def getName(self):
        return self.__show  

    def setName(self, value):
        # 如果 value 在 films 列表中,设置 __show 属性为 "您选择了《{value}》稍后将播放"
        # 否则设置为 "您点播的电影《{value}》不存在"
        if value in self.films:
            self.__show = f"您选择了《{value}》稍后将播放"
        else:
            self.__show = f"您点播的电影《{value}》不存在"

    def delName(self):
        # 删除 __show 属性
        print("无法删除哦~~~~")

    # 定义 show 属性,使用 getName、setName 和 delName 方法
    show = property(getName, setName, delName)

# 创建类的实例
tv_show = TvShow("铃牙之旅")
# 获取属性值
print(f"正在播放《{tv_show.show}》")  # 正在播放《铃牙之旅》
# 修改属性值
tv_show.show = "一人之下"  # 您点播的电影《一人之下》不存在
# 再次获取属性值
print(tv_show.show)
# 尝试删除属性
del tv_show.show  # 无法删除哦~~~~

# 将三个函数(get_name,set_name,del_name)统一为一个相同的符号show
# 当show 遇到查询时触发get_name函数的执行
# 当show 遇到赋值操作,即 = 时触发set_name函数的执行
# 当show 遇到删除操作,即 del  时触发del_name函数的执行

不论是双下划线开头的属性命名来隐藏属性,还是使用类装饰器property来将函数属性伪装成数据属性,本质上都是类的设计者封装类的行为。这样做的目的都是控制和规范类的使用者。

类的继承

image-20240102195131926

在程序当中,继承描述的是多个类之间的所属关系,如果一个类 A 里面的属性和方法可以被复用,那么我们就可以通过继承的方式,传递到 B 类里面。

那么这种情况,类 A 就是基类称为父类,类 B 就是派生类称为子类。我们一般使用父类与子类来进行描述。

没有使用继承之前

# 导入时间模块
import time

class A():

    # 实例方法
    def showTime(self):
        # 格式化输出当前时间
        print(time.strftime("%x %X %p"))


class B():
    # 实例方法
    def showTime(self):
        # 格式化输出当前时间
        print(time.strftime("%x %X %p"))

a = A()
a.showTime()  # 01/02/24 20:07:16 PM

b = B()
b.showTime()  # 01/02/24 20:07:16 PM

使用了继承

# 导入时间模块
import time

class A():

    # 实例方法
    def showTime(self):
        # 格式化输出当前时间
        print(time.strftime("%x %X %p"))

# 类 B 继承 类A
class B(A):
    pass

a = A()
a.showTime()  # 01/02/24 20:08:43 PM

b = B()
b.showTime()  # 01/02/24 20:08:43 PM

通过一个小故事了解类的继承

单继承

子类只继承一个父类(上面的例子使用到的就是单继承)

在很久很久以前,有一位专注于做葱油饼的大师在葱油饼子界摸爬滚打很多年,拥有一身精湛的葱油饼子技术,并且总结了一套秘制葱油饼配方

# 定义一个大师类
class Master():
    
    # 实例方法
    def makeCake(self):
        print("?按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子?")

可是这位大师现已年迈,他希望在嗝屁之前把自己的秘制配方传承下去,于是这位大师将自己的秘制配方传给了他的大徒弟:宝宝。

# 定义一个大师类
class Master():
    
    # 实例方法
    def makeCake(self):
        print("?按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子?")

# 定义 宝宝 类, 继承了Master 则 宝宝 是子类 Master 是父类
class Prentice(Master):
    # 子类可以继承父类所有的属性和方法,哪怕子类没有自己的属性和方法,也可以使用父类的属性和方法。
    pass

# 调用父类方法:实例对象名.方法名 
baobao = Prentice()
baobao.makeCake()

多继承

这位大师的大徒弟宝宝也是非常的努力,初到泰国就凭借着自己高超的手艺吸引了一大群迷妹,每天排队买葱油饼的顾客更是络绎不绝。

大徒弟宝宝,很快便在当地有了一定的名气,虽然当前的手艺受到了很多人的喜爱,但是这个宝宝也一直渴望学习掌握到新的制做技术。

直到有一天,他偶然遇到了人生中的第二个师傅。这位师傅手中掌握着酱制葱油饼子配方,宝宝为了学到新的技术,便拜入了这位师傅的门下,凭借着自己之前的手艺很快便得到了这位师傅的认可。

class Master():

    # 实例方法
    def makeCake(self):
        print("?按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子?")

# 定义一个新的 师傅 类
class NewMaster():

    def makeCakeNew(self):
        print("?按照 <酱制葱油饼子配方> 制作了一份香甜可口的葱油饼子?")

随着宝宝的不断努力学习,在第二个师傅手里也学到了酱制葱油饼子配方

宝宝的葱油饼事业从此又踏上一个台阶。

到目前为止,宝宝已经遇到了两个师傅并且从每个师傅手中都学到了一份独门配方。

我们如何使用代码来表示宝宝学习到的这两份独门配方呢?

class Prentice(Master, New_Master):  # 多继承,继承了多个父类(Master在前)
    pass

单继承只需要在类后面的括号中写一个父类名即可,而多继承只需要在后面的括号中写入多个要继承的父类名字即可。

class Master():

    # 实例方法
    def makeCake(self):
        print("?按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子?")

# 定义一个新的 师傅 类
class NewMaster():

    def makeCakeNew(self):
        print("?按照 <酱制葱油饼子配方> 制作了一份香甜可口的葱油饼子?")

class Prentice(Master, NewMaster):
    pass

baobao = Prentice()
# 调用 Master 类中的方法
baobao.makeCake()     # ?按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子?
# 调用 New_Master 类中的方法
baobao.makeCakeNew()  # ?按照 <酱制葱油饼子配方> 制作了一份香甜可口的葱油饼子?

注意点:

  1. 多继承可以继承多个父类,写法与单继承相似,只需要在继承的括号中写入需要继承的父类名即可。

  2. 当我们继承多个父类时,也继承了父类的属性和方法,但是有一点需要注意,如果继承的父类中有同名的方法或者属性,则默认使用第一个属性和方法,当然多个父类中如果属性名与方法名不重名,那么就不会受到影响。

    从左到右依次继承

继续讲故事~~ ^_^

随着故事剧情的发展,宝宝在学到了两位师傅的独门配方后,经过自己日夜的钻研,终于在这两个配方的基础上,创建了一种全新的葱油饼子配方,称之为:宝氏葱油饼配方

子类重写父类方法

子类重写父类方法,也就是说,在子类中定义一个与父类一模一样的方法,这就是重写父类方法。

宝宝经过自己的研制,成功研发出了‘宝氏葱油饼配方’。那么我们就可以在宝宝这个类中,新建一个与父类方法同名的方法,让我们来看看会是什么效果。

class Master():

    # 实例方法
    def makeCake(self):
        print("?按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子?")

# 定义一个新的 师傅 类
class NewMaster():

    def makeCakeNew(self):
        print("?按照 <酱制葱油饼子配方> 制作了一份香甜可口的葱油饼子?")

# 创建子类
class Prentice(Master, NewMaster):

    # 重写 NewMaster 类的方法
    def makeCakeNew(self):
        print("?按照 <宝氏葱油饼配方> 制作了一份让人流连忘返的葱油饼子?")

# 创建子类对象
baobao = Prentice()
# 调用 Master 类中的方法
baobao.makeCake()     # ?按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子?
# 调用子类重写的方法
baobao.makeCakeNew()  # ?按照 <宝氏葱油饼配方> 制作了一份让人流连忘返的葱油饼子?

注意点:

  1. 当我们在继承父类时,我们会自动继承父类的方法,而当我们在子类中根据自己的需求重写父类的方法之后,调用重名的方法,优先执行的是子类的方法。
  2. 所以,从这一点能总结出:子类和父类有同名的方法,则默认使用子类的。
  3. 其实不单单是方法,如果子类继承父类的话,子类同样可以使用父类的属性,当重写父类的属性之后,也是优先使用子类的属性。

image-20240102214456224

经典类和新式类

1. 只有在python2中才分新式类和经典类,python3中统一都是新式类(默认继承object)
2. 在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
3. 在python2中,显式地声明继承object的类,以及该类的子类,都是新式类

__mro__属性

用于获取类的方法解析顺序(MRO)的属性。MRO 是指在多继承的情况下,确定方法和属性的搜索顺序的规则。Python 使用 C3 线性化算法来计算 MRO,确保在多继承的情况下能够准确地确定方法和属性的搜索顺序。__mro__ 属性返回一个元组,其中包含了当前类及其基类的顺序。

经典类演示

class Phone(object):
    pass
    
class Android(Phone):
    pass

class XiaoMi(Android):
    pass

class RedMi(XiaoMi):
    pass


redmi = RedMi()

RedMi.__mro__ 
(__main__.RedMi, __main__.XiaoMi, __main__.Android, __main__.Phone, object)

image-20240104211018427

新式类演示
class SS(object):
    pass

class BB(SS):
    pass

class AA(BB, SS):
    pass

class EE(BB,SS):
    pass

class FF(AA, EE, SS):
    pass

class HH(FF, EE, BB, SS):
    pass


HH.__mro__
(__main__.HH,
 __main__.FF,
 __main__.AA,
 __main__.EE,
 __main__.BB,
 __main__.SS,
 object)

image-20240104211815438

__class__访问对象所的类

class People:
    pass

class Student(People):
    pass


student = Student()

student.__class__  # __main__.Student
type(student)      # __main__.Student
Student.__class__  # type
# 萨摩耶犬
class Samoyed:
    pass


# 柯基犬
class Corgi:
    pass


# 牧羊犬
class Collie:
    pass


def welcome(animal):
    # 需要逐个判断 animal 是不是已知犬种
    if type(animal) in [Samoyed, Corgi, Collie]:
        print(type(animal))  # <class '__main__.Samoyed'>
        print(animal.__class__)  # <class '__main__.Samoyed'>
        print(type(animal) == Samoyed)  # True
        print(animal.__class__ is Samoyed)  # True
        print('经检测,这是一只小狗,欢迎进入狗狗乐园!')
    else:
        print('这好像不是狗狗,不好意思,不能进入狗狗乐园')


dingding = Samoyed()
welcome(dingding)
# 输出:经检测,这是一只小狗,欢迎进入狗狗乐园!

__base__查看直接父类

class Master():

    # 实例方法
    def makeCake(self):
        print("?按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子?")

# 定义一个新的 师傅 类
class NewMaster():

    def makeCakeNew(self):
        print("?按照 <酱制葱油饼子配方> 制作了一份香甜可口的葱油饼子?")

# 创建子类
class Prentice(Master, NewMaster):

    # 重写 NewMaster 类的方法
    def makeCakeNew(self):
        print("?按照 <宝氏葱油饼配方> 制作了一份让人流连忘返的葱油饼子?")
Prentice.__base__  # __main__.Master

__bases__查看所有父类

得到一个元组

Prentice.__bases__ # (__main__.Master, __main__.NewMaster)

super()函数

super() 函数是用于调用父类(超类)的一个方法,语法是:super(type[, object-or-type])super(SubClass, self).method() 的意思是,根据 self 去找 SubClass 的「父亲」,然后调用这个「父亲」的 method()。经常用在我们在子类中重写了父类中的方法,但有时候还是需要用父类中的方法。

class Student:

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

    def say(self):
        print(f"Hello, my name is {self.name}.")

    def add(self, x, y):
        print(f"这个加法我会,{x}+{y}={x + y}")


class CollegesStudent(Student):
    def practice(self):
        print(f"我是{self.name},在世界500强实习。")
        super().say()
        super(CollegesStudent, self).add(29, 4)


eva = CollegesStudent('eva')
eva.say()
eva.practice()
Hello, my name is eva.
我是eva,在世界500强实习。
Hello, my name is eva.
这个加法我会,29+4=33

Python3.xPython2.x 的一个区别是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx

import time


class Person:

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

    @staticmethod
    def timeNow():
        now = time.strftime("%x %X %p")
        print(f"现在时间是: {now} 程序正常运行中。")
        
class Student(Person):

    @property
    def study(self):
        return f"{self.name}正在学习。。"
    

class Teacher(Person):

    def __init__(self, name, age, sex, score):
        # 方式1 直接使用父类.__init__
        # Person.__init__(self, name, age, sex)
        # 方式2 使用super()函数去调用, 注意使用super()的时候 不需要添加self
        # super().__init__(name, age, sex)
        # 方式3 指名道姓的去调用
        super(Teacher, self).__init__(name, age ,sex)
        self.score = score
        print(f"{self.name}给出了{score}分")

    def showStatic(self):
        # 调用父类的方法
        super().timeNow()

eva = Student('eva', 18, 'female')
print(eva.study)
amigo = Teacher('amigo', 20, 'male', 80)
amigo.showStatic()
eva正在学习。。
amigo给出了80分
现在时间是: 01/05/24 15:13:53 PM 程序正常运行中。

注意:super()函数应该在方法内部使用,而不是在类定义中使用。

class People:

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

class Student:
    super().__init__(name)
    self.name = name  # RuntimeError: super(): no arguments

派生

在面向对象编程中,被继承的类称为父类或基类,新的类称为子类或派生类。

class Fruit:
    
    color = "绿色"

    def harvest(self, color):
        # 输出形参的color
        print(f"水果是{color}的!")
        print("水果已经收获....")
        # 输出类属性的color
        print(f"水果原来是{Fruit.color}的!")

# 定义苹果类(派生类)
class Apple(Fruit):

    color = "红色"

    def __init__(self):
        print("我是苹果")

# 定义橘子类(派生类)
class Orange(Fruit):
    
    color = "橙色"

    def __init__(self):
        print("\n我是橘子")

# 创建类的实例(苹果)
apple = Apple()
# 调用积累的harvest()方法
apple.harvest(apple.color)
# 创建类实例(橘子)
orange = Orange()
# 调用基类的harvest()方法
orange.harvest(orange.color)

"""
我是苹果
水果是红色的!
水果已经收获....
水果原来是绿色的!

我是橘子
水果是橙色的!
水果已经收获....
水果原来是绿色的!
"""

在派生类中定义__init__()方法时,不会自动调用基类的__init__()方法。

class Fruit:

    def __init__(self, color="绿色"):
        Fruit.color = color

    def harvest(self):
        print(f"水果原来是{Fruit.color}的")


class Apple(Fruit):
    def __init__(self):
        print("我是苹果")


apple = Apple()
apple.harvest()  # AttributeError: type object 'Fruit' has no attribute 'color'

可以通过super()去解决

class Fruit:

    def __init__(self, color="绿色"):
        Fruit.color = color

    def harvest(self):
        print(f"水果原来是{Fruit.color}的")


class Apple(Fruit):
    def __init__(self):
        super().__init__()
        print("我是苹果")


apple = Apple()
apple.harvest()  

"""
我是苹果
水果原来是绿色的
"""
class Fruit:

    def __init__(self, color="绿色"):
        self.color = color

    def harvest(self, color):
        print(f"水果是{color}的")
        print("水果已经收获。。")
        print(f"水果原来是{self.color}的!")


class Apple(Fruit):
    color = "红色"

    def __init__(self):
        print("我是苹果")
        super().__init__()


class Sapodilla(Fruit):

    def __init__(self, color):
        print("\n我是人参果")
        super().__init__(color)

    def harvest(self, color):
        print(f"人参果是{color}的!")
        print("人参果已收获。。")
        print(f"人参果原来是{self.color}的!")


apple = Apple()
apple.harvest(apple.color)
sapodilla = Sapodilla('白色')
sapodilla.harvest("金黄色带紫色条纹")

print(vars(apple)) 
print(vars(sapodilla)) 

"""
我是苹果
水果是绿色的
水果已经收获。。
水果原来是绿色的!  ---> 基类的 __init__()方法中设置的默认值

我是人参果
人参果是金黄色带紫色条纹的!
人参果已收获。。
人参果原来是白色的!  ---> 派生类初始化时指定的
{'color': '绿色'}
{'color': '白色'}  ---> 这里指定了颜色
"""

__isinstance_()

isinstance() 函数接受两个参数,第一个参数为某个实例对象,第二个参数为某个类,能够检查第一个参数是否是第二个参数的 实例,并返回 TrueFalse

class Dog:
    # 犬科动物有四条腿
    leg_num = 4

    # 能奔跑
    def run(self):
        print('🐕💨 狗狗快跑')

    # 开心的时候会摇尾巴
    def happy(self):
        print('🐕💓 狗狗开心地摇起了尾巴')


# 萨摩耶犬
class Samoyed(Dog):
    def run(self):
        print('🐕💨💨 萨摩耶狂奔起来,像一团漂浮的云')


# 柯基犬
class Corgi(Dog):
    def run(self):
        print('🐕💨 柯基飞快地摆动它的小短腿,屁股一晃一晃的')


# 牧羊犬
class Collie(Dog):
    def run(self):
        print('🐕💨💨💨 牧羊犬咻地一下蹿了出去,不愧是竞速王者')


# 猫咪
class Cat:
    # 猫科动物有四条腿
    leg_num = 4

    # 走路时只会留下一列脚印
    def run(self):
        print('🐈🐾 猫咪迈着优雅的步伐')


# 布偶猫
class Dagdoll(Cat):
    def run(self):
        print('🐈🐾 布偶猫走起路来优雅,十分优雅!')


# 狸花猫
class DragonLi(Cat):
    def run(self):
        print('🐈🐾🐾🐾 狸花猫身手矫健,连走带跑,像只小豹子')


# 暹罗猫
class Siamese(Cat):
    def run(self):
        print('🐈🐾🐾 暹罗猫一边走一边喵喵地叫了起来')


pets = [Siamese(), DragonLi(), Corgi(), Collie(), Collie(), Samoyed(), Corgi(), DragonLi(), Siamese(), Samoyed()]


def welcome(animal):
    # 类型检测,属于猫的派生类,就执行run方法
    if isinstance(animal, Cat):
        animal.run()
    else:
        print("[{}]这好像不是小猫,不好意思,不能进入猫猫乐园".format(animal.__class__.__name__))


for pet in pets:
    welcome(pet)

"""
🐈🐾🐾 暹罗猫一边走一边喵喵地叫了起来
🐈🐾🐾🐾 狸花猫身手矫健,连走带跑,像只小豹子
[Corgi]这好像不是小猫,不好意思,不能进入猫猫乐园
[Collie]这好像不是小猫,不好意思,不能进入猫猫乐园
[Collie]这好像不是小猫,不好意思,不能进入猫猫乐园
[Samoyed]这好像不是小猫,不好意思,不能进入猫猫乐园
[Corgi]这好像不是小猫,不好意思,不能进入猫猫乐园
🐈🐾🐾🐾 狸花猫身手矫健,连走带跑,像只小豹子
🐈🐾🐾 暹罗猫一边走一边喵喵地叫了起来
[Samoyed]这好像不是小猫,不好意思,不能进入猫猫乐园
"""

狼人之夜

朋友聚会都要做些什么?一起玩狼人杀是个不错的选择。

按照最经典的角色配置,游戏分为两大阵营,分别是狼人阵营和好人阵营。狼人阵营每晚刀杀一个人;人类阵营里,除了普通人类“村民”之外,还有预言家、女巫和猎人三种特殊角色,每个角色都有特殊技能。

要求
1.定义若干个类,用于表示两大阵营中的五种角色,每名角色都有技能 skill、卡牌 card,并且能够展示自己的卡牌 show_card();
2.九人局中共有 3 狼、3 民、1 预言家、1 女巫、1 猎人,请你用合适的数据结构表示所有角色卡;
3.游戏开始后,你需要从九张角色卡中随机抽取一张,并调用 show_card() 方法查看自己的身份卡。
# card.py的内容
wolf_symbol = '''           🐺🐺🐺 狼人 🐺🐺🐺
╔══════════════════════════════════════╗
║                __        ___         ║
║          |  | /  \ |    |__          ║
║          |/\| \__/ |___ |            ║
║                                      ║
╚══════════════════════════════════════╝
'''

prophet_symbol = '''          🔮🔮🔮 预言家 🔮🔮🔮
╔══════════════════════════════════════╗
║   __   __   __   __        ___ ___   ║
║  |__) |__) /  \ |__) |__| |__   |    ║
║  |    |  \ \__/ |    |  | |___  |    ║
║                                      ║
╚══════════════════════════════════════╝
'''

witch_symbol = '''           🧙🧙🧙 女巫 🧙🧙🧙
╔══════════════════════════════════════╗
║                ___  __               ║
║         |  | |  |  /  ` |__|         ║
║         |/\| |  |  \__, |  |         ║
║                                      ║
╚══════════════════════════════════════╝
'''

hunter_symbol = '''           🏹?🏹?🏹? 猎人 🏹?🏹?🏹?
╔══════════════════════════════════════╗
║                    ___  ___  __      ║
║     |__| |  | |\ |  |  |__  |__)     ║
║     |  | \__/ | \|  |  |___ |  \     ║
║                                      ║                            
╚══════════════════════════════════════╝
'''

villager_symbol = '''           🌾🌾🌾 村民 🌾🌾🌾
╔══════════════════════════════════════╗
║                        __   ___  __  ║
║ \  / | |    |     /\  / _` |__  |__) ║
║  \/  | |___ |___ /~~\ \__> |___ |  \ ║
║                                      ║
╚══════════════════════════════════════╝
'''
# 主程序

import random
from card import *


# 狼人阵营
class Wolf:
    skill = "每晚可以和同伴一起杀死一名玩家"
    card = wolf_symbol

    def show_card(self):
        print("你的身份卡是".center(40, '='))
        print(self.card)
        print(f"你拥有的技能是:{self.skill}")


# 人类阵营
class Human:
    skill = "没有特殊技能"

    def show_card(self):
        print("你的身份卡是".center(40, '='))
        print(self.card)
        print(f"你拥有的技能是:{self.skill}")


# 预言家是人类阵营
# 每晚可查验任意一名在座玩家的身份
class Prophet(Human):

    def __init__(self):
        self.card = prophet_symbol
        self.skill = "每晚检查任意一名在座玩家的身份"


# 女巫是人类阵营
# 有一瓶毒药、一瓶解药
class Witch(Human):

    def __init__(self):
        self.card = witch_symbol
        self.skill = "有一瓶毒药、一瓶解药"


# 猎人是人类阵营
# 被投票出局或中刀身亡时,可开枪带走任意一名玩家
class Hunter(Human):

    def __init__(self):
        self.card = hunter_symbol
        self.skill = "被投票出局或中刀身亡时,可开枪带走任意一名玩家"


# 村民是人类阵营
# 没有特殊技能
class Villager(Human):

    def __init__(self):
        self.card = villager_symbol
        self.skill = "没有特殊技能"


# 狼人杀九人局标准配置:3 狼、3 村民、1 预言家、1 女巫、1 猎人
players = [Wolf(), Wolf(), Wolf(), Prophet(), Witch(), Hunter(), Villager(), Villager(), Villager()]


# 游戏开始时,你可从所有角色牌中抽取一张,作为自己的身份卡
player = random.choice(players)
# 请调用 show_card() 方法,观察自己的角色卡
player.show_card()

"""
=================你的身份卡是=================
           🏹?🏹?🏹? 猎人 🏹?🏹?🏹?
╔══════════════════════════════════════╗
║                    ___  ___  __      ║
║     |__| |  | |\ |  |  |__  |__)     ║
║     |  | \__/ | \|  |  |___ |  \     ║
║                                      ║                            
╚══════════════════════════════════════╝

你拥有的技能是:被投票出局或中刀身亡时,可开枪带走任意一名玩家
"""

继承的棱形问题

一般编程语言都不允许多重继承,主要是为了避免菱形问题,即两个父 类如果有共同的祖父类,但对祖父类相同部分做了不同的修改,则这个类再继承 两个父类就会产生冲突。

Python的Mixins机制

大多数面向对象语言都不支持多重继承,因为这会导致菱形问题, 而 Python 虽然形式上支持多重继承,但其实现机制却是利用 mixin,从而有效 地避免了菱形问题。

Mixins是一种在面向对象编程中,为了代码复用而提出的一种技术。它是一种类,提供了方法的实现,其他类可以访问Mixin类的方法而不必成为其子类。Mixin可以在多个类之间共享代码,而不需要继承。

# 定义一个Mixin类
class PrintInfoMixin:

    def print_info(self):
        print(f"Name: {self.name}, Age: {self.age}, Gender: {self.gender}, Hobby: {self.hobby}")


# 创建一个普通的类,并使用Mixin类
class Person(PrintInfoMixin):
    def __init__(self, name, age, gender, hobby):
        self.name = name
        self.age = age
        self.gender = gender
        self.hobby = hobby


# 创建Person的实例
person = Person("小满", 3, "Female", "摸鱼")
person.print_info()  # Name: 小满, Age: 3, Gender: Female, Hobby: 摸鱼

封装

访问限制

在类的内部可以定义属性和方法,而在类的外部则可以直接调用属性或方法来操作数据,从而隐藏了类内部的复杂逻辑。但是Python并没有对属性和方法的访问权限进行限制。为了保证类内部的某些属性或方法不被外部所访问,可以在属性或方法名前面添加双下划线(__foo)或首尾加双下划线(__foo__),从而限制访问权限。其中,双下划线、首尾双下划线的作用如下:

(1)首尾双下划线表示定义特殊方法,一般是系统定义名字,如__init__()
(2)双下划线表示private(私有)类型的成员,只允许定义该方法的类本身进行访问,而且也不能通过类的实例进行访问,但是可以通过“类的实例名._类名__xxx”方式访问。

class Hero:

    __hobby = "摸鱼"
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def showInfo(self):
        # 内部可以调用私有属性
        print(self.__hobby)


xm = Hero("小满", 3)
xm.showInfo()  # 摸鱼
# 实例不能直接访问私有属性
xm.__hobby  # AttributeError: 'Hero' object has no attribute '__hobby'
# 类也不能直接访问私有属性
Hero.__hobby  # AttributeError: type object 'Hero' has no attribute '__hobby'

修改私有属性以及执行私有方法:

class Hero:

    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.__hobby = '摸鱼'

    # 定义修改私有变量的方法,就可以在外部直接修改了
    def change(self, hobby):
        self.__hobby = hobby

    def __toys(self):
        print("里面是一大堆的玩具~~")

    def showToys(self):
        self.__toys()
    
    def __str__(self):
        return f"姓名:{self.name} 年龄:{self.age}岁 爱好:{self.__hobby}"

# 直接访问,报错
# print(xm.__hobby)  
# AttributeError: 'Student' object has no attribute '__hobby'

xm = Hero("小满", 3)  
print(xm)  # 姓名:小满 年龄:3岁 爱好:摸鱼

# 修改方式1  通过 实例名._类名__xx去修改
xm._Hero__hobby = "抢红buff"
print(xm)  # 姓名:小满 年龄:3岁 爱好:抢红buff

# 修改方式2 内部定义修改方法 外部调用修改方法
xm.change("抢人头")
print(xm)  # 姓名:小满 年龄:3岁 爱好:抢人头

# 直接调用私有方法,报错
# xm.__toys() 
# AttributeError: 'Hero' object has no attribute '__toys'

# 通过 实例名._类名__xx去访问
xm._Hero__toys()  # 里面是一大堆的玩具~~

# 内部定义访问方法 外部调用访问方法
xm.showToys()  # 里面是一大堆的玩具~~

注意:虽然两种方法都可以在外部访问到私有方法属性,以及外部也可以修改私有属性,但是不建议在外部操作

注意:封装__不支持跨类使用。

__slot__

__slots__ 是一个特殊的类属性,用于限制类的实例可以拥有的属性。

通过使用 __slots__,你可以告诉Python仅为类的实例分配指定的属性,从而节省了内存空间。当你知道类的实例只需要固定的一组属性时,使用 __slots__ 可以提高性能。 下面是一个示例,演示了如何使用 __slots__

class Hero:
    
    __slots__ = ('name', 'age')

    def __init__(self):
        self.name = '小满'
        self.age = 3


hero = Hero()
print(hero.name)  # 小满
print(hero.age)   # 3

hero.hobby = "摸鱼"  # # 无法为属性hobby分配内存,会引发 AttributeError
print(hero.hobby)  # AttributeError: 'Hero' object has no attribute 'hobby'

多态和多态性

多态

多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)

  1. 序列数据类型有多种形态:字符串,列表,元组
  2. 动物有多种形态:人,狗,猪

abc模块

多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象。

指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,抽象类本身不能被实例化,否则报错。

import abc


class Animals(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def run(self):
        print(1)
        pass


animals = Animals()  # TypeError: Can't instantiate abstract class Animals with abstract method run

子类约定俗成必须有这个抽象类指定的方法,不然会报错

import abc


class Animals(metaclass=abc.ABCMeta):

    @abc.abstractmethod # 子类约定俗成必须有这个父类指定的方法 不然会报错
    def language(self): # 通常情况下,父类指定的方法里面只给一个pass 因为会被子类给重写,只是约定规范 
        pass


class People(Animals):
    pass

# 若子类中没有一个定义抽象类指定的方法则会抛出异常TypeError,无法实例化
people = People() # TypeError: Can't instantiate abstract class People with abstract method language

正常设置

import abc


class Animals(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def language(self):
        pass


class People(Animals):

    def language(self):
        print("说各种语言")


class Pig(Animals):

    def language(self):
        print("哼唧哼唧")


class Dog(Animals):

    def language(self):
        print("汪汪汪")


people = People()
pig = Pig()
dog = Dog()

people.language()  # 说各种语言
pig.language()  # 哼唧哼唧
dog.language()  # 汪汪汪

多态性

注意:多态与多态性是两种概念

多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。

所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

import abc


class Animals(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def run(self):
        pass


class People(Animals):

    def run(self):
        print("人正在跑步")


class Dog(Animals):

    def run(self):
        print("狗正在狂奔")


people = People()
dog = Dog()


def func(obj):
    obj.run()


func(people)  # 人正在跑步
func(dog)  # 狗正在狂奔
多态性依赖于:继承
多态性:定义统一的接口
obj这个参数没有类型限制,可以传入不同类型的值
调用的逻辑都一样,执行的结果却不一样
综上可以说,多态性是一个接口(函数func)的多种实现(如obj.run(),obj.talk(),obj.click(),len(obj))

多态性的好处

增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用

鸭子类型

鸭子类型的名称来源于“如果它走起来像鸭子,游泳起来像鸭子,叫起来像鸭子,那么它就是鸭子”的说法。

这意味着在鸭子类型中,我们不关心对象的具体类型,而是关心它是否具有所需的方法和属性。

class Duck:
    def quack(self):  # 定义一个叫quack的方法,打印"Quaaaaaak!"
        print("Quaaaaaak!")

class Bird:
    def quack(self):  # 定义一个叫quack的方法,打印"bird imitate duck."
        print("bird imitate duck.")

class Dog:
    def quack(self):  # 定义一个叫quack的方法,打印"Dog imitate duck."
        print("Dog imitate duck.")

def zoo(animal):  # 定义一个叫zoo的函数,接受一个动物类型的对象作为参数
    animal.quack()  # 调用动物类型的quack方法

duck = Duck()  # 创建一个Duck类的实例
bird = Bird()  # 创建一个Bird类的实例
dog = Dog()  # 创建一个Dog类的实例

for animal in [duck, bird, dog]:  # 遍历包含动物类型的对象列表
    zoo(animal)  # 调用zoo函数,让动物类型的对象执行quack方法


"""
Quaaaaaack!
bird imitate duck.
Dog imitate duck.
"""

鸭子自然会嘎嘎的叫
小鸟也会叫,这鸟叫声跟鸭子叫声很类似, 哇哇啾啾
小狗也会叫,这狗叫声跟鸭子叫声也很类似,哇哇
既然叫声都很类似,那就认为它们都是鸭子,直接指鸟为鸭,指狗为鸭,

我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。

在Python中,鸭子类型编程可以通过以下方式实现:

  1. 不依赖具体的类型:编写代码时,不需要关注对象的具体类型,而是关注对象是否具有所需的方法或属性。例如,如果一个对象具有read()write()方法,那么它可以被当作文件对象来使用,而不需要是file类型的实例。
  2. 使用try-except语句:在使用某个方法或属性之前,可以使用try-except语句来捕获可能的异常。如果对象具有所需的方法或属性,那么代码将正常执行;如果对象没有所需的方法或属性,那么会抛出异常,可以在except块中处理该异常。
def proces_data(obj):
    try:
        obj.read()
        obj.process()
        obj.write()
    except AttributeError:
        print("对象不具有所需的方法或属性。")


class FileObject:

    def read(self):
        print("读取文件")

    def process(self):
        print("处理数据")

    def write(self):
        print("写入文件")


class DatabaseObject:

    def read(self):
        print("读取数据库")

    def process(self):
        print("处理数据")

    def write(self):
        print("写入数据库")


file_obj = FileObject()  
database_obj = DatabaseObject()  

proces_data(file_obj)  # 输出 读取文件  处理数据  写入文件
proces_data(database_obj)  # 输出 读取数据库  处理数据  写入数据库 
proces_data("deleted")  # 对象不具有所需的方法或属性。

在上面的示例中,我们定义了一个process_data()函数,它接受一个对象作为参数。函数内部通过调用对象的read()process()write()方法来处理数据。无论传入的对象是FileObject还是DatabaseObject,只要它们具有相应的方法,就可以被process_data()函数处理。 需要注意的是,鸭子类型编程的核心是关注对象的行为而不是具体的类型。这种编程方式可以提高代码的灵活性和可复用性,但也需要保证对象在使用时确实具有所需的方法和属性,否则可能会导致运行时错误。

反射

反射指的是在程序运行过程中可以"动态"获取对象的信息。比如判断用户输入的一个字符串是不是一个对象的属性。通过反射可以避免程序使用较为繁琐的if-elif-else判断,使得代码结构简洁清晰。

class Ftp:
    
    def get(self):
        pass
    
    def put(self):
        pass
    
    def set(self):
        pass
    

ftp = Ftp()
cmd = input("请输入指令:").strip()
if cmd == "get":
    ftp.get()
elif cmd == "put":
    ftp.put()
elif cmd == "set":
    ftp.set()
else:
    print("指令不存在")

hasattr()

判断对象是否有指定属性,返回布尔值

# 语法 hasattr(obj, name)
class Foo:
    
    name = "eva"

foo = Foo()
hasattr(foo, "name")  # True
hasattr(foo, "age")   # False

getattr()

动态获取对象的属性值

如果获取到的是对象中的函数,返回的结果是该函数的内存地址,可以加括号去调用

# 语法 getattr(obj, name, default=None)  如果捕不设置默认值,那么获取不到结果会直接报错。
# getattr 语法  getattr(obj, name)

class Foo:
    
    name = '小满'

    def say(self):
        print(f"你好!我是{self.name}")

foo = Foo()
getattr(foo, "name")  # '小满'
# getattr(foo, age)  # NameError: name 'age' is not defined
getattr(foo, "age", False) # False
getattr(foo, 'say')  # <bound method Foo.say of <__main__.Foo object at 0x000001A1E8B3E230>>
func = getattr(foo, "say")
func()  # 你好!我是小满

setattr()

动态的设置属性

# 语法 setattr(obj, key, value) 更新对象key的值,等价于 obj.key = value, 当key不存在时新增。
class Foo:

    age = 3

foo = Foo()
setattr(foo, "name", "小满")
foo.name  # '小满'
setattr(foo, 'age', 4)
foo.age  # 4

delattr()

删除一个对象的属性

# 语法 delattr(obj, name) 等价于 del obj.name 若不存在则会报错
class Foo:

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


foo = Foo("小满")

vars(foo) # {'name': '小满'}
delattr(foo, 'name')

vars(foo) # {}
foo.name  # AttributeError: 'Foo' object has no attribute 'name'

类也是对象,也可以使用反射

class Foo:
    name = "小满"


if hasattr(Foo, "name"):
    print(Foo.name)
    setattr(Foo, "age", 3)
else:
    delattr(Foo, "age")

vars(Foo)

"""
mappingproxy({'__module__': '__main__',
              'name': '小满',
              '__dict__': <attribute '__dict__' of 'Foo' objects>,
              '__weakref__': <attribute '__weakref__' of 'Foo' objects>,
              '__doc__': None,
              'age': 3})
"""

优化上面的案例

class Ftp:

    def get(self):
        pass

    def put(self):
        pass

    def set(self):
        pass


ftp = Ftp()
cmd = input("请输入指令:").strip()

if hasattr(ftp, cmd):
    getattr(ftp, cmd)
else:
    print("Error")
文章来源:https://blog.csdn.net/2304_81612168/article/details/135373971
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。