初始面向对象

发布时间 2024-01-03 21:57:00作者: 小满三岁啦

两种思维模式

面向过程思维

什么是面向过程?

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

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

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

缺点:扩展性很差。

面向对对象思维

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

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

类和对象

类的概念

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

img

对象的概念

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

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

静态部分被称为“属性”

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

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

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

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

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

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

定义类

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

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

定义类是否要添加括号的问题

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

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

class MyClass:
    pass

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

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

class MyClass(BaseClass):
    pass

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

总结起来,如果你的类不需要继承自其他类,可以选择省略括号;如果需要继承自其他类,必须在类名后面加括号并指定要继承的类。

简单演示

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

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

实例化对象

创建对象的格式如下:

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

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

实例对象的属性

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

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

在类外面定义的演示

class Hero:
    pass

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

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

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

魔法方法是指 Python 内部已经包含的,被双下划线所包围的方法,这些方法在进行特定的操作时会被自动调用,它们是 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__ 类名
# 使用__init__定义属性
# 1
# 此方法属性写死了,不推荐
class Hero:
    def __init__(self):
        self.name = "小满"
        self.age = 3

xm = Hero()

print(xm.name)  # 小满
print(xm.age)   # 3
# 2
# 推荐使用此方法
# 通过 __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

总结__init__方法

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

如果要运行里面的方法,可以通过类名.方法名()或者对象名().方法名(),运行属性同理。

注:代码在jupyterlab中运行

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

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

xm.name   # 小满'
xm.eat()  # 小满开始吃东西啦!

方法命名规范

类里面方法的命名规则同python命名规则,但是推荐使用小驼峰 _

heroName 这就是小驼峰

关于self参数

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

一言以蔽之:

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

# 注意,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()
你好!我是小满
你好!我是小满
你好!我是小满

小案例

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小时,地瓜:糊了

实例属性

我们通过 __init__ 魔法方法为对象创建的属性叫做实例属性

因为 self 表示调用这个方法的对象,所以此时 self.name 就等于 erha.name ,也就相当于我们是在为 erha 这个对象创建属性。

init函数只能返回 None,否则会引起 TypeError

image-20240102182321688

修改实例属性

class Student:

    # 类属性
    money = 1000

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

eva = Student("精英班")
amigo = Student("霍格沃兹班")

print(eva.grade)    # 精英班
print(amigo.grade)  # 霍格沃兹班

eva.grade = "霍格沃兹班"
print(eva.grade)  # 霍格沃兹班

注意:

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

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'

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

class Hero:
    pass

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

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

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

  • 先从自己本身开始找 从xx.__dict__开始找
  • 找不到再从类.__dict__
  • 别的方法没有初始化过这个属性,并且这个类没有继承其他的类
  • 找不到就报错了

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

类属性

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

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

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

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

类对象

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

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

类方法和静态方法

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

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

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

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

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

# 类方法,用@classmethod 来进行修饰
@classmethod
def get_country(cls):
    pass
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

__str__魔法方法

_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岁了

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

类的继承

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

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

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

访问限制

在类的内部可以定义属性和方法,而在类的外部则可以直接调用属性或方法来操作数据,从而隐藏了类内部的复杂逻辑。但是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, gender):
        self.__hobby = gender

    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)

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

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

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

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

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

属性方法命名

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

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

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