面向对象编程

面向对象基础

1
是一种抽象思想,将特征(属性)和行为(方法)封装为类,用类创建对象

类和对象

1
2
3
4
5
Python2中类分为:经典类(不由任意内置类型派生出的类,称之为经典类) 和 新式类
class 类名(): # 遵循大驼峰命名习惯。
    代码
    ......
对象名 = 类名() # 创建对象的过程也叫实例化对象。
  • self指的是调用该函数的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 定义类
class Washer():
    def wash(self):
        print('我会洗衣服')
        # <__main__.Washer object at 0x0000024BA2B34240>
        print(self)
# 2. 创建对象
haier1 = Washer()
# <__main__.Washer object at 0x0000018B7B224240>
print(haier1)
# haier1对象调用实例方法
haier1.wash()

# ps: self相当于存储了实例化的对象的地址,一个类创建多个对象,打印self得到的内存地址是不同的

添加/获取对象属性

1
2
3
类外面添加属性:对象名.属性名 = 值
类外面获取属性:对象名.属性名
类里面获取属性:self.属性名

魔法方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__xx__()的函数叫做魔法方法,指的是具有特殊功能的函数。
#### __init__() ####
初始化对象,在创建一个对象时默认被调用,不需要手动调用
def __init__(self, width, height): # 携带参数,这样实例化对象的时候,可以传参动态初始化
        self.width = width
        self.height = height
#### __str__() ####
print输出对象的时候,默认打印对象的内存地址。如果类定义了__str__方法,那么就会打印从在这个方法中 return 的数据。
#### __del__() ####
当删除对象时,python解释器也会默认调用__del__()方法。
class Washer():
    def __init__(self, width, height):
        self.width = width
        self.height = height
    def __del__(self):
        print(f'{self}对象已经被删除')
# <__main__.Washer object at 0x0000026118223278>对象已经被删除
del haier1
#### __mro__ ####
类名.__mro__ 可以快速查看这个类继承的父类,以及父类的层级关系(从最下面的类打印到Object )

#### __dict__ ####
返回类内部所有属性和方法对应的字典 或 返回实例属性和值组成的字典

继承

1
2
3
4
5
6
7
经典类,不由任意内置类型派生出的类,称之为经典类。
class 类名:
    代码
    ......
新式类
class 类名(object): # 在Python中,所有类默认继承object类,object类是顶级类或基类;其他子类叫做派生类。
  代码

image-20230927183947326

1
还可以多继承:当一个类有多个父类的时候,默认使用第一个父类的同名属性和方法。

image-20230927183952935

1
子类重写父类方法/属性:子类和父类具有同名属性和方法,默认使用子类的同名属性和方法

image-20230927184000282

1
子类调用父类方法/属性:子类和父类具有同名属性和方法,默认使用子类的同名属性和方法

image-20230927184006891

多重继承

1
子类默认继承都父类的所有属性和方法

super()调用父类方法

image-20230927184012996

1
使用super() 可以自动查找父类。调用顺序遵循 __mro__ 类属性的顺序。比较适合单继承使用。

私有权限

1
2
设置某个实例属性或实例方法不继承给子类。
私有属性和私有方法只能在类里面访问和修改。一般定义函数名get_xx用来获取私有属性,定义set_xx用来修改私有属性值

image-20230927184020236

image-20230927184026587

多态

1
2
3
4
5
6
7
8
一个抽象类有多个子类,因而多态的概念依赖于继承
* 定义:多态是一种使用对象的方式,子类重写父类方法,调用不同子类对象的相同父类方法,可以产生不同的执行结果
* 好处:调用灵活,有了多态,更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化!
* 实现步骤:
    * 定义父类,并提供公共方法
    * 定义子类,并重写父类方法
    * 传递子类对象给调用者,可以看到不同子类执行效果不同
多态不是必须但是最好依赖于继承

image-20230927181721089

类属性和实例属性

1
2
3
4
5
* 类属性就是 类对象 所拥有的属性,它被 该类的所有实例对象 所共有。
* 类属性可以使用 类对象 或 实例对象 访问。
类属性的优点
* 记录的某项数据 始终保持一致时,则定义类属性。
* 实例属性 要求 每个对象 为其 单独开辟一份内存空间 来记录数据,而 类属性 为全类所共有 ,仅占用一份内存,更加节省内存空间

image-20230927181730974

1
类属性只能通过类对象修改,不能通过实例对象修改,如果通过实例对象修改类属性,表示的是创建了一个实例属性。

image-20230927181738912

类方法

1
2
3
4
5
需要用装饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数

方法使用场景
* 当方法中 需要使用类对象 (如访问私有类属性等)时,定义类方法
* 类方法一般和类属性配合使用,类方法一般是操作私有类属性的时候用的

image-20230927181746427

静态方法

1
2
3
4
5
6
7
* 需要通过装饰器@staticmethod来进行修饰,静态方法既不需要传递类对象也不需要传递实例对象(形参没有self/cls)。
* 静态方法 也能够通过 实例对象 和 类对象 去访问。

