8.面向对象编程
1 面向对象编程思想
1.1 什么是面向过程
传统的面向过程的编程思想总结起来就八个字——自顶向下,逐步细化。
-
将要实现的功能描述为一个从开始到结束按部就班的连续的“步骤”。
-
依次逐步完成这些步骤,如果某一个步骤的难度较大,又可以将该步骤再次细化为若干个子步骤,以此类推,一直到结尾并得到我们想要的结果。
-
就是把要开发的系统分解为若干个步骤,每个步骤就是函数,当所有步骤全部完成以后,则这个系统就开发完毕了!
举个栗子:大家以来教育机构报名学习这件事情,可以分成哪些步骤?
开始 -> 学员提出报名,提供相关材料 -> 学生缴纳学费,获得缴费凭证 -> 教师凭借学生缴费凭证进行分配班级 -> 班级增加学生信息 -> 结束
所谓的面向过程,就是将上面分析好了的步骤,依次执行就行了!
1.2 什么是面向对象
思考:上面的整个报名过程,都有哪些动词?
提出、提供、缴纳、获得、分配、增加
有动词就一定有实现这个动作的实体!
所谓的模拟现实世界,就是使计算机的编程语言在解决相关业务逻辑的时候,与真实业务逻辑的发生保持一致,需要使任何一个动作的发生都存在一个支配给该动作的一个实体(主体),因为在现实世界中,任何一个功能的实现都可以看做是一个一个的实体在发挥其各自的“功能”(能力)并在内部进行协调有序的调用过程!
1.3 举个栗子:使用面向对象实现报名系统开发
☆ 第一步:分析哪些动作是由哪些实体发出的
-
学生提出报名
-
学生提供相关资料
-
学生缴费
-
机构收费
-
教师分配教室
-
班级增加学生信息
于是,在整个过程中,一共有四个实体:学生、机构、教师、班级!在现实中的一个具体的实体,就是计算机编程中的一个对象!
☆ 第二步:定义这些实体,为其增加相应的属性和功能
属性就是实体固有的某些特征特性信息,在面向对象的术语中,属性就是以前的变量。
比如:
一个人的属性有:身高、体重、三围、姓名、年龄、学历、电话、籍贯、毕业院校等……
一个手机的属性有:价格、品牌、操作系统、颜色、尺寸等……
功能就是就是实体可以完成的动作,在面向对象的术语中,功能就是封装成了函数或方法。

☆ 第三步:让实体去执行相应的功能或动作
-
学生提出报名
-
学生提供相关资料
-
教师登记学生信息
-
学生缴费
-
机构收费
-
教师分配教室
-
班级增加学生信息
1.4 面向对象编程思想迁移
以前写代码,首先想到的是需要实现什么功能——调用系统函数,或者自己自定义函数,然后按部就班的执行就行了!
以后写代码,首先想到的是应该由什么样的主体去实现什么样的功能,再把该主体的属性和功能统一的进行封装,最后才去实现各个实体的功能。
注意:面向对象并不是一种技术,而是一种思想,是一种解决问题的最基本的思维方式!
所以,面向对象的核心思想是:不仅仅是简单的将功能进行封装(封装成函数),更是对调用该功能的主体进行封装,实现某个主体拥有多个功能,在使用的过程中,先得到对应的主体,再使用主体去实现相关的功能!
1.5 面向对象要比面向过程好?
一个面试题:面向过程和面向对象的区别?
-
都可以实现代码重用和模块化编程,面向对象的模块化更深,数据也更封闭和安全。
-
面向对象的思维方式更加贴近现实生活,更容易解决大型的复杂的业务逻辑。
-
从前期开发的角度来看,面向对象比面向过程要更复杂,但是从维护和扩展的角度来看,面向对象要远比面向过程简单!
-
面向过程的代码执行效率比面向对象高。
1.6 面向对象编程简介
面向对象(Object oriented Programming,OOP)编程的思想主要是针对大型软件设计而来的。面向对象编程使程序的扩展性更强、可读性更好,使的编程可以像搭积木一样简单。
面向对象编程将数据和操作数据相关的方法封装到对象中,组织代码和数据的方式更加接近人的思维,从而大大提高了编程的效率。
Python 完全采用了面向对象的思想,是真正面向对象的编程语言,完全支持面向对象的基本功能,例如:继承、多态、封装等。
Python 中,一切皆对象。我们在前面学习的数据类型、函数等,都是对象。
1.7 对象的进化

随着编程面临的问题越来越复杂,编程语言本身也在进化,从主要处理简单数据开始,随着数据变多进化数组;数据类型变复杂,进化出了结构体;处理数据的方式和逻辑变复杂,进化出了对象。
-
简单数据
像30, 40, 50.4等这些数字,可以看做是简单数据。最初的计算机编程,都是像这样的数字。 -
C 语言中的数组
将同类型的数据放到一起。比如:整数数组[20, 30, 40],浮点数数组[10.2, 11.3, 12.4],字符串数组:["aa", "bb", "cc"]。[!tip] 注意
上面的[20, 30, 40]不是 Python 中的列表,是 C 语言中的数组。 -
C 语言中的结构体
将不同类型的数据放到一起,是 C 语言中的数据结构。比如:struct resume{ int age; char name[10]; double salary; }; -
对象
将不同类型的数据、方法(即函数)放到一起,就是对象。比如:class Student: company = "SXT" # 类属性 count = 0 # 类属性 def __init__(self, name, score): self.name = name # 实例属性 self.score = score Student.count = Student.count + 1 def say_score(self): # 实例方法 print("我的公司是:", Student.company) print(self.name, '的分数是:', self.score)
我们前面学习的数字也是对象。比如:整数 9,就是一个包含了加法、乘法等方法的对象。
2 面向对象的基本概念
2.1 面向对象中两个比较重要概念
☆ 类
对象如何产生?又是如何规定对象的属性和方法呢?
答:在 Python 中,采用类(class)来生产对象,用类来规定对象的属性和方法!也就是说,在 Python 中,要想得到对象,必须先有类!
为什么要引入类的概念?
类本来就是对现实世界的一种模拟,在现实生活中,任何一个实体都有一个类别,类就是具有相同或相似属性和动作的一组实体的集合!所以,在 Python 中,对象是指现实中的一个具体的实体,而既然现实中的实体都有一个类别,所以,OOP 中的对象也都应该有一个类!
☆ 对象
对象,object,现实业务逻辑的一个动作实体就对应着 OOP 编程中的一个对象!

一个对象所有应该具有的特征和特性信息,都是由其所属的类来决定的,但是每个对象又可以具有不同的特征和特性信息,比如,我自己(人类)这个对象,名字叫老王,性别男,会写代码,会教书;另一个对象(人类)可能叫赵薇,性别女,会演戏,会唱歌!
所以:
- 对象使用属性(property)保存数据!
- 对象使用方法(method)管理数据!
☆ 类与对象举例
我们把对象比作一个“饼干”,类就是制造这个饼干的“模具”。


我们通过类定义数据类型的属性(数据)和方法(行为),也就是说,“类将行为和状态打包在一起”。



对象是类的具体实体,一般称为“类的实例”。类看做“饼干模具”,对象就是根据这个“模具”制造出的“饼干”。
从一个类创建对象时,每个对象会共享这个类的行为(类中定义的方法),但会有自己的属性值(不共享状态)。更具体一点:“方法代码是共享的,属性数据不共享”。


