代码获取:https://github.com/qingxuly/hsp_python_course
完结版:Python基础学习(完结版)
面向对象编程(进阶)
面向对象编程三大特征
- 面向对象编程有三大特征:封装、继承、多态。
面向对象编程—封装
封装介绍
- 封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部。
- 程序只有通过被授权的操作,才能对数据进行访问。
- 封装经典案例:电视机的操作就是典型封装。
封装的理解和好处
- 隐藏实现细节:方法(绘制柱状图)<——调用(传入参数…)。
- 可以对数据进行验证(比如:age: 1~120、password: 长度要求),保证安全合理。
- 可以保护数据隐私(比如salary),要求授权才可以访问。
私有成员
- 公共的变量和方法介绍。
- 默认情况下,类中的变量和方法都是公有的,它们的名称前都没有下划线。
- 公共的变量和方法,在类的外部、类的内部,都可以正常访问。
- 如何将属性/方法进行私有化。
- 类中的变量或方法以双下划线
__
开头命名,则该变量或方法为私有的,私有的变量或方法,只能在本类内部使用,类的外部无法使用。
- 类中的变量或方法以双下划线
- 如何访问私有的属性/方法:提供公共的方法,用于对私有成员的操作。
快速入门
# 创建职员类(Clerk),属性:name, job, salary
# 1、不能随便查看职员 Clerk 的职位和工资等隐私,比如职员 ("tiger", "python 工程师", 20000)
# 2、提供公共方法,可以对职员和工资进行操作
class Clerk:
# 公共属性
name = None
# 私有属性
__job = None
__salary = None
# 构造方法
def __init__(self, name, job, salary):
self.name = name
self.__job = job
self.__salary = salary
# 提供公共的方法,对私有属性操作(根据实际的业务编写即可)
def set_job(self, job):
self.__job = job
def get_job(self):
return self.__job
# 私有方法
def __hi(self):
print("hi()")
# 提供公共方法,操作私有方法
def f1(self):
self.__hi()
clerk = Clerk("tiger", "python 工程师", 20000)
print(clerk.name)
# print(clerk.__job) # AttributeError: 'Clerk' object has no attribute '__ job'
print(clerk.get_job())
clerk.set_job("Java 工程师")
print(clerk.get_job())
# 私有方法不能在类的外部直接调用
# clerk.hi() # AttributeError: 'Clerk' object has no attribute 'hi'
# 通过公共方法,调用私有方法
clerk.f1()
注意事项和细节
- python语言的动态特性,会出现伪私有属性的情况
class Clerk:
# 公共属性
name = None
# 私有属性
__job = None
__salary = None
# 构造方法
def __init__(self, name, job, salary):
self.name = name
self.__job = job
self.__salary = salary
# 提供公共的方法,对私有属性操作
def get_job(self):
return self.__job
clerk = Clerk("apple", "python 工程师", 20000)
# 如果这样使用,因为 python 语言的动态特性,会动态的创建属性 __job,但是这个属性和我们在类中定义的私有属性 __ job 不是同一个变量,我们在类中定义的__job 私有属性完整的名字是 _Clerk__job,而并不是
# 可以通过 debug 的“Evaluate Expression”来观察。
clerk.__job = "Go 工程师"
print(f " job = {clerk.__job}") # job = Go 工程师
# 获取真正的私有属性__job
print(f "{clerk.get_job()}")
- 练习
# Account 类要求具有属性:姓名(长度为 2-4 位)、余额(必须 > 20)、密码(必须是六位),如果不满足,则给出提示
# 通过 set_xx 的方法给 Account 的属性赋值
# 编写 query_info() 接收姓名和密码,如果姓名和密码正确,返回该账号信息
"" "
思路分析:
类名:Account
私有属性:姓名(长度为 2-4 位)、余额(必须 > 20)、密码(必须是六位)
构造器:无
方法:set_xx(self, 属性名) 进行赋值,并且对各个接收到的数据进行校验
方法:query_info(self, name, pwd) 而且需要验证,才返回响应信息
"" "
class Account:
__name = None
__balance = None
__pwd = None
def set_name(self, name):
if 2 <= len(name) <= 4:
self.__name = name
else:
print("名字的长度不在 2-4 位之间")
def set_balance(self, balance):
if balance > 20:
self.__balance = balance
else:
print("余额(必须 > 20)")
def set_pwd(self, pwd):
if len(pwd) == 6:
self.__pwd = pwd
else:
print("密码(必须是六位)")
def query_info(self, name, pwd):
if name == self.__name and pwd == self.__pwd:
return f "账户信息:{self.__name} {self.__ balance}"
else:
return "请输入正确的名字和密码"
# 测试
account = Account()
account.set_name("tim")
account.set_pwd("000000")
account.set_balance(100)
print(account.query_info("tim", "000000"))
面向对象编程—继承
基本介绍
-
继承基本介绍
- 基础可以解决代码复用,让我们的编程更加靠近人类思维。
- 当多个类存在相同的属性(成员变量)和方法时,可以从这些类中抽象出方法,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法。
-
示意图
- 基本语法
class DerivedClassName(BaseClassName):
<statement-1>
...
<statement-N>
# 派生类就会自动拥有基类定义的属性和方法
# 基类习惯上也叫父类
# 派生类习惯上也叫子类
快速入门
# 编写父类
class Student:
name = None
age = None
__score = None
def __init__(self, name, age):
self.name = name
self.age = age
def show_info(self):
print(f " name ={self.name}, age ={self.age}, score ={self.__score}")
def set_score(self, score):
self.__score = score
# 小学生类,继承 Student
class Pupil(Student):
def testing(self):
print("小学生在考小学数学...")
# 大学生类,继承 Student
class Graduate(Student):
def testing(self):
print("大学生在考高等数学...")
# 测试
student1 = Pupil("apple", 10)
student1.testing()
student1.set_score(70)
student1.show_info()
student2 = Graduate("grape", 22)
student2.testing()
student2.set_score(80)
student2.show_info()
- 继承给编程带来的便利
- 代码的复用性提高了。
- 代码的扩展性和维护性提高了。
继承的注意事项和细节
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法在子类不能在子类直接访问,要通过父类提供公共的方法去访问。
# 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法在子类不能在子类直接访问,要通过父类提供公共的方法去访问。
class Base:
# 公共属性
n1 = 100
# 私有属性
__n2 = 200
def __init__(self):
print("Base 构造方法")
def hi(self):
print("hi() 公共方法")
def __hello(self):
print("__hello() 私有方法 ")
# 提供公共方法,访问私有属性和私有方法
def test(self):
print("属性:", self.n1, self.__n2)
self.__hello()
class Sub(Base):
def __init__(self):
print("Sub 构造方法")
def say_ok(self):
print("say_ok() ", self.n1)
self.hi()
# print(self.__n2) # AttributeError: 'Sub' object has no attribute '_Sub__n2'
# self.__hello() # AttributeError: 'Sub' object has no attribute '_Sub__n2'
# 测试
sub = Sub()
sub.say_ok()
# 调用子类继承父类的公共方法,去实现访问父类的私有成员的效果
sub.test()
-
Python语言中,
object
是所有其它类的基类。Ctrl + H
可以查看一个类的继承关系。 -
Python支持多继承。
class A:
n1 = 100
def sing(self):
print("A sing()...", self.n1)
class B:
n2 = 200
def dance(self):
print("B dance()...", self.n2)
class C(A, B):
# Python pass 是空语句,是为了保持程序结构的完整;pass 不做任何事情,一般用作站位语句。
pass
c = C()
# 继承的属性信息
print(f "属性信息:{c.n1} {c.n2}")
# 调用继承的方法
c.dance()
c.sing()
- 在多重继承中,如果有同名的成员,遵守从左到右的继承优先级(即:写在左边的父类优先级高,写在右边的父类优先级低)。
class A:
n1 = 100
def sing(self):
print("A sing()...", self.n1)
class B:
n2 = 200
def dance(self):
print("B dance()...", self.n2)
def sing(self):
print("B sing()...", self.n2)
class C(A, B):
# Python pass 是空语句,是为了保持程序结构的完整;pass 不做任何事情,一般用作站位语句。
pass
c = C()
# 继承的属性信息
print(f "属性信息:{c.n1} {c.n2}")
# 调用继承的方法
c.dance()
c.sing() # A sing()... 100
继承的练习题
class GrandPa:
name = "大头爷爷"
hobby = "旅游"
class Father(GrandPa):
name = "大头爸爸"
age = 39
class Son(Father):
name = "大头儿子"
son = Son()
print(f "son.name is {son.name} and son.age is {son.age} and son.hobby is {son.hobby}")
class Computer:
cpu = None
memory = None
disk = None
def __init__(self, cpu, memory, disk):
self.cpu = cpu
self.memory = memory
self.disk = disk
def get_details(self):
return f "CPU: {self.cpu}, Memory: {self.memory}, Disk: {self.disk}"
class PC(Computer):
brand = None
def __init__(self, cpu, memory, disk, brand):
# 初始化子类的属性——方式 1
# self.cpu = cpu
# self.memory = memory
# self.disk = disk
# self.brand = brand
# 初始化子类的属性——方式 2
# 通过 super().xx 方式可以去调用父类方法,这里通过 super().__init__(cpu, memory, disk, brand) 去调用父类的构造器
super().__init__(cpu, memory, disk)
self.brand = brand
def print_info(self):
print(f "品牌:{self.brand}\t{self.get_details()}")
pc = PC("inter", 32, 1000, "戴尔")
pc.print_info()
调用父类成员
基本介绍&实例
- 基本介绍
- 如果子类和父类出现同名的成员,可以通过
父类名
、super()
访问父类的成员。
- 如果子类和父类出现同名的成员,可以通过
- 基本语法
- 访问父类成员方式1
- 访问成员变量:
父类名.成员变量
- 访问成员方法:
父类名.成员方法(self)
- 访问成员变量:
- 访问父类成员方式2
- 访问成员变量:
super().成员变量
- 访问成员方法:
super().成员方法()
- 访问成员变量:
- 访问父类成员方式1
- 案例演示
class A:
n1 = 100
def run(self):
print("A-run()...")
class B(A):
n1 = 200
def run(self):
print("B-run()...")
# 通过父类名去访问父类的成员
def say(self):
print(f "父类的 n1 ={A.n1} 本类的 n1 ={self.n1}")
# 调用父类的 run
A.run(self)
# 调用本类的 run()
self.run()
# 通过 super()方式去访问父类对象
def hi(self):
print(f "父类的 n1 ={super().n1}")
b = B()
b.say()
b.hi()
注意事项和使用细节
- 子类不能直接访问父类的私有成员
class A:
n1 = 100
__n2 = 600
def run(self):
print("A-run()...")
def __jump(self):
print("A-jump()...")
class B(A):
# 子类不能直接访问父类的私有成员
def say(self):
# print(A.__n2) # AttributeError: type object 'A' has no attribute '_B_ _n2'. Did you mean: '_A__n2'?
# print(super().__n2) # AttributeError: 'super' object has no attribute '_B__n2'
# A.__jump(self)
# super().jump()
print("B-say()...")
b = B()
b.say()
- 访问不限于直接父类,而是建立从子类向上级父类的查找关系 A->B->C…
class Base:
n3 = 800
def fly(self):
print("Base-fly()...")
class A(Base):
n1 = 100
__n2 = 600
def run(self):
print("A-run()...")
def __jump(self):
print("A-jump()...")
class B(A):
# 访问不限于直接父类,而是建立从子类向上级父类的查找关系 B-> A-> Base...
def say(self):
print(Base.n3, A.n3, super().n3)
Base.fly(self)
A.fly(self)
super().fly()
self.fly()
b = B()
b.say()
- 建议使用
super()
的方式,因为如果使用父类名
方式,一旦父类变化,类名统一需要修改,比较麻烦。
练习题
- 分析下面的代码,看看输出什么内容?
class A:
n1 = 300
n2 = 500
n3 = 600
def fly(self):
print("A-fly()...")
class B(A):
n1 = 200
n2 = 400
def fly(self):
print("B-fly()...")
class C(B):
n1 = 100
def fly(self):
print("C-fly()...")
def say(self):
print(self.n1) # 100
print(self.n2) # 400
print(self.n3) # 600
print(super().n1) # 200
print(B.n1) # 200
print(C.n1) # 100
c = C()
c.say()
- 针对上面的程序,想在C的say()中,调用C的fly()和A的fly(),应该如何调用?
self.fly() # C-fly()...
A.fly(self) # A-fly()...
方法重写
基本介绍&实例
- 基本介绍
- 重写又称覆盖(override),即子类继承父类的属性和方法后,根据业务需要,再重新定义同名的属性和方法。
- 案例演示
练习题
class Person:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
return f "名字:{self.name} 年龄:{self.age}"
class Student(Person):
id = None
score = None
def __init__(self, id, score, name, age):
# 调用父类的构造器完成继承父类的属性和属性的初始化
super().__init__(name, age)
# 子类的特有的属性,我们自己完成初始化
self.id = id
self.score = score
def say(self):
return f "id:{self.id} score:{self.score} {super().say()}"
person = Person("tom", 12)
print(person.say())
student = Student("tom", 14, "tom", 18)
print(student.say())
类型注解-type hint
基本介绍
- 为什么需要类型注解
- 随着项目越来越大,代码也就会越来越多,在这种情况下,如通过没有类型注解,很容易不记得某一个方法的参数类型是什么。
- 一旦传入了错误类型的参数,python是解释性语言,只有运行时候才能发现问题,这对大项目来说是一个巨大的灾难。
# 对字符串进行遍历
# a: str 给形参 a 进行类型注解,标注 a 的类型是 str。
def fun1(a: str):
for ele in a:
print(ele)
# Ctrl + P 提示参数时,没有类型提示
# 如果类型传错了,就会出现异常
fun1(100) # TypeError: 'int' object is not iterable
- 类型注解作用和说明
- 从Python3.5开始,引入了类型注解机制,作用和说明如下:
- 类型提示,防止运行时出现参数类型、返回值类型、变量类型不符合。
- 作为开发文档附加说明,方便使用者调用时传入和返回类型参数。
- 加入后并不会影响程序的运行,不会报正式的错误,只有提示。
- Pycharm支持类型注解,参数类型错误会黄色提示。
- 从Python3.5开始,引入了类型注解机制,作用和说明如下:
变量的类型注释
-
基本语法:
变量: 类型
。 -
基础数据类型注解
n1: int = 10 # 对n1进行类型注解,标注n1的类型为int;如果给出的类型与标注的类型类型不一致,则Pycharm会给出黄色警告
n2: float = 10.1
is_pass: bool = True
name: str = "Tom"
- 实例对象类型注解
class Cat:
pass
cat: Cat = Cat() # 对cat进行类型注解,标注cat类型是Cat
- 容器类型注解
my_list: list = [1, 2, 3] # 对my_list进行类型注解,标注my_list类型为list。
my_tuple: tuple = ("run", "sing", "fly")
my_set: set = {1, 2, 3}
my_dict: dict = {"name": "Tom", "age": 22}
- 容器详细类型注解
my_list1: list[int] = [1, 2, 3] # 对my_list1进行类型注解,标注my_list1类型是list,而且该list元素是int。
# 元组类型设置详细类型注解,需要把每个元素类型都标注一下
my_tuple1: tuple[str, str, str, float] = ("run", "sing", "fly", 1.1)
my_set1: set[int] = {1, 2, 3}
# 字典类型设置详细类型注解,需要设置两个类型,即[key类型, value类型]
# 对my_dict1进行类型注解,标注my_dict1类型是dict,而且key的类型是str,values的类型是int。
my_dict1: dict[str, int] = {"score": 100, "score2": 80}
- 在注释中使用注解
- 基本语法:
# type: 类型
。
- 基本语法:
# # type: float 用于标注变量n3的类型是float。
n3 = 89.9 # type: float
my_list3 = [100, 200, 300] # type: list[int]
email = "[email protected]" # type: str
函数(方法)的类型注解
- 基本语法
def 函数/方法名(形参名: 类型, 形参名: 类型 ...) -> 返回值类型:
函数/方法体
- 实例演示
# 对字符串进行遍历
# name: str 对形参name进行类型注解:标注name类型是str;在调用方法/函数时,传入的实参类型不是一样的,则给出黄色的警告。
def fun1(name: str):
for ele in name:
print(ele)
fun1("hsp")
# 接收两个整数,返回整数
# a: int, b: int 对形参a、b进行类型注解,标注a、b的类型为int; -> int 对返回值进行类型注解,标注返回值的类型为int。
def fun2(a: int, b: int) -> int:
return a + b
print(f"结果是:{fun2(1, 2)}")
类型注解是提示性的,并不是强制性的,如果你给的类型和指定/标注的类型不一致,Pycharm检测到会给出黄色警告,但是仍然可以运行。
Union类型
- 基本介绍
- Union类型可以定义联合类型注解。
- 在变量、函数(方法)都可以使用Union联合类型注解。
- 使用的时候,需要先导入Union:
from typing import Union
。
- 基本语法
Union[类型, 类型...]
比如:联合类型:Union[X, Y] 等价于 X|Y,意味着满足X或Y之一。
- 实例演示
from typing import Union
# 联合类型注解, a可以是int或str
a: Union[int, str] = 100
# my_list是str类型,元素可以是int或str
my_list: list[Union[int, str]] = [1, 2, 3, "tom"]
# 函数/方法使用联合函数注解
# 接收两个数(可以是int/float),返回数(int/float)
def cal(num1: Union[int, float], num2: Union[int, float]) -> Union[int, float]:
return num1 + num2
print(cal(1, 2.1))
面向对象编程—多态
问题引入
- Master类中有一个feed(喂食)方法,可以完成主人给动物喂食物的信息。
# 使用传统的方式
class Food:
name = None
def __init__(self, name):
self.name = name
class Fish(Food):
pass
class Bone(Food):
pass
class Animal:
name = None
def __init__(self, name):
self.name = name
class Dog(Animal):
pass
class Cat(Animal):
pass
class Master:
name = None
def __init__(self, name):
self.name = name
# 给猫喂鱼
def feed_cat(self, cat: Cat, fish: Fish):
print(f"主任{self.name} 给动物{cat.name} 喂{fish.name}")
# 给狗喂骨头
def feed_dog(self, dog: Dog, bone: Bone):
print(f"主人{self.name} 给动物{dog.name} 喂{bone.name}")
master = Master("老韩")
cat = Cat("小花猫")
fish = Fish("黄花鱼")
dog = Dog("大黄狗")
bone = Bone("大棒骨")
master.feed_cat(cat, fish)
master.feed_dog(dog, bone)
- 传统方法带来的问题:代码的复用性不高,而且不利于代码维护和功能扩展。解决方法是使用多态。
多态介绍
- 多态
- 多态顾名思义即多种状态,不同的对象调用相同的方法,表现出不同的状态,称为多态。
- 多态通常作用在继承关系上。
- 举例说明:一个父类,具有多个子类,不同的子类对象调用相同的方法,执行的时候产生不同的状态,就是多态。
- 代码演示
class Animal:
def cry(self):
pass
class Cat(Animal):
def cry(self):
print("喵喵喵")
class Dog(Animal):
def cry(self):
print("汪汪汪")
class Pig(Animal):
def cry(self):
print("噜噜噜")
# 在Python面向对象编程中,子类对象可以传递给父类类型
def func(animal: Animal):
print(f"animal类型:{type(animal)}")
animal.cry()
cat = Cat()
dog = Dog()
pig = Pig()
func(cat)
func(dog)
func(pig)
-
多态的好处
- 增加了程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal),代码说明。
- 增加了程序的可扩展性,通过继承Animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用。
-
Python多态特点
- Python中函数/方法的参数是没有类型限制的,所以多态在Python中的体现并不是很严谨(与Java等强类型语言比)。
- Python并不要求严格的继承体系,关注的不是对象的类型本身,而是它是否具有要调用的方法(行为)。
class AA:
def hi(self):
print("AA-hi()...")
class BB:
def hi(self):
print("BB-hi()...")
def test(obj):
obj.hi()
aa = AA()
bb = BB()
test(aa)
test(bb)
二说喂动物问题
# 使用多态的方式
class Food:
name = None
def __init__(self, name):
self.name = name
class Fish(Food):
pass
class Bone(Food):
pass
class Animal:
name = None
def __init__(self, name):
self.name = name
class Dog(Animal):
pass
class Cat(Animal):
pass
# 扩展 Horse、Grass,体会多态的机制带来的好处
class Horse(Animal):
pass
class Grass(Food):
pass
class Master:
name = None
def __init__(self, name):
self.name = name
def feed(self, animal: Animal, food: Food):
print(f"主人{self.name} 给动物{animal.name} 喂{food.name}")
master = Master("老韩")
cat = Cat("小花猫")
fish = Fish("黄花鱼")
dog = Dog("大黄狗")
bone = Bone("大棒骨")
horse = Horse("枣红马")
grass = Grass("嫩草")
master.feed(cat, fish)
master.feed(dog, bone)
master.feed(horse, grass)
isinstance函数
- 基本说明
- isinstance() 用于判断对象是否为某个类或其子类的对象
- 基本语法:
isinstance(object, classinfo)
。- object:对象。
- classinfo:可以是类名、基本类型或者由它们组成的元组。
class AA:
pass
class BB(AA):
pass
class CC:
pass
obj = BB()
obj2 = AA()
print(f"obj 是不是BB的对象 {isinstance(obj, BB)}")
print(f"obj 是不是AA的对象 {isinstance(obj, AA)}")
print(f"obj 是不是CC的对象 {isinstance(obj, CC)}")
num = 9
print(f"num 是不是int:{isinstance(num, int)}")
print(f"num 是不是str:{isinstance(num, str)}")
print(f"num 是不是str/int/list:{isinstance(num, (str, int, list))}")
练习
- 分析代码输出结果。
当调用对象成员的时候,会和对象本身动态关联。
class A:
i = 10
def sum(self):
# self是A还是B?——B
# 当调用对象成员的时候,会和对象本身动态关联
return self.getI() + 10
def sum1(self):
return self.i + 10
def getI(self):
return self.i
class B(A):
i = 20
# def sum(self):
# return self.i + 20
def getI(self):
return self.i
# def sum1(self):
# return self.i + 10
b = B()
print(b.sum()) # 30
print(b.sum1()) # 30
- 编程题
定义员工类Employee,包含私有属性(姓名和月工资),以及计算年工资get_annual的方法。
普通员工(Worker)和经理(Manager)继承员工类,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求根据需要重写get_annual方法。
编写函数show emp annual(e:Employee),实现获取任何员工对象的年工资。
编写函数working(e:Employee),如果是普通员工,则调用work方法,如果是经理,则调用manage方法。
class Employee:
__name = None
__salary = None
# 构造器
def __init__(self, name, salary):
self.__name = name
self.__salary = salary
def get_annual(self):
return self.__salary * 12
def set_name(self, name):
self.__name = name
def get_name(self):
return self.__name
def get_salary(self):
return self.__salary
class Worker(Employee):
def work(self):
print(f"普通工人 {self.get_name()} 正在工作中")
class Manager(Employee):
__bonus = None
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.__bonus = bonus
def manage(self):
print(f"经理 {self.get_name()} 正在工作中")
def get_annual(self):
return super().get_annual() + self.__bonus
def show_emp_annual(e: Employee):
print(f"{e.get_name()} 年工资: {e.get_annual()}")
worker = Worker("king", 10000)
show_emp_annual(worker)
manager = Manager("tim", 20000, 100000)
show_emp_annual(manager)
def working(e: Employee):
if isinstance(e, Worker):
e.work()
elif isinstance(e, Manager):
e.manage()
else:
print("无法确定工作状态")
working(worker)
working(manager)
[魔术方法](3. 数据模型 — Python 3.13.0 文档)
基本介绍
- 什么是魔术方法
- 在Python中,所有以双下划线
__
包起来的方法,统称为Magic Method(魔术方法),它是一种特殊方法,普通方法需要调用,而魔术方法不需要调用就可以自动执行。 - 魔术方法在类或对象的某些事情发生时会自动执行,让类具有神奇的“魔力”。如果希望根据自己的程序定制特殊功能的类,那么就需要对这些方法进行重写。
- Python中常用的运算符、for循环以及类操作都是运行在魔术方法之上的。
- 在Python中,所有以双下划线
- 常见的魔术方法
序号 | 操作 |
---|---|
1 | __init__ :初始化对象的成员 |
2 | __str__(self) :定义对象转字符的行为:print(对象)或str(对象) |
3 | __eq__(self, other) :定义等于号的行为:x == y |
4 | __lt__(self, otehr) :定义小于号的行为:x < y |
5 | __le__(self, otehr) :定义小于等于号的行为:x ≤ y |
6 | __ne__(self, otehr) :定义不等号的行为:x != y |
7 | __gt__(self, self) :定义大于号的行为:x > y |
8 | __ge__(self, other) :定义大于等于号的行为:x ≥ y |
__str__
- 基本介绍
- 打印对象默认返回:类型名+对象内存地址,子类往往重写
__str__
,用于返回对象的属性信息。<__main__.Monster object at 0x000002414EC85070>
- 重写
__str__
方法,print(对象)或str(对象)时,都会自动调用该对象的__str__
。
- 打印对象默认返回:类型名+对象内存地址,子类往往重写
- 应用实例
class Monster:
name = None
age = None
gender = None
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
# 请输出 Monster[name, job, sal] 对象的属性信息
"""
1. 在默认情况下,调用的是父类。Monster的父类object的__str__
2. 父类object的__str__ 返回的就是类型+地址
3. 可以根据需要重写__str__
"""
def __str__(self):
# return super().__str__()
return f"{self.name} {self.age} {self.gender}"
m = Monster("青牛怪", 500, "n男")
print(m) # 默认输出类型+对象的地址
print(m, hex(id(m)))
__eq__
-
基本介绍
- == 是一个比较运算符:对象之间进行比较时,比较的内存地址是否相等,即判断是不是同一个对象。
- 重写
__eq__
方法,可以用于判断对象内容/属性是否相等。
-
应用实例
判断两个Person对象的内容是否相等,如果两个Person对象的各个属性值都一样,则返回True,反之返回False。
Person类,属性(name, age, gender)
class Person:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
# 判断两个Person对象的内容是否相等,如果两个Person对象的各个属性值都一样,则返回True,反之返回False。
def __eq__(self, other):
# 判断other 是不是 Person
if isinstance(other, Person):
return (self.name == other.name and
self.age == other.age and
self.gender == other.gender)
return False
class Dog:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
# 没有重写 __eq__ 前, == 比较的是内存地址;重写 __eq__ 后, == 比较的是属性/内容
p1 = Person("smith", 20, "male")
p2 = Person("smith", 20, "male")
dog = Dog("smith", 20, "male")
print(f"p1 == p2: {p1 == p2}")
print(f"p1 == dog: {p1 == dog}")
其它几个魔术方法
-
其它几个魔术方法
__lt__(self, otehr)
:定义小于号的行为:x < y。__le__(self, otehr)
:定义小于等于号的行为:x ≤ y。__ne__(self, otehr)
:定义不等号的行为:x != y。__gt__(self, self)
:定义大于号的行为:x > y。__ge__(self, other)
:定义大于等于号的行为:x ≥ y。
-
应用实例
判断两个Person对象的年龄进行比较大小,重写相应的魔术方法。
Person类,属性(name, age, gender)
class Person:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
# 判断两个Person对象的内容是否相等,如果两个Person对象的各个属性值都一样,则返回True,反之返回False。
def __eq__(self, other):
# 判断other 是不是 Person
if isinstance(other, Person):
return (self.name == other.name and
self.age == other.age and
self.gender == other.gender)
return False
# 重写 __lt__
def __lt__(self, other):
if isinstance(other, Person):
return self.age < other.age
return "类型不一致,不能比较"
# 重写 __lt__
def __le__(self, other):
if isinstance(other, Person):
return self.age <= other.age
return "类型不一致,不能比较"
# 重写 __ne__
def __ne__(self, other):
return not self.__eq__(other)
class Dog:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
# 没有重写 __eq__ 前, == 比较的是内存地址;重写 __eq__ 后, == 比较的是属性/内容
p1 = Person("smith", 20, "male")
p2 = Person("smith", 20, "male")
dog = Dog("smith", 20, "male")
print(f"p1 == p2: {p1 == p2}")
print(f"p1 == dog: {p1 == dog}")
print(f"p1 < p2: {p1 < p2}")
print(f"p1 <= p2: {p1 <= p2}")
print(f"p1 != p2: {p1 != p2}")
class对象和静态方法
[class 对象](9. 类 — Python 3.13.0 文档)
- 基本介绍
- 类本身也是对象,即:Class对象。
- 应用实例
class Monster:
name = "蝎子精"
age = 300
def hi(self):
print(f"hi() {self.name} {self.age}")
# 下一个断点,可以看到Monster的情况
print(Monster)
# 通过Class对象,可以引用属性(没有创建实例对象也可以引用/访问)
print(f"Monster.name is {Monster.name} Monster.age is {Monster.age}")
# 通过类名如何调用非静态成员方法
Monster.hi(Monster)
[静态方法](描述器指南 — Python 3.13.0 文档)
@staticmethod
将方法转换为静态方法。- 静态方法不会接收隐式的第一个参数,要声明一个静态方法。
class C:
@staticmethod
def f(arg1, arg2, argN): ...
- 静态方法既可以由类调用(如
C.f()
),也可以由实例中调用(如C().f()
)。
class Monster:
name = "蝎子精"
age = 300
def hi(self):
print(f"hi() {self.name} {self.age}")
@staticmethod
def ok():
print("ok()")
# 不需要实例化,通过类即可调用静态方法
Monster.ok()
# 通过实例对象,也可以调用静态方法
monster = Monster()
monster.ok()
[抽象类](abc — 抽象基类 — Python 3.13.0 文档)
抽象类的介绍
- 基本介绍
- 默认情况下,Python不提供抽象类,Python附带一个模块,该模块为定义抽象基类提供了基础,该模块名称为
abc
。 - 当我们需要抽象基类时,让类继承
ABC(abc模块的ABC类)
,使用@abstractmethod
声明抽象方法(@abstractmethod
用于声明抽象方法的装饰器,在abc
模块中),那么这个类就是抽象类。 - 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类基础病实现抽象类的抽象方法。
- 默认情况下,Python不提供抽象类,Python附带一个模块,该模块为定义抽象基类提供了基础,该模块名称为
快速入门
from abc import ABC, abstractmethod
# 将 Animal 做成抽象类,并让子类 Tiger 实现。
# Animal 是抽象类
class Animal(ABC):
def __init__(self, name, age):
self.name = name
self.age = age
# cry() 是抽象方法
@abstractmethod
def cry(self):
pass
# 注意:抽象类(含有抽象方法),不能实例化。
# TypeError: Can't instantiate abstract class Animal without an implementation for abstract method 'cry'
# animal = Animal("动物", 3)
# 编写子类 Tiger,继承 Animal 并实现抽象方法。
class Tiger(Animal):
def cry(self):
print('Tiger is crying')
tiger = Tiger('Tiger', 20)
tiger.cry()
注意事项和细节讨论
- 抽象类不能被实例化。
- 抽象类需要继承
ABC
,并且需要至少一个抽象方法。
# 抽象类需要继承`ABC`,并且需要至少一个抽象方法。
from abc import ABC, abstractmethod
class AAA(ABC):
name = "tim"
# @abstractmethod
# def f1(self):
# pass
# 如果没有一个抽象方法,能实例化。
obj1 = AAA()
- 抽象类中可以有普通方法。
# 抽象类中可以有普通方法。
from abc import ABC, abstractmethod
class AAA(ABC):
name = "tim"
@abstractmethod
def f1(self):
pass
def hi(self):
print("hi()")
def ok(self):
pass
class BBB(AAA):
# 实现父类的f1抽象方法
def f1(self):
print("BBB-f1")
obj2 = BBB()
obj2.f1()
obj2.hi()
obj2.ok()
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,否则它仍然是一个抽象类。
# 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,否则它仍然是一个抽象类。
from abc import ABC, abstractmethod
class AAA(ABC):
name = "tim"
@abstractmethod
def f1(self):
pass
@abstractmethod
def f2(self):
pass
def hi(self):
print("hi()")
def ok(self):
pass
class BBB(AAA):
# 实现父类的f1抽象方法
def f1(self):
print("BBB-f1")
# 如果没有完全实现AAA的抽象方法
# TypeError: Can't instantiate abstract class BBB without an implementation for abstract method 'f2'
def f2(BBB):
print("BBB-f2")
obj2 = BBB()
obj2.f1()
obj2.hi()
obj2.ok()
练习题
编写一个Employee类,做成抽象基类,包含如下三个属性:name,id,salary,提供必要的构造器和抽象方法:work()。
对应Manager类来说,他既是员工,还具有奖金(bonus)的属性,请使用继承的思想,设计CommonEmployee类和Manager类,要求实现work(),提示“经理/普通员工 名字 工作中"。
OOP基础 + 抽象类
from abc import abstractmethod, ABC
class Employee(ABC):
def __init__(self, name, id, salary):
self.name = name
self.id = id
self.salary = salary
@abstractmethod
def work(self):
pass
class CommonEmployee(Employee):
def work(self):
print(f"普通员工 {self.name} 工作中")
class Manager(Employee):
def __init__(self, name, id, salary, bonus):
super().__init__(name, id, salary)
self.bonus = bonus
def work(self):
print(f"经理 {self.name} 工作中")
manager = Manager("king", "1-111", 10000, 100000)
manager.work()
commonEmployee = CommonEmployee("time", "2-222", 5000)
commonEmployee.work()
最佳实践—模板设计模式
什么是设计模式
- 设计模式是在大量实践中总结和理论化之后优选的代码结构、编程风格、以及解决思考的思考方式。
- 设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索。
模板设计模式—介绍
- 基本介绍
- 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模版,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
- 模板设计模式能解决的问题
- 当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。
模板设计模式—最佳实践
需求:有多个类,完成不同的任务job,要求统计得到各自完成任务的时间。
- 传统方法
import time
class AA:
def job(self):
# 得到开始的时间,秒数
start = time.time()
num = 0
for i in range(1, 800001):
num += 1
# 得到结束的时间,秒数
end = time.time()
print("计算任务执行时间(秒)", (end - start))
class BB:
def job(self):
# 得到开始的时间,秒数
start = time.time()
num = 1
for i in range(1, 90001):
num -= 1
# 得到结束的时间,秒数
end = time.time()
print("计算任务执行时间(秒)", (end - start))
if __name__ == '__main__':
aa = AA()
bb = BB()
aa.job()
bb.job()
- 优化—使用模板设计模式来解决。
编写方法 calc_time(),可以计算某段代码的耗时时间。
编写抽象方法 job()。
编写一个子类,基础抽象类 Template,并实现 job 方法。
import time
from abc import abstractmethod, ABC
class Template(ABC):
@abstractmethod
def job(self):
pass
# 统计任务执行时间
def cal_time(self):
# 得到开始的时间,秒数
start = time.time()
self.job()
# 得到结束的时间,秒数
end = time.time()
print("计算任务执行时间(秒)", (end - start))
class AA(Template):
def job(self):
num = 0
for i in range(1, 800001):
num += 1
class BB(Template):
def job(self):
num = 1
for i in range(1, 90001):
num -= 1
if __name__ == '__main__':
aa = AA()
bb = BB()
aa.cal_time()
bb.cal_time()
练习
- 定义一个Person类,属性:name,age,job,创建Person,有3个person对象,并按照age从大到小排序。
class Person:
def __init__(self, name, age, job):
self.name = name
self.age = age
self.job = job
def __str__(self):
return f'{self.name}, {self.age}, {self.job}'
p1 = Person('smith', 21, 'python')
p2 = Person('king', 30, 'java')
p3 = Person('tom', 40, '老师')
my_list = [p1, p2, p3]
for p in my_list:
print(p)
# 实现按照age从大到小排序。
# 解决方案1:冒泡排序
# def bubble_sort(my_list: list[Person]):
# for i in range(1, len(my_list)):
# # j变量控制比较的次数,同时可以作为比较元素的索引下标
# for j in range(len(my_list) - i):
# # 如果前面的年龄 < 后面的年龄,就交换
# if my_list[j].age < my_list[j + 1].age:
# my_list[j], my_list[j + 1] = my_list[j + 1], my_list[j]
# bubble_sort(my_list)
# print("排序后".center(50, '='))
#
# for p in my_list:
# print(p)
# 解决方案2:直接使用列表的 .sort
# key=lambda ele: ele.age 表示指定按照列表元素的age属性进行排序。
# reverse=True 表示倒序
print("排序后".center(50, '='))
my_list.sort(key=lambda ele: ele.age, reverse=True)
for p in my_list:
print(p)
- 文件中有 Grand、Father 和 Son,问:父类和子类中通过self和super()都可以调用哪些属性和方法。
class Grand:
name = 'AA'
__age = 100
def g1(self):
print('Grand-g1()')
class Father(Grand):
id = '001'
__score = None
def f1(self):
# super() 可以访问哪些成员(属性和方法)
super().name
super().g1()
# self() 可以访问哪些成员
self.id
self.__score
self.name
self.f1()
self.g1()
print('Father-f1()')
class Son(Father):
name = 'BB'
def g1(self):
print('Son-g1()')
def __show(self):
# super() 可以访问哪些成员(属性和方法)
super().id
super().name
super().f1()
super().g1()
# self() 可以访问哪些成员
self.name
self.id
self.g1()
self.__show()
self.f1()
print('Son-show()')
- 编写Doctor类,属性:name, age, job, gender, sal 5个参数的构造器,重写
__eq__()
方法,并判断测试类中创建的两个对象是否相等(相等就是判断属性是否相同)。
class Doctor:
def __init__(self, name, age, gender, sal):
self.name = name
self.age = age
self.gender = gender
self.sal = sal
def __eq__(self, other):
if not isinstance(other, Doctor):
return False
return self.name == other.name and self.age == other.age and self.sal == other.sal
doctor1 = Doctor('tom', 24, 1, 'male')
doctor2 = Doctor('tom', 24, 1, 'male')
doctor3 = Doctor('tom', 25, 1, 'male')
print(doctor1 == doctor2)
print(doctor1 == doctor3)
标签:__,进阶,Python,self,面向对象编程,print,class,def,name From: https://blog.csdn.net/qingxuly/article/details/143454260欢迎关注我的博客,如有疑问或建议,请随时留言讨论。