使用场景
* 当方法中 既不需要使用实例对象(如实例对象,实例属性),也不需要使用类对象 (如类属性、类方法、创建实例等)时,定义静态方法
【就是说,如果没有self,cls这样的形参使用的话,可以用静态方法】
* 取消不需要的参数传递,有利于 减少不必要的内存占用和性能消耗

image-20230927181754816

异常

python中的异常是什么

1
2
3
4
5
6
7
8
9
10
就是程序出现的错误/BUG,捕获处理可能出现的异常,让程序可以继续执行后面的代码。
try:
    // 可能发生错误的代码
    f = open('test.txt', 'r')
/// except 异常类型:
except:
    // 如果捕获到该异常类型执行的代码
    f = open('test.txt', 'w')
1. 如果尝试执行的代码的异常类型和要捕获的异常类型不一致,则无法捕获异常。
2. 一般try下方只放一行尝试执行的代码。

image-20230927181800975

异常基本使用

1
2
3
4
5
6
7
8
9
10
11
try:
    f = open('test.txt', 'r')
# except (NameError, ZeroDivisionError): # 捕获多个异常,使用元组
# as result 捕获异常描述信息
# Exception捕获所有异常
except Exception as result: # Exception是所有程序异常类的父类
    f = open('test.txt', 'w')
else: # 表示的是如果没有异常要执行的代码。
    print('没有异常,真开心')
finally: # 表示的是无论是否异常都要执行的代码,例如关闭文件。关闭流
    f.close()

异常传递

image-20230927181809775

自定义异常

1
raise 异常类对象

image-20230927181816105

模块

模块是什么

1
2
Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句。
模块能定义函数,类和变量,模块里也能包含可执行的代码。

导入模块的方式

1
2
3
4
5
import 模块名
from 模块名 import 功能名
from 模块名 import *
import 模块名 as 别名
from 模块名 import 功能名 as 别名
  • import

image-20230927181825250

  • from ... import ...

image-20230927181831372

1
ps: 不能再使用math.sqrt()了.只能 功能名()
  • from ... import *

image-20230927181943638

1
* 代表所有
  • as

image-20230927181949937

1
ps: 这里页只能用 别名() 而不能用 功能名()

制作模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
在Python中,每个Python文件都可以作为一个模块,模块的名字就是文件的名字。也就是说自定义模块名必须要符合标识符命名规则。

1.新建一个Python文件,命名为my_module1.py,并定义testA函数。
def testA(a, b):
    print(a + b)

# 在实际开中,当一个开发人员编写完一个模块后,为了让模块能够在项目中达到想要的效果,这个开发人员会自行在py文件中添加一些测试信息.,例如,在my_module1.py文件中添加测试代码。
def testA(a, b):
    print(a + b)
testA(1, 1)

# 此时,无论是当前文件,还是其他已经导入了该模块的文件,在运行的时候都会自动执行testA函数的调用。
解决办法如下:
def testA(a, b):
    print(a + b)

# 只在当前文件中调用该函数,其他导入的文件内不符合该条件,则不执行testA函数调用
if __name__ == '__main__':
    testA(1, 1)

2.调用模块
import my_module1
my_module1.testA(1, 1)

如果使用from .. import ..或from .. import *导入多个模块的时候,且模块内有同名功能。当调用这个同名功能的时候,调用到的是后面导入的模块的功能。

image-20230927181959671

模块定位顺序

1
2
3
4
5
6
7
8
9
10
当导入一个模块,Python解析器对模块位置的搜索顺序是: 
1. 当前目录(优先搜索当前,所以自己的文件夹不能和模块名重复)
2. 如果不在当前目录,Python则搜索在shell变量PYTHONPATH下的每个目录。
3. 如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/
模块搜索路径存储在system模块的sys.path变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。
注意
    * 自己的文件名不要和已有模块名重复,否则导致模块功能无法使用
    * 使用from 模块名 import 功能的时候,如果功能名字重复,调用到的是最后定义或导入的功能。
(避免功能名字重复,否则会使用后定义的。有点像css的样式覆盖)
import 模块名 这种导入方式,使用是模块名.功能().这种方式不用担心重名问题

image-20230927182005744

__all__

1
如果一个模块文件中有__all__变量,当使用from xxx import *导入时,只能导入这个列表中的元素。

image-20230927182012126

image-20230927182019596

包是什么

1
2
3
4
包将有联系的模块组织在一起,即放到同一个文件夹下,并且在这个文件夹创建一个名字为__init__.py 文件,那么这个文件夹就称之为包。

1. [New] — [Python Package] — 输入包名 — [OK] — 新建功能模块(有联系的模块)。
注意:新建包后,包内部会自动创建__init__.py文件,这个文件控制着包的导入行为。

image-20230927182026540

image-20230927182032871

image-20230927182039405

  • 关于模块的命名
1
2
如果模块已数字开头,则只能自己使用,不能给别人用。
因为import导入的时候无法导入。类似变量的命名规范。

案例-面向对象版学院管理系统