2.2 类的定义
在 Python 中,我们可以有两种类的定义方式:Python2(经典类)和 Python3(新式类)。
经典类:不由任意内置类型派生出的类,称之为经典类。
class 类名:
# 属性
# 方法
新式类:
class 类名():
# 属性
# 方法
在 Python 2 及以前的版本中,由任意内置类型派生出的类(只要一个内置类型位于类树的某个位置),都属于“新式类”,都会获得所有“新式类”的特性;反之,即不由任意内置类型派生出的类,则称之为“经典类”。
“新式类”和“经典类”的区分在 Python 3 之后就已经不存在,在 Python 3.x 之后的版本,因为所有的类都派生自内置类型 object(即使没有显示的继承 object 类型),即所有的类都是“新式类”。
基本语法:
class 类名:
类体
要点如下:
- 类名必须符合标识符的规则;一般规定类名使用大驼峰原则。
- 类体中我们可以定义属性和方法。
- 属性用来描述数据,方法(即函数)用来描述这些数据相关的操作。
案例:一个典型的类的定义。
class Student:
def __init__(self, name, score): # 构造方法第一个参数必须为 self
self.name = name # 实例属性
self.score = score
def say_score(self): # 实例方法
print("{0}的分数是{1}".format(self.name, self.score))
if __name__ == '__main__':
# s1 是实例对象,自动调用 __init__() 方法
s1 = Student('张三', 80)
s1.say_score()
2.3 类的实例化(创建对象)
类的实例化就是把抽象的事务具体为现实世界中的实体。
类的实例化就是通过类得到对象!
类只是对象的一种规范,类本身基本上什么都做不了,必须利用类得到对象,这个过程就叫作类的实例化。
类是抽象的,也称之为“对象的模板”。我们需要通过类这个模板,创建类的实例对象,然后才能使用类定义的功能。
我们前面说过一个 Python 对象包含三个部分:id(identity 识别码)、type(对象类型)、value(对象的值)。
现在,我们可以更进一步的说,一个 Python 对象包含如下部分:
id(identity 识别码)type(对象类型)value(对象的值)
(1)属性(attribute)
(2)方法(method)

基本语法:
对象名 = 类名()
-
构造实例过程,实例化。通过
__new__魔术方法生成并返回一个实例。生产线上生成一辆车。
-
初始化过程,初始化。通过魔术方法
__init__方法完成初始化。对新造的车做出场配置,如软装,车载系统等。
案例:把 Person 类实例化为为对象 p1。
# 1. 定义一个类
class Person():
# 定义相关方法
def eat(self):
print('我喜欢吃零食')
def drink(self):
print('我喜欢喝可乐')
# 2. 实例化对象
p1 = Person()
# 3. 调用类中的方法
p1.eat()
p1.drink()

2.4 类中的 self 关键字
self 也是 Python 内置的关键字之一,其指向了类实例对象本身。
Python 中的 self 相当于 C++ 中的 self 指针,JAVA 和 C# 中的 this 关键字。Python 中,self 必须为构造函数的第一个参数,名字可以任意修改。但一般遵守惯例,都叫做 self。

# 1. 定义一个类
class Person():
# 定义一个方法
def speak(self):
print(self)
print('Nice to meet you!')
# 2. 类的实例化(生成对象)
p1 = Person()
print(p1)
p1.speak()
p2 = Person()
print(p2)
p2.speak()

3.1 什么是属性
在 Python 中,任何一个对象都应该由两部分组成:属性 + 方法。
属性即是特征,比如:人的姓名、年龄、身高、体重……都是对象的属性。
车的品牌、型号、颜色、载重量……都是对象的属性。
对象属性既可以在类外面添加和获取,也能在类里面添加和获取。
3.2 在类的外面添加属性和获取属性
☆ 设置
对象名.属性 = 属性值
案例:
# 1. 定义一个 Person 类
class Person:
pass
# 2. 实例化 Person 类,生成 p1 对象
p1 = Person()
# 3. 为 p1 对象添加属性
p1.name = '老王'
p1.age = 18
p1.address = '北京市顺义区京顺路99号'
☆ 获取
在 Python 中,对象的属性值我们可以通过 对象名.属性 来获取。
# 1. 定义一个 Person 类
class Person():
pass
# 2. 实例化 Person 类,生成 p1 对象
p1 = Person()
# 3. 为 p1 对象添加属性
p1.name = '老王'
p1.age = 18
p1.address = '北京市顺义区京顺路99号'
# 4. 获取 p1 对象的属性
print(f'我的姓名:{p1.name}')
print(f'我的年龄:{p1.age}')
print(f'我的住址:{p1.address}')

3.3 在类的内部获取外部定义的属性
# 1. 定义一个 Person 类
class Person:
def speak(self):
print(f'我的名字:{self.name},我的年龄:{self.age},我的住址:{self.address}')
# 2. 实例化 Person 类,生成 p1 对象
p1 = Person()
# 3. 添加属性
p1.name = '孙悟空'
p1.age = 500
p1.address = '花果山水帘洞'
# 4. 调用 speak 方法
p1.speak()

4 魔术方法
4.1 什么是魔术方法
在 Python 中,__xxx__() 的函数叫做魔法方法,指的是具有特殊功能的函数。
4.2 __init__() 方法(初始化方法或构造方法)
__init__() 的作用:实例化对象时,连带其中的参数,会一并传给 __init__ 函数自动并执行它。__init__() 函数的参数列表会在开头多出一项,它永远指向新建的那个实例对象,Python 语法要求这个参数必须要有,习惯命名为 self。
构造方法用于执行实例对象的初始化工作,即对象创建后,初始化当前对象的相关属性,不能有返回值,也就是只能是 return None。
__init__() 的要点如下:
-
名称固定,必须为:
__init__()。 -
第一个参数固定,必须为:
self。self指的就是刚刚创建好的实例对象。 -
构造函数通常用来初始化实例对象的实例属性,如下代码就是初始化实例属性:
name和score。def init (self, name, score): # 实例属性 self.score = score self.name = name -
通过
类名(参数列表)来调用构造函数。调用后,将创建好的对象返回给相应的变量。s1 = Student('张三', 80) -
__init__()方法:初始化创建好的对象。初始化指的是:给实例属性赋值。
-
__new__()方法:用于创建对象,但我们一般无需重定义该方法。 -
如果我们不定义
__init__方法,系统会提供一个默认的__init__方法。如果我们定义了带参的__init__方法,系统不创建默认的__init__方法。
# 1. 定义一个类
class Person():
# 初始化实例对象属性
def __init__(self, name, age):
# 赋予 name 属性、age 属性给实例化对象本身
# self.实例化对象属性 = 参数
self.name = name
self.age = age
# 2. 实例化对象并传入初始化属性值
p1 = Person('孙悟空', 500)
# 3. 调用 p1 对象自身属性 name 与 age
print(p1.name)
print(p1.age)

4.3 __str__() 方法
当使用 print 输出对象的时候,默认打印对象的内存地址。如果类定义了 __str__ 方法,那么就会打印从在这个方法中 return 的数据。
没有定义 __str__() 方法的类:
# 1. 定义一个类
class Car:
# 首先定义一个 __init__ 方法,用于初始化实例对象属性
def __init__(self, brand, model, color):
self.brand = brand
self.model = model
self.color = color
# 2. 实例化对象 c1
c1 = Car('奔驰', 'S600', '黑色')
print(c1)

使用 __str__() 方法的类:
# 1. 定义一个类
class Car:
# 首先定义一个 __init__ 方法,用于初始化实例对象属性
def __init__(self, brand, model, color):
self.brand = brand
self.model = model
self.color = color
# 定义一个 __str__ 内置魔术方法,用于输出小汽车的相关信息
def __str__(self):
return f'汽车品牌:{self.brand},汽车型号:{self.model},汽车颜色:{self.color}'
# 2. 实例化对象 c1
c1 = Car('奔驰', 'S600', '黑色')
print(c1)

4.4 __del__() 方法(删除方法或析构方法)
当删除对象时,Python 解释器也会默认调用 __del__() 方法。
__del__() 方法称为析构方法,用于实现对象被销毁时所需的操作。比如:释放对象占用的资源,例如:打开的文件资源、网络连接等。
Python 实现自动的垃圾回收,当对象没有被引用时(引用计数为 0),由垃圾回收器调用 __del__() 方法。
我们也可以通过 del 语句删除对象,从而保证调用 __del__() 方法。
系统会自动提供 __del__() 方法,一般不需要自定义析构方法。
class Person():
# 构造函数 __init__
def __init__(self, name, age):
self.name = name
self.age = age
# 析构方法 __del__
def __del__(self):
print(f'{self} 对象已经被删除')
# 实例化对象
p1 = Person('白骨精', 100)
# 删除对象 p1
del p1

