目录:
- python封装与property装饰器
- python继承与类型检查
- python多态与super
- python 模块与包
- 错误与异常
- Debug 调试与分析
- python类型注解
- python数据类dataclass
- python内置装饰器
- python装饰器
- 学生信息管理系统
1.python封装与property装饰器
封装的概念
- 封装(
Encapsulation
)- 隐藏:属性和实现细节,不允许外部直接访问
- 暴露:公开方法,实现对内部信息的操作和访问
封装的作用
- 限制安全的访问和操作,提高数据安全性
- 可进行数据检查,从而有利于保证对象信息的完整性
封装的实现:隐藏
- 保护属性:
_属性名
- 私有属性:
__属性名
- 被视作
_类名__属性名
- 被视作
class Account:
# 普通属性
bank = "BOC"
# 内部属性
_username = "tom"
# 私有属性
__password = "888"
# 通过类名访问类属性
print(Account.bank) # 将会打印 BOC
print(Account._username) # 将会打印 tom
print(Account.__password) # 将会引发 AttributeError
print(Account.__dict__)
# 实例化
obj = Account()
# 实例访问类属性
print(obj.bank) # 将会打印 BOC
print(obj._username) # 将会打印 tom
print(obj.__password) # 将会引发AttributeError
封装的实现:暴露
- 提供数据访问功能(
getter
)- 计算属性
- 语法:使用
@property
装饰器 - 调用:
实例.方法名
class Account:
# 普通属性
bank = "BOC"
# 内部属性
_username = "tom"
# 私有属性
__password = "888"
@property
def password(self):
return self.__password
# 实例化对象
obj = Account()
# 访问实例的私有属性
print(obj.password) # 将会打印 888
封装的实现:暴露
- 提供数据操作功能(
setter
)- 语法:使用
@计算属性名.setter
装饰器 - 调用:
实例.方法名
- 语法:使用
class Account:
# 普通属性
bank = "BOC"
# 内部属性
_username = "tom"
# 私有属性
__password = "888"
@property
def password(self):
return self.__password
@password.setter
def password(self, value):
# 增加数据的校验
if len(value) >= 8:
self.__password = value
else:
print("密码长度最少要有8位!")
# 实例化对象
obj1 = Account()
# 修改私有属性(满足校验条件)
obj1.password = "tomtom666" # 修改成功
print(obj1.password) # 将会打印 tomtom666
# 实例化对象
obj = Account()
# 修改私有属性(不满足校验条件)
obj.password = "123" # 修改不会生效
print(obj.password) # 将会打印 888
2.python继承与类型检查
继承的概念
- 继承(
Inheritance
)- 复用父类的公开属性和方法
- 拓展出新的属性和方法
继承的实现
- 语法:
class 类名(父类列表)
- 默认父类是
object
- Python 支持多继承
class Human:
"""人类"""
# 类属性
message = "这是Human的类属性"
# 构造方法
def __init__(self, name, age):
# 实例属性
self.name = name
self.age = age
# 实例方法
def live(self):
print("住在地球上")
class Student(Human):
"""学生类"""
def study(self):
print("正在学习")
# 实例化子类对象
stu = Student("哈利波特", 12)
# 访问类属性(继承)
print(stu.message)
# 访问实例属性(继承)
print(stu.name, stu.age)
# 访问实例方法(继承)
stu.live()
# 访问实例方法(扩展)
stu.study()
类型检查
isinstance(实例, 类名)
- 检查对象是否是某个类及其派生类的实例
issubclass(类名1, 类名2)
- 检查类名 1 是否是类名 2 的子类
# 人类
class Human:
pass
# 学生类
class Student(Human):
pass
# 老师类
class Teacher(Human):
pass
# 检查实例与类的关系
stu = Student()
print(isinstance(stu, Human)) # 将会打印 True
# 检查类与类的关系
print(issubclass(Student, Human)) # 将会打印 True
print(issubclass(Student, Teacher)) # 将会打印 False
3.python多态与super
多态的概念
- 多态(
Polymorphism
)- 同名方法呈现多种行为
运算符的多态表现
+
号- 加法:数字+数字
- 拼接:字符串+字符串
- 合并:列表+列表
# 加法:数字+数字
print(1 + 1) # 打印 2
# 拼接:字符串+字符串
print("Hello" + "World") # 打印 Hello World
# 合并:列表+列表
print([1, 2] + [3]) # 打印 [1, 2, 3]
函数的多态表现
len()
函数- 可以接收字符串
- 可以接收列表
# 参数是字符串
print(len("hello"))
# 参数是列表
print(len([1, 3, 5]))
方法的多态表现
- 同名变量调用同名方法呈现多种行为
class China:
def speak(self):
print("汉语")
class Usa:
def speak(self):
print("英语")
# 实例化对象
cn = China()
us = Usa()
for x in (cn, us):
# 同一个变量在调用同名方法时,呈现不同的行为
# 具体呈现哪种行为,由该变量所引用的对象决定
x.speak()
多态与继承
- 方法重写(
Override
):子类的方法名称与父类的相同 - 重写构造方法
super().__init__()
父类名.__init__(self)
class Human:
"""人类"""
message = "这是Human的类属性"
# 构造方法
def __init__(self, name, age):
# 实例属性
self.name = name
self.age = age
# 实例方法
def live(self):
print(f"住在地球上")
class Student(Human):
"""学生类"""
# 重写父类的构造方法
def __init__(self, name, age, school):
# 访问父类的构造方法
super().__init__(name, age)
# super(Student, self).__init__(name, age)
# Human.__init__(self, name, age)
# 子类实例属性(个性)
self.school = school
# 重写父类的实例方法
def live(self):
print(f"住在{self.school}")
# 实例化子类对象
stu = Student("哈利波特", 12, "月球")
# 访问实例方法
stu.live() # 将会打印 住在月球
4.python 模块与包
模块
- 模块是在代码量变得相当⼤了之后,为了将需要重复使⽤的有组织的代码放在⼀起,这部分代码可以被其他程序引⽤,从⽽使⽤该模块⾥的函数等功能,引⽤的过程叫做导⼊(import)
- 在python中,⼀个⽂件(以“.py”为后缀名的⽂件)就叫做⼀个模块
- 导⼊模块的写法:
- ⽅法⼀:import modul1[,module2…[,moduleN]]
- ⽅法⼆:from module import <name[,name2,….[,nameN]]>
模块分类
- 系统内置模块
- sys、time、json模块等等;
- 第三⽅的开源模块
- 通过pip install进⾏安装,有开源的代码
- ⾃定义模块
- ⾃⼰写的模块,对某段逻辑或某些函数进⾏封装后供其他函数调⽤。
如何使⽤模块?
- 系统内置模块
- Python安装好之后⾃带的⼀些⾮常有⽤的模块(sys,os,time,json模块等)
import sys
print("调⽤了sys模块")
for i in sys.argv:
print(i)
第三⽅开源模块
- 是通过包管理⼯具pip完成的。必须先知道该库的名称,可以在官⽹或者pypi上搜索,⽐如MySQL驱动程序,Web框架Flask,科学计算Numpy等
- NumPy(Numerical Python)是Python语⾔的⼀个扩展程序库,⽀持⼤量的维度数组与矩阵运算,此外也针对数组运算提供⼤量的数学函数库
- pip install numpy
import numpy
# 创建一个数组
arr = numpy.array([1, 2, 3, 4, 5])
# 打印数组
print("原始数组:", arr)
⾃定义模块
- ⾃定义模块是⾃⼰写的模块,对某段逻辑或某些函数进⾏封装后供其他函数调⽤。
- 模块由变量,函数,或类组成
- 举例:
- 创建⼀个模块baidu.py
- 创建⼀个调⽤模块index.py
- 注意:⾃定义模块的名字⼀定不能和系统内置的模块重名,否则将不能再导⼊系统的内置模块。例如,⾃定义了⼀个sys.py模块后,那系统的sys模块就不能使⽤
导⼊模块
- import 模块名 引⼊⼀个完整的模块
- from <模块名> import <⽅法|变量|类>引⼊模块中的⼀个或多个指定部分
- from <模块名> import *导⼊模块⾥⾯的所有的函数,变量
- 区别:
- import导⼊模块,每次使⽤模块中的函数,都要是定是哪个模块
- from…import *导⼊模块,每次使⽤模块中的函数,直接使⽤函数就可以了(因为已经知道该函数是那个模块中的了)
作⽤域:
- 搜索路径
- 当你导⼊⼀个模块,Python解析器对模块位置的搜索顺序是:
- 1、当前⽬录
- 2、如果不在当前⽬录,Python则搜索在shell变量PYTHONPATH下的每个⽬录
- 3、如果都找不到,Python会察看默认路径。UNIX下,默认路径⼀般为/usr/local/lib/python/
- 模块搜索路径存储在system模块的sys.path变量中。变量⾥包含当前⽬录,PYTHONPATH和由安装过程决定的默认⽬录。
使⽤模块的总结
- 使⽤模块的好处
- 提⾼代码的可维护性
- ⼀个模块编写完毕之后,其他模块直接调⽤,不⽤再从零开始写代码了,节约了⼯作时间;
- 避免函数名称和变量名称重复,在不同的模块中可以存在相同名字的函数名和变量名(不要和系统内置的模块名称重复)
常用方法
- dir()找出当前模块定义的对象
- dir(sys)找出参数模块定义的对象
5.错误与异常
什么是异常?
- 错误与异常的区别?
- 错误与异常都是在程序编译和运⾏时出现的错误
- 异常可以被开发⼈员捕捉和处理
- 错误⼀般是系统错误,⼀般不需要开发⼈员处理(也⽆法处理),⽐如内存溢出
- 什么是异常?
- 异常即是⼀个事件,该事件会在程序执⾏过程中发⽣,影响了程序的正常执⾏。
- 有些是由于拼写、配置、选项等等各种引起的程序错误,有些是由于程序功能处理逻辑不完善引起的漏洞,这些统称为程序中的异常
错误
- 语法错误
- 逻辑错误
- 系统错误
常见的异常类型:
- ~除零类型,名称异常,索引异常,键异常,值异常,属性异常等等
异常处理流程
异常解决⽅案
- 如果是拼写、配置等引起的错误,根据出错信息进⾏排查错误出现的位置进⾏解决
- 如果是程序设计不完善引起的漏洞,根据漏洞的情况进⾏设计处理漏洞的逻辑
异常捕获与异常处理
(情况一)
(情况二)
(情况三)
代码示例:
try:
# 可能会抛出异常的代码
x = int(input("请输入一个整数:"))
y = int(input("请输入另一个整数:"))
# 执行正常代码
result = x / y
except ZeroDivisionError:
# 异常处理代码
print("0不能做除数,请重新输入!")
else:
# 如果没有异常,执行下面的代码
print("计算结果为:", result)
finally:
# 无论是否有异常,都会执行的代码
print("程序执行完毕!")
使⽤raise抛出异常
def divide(a, b):
if b == 0:
raise ValueError("除数为0!")
return a / b
try:
result = divide(10, 0)
except ValueError as e:
print("发生异常:", e)
⾃定义异常
# 定义自定义异常类
class MyException(Exception):
def __init__(self, message):
self.message = message
# 定义一个函数,可能会抛出自定义异常
def divide(a, b):
if b == 0:
raise MyException("除数为0!")
return a / b
# 在try块中调用函数,如果发生异常,则进入except块处理
try:
result = divide(10, 0)
except MyException as e:
print("捕获异常:", e)
6.Debug 调试与分析
程序调试
- 程序调试是将编制的程序投⼊实际运⾏前,⽤⼿⼯或编译程序等⽅法进⾏测试,修正【语法错误和逻辑错误】的过程。
语法错误
- 编写的python语法不正确,程序编译失败。
逻辑错误
- 代码本⾝能够正常执⾏,但是执⾏完成的结果不符合预期结果。
什么是Bug
- bug是计算机领域专业术语(⼩昆⾍;⾍⼦),计算机⾥叫漏洞,隐藏在程序中的⼀些未被发现的缺陷或问题统称为bug(漏洞)
调试分类
1、语法错误
类型错误,语法错误,缩进错误,索引错误,键错误
2、逻辑错误
业务逻辑错误,并不会报错
调试⽅法
- 对应位置使⽤`print`或者`logging`打印⽇志信息
- 启动断点模式debug调试
7.python类型注解
-
https://docs.python.org/zh-cn/3/library/typing.html
def greeting(name: str) -> str:
return 'Hello ' + name.split(',')[1]
print(greeting('python,java'))
类型提示的好处
- 1、增强代码可读性
- 2、ide 中代码提示
- 3、静态代码检查
类型别名:
Vector = list[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
nums = [1.0, 2.0, 3.0]
print(scale(2.0,nums))
自定义类型:
class Student:
name: str
age: int
def get_stu(name: str) -> Student:
return Student()
# --> 有相应的提示
get_stu().
静态代码检查功能
安装 mypy
pip install mypy
8.python数据类dataclass
dataclass 介绍:
- dataclass 优势
- 可读性强
- 操作灵活
- 轻量
- 应用场景
- 创建对象
- 完美融合平台开发 ORM 框架
案例
- 场景:如果创建一只猫,信息包括猫的名字、体重、颜色。同时打印这个对象的时候,希望能打印出一个字符串(包含猫的各种信息)应该如何编写代码
- 问题:
- 数据修改不方便
- 代码冗余
- 解决方案:
- 使用自定义类实现数据类
- 问题:
class Cat:
name: str
color: str
weight: int
def __init__(self, name, weight, color):
self.name = name
self.weight = weight
self.color = color
def __str__(self):
return f"喵星人姓名:{self.name}, 年龄:{self.weight},颜色:{self.color}"
def __repr__(self):
return f"===>>>>> 喵星人姓名:{self.name}, 年龄:{self.weight},颜色:{self.color}"
if __name__ == '__main__':
cat = Cat("黑子", 10, "黑色")
print(cat)
数据类更优雅的实现方案
- 使用 dataclass 创建数据类
- 实例化的时候自动生成构造函数
from dataclasses import dataclass
@dataclass
class Cat:
name: str
color: str
weight: int
if __name__ == '__main__':
cat = Cat("菠萝", "橘猫", 9)
print(cat)
field 的使用
# 错误写法,执行报错
from dataclasses import dataclass
@dataclass
class Cat:
name: str
color: str
weight: int
children: list = [1, 2, 3]
# 正确写法,可变类型必须使用field
from dataclasses import dataclass, field
@dataclass
class Cat:
name: str
color: str
weight: int = 2
children: list = field(default_factory=list)
if __name__ == '__main__':
cat = Cat("菠萝", "橘猫", 9, [1, 2, 3])
print(cat)
field 常用参数
参数名 | 参数功能 |
---|---|
default | 字段的默认值 |
default_factory | 定义可变量参数的默认值,default 和 default_factory 不能同时存在 |
init | 如果为 true (默认值),该字段作为参数包含在生成的 init() 方法中。 |
repr | 如果为 true (默认值),该字段包含在生成的 repr() 方法返回的字符串中。 |
field default 参数
- 字段的默认值
import dataclasses
@dataclasses.dataclass
class Cat:
name: str
color: str
weight: str = dataclasses.field(default=5)
children: list = dataclasses.field(default_factory=list)
children1: list = dataclasses.field(default_factory=lambda: [1, 2, 3])
children2: dict = dataclasses.field(default_factory=lambda: {"name": "喵"})
if __name__ == '__main__':
cat = Cat("菠萝", "橘猫", 9, [1, 2, 3])
print(cat)
field init 参数
- 如果 init=True(未显式指定时的默认行为): 该字段将包含在生成的构造函数中, 可以在创建数据类的实例时为其提供值。name 和 color , weight 字段具有“init=True”,因此它们将包含在构造函数中。
- 如果 init=False: 该字段将被排除
import dataclasses
@dataclasses.dataclass
class Cat:
name: str
color: str
weight: str = dataclasses.field(default=5)
children: list = dataclasses.field(default_factory=list, init=False)
if __name__ == '__main__':
cat = Cat("菠萝", "橘猫", 9)
print(cat)
field repr 参数
- 如果为
True
(默认值),该字段包含在生成的repr()
方法返回的字符串中。 - 如果为
False
,该字段不会包含在生成的repr()
方法返回的字符串中。
import dataclasses
@dataclasses.dataclass
class Cat:
name: str
color: str
weight: str = dataclasses.field(default=5)
children: list = dataclasses.field(default_factory=list, repr=False)
if __name__ == '__main__':
cat = Cat("菠萝", "橘猫", 9, [1, 2, 3])
print(cat)
运行结果:
常用的方法
asdict()
转化实例对象为字典格式
import dataclasses
@dataclasses.dataclass
class Cat:
name: str
color: str
weight: int = 5
children: list = dataclasses.field(default_factory=lambda: [1, 2, 3])
children1: dict = dataclasses.field(default_factory=lambda: {"name": "喵"})
cat = Cat("aa", "red", 10, [1, 3], {"name": "喵喵"})
print(dataclasses.asdict(cat))
9.python内置装饰器
内置类装饰器
- 不用实例化、直接调用
- 提升代码的可读性
内置装饰器 | 含义 |
---|---|
classmethod | 类方法 |
staticmethod | 静态方法 |
普通方法
- 定义:
- 第一个参数为self,代表 实例本身
- 调用:
- 要有实例化的过程,通过 实例对象.方法名 调用
# 1. 定义
class MethodsDemo:
param_a = 0 # 类变量
def normal_demo(self): # 定义一个类方法,第一个参数必须为self
"""
普通方法
:return:
"""
print("这是一个普通方法", self.param_a)
# 2. 调用
md = MethodsDemo()
md.normal_demo()
类方法
- 定义:
- 使用 @classmethod 装饰器,第一个参数为类本身,所以通常使用cls命名做区分(非强制)
- 在类内可以直接使用类方法或类变量,无法直接使用实例变量或方法
- 调用:
- 无需实例化,直接通过 类.方法名 调用,也可以通过 实例.方法名 调用
# 1. 类的定义
class MethodsDemo:
param_a = 0
# 定义类方法必须加 classmethod装饰器
@classmethod
def classmethod_demo(cls):
"""
类方法,第一个参数需要改为cls
:return:
"""
print("类方法", cls.param_a)
# 2. 类的调用
MethodsDemo.classmethod_demo() # 无需实例化,直接调用
静态方法
- 定义:
- 使用 @staticmethod 装饰器,没有和类本身有关的参数
- 无法直接使用任何类变量、类方法或者实例方法、实例变量
- 调用:
- 无需实例化,直接通过 类.方法名 调用,也可以通过 实例.方法名 调用
# 1. 定义
class MethodsDemo:
param_a = 0
@staticmethod
def static_demo():
"""
静态方法
:return:
"""
print("静态方法") # 无法直接调用类变量
# 2. 调用
MethodsDemo.static_demo()
普通方法、类方法、静态方法
名称 | 定义 | 调用 | 关键字 | 使用场景 |
---|---|---|---|---|
普通方法 | 至少需要一个参数self | 实例名.方法名() | 无 | 方法内部涉及到实例对象属性的操作 |
类方法 | 至少需要一个cls参数 | 类名.方法名() 或者实例名.方法名() | @classmethod | 如果需要对类属性,即静态变量进行限制性操作 |
静态方法 | 无默认参数 | 类名.方法名() 或者实例名.方法名() | @staticmethod | 无需类或实例参与 |
普通方法实际案例
- 下边的代码实现的需求是格式化输出时间
- 如果现在需求变更,输入 年、月、日 没法保证格式统一,可能是json,可能是其他格式的字符串,在不修改构造函数的前提下,如何更改代码
class DateFormat:
def __init__(self, year=0, month=0, day=0):
self.year = year
self.month = month
self.day = day
def out_date(self):
return f"输入的时间为{self.year}年,{self.month}月,{self.day}日"
year, month, day = 2023, 8, 2
demo = DateFormat(year, month, day)
print(demo.out_date())
class DateFormat:
def __init__(self, year=0, month=0, day=0):
self.year = year
self.month = month
self.day = day
def out_date(self):
return f"输入的时间为{self.year}年,{self.month}月,{self.day}日"
@classmethod
def json_format(cls, json_data):
year, month, day = json_data["year"], json_data["month"], json_data["day"],
return cls(year, month, day)
json_date = {"year": 2023, "month": 12, "day": 24}
demo = DateFormat.json_format(json_date)
print(demo.out_date())
静态方法实际案例
- 此方法没有任何和实例、类相关的部分,可以作为一个独立函数使用
- 某些场景下,从业务逻辑来说又属于类的一部分
- 例子:简单工厂方法
class Game:
def __init__(self, first_hero, second_hero):
self.first_hero = first_hero
self.second_hero = second_hero
# fight有和实例变量交互的部分,所以需要定义为一个普通方法
def fight(self):
print(f"本轮比赛开始,由{self.first_hero} vs {self.second_hero}")
# start没有和类或实例交互的部分,那么就可以使用staticmethod
@staticmethod
def start():
print("游戏开始")
Game.start()
game1 = Game("Bob", "Mary")
game2 = Game("Mike", "Henry")
game1.fight()
game2.fight()
10.python装饰器
函数引用
- 函数可以被引用
- 函数可以被赋值给一个变量
def hello():
print("hello world!")
harry = hello
harry()
闭包函数
- 闭包的内部函数中,对外部作用域的变量进行引用
- 闭包无法修改外部函数的局部变量
- 闭包可以保存当前的运行环境
def output_student(grade):
def inner(name, gender):
print(f"hdc开学啦!学生的名称是{name},性别是{gender},年级是{grade}")
return inner
student = output_student(1)
student("罗恩", "男")
student("哈利", "男")
student("赫敏", "女")
为什么要学习装饰器
- 行业需求: 涉及 Python 技术栈,面试常见题
- 使用需求: 优化代码的可读性,可维护性
装饰器示例
- 函数体开始执行与结束执行的时候分别添加打印信息
# 不使用装饰器的代码
def timer(func):
print("计时开始")
func()
print("计时结束")
def hdc():
print("xx学院")
timer(hdc)
装饰器:
# 使用装饰器的代码
def timer(func):
def inner():
print("计时开始")
func()
print("计时结束")
return inner
@timer
def hdc():
print("xx学院")
hdc()
装饰器练习
- 实现一个计时器的装饰器,计算函数执行时间
装饰带参数函数
import datetime
def timer(func):
def inner(*args, **kwargs):
# 获取当前时间
start_time = datetime.datetime.now()
func(*args, **kwargs)
for i in range(1, 10000000):
i += 1
end_time = datetime.datetime.now()
print(f"函数的执行时间{end_time - start_time}")
return inner
@timer
def xueyuan(name):
print("xxx学院", name)
xueyuan("hello")
11.学生信息管理系统
- 编写学员实体类 Student,对应成员变量包含:学号 id、姓名 name、性别 sex;
- 编写学员管理类 StudentManagement ,实现添加学员方法 addStudent()。
- 编写StudentManagement的main()方法进行学员信息的添加:
学号:1001,姓名:张三,性别:男。
学号:1002,姓名:莉丝,性别:女。
学号:1003,姓名:王武,性别:男。
- 编写学员管理类 StudentManagement ,实现删除学员方法 deleteStudent(),根据学员id 删除以下学员:
学号:1002,姓名:莉丝,性别:女。
示例效果 命令行输出打印效果如下:
添加的学员信息:
- 学号:1001,姓名:张三,性别:男
- 学号:1002,姓名:莉丝,性别:女
- 学号:1003,姓名:王武,性别:男
删除后的学员信息:
- 学号:1001,姓名:张三,性别:男
- 学号:1003,姓名:王武,性别:男