4.5 __call__ 方法和可调用对象
定义了 __call__ 方法的对象,称为可调用对象,即该对象可以像函数一样被调用。
- Python 中,凡是可以将
()直接应用到自身并执行,都称为可调用对象。 - 可调用对象包括自定义的函数、Python 内置函数、以及本节所讲的实例对象。
- 定义了
__call__()的对象,称为可调用对象,即该对象可以像函数一样被调用。 - 该方法使得实例对象可以像调用普通函数那样,以
对象名()的形式使用。
案例:测试可调用对象。
class SalaryAccount:
"""工资计算类"""
def __call__(self, salary):
year_salary = salary * 12
day_salary = salary // 30
hour_salary = day_salary // 8
return dict(monthSalary=salary,
yearSalary=year_salary,
daySalary=day_salary,
hourSalary=hour_salary)
s = SalaryAccount()
# 可以像调用函数一样调用对象的 __call__ 方法
print(s(5000))

案例:类装饰器的使用案例。
class MyLogDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("日志纪录...")
return self.func(*args, **kwargs)
@MyLogDecorator # 等价于 fun2 = MyLogDecorator(fun2)
def fun2():
print("使用功能2")
if __name__ == '__main__':
fun2()

4.6 总结
提到魔术方法:
- 这个方法在什么情况下被触发。
- 这个方法有什么实际的作用。
__new__:创建一个类的实例。在创建对象时自动调用,其作用就是返回一个新创建的类的实例。一般我们无需重新定义此方法。
__init__():初始化方法或者称之构造函数,在对象初始化时执行,其主要作用就是在对象初始化时,对对象进行初始化操作(如赋予属性)。
__str__():对象字符串方法,当我们在类的外部,使用 print 方法输出对象时被触发,其主要功能就是对对象进行打印输出操作,要求方法必须使用 return 返回字符串格式的数据。
__del__():删除方法或者称之为析构方法,在对象被 del 删除时触发,其主要作用就是适用于关闭文件、关闭数据库连接等等。
__call__:让一个对象成为可调用对象。在使用 对象名() 时触发,自动执行 __call__ 中的代码。让对象可以向函数那样被调用。
[[魔法方法详解]]
5 实例属性和实例方法
5.1 实例属性
实例属性是从属于实例对象的属性,也称为实例变量。他的使用有如下几个要点:
-
实例属性一般在
__init__()方法中通过如下代码定义:self.实例属性名 = 初始值 -
在本类的其他实例方法中,也是通过
self进行访问:self.实例属性名 -
创建实例对象后,通过实例对象访问:
# 创建对象,调用 __init__() 初始化属性
obj = 类名()
# 可以给已有属性赋值,也可以新加属性
obj.实例属性名 = 值
案例:实例属性测试。
class Student:
def __init__(self, name, score):
self.name = name # 增加 name 属性
self.score = score # 增加 score 属性
def say_score(self):
self.age = 18 # 增加 age 属性
print(f"{self.name} 的分数是 {self.score}")
s1 = Student("张三", 80)
s1.say_score()
print(f"{s1.name} 的年龄是 {s1.age}")
s1.salary = 3000 # s1对象增加salary属性
s2 = Student("李四", 90)
s2.say_score()
print(f"{s2.name} 的年龄是 {s2.age}")

5.2 实例方法
实例方法是从属于实例对象的方法。实例方法的定义格式如下:
def 方法名(self [, 形参列表]):
函数体
方法的调用格式如下:
对象.方法名([实参列表])
要点:
- 定义实例方法时,第一个参数必须为
self。和前面一样,self指当前的实例对象。 - 调用实例方法时,不需要也不能给
self传参。self由解释器自动传参。
案例:实例方法测试。
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
def say_score(self):
print(f"{self.name} 的分数是 {self.score}")
a = Student("小龙女", 18, 90)
a.say_score()

函数和实例方法的区别
- 都是用来完成一个功能的语句块,本质一样。
- 实例方法调用时,通过对象来调用。实例方法从属于特定实例对象,普通函数没有这个特点。
- 直观上看,实例方法定义时需要传递
self,函数不需要。
实例对象的方法调用本质

class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
def say_score(self):
print(f"{self.name} 的分数是 {self.score}")
a = Student("小龙女", 18, 90)
a.say_score()
# 本质上是
Student.say_score(a)

其他操作
dir(obj)可以获得对象的所有属性、方法。obj.__dict__对象的实例属性字典。pass空语句。isinstance(对象, 类型)判断对象是不是指定类型。
class Student:
def __init__(self, name, score):
self.name = name # 增加 name 属性
self.score = score # 增加 score 属性
def say_score(self):
self.age = 18 # 增加 age 属性
print("{0}的分数是{1}".format(self.name, self.score))
stu = Student("小龙女", 90)
# dir(obj) 可以获得对象的所有属性、方法
print(f"dir方法:{dir(stu)}")
# obj.__dict__ 对象的属性字典
print(f"__dict__属性:{stu.__dict__}")
# isinstance(对象, 类型) 判断“对象”是不是“指定类型”
print(f"isinstance方法:{isinstance(stu, Student)}")
"""
运行结果:
dir方法:['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'name', 'say_score', 'score']
__dict__属性:{'name': '小龙女', 'score': 90}
isinstance方法:True
"""

6 类对象、类属性、类方法、静态方法
6.1 类对象
我们在前面讲的类定义格式中,class 类名:。实际上,当解释器执行 class 语句时,就会创建一个类对象。
案例:测试类对象的生成。
class Student:
# 空语句
pass
print(type(Student))
print(id(Student))
Stu2 = Student
s1 = Stu2()
print(s1)

我们可以看到实际上生成了一个变量名就是类名 Student 的对象。我们通过赋值给新变量 Stu2,也能实现相关的调用。说明,确实创建了类对象。
6.2 类属性
类属性是从属于类对象的属性,也称为类变量。由于类属性从属于类对象,所以可以被所有实例对象共享。
类属性的定义方式:
class 类名:
类变量名 = 初始值
在类中或者类的外面,我们可以通过:类名.类变量名 来读写。
案例:类属性的使用测试。
class Student:
# 类属性
company = "古墓派"
# 类属性
count = 0
def __init__(self, name, score):
# 实例属性
self.name = name
self.score = score
Student.count = Student.count + 1
def say_score(self):
# 实例方法
print("我的门派是:", Student.company)
print(self.name, "的分数是:", self.score)
# s1 是实例对象,自动调用 __init__()方法
s1 = Student("小龙女", 99)
s1.say_score()
print(f"一共创建 {Student.count} 个 Student 对象")
print("=" * 40)
s1 = Student("杨过", 80)
s1.say_score()
print(f"一共创建 {Student.count} 个 Student 对象")

6.3 类方法
类方法是从属于类对象的方法。类方法通过装饰器 @classmethod 来定义。
格式如下:
@classmethod
def 类方法名(cls[, 形参列表]):
函数体
要点如下:
@classmethod必须位于方法上面一行。- 第一个参数
cls必须有;cls指的就是类对象本身。 - 调用类方法格式:
类名.类方法名(参数列表)。参数列表中,不需要也不能给cls传值。 - 类方法中访问实例属性和实例方法会导致错误。
- 子类继承父类方法时,传入
cls是子类的类对象,而非父类的类对象。
案例:类方法使用测试。
class Student:
# 类属性
company = "古墓派"
# 类方法
@classmethod
def print_company(cls, person):
print(f"{cls.company}--{person}")
Student.print_company("小龙女")
Student.print_company("杨过")

6.4 静态方法
Python 中允许定义与类对象无关的方法,称为静态方法。
既不操作类属性,也不操作实例属性的方法可以考虑定义成静态方法
静态方法和在模块中定义普通函数没有区别,只不过静态方法放到了类的名字空间里面,需要通过类调用。

静态方法通过装饰器 @staticmethod 来定义。
格式如下:
@staticmethod
def 静态方法名([形参列表]):
函数体
要点如下:
@staticmethod必须位于方法上面一行。- 调用静态方法格式:
类名.静态方法名(参数列表)。 - 静态方法中访问实例属性和实例方法会导致错误。
案例:静态方法使用测试。
class Student:
# 类属性
company = "SXT"
@staticmethod
def add(a, b):
# 静态方法
print(f"{a} + {b} = {a + b}")
return a + b
Student.add(20, 30)

7 内存分析实例对象和类对象创建过程(重要)
以下面代码为例,分析整个创建过程,让大家对面向对象概念掌握更加深刻:
class Student:
# 类属性
company = "尚学堂"
# 类属性
count = 0
def __init__(self, name, score):
# 实例属性
self.name = name
self.score = score
Student.count = Student.count + 1
# 实例方法
def say_score(self):
print("我的公司是:", Student.company)
print(self.name, '的分数是:', self.score)
# s1 是实例对象,自动调用 __init__()方法
s1 = Student('高淇', 80)
s1.say_score()
print(f'一共创建 {Student.count} 个 Student 对象')
print("=" * 40)
# s2 是实例对象,自动调用 __init__()方法
s2 = Student('张三', 70)
s2.say_score()
print(f'一共创建 {Student.count} 个 Student 对象')


8 方法没有重载
在其他语言中,可以定义多个重名的方法,只要保证方法签名唯一即可。方法签名包含 3 个部分:方法名、参数数量、参数类型。
Python 中,方法的的参数没有声明类型(调用时确定参数的类型),参数的数量也可以由可变参数控制。因此,Python 中是没有方法的重载的。定义一个方法即可有多种调用方式,相当于实现了其他语言中的方法的重载。
如果我们在类体中定义了多个重名的方法,只有最后一个方法有效。
案例:Python 中没有方法的重载。定义多个同名方法,只有最后一个有效。
class Person:
def say_hi(self):
print("hello")
def say_hi(self, name):
print("{0},hello".format(name))
p1 = Person()
p1.say_hi("高淇")
# 不带参,报错:
# TypeError: Person.say_hi() missing 1 required positional argument: 'name'
p1.say_hi()

9 方法的动态性
Python 是动态语言,我们可以动态的为类添加新的方法,或者动态的修改类已有的方法。
案例:测试方法的动态性。
class Person:
def __init__(self, name):
self.name = name
def work(self):
print(f"{self.name} 努力上班!")
def play_game(self):
print(f"{self.name} 玩游戏")
# s 就是为了在调用时接收 self 参数
def work2(s):
print(f"{s.name} 好好工作,努力上班!")
Person.play = play_game
Person.work = work2
p = Person("小龙女")
p.play()
p.work()

我们可以看到,Person 动态的新增了 play_game 方法,以及用 work2 替换了 work 方法。
10 面向对象的三大特性——封装
10.1 面向对象有哪些特性
三种:封装性、继承性、多态性。
10.2 私有属性和私有方法(实现封装)
Python 对于类的成员没有严格的访问控制限制,这与其他面向对象语言有区别。

关于私有属性和私有方法,有如下要点:
- 通常我们约定,两个下划线开头的属性是私有的(private)。其他为公共的(public)。
- 类内部可以访问私有属性(方法)。
- 类外部不能直接访问私有属性(方法)。
- 类外部可以通过
对象._类名__私有属性(方法)名访问私有属性(方法)。
案例:测试私有属性、私有方法。
class Employee:
# 私有类属性。通过 dir 可以查到 _Employee__company
__company = "古墓派"
def __init__(self, name, age):
self.name = name
# 私有实例属性
self.__age = age
def say_company(self):
# 类内部可以直接访问私有属性
print(f"我的门派是:{Employee.__company}")
print(f"{self.name} 的年龄是:{self.__age}")
self.__work()
# 私有实例方法 通过 dir 可以查到 _Employee__work
def __work(self):
print("练武!好好练武!")
p1 = Employee("杨过", 18)
print(p1.name)
# 通过 dir 可以查到属性:_Employee__age
print(dir(p1))
p1.say_company()
# 通过这种方式可以直接访问到私有属性。
print(p1._Employee__age)
# 直接访问私有属性,报错
# print(p1.__age)
# 直接访问私有方法,报错
# p1.__sleep()
结果:
杨过
['_Employee__age', '_Employee__company', '_Employee__work', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'say_company']
我的门派是:古墓派
杨过 的年龄是:18
练武!好好练武!
18
从打印的 Employee 对象所有属性我们可以看出。私有属性 age 在实际存储时是按照 _Employee__age 这个属性来存储的。这也就是为什么我们不能直接使用 __age 而可以使用 _Employee__age 的根本原因。
10.3 私有属性设置与访问接口
在 Python 中,一般定义函数名 get_xx 用来获取私有属性,定义 set_xx 用来修改私有属性值。
class Girl:
def __init__(self, name, age):
self.name = name
self.__age = age
# 公共方法:提供给外部的访问接口
def get_age(self):
# 本人访问:允许直接访问
# 外人访问:加上限制条件
return self.__age
# 公共方法:提供给外部的设置接口
def set_age(self, age):
self.__age = age
girl = Girl("小龙女", 18)
# 通过接口获取私有属性
print(f"{girl.name} {girl.get_age()} 岁了!!")
# 通过接口设置私有属性
girl.set_age(19)
# 通过接口获取私有属性
print(f"{girl.name} {girl.get_age()} 岁了!!")

10.4 封装性到底有何意义
-
以面向对象的编程思想进行项目开发。
-
封装数据属性:明确的区分内外,控制外部对隐藏的属性的操作行为(过滤掉异常数据)。
class People: def __init__(self, name, age): self.__name = name self.__age = age def tell_info(self): print(f"Name:{self.__name} Age:{self.__age}") # 对私有属性的访问接口 def set_info(self, name, age): if not isinstance(name, str): print("名字必须是字符串类型") return if not isinstance(age, int): print("年龄必须是数字类型") return # 过滤异常数据 if not 0 <= age <= 125: print("年龄必须在 0~125 之间") return self.__name = name self.__age = age p = People("jack", 38) p.tell_info() p.set_info("jennifer", 18) p.tell_info() p.set_info(123, 35) p.tell_info() p.set_info("tom", 135) p.tell_info()![]()
-
私有方法封装的意义:降低程序的复杂度。
class ATM: def __card(self): print('插卡') def __auth(self): print('用户认证') def __input(self): print('输入取款金额') def __print_bill(self): print('打印账单') def __take_money(self): print('取款') # 定义一个对外提供服务的公共方法 def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() atm = ATM() atm.withdraw()![]()
10.5 @property 装饰器
@property 可以将一个方法的调用方式变成属性调用。
下面是一个简单的示例,让大家体会一下这种转变。
案例:简单测试 @property。
class Employee:
@property
def salary(self):
return 30000
emp1 = Employee()
print(emp1.salary)
print(type(emp1.salary))
# 报错:TypeError: 'int' object is not callable
# emp1.salary()
# @property 修饰的属性,如果没有加 setter 方法,则为只读属性。
# 此处修改报错:AttributeError: can't set attribute
# emp1.salary = 1000



# 读取 salary 属性
emp.salary
# 设置 salary 属性
emp.salary = 30000
如上可以实现属性的读操作、写操作。但是,这种做法不安全。比如,我需要限制薪水必须为 1~10000 的数字。这时候,我们就需要通过 getter、setter 方法来处理。
# 方法一:使用 getter 和 setter 方法
class Employee:
def __init__(self, name, salary):
self.name = name
self.__salary = salary
# salary 属性的 getter 方法
def get_salary(self):
return self.__salary
# salary 属性的 setter 方法
def set_salary(self, salary):
if 0 < salary < 1000000:
self.__salary = salary
else:
print("薪水录入错误!薪水只能在 0~1000000 之间")
if __name__ == '__main__':
emp = Employee("小龙女", 50000)
print(f"月薪为 {emp.get_salary()}, 年薪为{emp.get_salary()}")
print("=========更改薪水=========")
emp.set_salary(200999)
print(f"月薪为 {emp.get_salary()}, 年薪为{emp.get_salary()}")
print("=========更改薪水=========")
emp.set_salary(-200)
print(f"月薪为 {emp.get_salary()}, 年薪为{emp.get_salary()}")

上面的方式使用函数来操作属性,我们还可以通过 property 和 setter 装饰器来像操作属性那样进行处理。
# 方法二:使用 @property 和 setter 装饰器
class Employee:
def __init__(self, name, salary):
self.name = name
self.__salary = salary
@property
# 相当于 salary 属性的 getter 方法
def salary(self):
return self.__salary
# @ 后面的名字一定要和 @property 修饰的函数名保持一致
@salary.setter
# 相当于 salary 属性的 setter 方法
def salary(self, salary):
if 0 < salary < 1000000:
self.__salary = salary
else:
print("薪水录入错误!薪水只能在 0-1000000 之间")
if __name__ == '__main__':
emp = Employee("小龙女", 50000)
print(f"月薪为 {emp.salary}, 年薪为{emp.salary}")
print("=========更改薪水=========")
emp.salary = 200999
print(f"月薪为 {emp.salary}, 年薪为{emp.salary}")
print("=========更改薪水=========")
emp.salary = -200
print(f"月薪为 {emp.salary}, 年薪为{emp.salary}")

10.6 属性和方法命名总结
_xxx:保护成员,不能用 from module import * 导入,只有类对象和子类对象能访问这些成员。
__xxx__:系统定义的特殊成员。
__xxx:类中的私有成员,只有类对象自己能访问,子类对象也不能访问。(但,在类外部可以通过 对象名._类名 __xxx 这种特殊方式访问。Python 不存在严格意义的私有成员)。
10.7 类编码风格
- 类名首字母大写,多个单词之间采用大驼峰原则。
- 实例名、模块名采用小写,多个单词之间采用下划线隔开。
- 每个类,应紧跟文档字符串,说明这个类的作用。
- 可以用空行组织代码,但不能滥用。在类中,使用一个空行隔开方法;模块中,使用两个空行隔开多个类。
11 面向对象三大特征介绍
Python 是面向对象的语言,也支持面向对象编程的三大特性:继承、封装(隐藏)、多态。

11.1 封装(隐藏)
隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将细节封装起来,只对外暴露相关调用方法。
通过前面学习的“私有属性、私有方法”的方式,实现“封装”。Python 追求简洁的语法,没有严格的语法级别的“访问控制符”,更多的是依靠程序员自觉实现。
11.2 继承
继承可以让子类具有父类的特性,提高了代码的重用性。
从设计上是一种增量进化,原有父类设计不变的情况下,可以增加新的功能,或者改进已有的算法。
11.3 多态
多态是指同一个方法调用由于对象不同会产生不同的行为。生活中这样的例子比比皆是:同样是休息方法,人不同休息方法不同。张三休息是睡觉,李四休息是玩游戏,程序员休息是“敲几行代码”。
12 面向对象三大特征——继承

继承是面向对象程序设计的重要特征,也是实现“代码复用”的重要手段。
如果一个新类继承自一个设计好的类,就直接具备了已有类的特征,就大大降低了工作难度。已有的类,我们称为父类或者基类,新的类,我们称为子类或者派生类。

12.1 什么是继承
我们接下来来聊聊 Python 代码中的继承:类是用来描述现实世界中同一组事务的共有特性的抽象模型,但是类也有上下级和范围之分,比如:生物 => 动物 => 哺乳动物 => 灵长型动物 => 人类 => 黄种人。
从哲学上说,就是共性与个性之间的关系,比如:白马和马!所以,我们在 OOP 代码中,也一样要体现出类与类之间的共性与个性关系,这里就需要通过类的继承来体现。简单来说,如果一个类 A 使用了另一个类 B 的成员(属性和方法),我们就可以说 A 类继承了 B 类,同时这也体现了 OOP 中代码重用的特性!

12.2 继承的基本语法
Python 支持多重继承,一个子类可以继承多个父类。继承的语法格式如下:
class 子类类名(父类1[, 父类2, ...]):
类体
如果在类定义中没有指定父类,则默认父类是 object 类。也就是说,object 是所有类的父类,里面定义了一些所有类共有的默认实现,比如:__new__()。
假设 A 类要继承 B 类中的所有属性和方法(私有属性和私有方法除外)。
class B:
pass
clss A(B):
pass
a = A()
a.B中的所有公共属性
a.B中的所有公共方法
案例:Person 类与 Teacher、Student 类之间的继承关系。
class Person:
def eat(self):
print('i can eat food!')
def speak(self):
print('i can speak!')
class Teacher(Person):
pass
class Student(Person):
pass
teacher = Teacher()
teacher.eat()
teacher.speak()
print("=" * 40)
student = Student()
student.eat()
student.speak()

12.3 与继承相关的几个概念
-
继承:一个类从另一个已有的类获得其成员的相关特性,就叫作继承!
-
派生:从一个已有的类产生一个新的类,称为派生!
很显然,继承和派生其实就是从不同的方向来描述的相同的概念而已,本质上是一样的!
-
父类:也叫作基类,就是指已有被继承的类!
-
子类:也叫作派生类或扩展类!
-
扩展:在子类中增加一些自己特有的特性,就叫作扩展,没有扩展,继承也就没有意义了!
-
单继承:一个类只能继承自一个其他的类,不能继承多个类,单继承也是大多数面向对象语言的特性!
-
多继承:一个类同时继承了多个父类,(C++、Python 等语言都支持多继承)。
12.4 单继承
单继承:一个类只能继承自一个其他的类,不能继承多个类。这个类会有具有父类的属性和方法。
基本语法:
# 1、定义一个共性类(父类)
class Person:
pass
# 2、定义一个个性类(子类)
class Teacher(Person):
pass
案例:比如汽车可以分为两种类型(汽油车、电动车)。
# 1、定义一个共性类(车类)
class Car:
def run(self):
print('I can run')
# 2、定义汽油车
class GasolineCar(Car):
pass
# 3、定义电动车
class ElectricCar(Car):
pass
bwm = GasolineCar()
bwm.run()
byd = ElectricCar()
byd.run()

12.5 单继承特性:传递性
在 Python 继承中,如 A 类继承了 B 类,B 类又继承了 C 类。则根据继承的传递性,则 A 类也会自动继承 C 类中所有属性和方法(公共)。
class C:
def func(self):
print('我是C类中的相关方法func')
class B(C):
pass
class A(B):
pass
a = A()
a.func()
print(a)

12.6 编写面向对象代码中的常见问题

问题 3:打印属性和方法时,都喜欢用 print。
class Person():
def __init__(self, name):
self.name = name
def speak(self):
print('i can speak')
# 创建对象,打印属性和方法
p = Person('Tom')
print(p.name)
p.speak()
12.7 多继承
什么是多继承?
Python 语言是少数支持多继承的一门编程语言,所谓的多继承就是允许一个类同时继承自多个类的特性。
语法格式:
class 子类类名(父类 1[,父类 2,...]):
类体

基本语法:
class B:
pass
class C:
pass
class A(B, C):
pass
a = A()
a.B中的所有属性和方法
a.C中的所有属性和方法
案例:汽油车、电动车 => 混合动力汽车(汽车 + 电动)。
class GasolineCar(object):
def run_with_gasoline(self):
print('I can run with gasoline')
class EletricCar(object):
def run_with_eletric(self):
print('I can run with eletric')
class HybridCar(GasolineCar, EletricCar):
pass
tesla = HybridCar()
tesla.run_with_gasoline()
tesla.run_with_eletric()

案例:多重继承。

class A:
def aa(self):
print("aa")
class B:
def bb(self):
print("bb")
class C(B, A):
def cc(self):
print("cc")
c = C()
c.aa()
c.bb()
c.cc()

12.8 子类扩展:重写父类属性和方法
扩展特性:继承让子类继承父类的所有公共属性和方法,但是如果仅仅是为了继承公共属性和方法,继承就没有实际的意义了,应该是在继承以后,子类应该有一些自己的属性和方法。
什么是重写?
重写也叫作覆盖,就是当子类成员与父类成员名字相同的时候,从父类继承下来的成员会重新定义!
此时,通过子类实例化出来的对象访问相关成员的时候,真正起作用的是子类中定义的成员!
-
成员继承:子类继承了父类除构造方法之外的所有成员。
[!tip] 注意
Python 中没有严格的访问控制,私有属性其实也会继承。私有属性、私有方法被继承时,名称为_类名__属性名。 -
方法重写:子类可以重新定义父类中的方法,这样就会覆盖父类的方法,也称为重写。
上面单继承例子中 Animal 的子类 Cat 和 Dog 继承了父类的属性和方法,但是我们狗类 Dog 有自己的叫声 '汪汪叫',猫类 Cat 有自己的叫声 '喵喵叫',这时我们需要对父类的 call() 方法进行重构。如下:
class Animal:
def eat(self):
print('I can eat')
def call(self):
print('I can call')
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
dog.eat()
dog.call()
print("=" * 40)
cat = Cat()
cat.eat()
cat.call()

Dog、Cat 子类重写父类 Animal 中的 call 方法。
class Animal:
def eat(self):
print('I can eat')
# 公共方法
def call(self):
print('I can call')
class Dog(Animal):
# 重写父类的 call 方法
# @override
def call(self):
print('I can wang wang wang')
class Cat(Animal):
# 重写父类的 call 方法
# @override
def call(self):
print('I can miao miao miao')
dog = Dog()
dog.eat()
dog.call()
print("=" * 40)
cat = Cat()
cat.eat()
cat.call()

12.9 super() 调用父类属性和方法
super():调用父类属性或方法,完整写法:super(当前类名, self).属性 或 super(当前类名, self).方法(),在 Python3 以后版本中,调用父类的属性和方法我们只需要使用 super().属性 或 super().方法名() 就可以完成调用了。
super() 代表父类的定义,不是父类对象。即 super().方法名 相当于 父类名.方法名。
定义子类时,如果重写 __init__ 方法后还想使用父类 __init__ 中定义的属性,必须在其构造函数中调用父类的构造函数。
关于构造函数:
- 子类不重写
__init__,实例化子类时,会自动调用父类定义的__init__。 - 子类重写了
__init__时,实例化子类,就不会调用父类已经定义的__init__。 - 如果重写了
__init__时,要使用父类的构造方法,可以使用super关键字,也可以使用如下格式调用:super().__init__(参数列表)或父类名.__init__(self, 参数列表)。
调用格式如下:
父类名.__init__(self, 参数列表)
# 或
super().__init__(参数列表)
案例:Car 汽车类、GasolineCar 汽油车、ElectricCar 电动车。
class Car:
def __init__(self, brand, model, color):
self.brand = brand
self.model = model
self.color = color
def run(self):
print('I can run')
class GasolineCar(Car):
def __init__(self, brand, model, color):
super().__init__(brand, model, color)
def run(self):
print('I can run with gasoline')
class ElectricCar(Car):
def __init__(self, brand, model, color):
super().__init__(brand, model, color)
# 电池属性
self.battery = 70
def run(self):
print(f'I can run with electric,remain:{self.battery}')
bwm = GasolineCar('宝马', 'X5', '白色')
bwm.run()
tesla = ElectricCar('特斯拉', 'Model S', '红色')
tesla.run()

12.10 MRO 属性或 MRO 方法:方法解析顺序
Python 支持多继承,如果父类中有相同名字的方法,在子类没有指定父类名时,解释器将从左向右按顺序搜索。
MRO(Method Resolution Order):方法解析顺序,我们可以通过类的属性 类名.__mro__ 或类的方法 类名.mro() 获得类的层次结构,方法解析顺序也是按照这个类的层次结构寻找的。
class Car(object):
def __init__(self, brand, model, color):
self.brand = brand
self.model = model
self.color = color
def run(self):
print('i can run')
class GasolineCar(Car):
def __init__(self, brand, model, color):
super().__init__(brand, model, color)
def run(self):
print('i can run with gasoline')
class ElectricCar(Car):
def __init__(self, brand, model, color):
super().__init__(brand, model, color)
# 电池属性
self.battery = 70
def run(self):
print(f'i can run with electric,remain:{self.battery}')
print("=" * 20 + "GasolineCar" + "=" * 20)
print(GasolineCar.__mro__)
print(GasolineCar.mro())
print("=" * 20 + "ElectricCar" + "=" * 20)
print(ElectricCar.__mro__)
print(ElectricCar.mro())

class A:
def aa(self):
print("aa")
def say(self):
print("say AAA!")
class B:
def bb(self):
print("bb")
def say(self):
print("say BBB!")
class C(B, A):
def cc(self):
print("cc")
c = C()
# 打印类的层次结构
print(C.mro())
# 解释器寻找方法是“从左到右”的方式寻找,此时会执行 B 类中的 say()
c.say()

12.11 object 根类

object 类是所有类的父类,因此所有的类都有 object 类的属性和方法。我们显然有必要深入研究一下 object 类的结构。对于我们继续深入学习 Python 很有好处。
dir() 查看对象属性
为了深入学习对象,我们先学习内置函数 dir(),他可以让我们方便的看到指定对象所有的属性。
案例:查看对象所有属性以及和 object 进行比对。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_age(self):
print(f"{self.name} 的年龄是 {self.age}")
obj = object()
print(dir(obj))
s2 = Person("小龙女", 19)
print(dir(s2))
执行结果:
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'say_age']
从上面我们可以发现这样几个要点:
-
Person 对象增加了六个属性:
__dict__ __module__ __weakref__ age name say_age -
object 的所有属性,Person 类作为 object 的子类,显然包含了所有的属性。
-
我们打印
age、name、say_age,发现say_age虽然是方法,实际上也是属性。只不过,这个属性的类型是method而已。age <class 'int'> name <class 'str'> say_age <class 'method'>
重写 __str__() 方法
- object 有一个
__str__()方法,用于返回一个对于对象的描述,内置函数str(对象),调用的就是__str__()。 __str__()经常用于print()方法,帮助我们查看对象的信息。__str__()可以重写。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
"""将对象转化成一个字符串描述,一般用于 print 方法"""
return f"名字是:{self.name},年龄是 {self.age}"
p = Person("小龙女", 19)
print(p)

13 面向对象三大特征——多态
13.1 什么是多态
多态指的是一类事物有多种形态。
定义:多态(polymorphism)是指调用同一个方法,但由于调用对象不同可能会产生不同的行为。
关于多态要注意以下 2 点:
- 多态是方法的多态,属性没有多态。
- 多态的存在有 2 个必要条件:继承、方法重写。
好处:调用灵活,有了多态,更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化!
13.2 多态原理图

公共接口 service 就是多态的体现,随着传入水果对象的不同,能返回不同的结果。
13.3 多态代码实现
class Fruit(object):
# 公共方法
def makejuice(self):
print('I can make juice')
class Apple(Fruit):
def makejuice(self):
print('I can make apple juice')
class Banana(Fruit):
def makejuice(self):
print('I can make banana juice')
class Orange(Fruit):
def makejuice(self):
print('I can make orange juice')
class Peach(Fruit):
def makejuice(self):
print('I can make peach juice')
apple = Apple()
banana = Banana()
orange = Orange()
peach = Peach()
for i in (apple, banana, orange, peach):
i.makejuice()

14 特殊方法和运算符重载
14.1 特殊方法
Python 的运算符实际上是通过调用对象的特殊方法实现的。比如:
a = 20
b = 30
c = a + b
d = a.__add__(b)
print("c=", c)
print("d=", d)

常见的特殊方法统计如下:
| 方法 | 说明 | 例子 |
|---|---|---|
__init__ |
构造方法 | 对象创建:p = Person() |
__del__ |
析构方法 | 对象回收 |
__repr__,__str__ |
打印,转换 | print(a) |
__call__ |
函数调用 | a() |
__getattr__ |
点号运算 | a.xxx |
__setattr__ |
属性赋值 | a.xxx = value |
__getitem__ |
索引运算 | a[key] |
__setitem__ |
索引赋值 | a[key] = value |
__len__ |
长度 | len(a) |
每个运算符实际上都对应了相应的方法,统计如下:
| 运算符 | 特殊方法 | 说明 |
|---|---|---|
+ |
__add__ |
加法 |
- |
__sub__ |
减法 |
<,<=,== |
__lt__,__le__,__eq__ |
比较运算符 |
>,>=,!= |
__gt__,__ge__,__ne__ |
比较运算符 |
|,^,& |
__or__,__xor__,__and__ |
或、异或、与 |
<<,>> |
__lshift__,__rshift__ |
左移、右移 |
*,/,%,// |
__mul__,__truediv__,__mod__,__floordiv__ |
乘、浮点除、模运算(取余)、整数除 |
** |
__pow__ |
指数运算 |
我们可以重写上面的特殊方法,即实现了运算符的重载。
# 测试运算符的重载
class Person:
def __init__(self, name):
self.name = name
# 重写加法
def __add__(self, other):
if isinstance(other, Person):
return f"{self.name}--{other.name}"
else:
return "不是同类对象,不能相加"
# 重写乘法
def __mul__(self, other):
if isinstance(other, Person):
return f"{self.name} * {other.name}"
else:
return "不是同类对象,不能相乘"
p1 = Person("赵敏")
p2 = Person("周芷若")
print(p1 + p2)
print(p1 + 1)
print("=" * 40)
print(p1 * 3)
print(p1 * p2)

14.2 特殊属性
Python 对象中包含了很多双下划线开始和结束的属性,这些是特殊属性,有特殊用法。
这里我们列出常见的特殊属性:
| 特殊方法 | 含义 |
|---|---|
obj.__dict__ |
对象的属性字典 |
obj.__class__ |
对象所属的类 |
class.__bases__ |
类的基类元组(多继承) |
class.__base__ |
类的基类 |
class.__mro__ |
类层次结构 |
class.__subclasses__() |
子类列表 |
# 测试特殊属性
class A:
pass
class B:
pass
class C(B, A):
def __init__(self, nn):
self.nn = nn
def cc(self):
print("cc")
class D(C, B, A):
def __init__(self, nn, ndd):
super().__init__(nn)
self.ndd = ndd
def dd(self):
print("dd")
d = D(3, 4)
print(f"{'=' * 30}对象的所有属性{'=' * 30}")
print(dir(d))
print(f"{'=' * 30}对象的属性字典{'=' * 30}")
print(d.__dict__)
print(f"{'=' * 30}对象所属的类{'=' * 30}")
print(d.__class__)
print(f"{'=' * 30}类的基类元组(多继承){'=' * 30}")
print(D.__bases__)
print(f"{'=' * 30}类层次结构{'=' * 30}")
# print(D.mro())
print(D.__mro__)
print(f"{'=' * 30}子类列表{'=' * 30}")
print(A.__subclasses__())
运行结果:
==============================对象的所有属性==============================
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'cc', 'dd', 'ndd', 'nn']
==============================对象的属性字典==============================
{'nn': 3, 'ndd': 4}
==============================对象所属的类==============================
<class '__main__.D'>
==============================类的基类元组(多继承)==============================
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>)
==============================类层次结构==============================
(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
==============================子类列表==============================
[<class '__main__.C'>, <class '__main__.D'>]
15 对象的浅拷贝和深拷贝

15.1 变量的赋值操作
只是形成两个变量,实际还是指向同一个对象。
15.2 浅拷贝
Python 拷贝一般都是浅拷贝。拷贝时,对象包含的子对象内容不拷贝。因此,源对象和拷贝对象会引用同一个子对象。
15.3 深拷贝
使用 copy 模块的 deepcopy 函数,递归拷贝对象中包含的子对象。源对象和拷贝对象所有的子对象也不同。
# 测试对象的引用赋值、浅拷贝、深拷贝
import copy
class MobilePhone:
def __init__(self, cpu, screen):
self.cpu = cpu
self.screen = screen
class CPU:
def calculate(self):
print("计算,算个 12345")
print("CPU 对象:", self)
class Screen:
def show(self):
print("显示一个好看的画面,亮瞎你的钛合金大眼")
print("屏幕对象:", self)
c = CPU()
s = Screen()
m = MobilePhone(c, s)
m.cpu.calculate()
print(f"{'=' * 30}引用赋值测试{'=' * 30}")
# 两个变量,但是指向了同一个对象
n = m
print('m, n:', m, n)
print(f"{'=' * 30}浅拷贝测试{'=' * 30}")
# m2 是新拷贝的另一个手机对象
m2 = copy.copy(m)
print('m, m2:', m, m2)
m.cpu.calculate()
# m2 和 m 拥有了一样的 cpu 对象和 screen 对象
m2.cpu.calculate()
print(f"{'=' * 30}深拷贝测试{'=' * 30}")
m3 = copy.deepcopy(m)
print('m, m3:', m, m3)
m.cpu.calculate()
# m3 和 m 拥有不一样的 cpu 对象和 screen 对象
m3.cpu.calculate()

内存分析:

16 组合

结婚就是组合。两人组合后,可以复用对方的属性和方法!
除了继承,组合也能实现代码的复用!组合核心是将其它类的对象作为本类的属性。
# 组合测试
class MobilePhone:
def __init__(self, cpu, screen):
self.cpu = cpu
self.screen = screen
class CPU:
def calculate(self):
print("计算,算个 12345")
class Screen:
def show(self):
print("显示一个好看的画面,亮瞎你的钛合金大眼")
c = CPU()
s = Screen()
m = MobilePhone(c, s)
# 通过组合,我们也能调用 cpu 对象的方法。相当于手机对象间接拥有了 “cpu 的方法”
m.cpu.calculate()
m.screen.show()

17 设计模式——工厂模式实现

设计模式是面向对象语言特有的内容,是我们在面临某一类问题时候固定的做法,设计模式有很多种,比较流行的是:GOF(Goup Of Four)23 种设计模式。当然,我们没有必要全部学习,学习几个常用的即可。
对于初学者,我们学习两个最常用的模式:工厂模式和单例模式。
工厂模式实现了创建者和调用者的分离,使用专门的工厂类将选择实现类、创建对象进行统一的管理和控制。

# 工厂模式
class CarFactory:
def create_car(self, brand):
if brand == "奔驰":
return Benz()
elif brand == "宝马":
return BMW()
elif brand == '比亚迪':
return BYD()
else:
return "未知品牌,无法创建"
class Benz:
def run(self):
print(f"奔驰正在奔驰!!!")
class BMW:
def run(self):
print(f"宝马正在驰骋!!!")
class BYD:
def run(self):
print(f"比亚迪正在飞驰!!!")
factory = CarFactory()
c1 = factory.create_car("奔驰")
c2 = factory.create_car("宝马")
c3 = factory.create_car("比亚迪")
c4 = factory.create_car("迈巴赫")
print(c1)
c1.run()
print(c2)
c2.run()
print(c3)
c3.run()
print(c4)

18 设计模式——单例模式

单例模式是一种常见的设计模式!
单例模式(Singleton Pattern)的核心作用是确保一个类只有一个实例,并且提供一个访问该实例的全局访问点。
单例模式只生成一个实例对象,减少了对系统资源的开销。当一个对象的产生需要比较多的资源,如读取配置文件、产生其他依赖对象时,可以产生一个单例对象,然后永久驻留内存中,从而极大的降低开销。

应用场景:音乐播放器对象、回收站对象、打印机对象。
18.1 __new__() 方法
在 Python 中,我们目前已经学了几个魔术方法了,分别是 __init__()、__str__()、__del__(),接下来介绍一下 __new__() 方法。
使用 类名() 创建对象时,Python 的解释器首先会调用 __new__方法 为对象分配空间。
__new__ 是一个由 object 基类提供的内置的静态方法,主要作用有两个:
-
在内存中为对象分配空间。
-
返回对象的引用。
Python 解析器获得对象的引用后,将引用作为第一个参数,传递给 __init__方法。
重写 __new__方法 的代码非常固定,一定要在函数最后写上 return super().__new__(cls) 语句,否则 Python 解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法。
# ==========未使用单例模式==========
# 定义一个播放器类
class MusicPlayer(object):
# 重写 __new__() 魔术方法
def __new__(cls, *args, **kwargs):
print('1、开辟内存空间')
print('2、返回实例化对象引用地址')
return super().__new__(cls)
def __init__(self, name):
self.name = name
# 1. 实例 mp1 对象
mp1 = MusicPlayer('红色的高跟鞋')
print(mp1)
# 2. 实例化 mp2 对象
mp2 = MusicPlayer('春夏秋冬')
print(mp2)

18.2 单例模式的代码实现
# ==========使用单例模式==========
class MySingleton:
__obj = None
# 是否已经初始化
__init_flag = False
def __new__(cls, *args, **kwargs):
if cls.__obj is None:
print("正在开辟内存空间……")
cls.__obj = super().__new__(cls)
return cls.__obj
def __init__(self, name):
# 判断是否需要进行初始化操作
if MySingleton.__init_flag:
return
print("init....")
self.name = name
MySingleton.__init_flag = True
a = MySingleton("红色的高跟鞋")
print(a)
print(f"a.name:{a.name}")
b = MySingleton("春夏秋冬")
print(b)
print(f"b.name:{b.name}")

19 工厂和单例模式结合

设计模式称之为“模式”,就是一些固定的套路。我们很容易用到其他场景上,比如前面讲的工厂模式,我们需要将工厂类定义成“单例”,只需要简单的套用即可实现:
# 测试工厂模式和单例模式的整合使用
class CarFactory:
__obj = None
__init_flag = False
def __new__(cls):
if cls.__obj is None:
print('1、开辟内存空间')
print('2、返回实例化对象引用地址')
CarFactory.__obj = super().__new__(cls)
return CarFactory.__obj
def __init__(self):
if CarFactory.__init_flag:
return
print("初始化工厂……")
CarFactory.__init_flag = True
def create_car(self, brand):
if brand == "奔驰":
return Benz()
elif brand == "宝马":
return BMW()
elif brand == '比亚迪':
return BYD()
else:
return "未知品牌,无法创建"
class Benz:
def run(self):
print(f"奔驰正在奔驰!!!")
class BMW:
def run(self):
print(f"宝马正在驰骋!!!")
class BYD:
def run(self):
print(f"比亚迪正在飞驰!!!")
if __name__ == '__main__':
factory = CarFactory()
c1 = factory.create_car("奔驰")
c2 = factory.create_car("比亚迪")
c1.run()
c2.run()
factory2 = CarFactory()
print(factory)
print(factory2)
print(factory2 is factory)

20 工厂模式和单例模式练习
20.1 练习一
定义发动机类 Motor、底盘类 Chassis、座椅类 Seat,车辆外壳类 Shell,并使用组合关系定义汽车类。其他要求如下:
- 定义汽车的
run()方法,里面需要调用 Motor 类的work()方法,也需要调用座椅类 Seat 的work()方法,也需要调用底盘类 Chassis 的work()方法。
class Motor:
def work(self):
print("发动机正在工作……")
class Chassis:
def work(self):
print("底盘正在工作……")
class Seat:
def work(self):
print("座椅正在工作……")
class Shell:
pass
class Car:
def __init__(self, motor, chassis, seat, shell):
self.motor = motor
self.chassis = chassis
self.seat = seat
self.shell = shell
def run(self):
print("汽车开始运行……")
self.motor.work()
self.chassis.work()
self.seat.work()
motor = Motor()
chassis = Chassis()
seat = Seat()
shell = Shell()
car = Car(motor, chassis, seat, shell)
car.run()

20.2 练习二
使用工厂模式、单例模式实现如下需求:
- 电脑工厂类 ComputerFactory 用于生产电脑 Computer。工厂类使用单例模式,也就是说只能有一个工厂对象。
- 工厂类中可以生产各种品牌的电脑:联想、华硕、神舟。
- 各种品牌的电脑使用继承实现。
- 父类是 Computer 类,定义了
calculate方法 - 各品牌电脑类需要重写父类的
calculate方法。
class ComputerFactory:
__obj = None
__init_flag = False
__id = 1000
def __new__(cls):
if ComputerFactory.__obj is None:
print("开辟内存空间……")
ComputerFactory.__obj = super().__new__(cls)
return ComputerFactory.__obj
def __init__(self):
if ComputerFactory.__init_flag:
return
print("初始化工厂……")
ComputerFactory.__init_flag = True
def create_computer(self, brand):
if brand == "联想":
ComputerFactory.__id += 1
return Lenovo(ComputerFactory.__id)
elif brand == "华硕":
ComputerFactory.__id += 1
return ASUS(ComputerFactory.__id)
elif brand == "神舟":
ComputerFactory.__id += 1
return Hasee(ComputerFactory.__id)
else:
print("未知品牌,无法创建")
return None
class Computer:
def __init__(self, computer_id):
self.computer_id = computer_id
def calculate(self):
print(f"计算机 {self.computer_id} 正在计算……")
class Lenovo(Computer):
def calculate(self):
print(f"联想计算机 {self.computer_id} 正在计算……")
class ASUS(Computer):
def calculate(self):
print(f"华硕计算机 {self.computer_id} 正在计算……")
class Hasee(Computer):
def calculate(self):
print(f"神舟计算机 {self.computer_id} 正在计算……")
if __name__ == '__main__':
factory = ComputerFactory()
lenovo = factory.create_computer("联想")
asus = factory.create_computer("华硕")
hasee = factory.create_computer("神舟")
lenovo.calculate()
asus.calculate()
hasee.calculate()
print("=" * 40)
factory1 = ComputerFactory()
print(factory)
print(factory1)
print(factory1 is factory)

20.3 练习三
定义一个 Employee 雇员类,要求如下:
- 属性有:
id、name、salary。 - 运算符重载
+:实现两个对象相加时,默认返回他们的薪水和。 - 构造方法要求:输入
name、salary,不输入id。id采用自增的方式,从 1000 开始自增,第一个新增对象是 1001,第二个新增对象是 1002。 - 根据 salary 属性,使用
@property设置属性的 get 和 set 方法。set 方法要求输入:1000~50000 范围的数字。
"""
定义一个 Employee 雇员类,要求如下:
1. 属性有:`id`、`name`、`salary`。
2. 运算符重载 `+`:实现两个对象相加时,默认返回他们的薪水和。
3. 构造方法要求:输入 `name`、`salary`,不输入 `id`。
`id` 采用自增的方式,从 1000 开始自增,第一个新增对象是 1001,第二个新增对象是 1002。
4. 根据 salary 属性,使用 `@property` 设置属性的 get 和 set 方法。
set 方法要求输入:1000~50000 范围的数字
"""
class Employee:
__id = 1000
def __init__(self, name, salary):
Employee.__id += 1
self.id = Employee.__id
self.name = name
self.__salary = salary
@property
def salary(self):
return self.__salary
@salary.setter
def salary(self, salary):
if 1000 < salary < 50000:
self.__salary = salary
else:
raise ValueError("salary 必须为 1000~50000 范围的数字")
def __add__(self, other):
if isinstance(other, Employee):
return self.salary + other.salary
else:
raise TypeError("类型不同不能相加")
def __str__(self):
return f"{self.name} 的 id 为 {self.id},薪水为 {self.salary}。"
if __name__ == '__main__':
emp1 = Employee("小龙女", 20000)
emp2 = Employee("赵敏", 15000)
print(emp1)
print(emp2)
print(f"emp1 + emp2:{emp1 + emp2}")
# print(f"emp1 + 1:{emp1 + 1}")
emp1.salary = 50000
print(emp1.salary)
# emp1.salary = 500000
# print(emp1.salary)





浙公网安备 33010602011771号