导图社区 面向对象编程基础
这是一篇关于面向对象编程基础的思维导图,主要内容包括:概念,类和对象【重点掌握】,类中的属性【重点掌握】,类中的函数【重点掌握】。
编辑于2025-08-07 08:03:00这是一篇关于面向对象编程基础的思维导图,主要内容包括:概念,类和对象【重点掌握】,类中的属性【重点掌握】,类中的函数【重点掌握】。
Python:简洁高效的跨平台编程利器 Python是一门面向对象的高级解释语言,兼具脚本语言的灵活性与跨平台优势它以简洁开源、生态广泛著称,覆盖Web开发、数据分析、AIoT及自动化等领域,适合非计算机专业快速上手通过解释器将代码转换为机器语言,支持多种IDE(如JetBrains系列),但需注意其运行速度较慢、并发处理能力有限,适合中小型项目无论是打破专业壁垒还是赋能行业应用,Python都是理想选择。
这是一篇关于生物化学与分子生物学(遗传信息:传递和表达)的思维导图,主要内容包括:基因和染色体、DNA、RNA、蛋白质。
社区模板帮助中心,点此进入>>
这是一篇关于面向对象编程基础的思维导图,主要内容包括:概念,类和对象【重点掌握】,类中的属性【重点掌握】,类中的函数【重点掌握】。
Python:简洁高效的跨平台编程利器 Python是一门面向对象的高级解释语言,兼具脚本语言的灵活性与跨平台优势它以简洁开源、生态广泛著称,覆盖Web开发、数据分析、AIoT及自动化等领域,适合非计算机专业快速上手通过解释器将代码转换为机器语言,支持多种IDE(如JetBrains系列),但需注意其运行速度较慢、并发处理能力有限,适合中小型项目无论是打破专业壁垒还是赋能行业应用,Python都是理想选择。
这是一篇关于生物化学与分子生物学(遗传信息:传递和表达)的思维导图,主要内容包括:基因和染色体、DNA、RNA、蛋白质。
Python 函数(function)编程基本使用
I. 函数编程 基本使用
函数概念
在一个完整的项目中,某些功能可能会被反复使用,如果将反复出现的代码封装成函数,以后如果要继续使用该功能则直接使用函数即可,另外,如果要修改需求,只需要修改函数即可。
本质:对某些特殊功能的封装
优点:
a.简化代码结构,提高应用的模块性
b.提高了代码的复用性
c.提高了代码维护性
定义函数
def 函数名(形参, 形参=默认值): 函数体
def print(self, *args, sep=' ', end='\n', file=None): pass
函数的定义分为两部分:函数的声明和函数的实现
说明:
a. def是一个关键字,是definition的缩写,专门定义函数
b. 函数名:遵循合法标识符的规则和规范即可,尽量做到见名知意,注意:和变量的定义类似,全部小写
c. (变量1,变量2....):被称为形式参数,是一个参数列表,都只是没有赋值的变量
函数的声明
d. 函数体:封装某些特殊的功能
e. return是一个关键字,表示返回,注意:只能用在函数中,表示结束函数,可以单独使用,也可以携带数据,当携带数据,则表示该函数的返回值
f. 返回值: 常量,变量,表达式
函数的实现
变量1,变量2.... 和 return 返回值 可以根据具体的需求选择性的省略
函数的定义就相当于 将指定的函数加载到计算机内存中
当函数定义完毕之后,只有当该函数被使用【被调用】的时候,函数【函数体】才会被执行
其他
def func3(name: str) -> int: print('333333') func3('fafgqg4')
name: str 表示给name声明参数类型
->int 表示声明返回值类型为int
调用(call) 函数
函数名(实参, 实参)
print("程杰", "cj", sep="|")
函数调用的本质: 就是使用函数的过程, 当然,同时需要注意传参的问题
传参:在调用函数的过程中,实参给形参赋值的过程
实参:实际参数,出现在函数的调用部分,实际上是一个数据【常量,变量,表达式】,目的是为了给形参赋值
在代码执行的过程中,一旦遇到某个函数的调用,则会先执行对应函数中的代码块,函数执行完毕之后,回到调用函数的地方,代码继续向下执行
函数之间可以相互调用,也可以自己调用自己(递归)
使用时注意递归深度问题
参数和返回值
参数: 函数调用时 为函数传递信息。
形参(formal parameters): 接收信息的变量
1. 位置参数,按照位置声明变量。
调用时必须传参(必须参数)
2. 默认值参数,函数声明时给变量一个默认值,如果实参不传递信息则生效。
3. 动态参数 /不定长参数 /可变参数
*args/*变量名, 接收所有位置参数的动态传参, 并将数据打包传到元组中。
可以不传也可以随便以位置参数的形式传参。
def f1(*args): print(args, type(args), *args) # 此时元组被*打散后给print函数传参 return args print(f1()) print(f1(1, '222a', 3, '*')) list1 = f1(1, 2, 3) print(*list1) a, b = f1(1, 2) print(a, b)
**kwargs/**变量名 , 接收所有关键字参数的动态传参, 并将数据打包传到字典中。
2. 关键字参数。
可以不传也可以随便以关键字参数的形式传参。
def f2(**kwargs): print(kwargs, type(kwargs), **kwargs) # 此时字典被**打散后给print函数传参 return kwargs print(f2()) # print(f2(num1=1, num3='222a', num2=3, num4='*')) dic1 = f2(sep='-', end='\n') print(1, 2, dic1, **dic1) print(dic1['sep'])
在同一函数中,*和**可同时出现且只能出现一次。
混合定义顺序
位置参数 > *args > 默认值参数 > **kwargs
def login(username, password, *args, is_remember=False, is_auto_login=False, **kwargs): pass
实参(actual argument): 实际调用的时候实际传递的数据信息, 若非相应的动态参数调用时必须保证形参有数据。
两种 传参 方式
1. 位置参数。
必须按位置顺序传参,少传也不行
2. 关键字参数。
is_auto_login=True
指定变量名赋值
传参时不用考虑顺序。
混合参数
login(username_input, password_input, is_auto_login=True)
位置参数 > 关键词参数
拆包
动态参数的元组处理
* 打散元组
t1 = f1(1, 2, 3) print(*t1)
产生位置参数,可传递给正确的函数
元组的常规处理
a, b = f1(1, 2) print(a, b)
动态参数的字典处理
** 打散字典
dic1 = f2(sep='-', end='') print(1, 2, **dic1)
产出关键字参数,可传递给正确的函数
字典的常规处理
print(dic1['sep'])
(对象)引用传递
Python采用的是"对象引用传递"(有时称为"共享传参"): 对于不可变对象(数字、字符串、元组):表现得像值传递 对于可变对象(列表、字典、集合):表现得像引用传递
类值传递:传参的时候,传递的是不可变的数据类型,如:int/float/str/tuple/bool,当形参发生修改【修改的是变量的指向】,对实参没有影响
# 类值传递 def f1(f): f = 'abc' return f t1 = '12' r = f1(t1) print(r, t1) # 12
引用传递:传参的时候,传递的是可变的数据类型,如:list/dict/set等,当形参中的元素发生修改【修改列表,字典等可变数据类型中的元素】,则实参会随着修改
引用赋值
# 引用传递 def f2(f): f[0] = '100' return f list1 = [1, 2, 32] r = f2(list1) print(r, list1) # ['100', 2, 32]
return 返回值
常量、变量、表达式
一旦return被执行则立即返回结果并结束函数。
在任意位置调用函数后, 可将返回值赋值给变量。
不写或只写return, 默认返回None
拓展
exit()
结束进程
函数注释
函数体内的多行注释, 在调用函数时可见。
函数说明
参数说明
返回值说明
匿名函数(无函数名) lambda函数
概念:不再使用def这种标准形式定义函数,而是使用lambda表达式来创建函数,该函数没有函数名,被称为匿名函数
小型、匿名、内联
简单、单行函数
通常作为参数传递。
语法:lambda 形参列表: 返回值
lambda 参数(n): 表达式(1)
lambda x, y: x + y
等同
def add(x, y): return x + y
默认参数和动态参数 也可以使用
lambda x, y=0: x + y
lambda *x: sum(x)
可以与三目运算符结合使用。
ef is_even(num): if num % 2 == 0: return True return False r21 = is_even(10) print(r21) is_even2 = lambda num: True if num % 2 == 0 else False r22 = is_even2(13) print(r22)
说明:
a.lambda只是一个表达式,用一行代码实现一个简单的逻辑,可以达到对函数的简化【优点】
b.lambda主体是一个表达式,没有函数体,只能封装有限的逻辑【缺点】
c.lambda拥有自己的命名空间,不能访问自有参数列表之外的参数
【缺点】
使用方法
直接调用
(lambda *x: sum(x))(1, 2, 3, 4)
赋值给变量, 变量成为函数后调用
f1 = lambda x, y: x + y print(f1(1, 2)) # 3
作为参数 传给其他函数
常见用法
自定义函数
def apply_func(func, arg): return func(arg) print(apply_func(lambda x: x ** 2, 3)) # 9
高阶函数
students.sort(key=lambda x: x['score'],reverse=True)
print(max(dic, key=lambda ele: dic[ele])) # jack
存储在容器中
调用才会执行
函数编程 进阶使用
函数的本质
1.函数本质是一个变量, 函数名其实就是一个变量名
函数名
获得函数对象的内存地址
print(abs) # 函数本身,<built-in内置/内建 function abs> print(abs(-23)) # 函数调用,获取返回值
def func(): print('1111111') print(func) # 函数本身,<function func at 0x0000020280F4F040> func() # 函数的调用
数据赋值给f1变量
f1 = abs # 将abs作为数据赋值给f1变量,f1中存储的是可以求绝对值的函数 print(f1) # <built-in function abs> print(f1(-18))
既然函数名相当于一个变量名, 所以可以给该变量重新赋值, 但是会导致该系统的功能失效
所以自定义变量的时候,除了要避开关键字之外,还要避开系统函数名
print(abs, type(abs)) # abs = 'abc' # 该变量重新赋值,导致系统的功能失效 print(abs, type(abs)) # print(abs(-50)) # TypeError: 'str' object is not callable可调用的,注意:只有function或method才能被调用,其他类型都无法调用
2.一个函数可以作为另一个函数的参数或返回值使用, 只需要传递或返回函数名即可
# a.作为参数使用 def func(a, b, f): print(a, b, f) # 在函数体中,f是当作函数被调用的,所以传参的时候一定要给f传一个函数,且该函数必须有一个参数 total = f(a) + f(b) # 等价于abs(a) + abs(b) return total print(func(34, -67, abs)) # a = 34 b = -67 f = abs
def func(): return sum # 返回函数本身 r = func() # r = sum print(r) # <built-in function sum>
def func(): return sum([1,2]) # 返回函数调用的返回值 r = func() # r = 3 print(r) # 3
函数嵌套
定义
def func1(): def func2(): xxx # func1:外部函数 # func2:内部函数 # func1和func2的参数,返回值的使用和最基本的使用完全相同
内部函数和外部函数均可正常设置参数和返回值。
内部函数的调用方法
# 方式一: 在外部函数中直接调用内部函数(一般不会这么写)
# 方式二: 将内部函数作为外部函数的返回值返回(推荐)
def func1(): print('外部~~~~11111') n1 = 10 def func2(): n2 = 20 print(n1 + n2) print('内部~~~111111') print('外部~~~~222222') return func2 func2 = 0 # 尝试对内部函数变量重新赋值,无法使内部函数失效。 f = func1() # 将返回的内部函数赋值给新的变量 print(f) # <function func1.<locals>.func2 at 0x0000019A60BFF040> f() # 相当于调用的是func2
意义:
定义的内部函数可以访问到外部函数的局部变量。
在外部隐藏内部函数的函数名(无法直接访问内部函数)。
变量作用域的作用域
概念
变量的作用域指的是变量可以使用的范围
程序的变量并不是在任意位置都可以访问,访问权限取决于这个变量是在哪里定义的
变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。
引入作用域的方式
定义函数
定义类
模块的创建与引入
【面试题】Python的作用域一共有4种,分别是
# 2. 嵌套函数 num1 = 10 # 全局作用域的全局变量 def f0(): num2 = 20 # 函数作用域的局部变量 def inner_f(): num3 = 30 # 局部作用域的局部变量 print(num1, num2, num3) print(num1, num2) inner_f() print(num1) f0()
# 1. 单层函数 num1 = 10 # 全局作用域 def f(): num2 = 20 # 局部作用域 print(num1, num2) print(num1) f()
L:Local, 局部作用域, 如果嵌套定义的前提下,特指内部函数
定义在此作用域中的变量称为局部变量
访问权限:只能在定义的函数内部被访问,外部无访问权限。
E:Enclosing, 函数作用域【外部函数中】
*
定义在此作用域中的变量称为局部变量
访问权限:在定义的函数内部被访问(包括定义在里面的内部函数),外部无访问权限。
G:Global, 全局作用域
定义在此作用域中的变量称为全局变量
访问权限:在当前py文件(或被导入到其他模块)中的任意位置都可以访问该变量。
B:Built-in, 内建作用域【内置作用域】 num = int("244")
global和nonlocal【面试题】
# 3. 当不同作用域中的变量重名时 num = 10 def f1(): num = 20 def inner_f(): num = 30 print('local:', num) inner_f() print('enclosing:', num) f1() print('global:', num)
当不同作用域的变量重名时 (就近原则)
在全局作用域访问时, 全局变量的值不受影响
在局部作用域访问时, 局部变量的值会覆盖掉全局变量的值
【面试题】 二者都是使用在变量重名的前提下
global: 全局的, 在局部变量中【单层函数、内层函数】 或者 函数作用域中【外层函数】声明一个变量来自于全局变量,对全局变量做出指定的操作【一般指的是对变量重新赋值】
可以跨越外部函数直接声明于内部函数
nonlocal: 不是局部的, 在嵌套定义的函数中,在内部函数中需要使用函数作用域中的变量。声明一个变量不是当前作用域的局部变量(外部函数存在的变量)
只能声明于内部函数
num = 10 num_for_global = 10 num_for_nonlocal = 2 # 未被引入到其他作用域 ***** def f1(): global num # 在函数作用域,声明变量来自于全局变量 num_for_global = 20 num_for_nonlocal = 22 # 重新定义了自己的局部变量(与全局变量重名) ***** num = 20 num_f1 = '20' # 定义自己的局部变量 def inner_f(): global num # 在局部作用域,声明变量来自于全局变量 global num_for_global nonlocal num_f1 # 在局部作用域,声明变量不是当前作用域的局部变量 nonlocal num_for_nonlocal num_f1 = '30' # num = 30 num_for_global += 30 num_for_nonlocal = 32 print('local:', num, num_for_global, num_f1, num_for_nonlocal) # 20 40 30 32 inner_f() print('enclosing:', num, num_for_global, num_f1, num_for_nonlocal) # 20 20 30 32 f1() print('global:', num, num_for_global, num_for_nonlocal) # 20 30 2 *****
闭包与装饰器
闭包:
函数做返回值
概念
函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点(即调用)便可以执行函数了。 函数还可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题
闭包:如果两个函数嵌套定义,如果在内部函数中访问了外部函数中的变量,则构成一个闭包
定义闭包
def func(): a = 12 def inner(): nonlocal a a += 1 return a return inner
修改
def func(x, y): a = 12 def inner(): b = 1 return a + b, x, y return inner
访问
使用闭包
ret = func() print(ret) # <function func.<locals>.inner at 0x0000018A9AADF550> va = ret() print(va) # 13
局部变量a没有销毁,他被内部函数访问
优点
1. 可以让一个局部变量常驻于内存。
2. 可以避免局部变量被随意修改。
装饰器 wrapper
装饰器
函数做返回值和参数
概念:已知一个函数,如果需要给该函数增加新的功能,但是不希望修改原函数,在Python中,这种在代码运行期间动态执行的机制被称为装饰器【Decorator】
装饰器的作用:为已经存在的函数或者类添加额外的功能
装饰器的本质:实际上就是一个闭包,概念:内部函数访问外部函数中的变量【一个已知的函数】
实际大多数情况下,使用系统提供的装饰器。
使用步骤
1. 装饰器的定义
def wrapper(tar_func): # 1.外部函数设置的参数表示被修饰的函数,建议命名:f/fun/func print("装饰器启动") def inner(*args, **kwargs): # 2. 内部函数(装饰器的核心)访问外部函数传入的参数(原函数),并作为外部函数的返回值返回。 # 增加前功能 print("前功能") re = tar_func(*args, **kwargs) # 调用被装饰的原函数,并获取返回值。 # 增加后功能 print("后功能") return re return inner
注意:如果一个装饰器装饰多个不同的函数,为了满足不同函数的参数需求,则给装饰器的内部函数设置不定长参数,格式为:*xxx,**xxx
先将传入的参数打包
调用时再将参数拆包
2. 装饰器的使用
使用
使用 @xxx 可以将一个装饰器作用于一个函数上,只需要将@xxx书写在一个函数的前面,则表示xxx装饰器装饰指定的函数
@xxx:
xxx表示装饰器的名称【即,外部函数的函数名】
注意:如果使用@xxx加载装饰器,则必须先定义装饰器,然后才能使用
@wrapper # 相当于 target = wrapper(target),调用装饰器并将目标函数导入到装饰器中。将修饰完成后返回的内部函数重新赋值给原目标函数的变量名。 def target(name="world"): # 不希望修改的原函数 print(f"{name}: 这是原本功能") return f"返回值name: {name}" ret0 = target() # 调用被修饰的目标函数,并获取返回值 print(ret0)
注意:
a. 一个装饰器装饰多个函数,则@xxx需要书写多次
b. 每装饰一次则调用一次装饰器
c. 多装饰器问题
距离函数近的修饰器先修饰
面试题示例
需求:书写一个装饰器,同时装饰多个函数,给多个函数同时增加同一个新的功能
# 【面试题】需求:书写一个装饰器,同时装饰多个函数,给多个函数同时增加同一个新的功能 def wrapper(func): print(f'wrapper on!{func}') def inner(*args, **kwargs): # 打包 print(args, kwargs) func(*args, **kwargs) # 调用原函数,拆包 print('new~~~~~') return inner @wrapper def a(): print('aaa') @wrapper def b(m,n): print('bbbbb',m,n) @wrapper def c(num1,num2,num3): print('cccc',num1,num2,num3) a() # 调用inner print('*'*50) b(3, 5) # 调用inner print('*'*50) c(23, 5, 7)
案例/面试题:书写一个装饰器,可以统计任意一个函数的执行时间
# 案例/面试题:书写一个装饰器,可以统计任意一个函数的执行时间 import time # print(time.time()) # 获取从1970.1.1 00:00:00到当前的时间戳【秒数】 def wrapper(func): def get_time(*args, **kwargs): start = time.time() func() end = time.time() return round(end - start, 3) # 保留小数点后3位 return get_time @wrapper def check(): for i in range(138232300): pass r = check() print(f'花费的时间为:{r}')
3. 执行被修饰的目标函数
前面添加的功能
原功能
后面添加的功能
类装饰器
内置装饰器
@staticmethod
方法定义为静态方法。
@classmethod
类方法
@property
转换为属性
堆叠次序问题
子主题
迭代器 与 生成器
一次性
迭代器(iterator)
简述可迭代对象和 迭代器之间的区别和联系
区别:
可迭代对象:Iterable,可以直接作用于for循环的对象【可以使用for循环遍历其中元素的对象】,
如:list,tuple,dict,set,str,range(),生成器等
迭代器: Iterator,可以直接作用于for循环,或者可以通过next()获取下一个元素的对象,
如:生成器
联系:
迭代器一定是可迭代对象
可迭代对象不一定是迭代器,但是,可以通过系统功能iter()将不是迭代器的可迭代对象转换为迭代器
作用:可以从可迭代(iterable)的数据中逐一拿到单个数据
featrue:
1. 迭代器本身可迭代
2. 只能向前不能反复
3. 节省内存
4. 惰性机制()
使用步骤
获取迭代器
iter(可迭代对象)
系统函数
可迭代对象.__iter__()
类中的方法
从迭代器中拿到数据
next(迭代器)
迭代器.__next__()
ite = list.__iter__() while 1: try: ite.__next__() except StopIteration break
for item in list: 循环体
for循环的实现原理
与for循环的对比
生成器(generator) ——迭代器的一种
问题:
若可迭代对象中的元素过多, 并且只需要访问其中的前几个元素,会浪费很多内存。
list1 = [n**2 for n in range(1000)] # 会占用大量的内存空间
解决方案:
使用第n个元素,则只需要生成前n元素,在Python中,将这种一边使用,一般计算的机制被称为生成器(generator),则在代码执行的过程中,大量的内存空间会被节约下来
优势:
节省内存
生成器的定义方式有两种:
a.将列表推导式中的[]改为()
list1 = (n**2 for n in range(1000)) print(list1) # <generator object <genexpr> at 0x000001E61C4F5900>
b.函数结合yield,定义函数生成器
yield
yield每执行一次, 则表示给生成器预定义一个元素
def f(): yield 10 yield 11.1 yield True yield 'cj' yield [1, 2, 3, 4] g = f() for data in g: print(data)
生成器中的元素访问
同迭代器
1. for循环遍历
2. 转成列表后输出
不推荐
3. next(generator)
推荐
next()调用一次,表示从生成器中获取一个元素,按照顺序获取的, 生成器是一个只出不进的容器,前面的元素一旦被回去即从生成器中消除。
当生成器中的元素全部获取完毕,接着再获取,则报错StopIteration
def order(): lst = [] for i in range(1000): lst.append(f"衣服{i}") if len(lst) == 50: yield lst lst = [] gen = order() data = gen.__next__() print(data) data = gen.__next__() print(data) data = gen.__next__() print(data)
高阶函数
高阶函数:
若函数a的参数中传入了函数b,并最终返回了一个结果,则函数a被称为高阶函数。
常用的 高阶函数
map(func, *iterable)
标注
参数
func:函数
注意
注意1: map中的func函数需要设置几个参数,取决于有几个iterable参与运算
注意2:当有多个iterable参与运算,则会自动调用func函数,将多个iterable相同位置处的元素同时传参给func
*iterable:可迭代对象【容器】,可以是多个,常用列表
功能
将iterable容器中的每一个元素传递给func, func返回一个结果,结果会成为iterator中的元素
返回值
map【容器,迭代器】
存储各个结果,其元素个数由元素个数最少的容器决定。
使用示例
it = map(lambda n: n**2, range(1, 10)) list1 = list(it) print(list1)
filter(function or None, iterable) --> filter object
标注
参数
function or None
a.func必须设置一个参数
b.func必须设置返回值,且返回值必须是布尔值
iterable
功能
将iterable中的元素依次传递给func,根据func的返回值决定是否保留该元素,如果func的返回值为True,则表示当前元素需要保留,如果为False,则表示过滤
返回值
filter
迭代器
存储经过函数运算为True的元素。
使用示例
f = filter(lambda x: True if x > 2 else False, [1, 2, 3]) print(list(f)) f = filter(None, {0, 1, '', 2, 3, 2, 2, -1}) print(list(f)) # [1, 2, 3, -1] f = filter(lambda x: True if x % 5 == 0 else False, range(1, 101)) print(list(f)) f = filter(lambda x: x % 5 == 0, range(1, 101)) print(list(f))
functools. reduce(function, sequence, initial=_initial_missing)
标注
参数
func:函数
a.func函数必须设置两个参数,设置1个返回值
seq:序列【容器】
initial
设置初始值,位于序列之前一同运算
功能
减少
首先将seq中的第0个元素和第1个元素传递给func,进行运算,返回结果1
接着,将 结果1 和第2个元素传递给func,进行运算,返回结果2
接着,将 结果2 和第3个元素传递给func,进行运算,返回结果3
....
直到所有的元素全部参与运算,表示运算结束
返回值
value
reduce 的命名反映了函数式编程中“归约”的思想,即通过逐步合并序列的元素,最终生成一个结果。
使用示例
from functools import reduce value = reduce(lambda x, y: x+y, [1, 2, 3], 9) print(value) value = reduce(lambda x, y: x+y, range(1, 101)) # 1~100之间所有整数的和 print(value) value = reduce(lambda x, y: x*y, range(1, 16)) # 15! print(value) value = reduce(lambda x, y: x*10+y, [3, 1, 7, 4]) print(value) # 3174
sorted(iterable,reverse,key=func)
【面试题】lst.sort()和sorted()之间的区别 1.二者的调用方式不同 lst.sort(reverse,key),sorted(lst,reverse,key) 2.返回值不同 lst.sort()返回None,是在原列表内部直接排序的 sorted()返回一个新的列表,对原列表没有任何影响 3.二者默认情况下都是升序,如果要降序,都是设置reverse=True 4.sort只能列表调用,但是sorted可以适用于任意的可迭代对象 5.二者默认的情况下,都只针对列表中的元素可以比较大小的情况,如果要自定义排序规则,则都要设置key=func
list1 = [1, 2, 2, 3, 4, 0] r = sorted(list1) # 返回排序后新的列表,对原列表不受影响。 print(list1, r) # [1, 2, 2, 3, 4, 0] [0, 1, 2, 2, 3, 4] list1.sort() # 在列表内部排序 print(list1) # [0, 1, 2, 2, 3, 4] set1 = set(list1) print(set1) r = sorted(set1) # 可以对容器排序, 返回的结果依然是列表 print(r) # [0, 1, 2, 3, 4]
参数
iterable
reverse
是否翻转,默认False(升序)
key=func
自定义排序规则
功能
对可迭代对象进行排序
返回值
list
无论对哪种容器进行排序
排序结果会存储在新列表中
使用示例
students = [ {'name': '小花', 'age': 19, 'score': 90, 'gender': '女', 'tel': '15300022839'}, {'name': '明明', 'age': 20, 'score': 40, 'gender': '男', 'tel': '15300022838'}, {'name': '华仔', 'age': 18, 'score': 90, 'gender': '女', 'tel': '15300022839'}, {'name': '静静', 'age': 16, 'score': 90, 'gender': '不明', 'tel': '15300022428'}, {'name': 'Tom', 'age': 17, 'score': 59, 'gender': '不明', 'tel': '15300022839'}, {'name': 'Bob', 'age': 18, 'score': 90, 'gender': '男', 'tel': '15300022839'} ]
# 对成绩进行降序 new_students1 = sorted(students, reverse=True, key=lambda dic: dic['score']) print(new_students1)
函数递归
函数递归:函数调用自身
处理递归的关键:
a.需要找到一个临界值【让程序停止下来的条件】
def f(): print('aaaaaaaa') f() # 恶意调用 f() # RecursionError: maximum recursion depth exceeded while calling a Python object
b.函数相邻两次调用之间的关系
递归示例
# 斐波那契数列 def f(n): print(f'{n}'.center(50, '-')) if n == 1 or n == 2: return 1 else: return f(n-1) + f(n-2) # 相邻两次调用之间的关系 r = f(10) print(r) # 55 r = f(6) print(r) # 8 # print(f(100)) # 指数增长,运行效率很低
# 1~100之间所有整数的和 def f(n): if n == 1: # 临界值 return 1 else: return f(n-1) + n # 相邻两次调用之间的关系 print(f(10))
注意:在实际应用中,不推荐使用递归,相比于循环,递归的效率较低
python库 包和 模块(module)
第三方Python库
模块,也被称为库,其实相当于是一个工具
Python中的模块/库分为三大类
a.系统模块,特点:只需要import导入就可以使用,如:random math string csv pickle json等
b.自定义模块,特点:需要自己创建py文件,在该文件自定义函数或类
c.第三方模块,特点:需要先安装,然后再import导入使用,如:openpyxl docx requests bs4等
如何安装第三方库
方式一:在pycharm直接添加
windows:File---->Settings---->Project:xxx---->Python Intepreter----> +【install】--->搜索----》install Package
Mac:Pycharm--->Perference---->Project:xxx---->Python Intepreter----> +【install】--->搜索----》install Package
方式二:在cmd中
命令提示符
Windows:pip install xxx
不指定默认最新版本
pip install openpyxl==3.1.1
指定版本
pip install openpyxl --upgrade --user
更新库
Mac:pip3 install xxx
方式三:Terminal【作用等同于cmd】
终端
Windows:pip install xxx
Mac:pip3 install xxx
问题说明
问题一:pip不是内部或外部的命令
原因:没有配置环境变量
解决方案:将Python或Anaconda对应的路径全部添加到环境变量中
D:\software\Anaconda D:\software\Anaconda\Library\mingw-w64\bin D:\software\Anaconda\Library\usr\bin D:\software\Anaconda\Library\bin D:\software\Anaconda\Scripts
问题二:timed out
原因:网络不好
下载源为国外网站,下载速度慢
解决方案:切换网络 或者 切换镜像【pip install xxx -i 镜像】
命令:pip install pandas -i https://pypi.douban.com/simple/
国内 pip 镜像源包括但不限于以下几种:
阿里云Python镜像源:https://mirrors.aliyun.com/pypi/simple/
豆瓣Python镜像源:https://pypi.douban.com/simple/
清华大学Python镜像源:https://pypi.tuna.tsinghua.edu.cn/simple/
中国科学技术大学Python镜像源:http://pypi.mirrors.ustc.edu.cn/simple/
华中科技大学Python镜像源:http://pypi.hustunique.com/
安装第三方库工具
pip
Python的包管理工具,可通过DOS指令 从仓库中进行安装和更新Python包 以及对已安装的包进行卸载和管理等
pip install numpy -i
引入
import pandas
开源与闭源
开源
Copyright (c) 2010-2021 openpyxl
Copyright (c) 2010-2021 表示版权归属的年份范围(从2010年到2021年)
openpyxl 是版权所有者,这是一个用于读写Excel文件的Python库
Python标准库
系统模块
模块导入 import
模块的搜索路径
当使用 import 导入模块时, Python 会按以下顺序查找模块 (即 sys.path 列表的顺序):
当前脚本所在目录(空字符串 '' 表示当前目录)
若脚本位于 /home/user/project/main.py,则优先搜索 /home/user/project/。
环境变量 PYTHONPATH 中的路径
用户或系统配置的额外路径(类似 PATH 环境变量)。
Python 标准库路径(lib)
如 /usr/lib/python3.9/(Linux)或 C:\Python39\Lib\(Windows)。
第三方库路径(site-packages)
如 /usr/lib/python3.9/site-packages/ 或 ~/.local/lib/python3.9/site-packages/。
不在脚本所在目录 自定义模块 不在搜索路径中
方法一:引入不在搜索路径中的模块
append()
方法二: 模块路径导入
import modul1, 模块路径, ...
from 模块路径 import *
项目下的路径:
mypackage.base_module
模块路径
只需要观察当前py文件与要被导入的py文件关系即可。
模块导入的两种方式
import 模块名
直接导入整个模块
方式一
import modul1 import modul2 ...
方式二
import modul1, modul2, ...
不推荐
一个模块只会被导入一次。 模块中的函数需要使用模块名称来访问。
如:random.randint()
from 模块名 import 变量名/函数名/类名, name2, ...
导入模块中的功能
从其他模块中导入部分功能到当前命名空间中。
导入的函数等数据可直接访问
如:randint()
缺点:
没导入的没法访问
会出现命名冲突。
后面的会覆盖掉前面的
from ... import *
将模块中的变量、函数、类和方法全部导入。
不推荐,易引起命名冲突。
重命名
as
项目结构
项目库
包(package) —— modul __init__.py
也是一个模块
包是一种管理 Python 模块命名空间的形式, 采用"点模块名称"
就好像使用模块的时候,你不用担心不同模块之间的全局变量相互影响一样,采用点模块名称这种形式也不用担心不同库之间的模块重名的情况。
Python package本质是一个文件夹【目录】, 但是特殊之处在于在该目录下有一个文件,名为__init__.py
代表初始化,前期其中不写任何内容,后期会在其中书写项目的配置信息
注意:
a.创建包:选中工程-----》右键----》new --->Python Package,特点:其中会自动包含一个__init__.py文件
b.使用的过程中,包和普通文件夹的使用区别不大
c.点模块名称:本质上指的是路径,此时的路径也就是被当作模块的py文件的路径,包括包或文件夹,其中的点表示的是路径的层级关系
d.常说的模块本质上指的就是一个py文件
从包中导入模块
导入系统模块
导入自定义模块, 注意:一般情况下,导入模块的时候,实际包的概念已经包含在内了
自定义模块路径并未在Python的系统路径(os.path)或第三方库路径中。 导入时直接输入模块名Python找不到该模块。
导入时需要输入详细的路径信息
自定义 模块(modul)
可以不在包内
.py文件
注意:其实一个.py文件就是一个模块
模块名
应该遵循标识符的规则和规范
否则,导入时会出问题
不能与系统模块重名
否则,会导致系统模块失效
问题
目前代码比较少,写在一个文件中还体现不出什么缺点,但是随着代码量越来越多,代码就越来越难以维护。
解决方法
为了解决难以维护的问题,我们把很多相似功能的函数进行分组,分别放到不同的文件中。这样每个文件所包含的内容相对较少,而且对于每一个文件的大致功能可用文件名来体现。很多编程语言都是这么来组织代码结构。
优点:
提高代码的可维护性
提高了代码的复用度,当一个模块书写完毕,可以被多个地方引用
引用其他的模块
避免函数名和变量名的冲突
作用
代码复用:将常用的功能封装到模块中,可以在多个程序中重复使用。
命名空间管理:模块可以避免命名冲突,不同模块中的同名函数或变量不会互相干扰。
代码组织:将代码按功能划分到不同的模块中,使程序结构更清晰。
程序运行
模块中的可执行代码
只有在第一次被导入时才被执行。
使代码块只在 自身运行时执行
if __name__ == "__main__": print(f"{__name__}:作为主程序运行") else: i = 0 i += 1 print(f" {__name__}已作为模块导入:{i}次")
__name__ 属性
自身直接运行时,值为__main__
表示当前模块作为主程序运行,
被导入到其他模块,值为模块名
符号表
每个模块有各自独立的符号表。
内置函数
dir()
查询模块中定义的所有名称。 返回字符串列表
面向对象编程基础
概念
面向对象的设计思想
面向对象是基于万物皆对象这个哲学观点,在Python中,一切皆对象。
举例说明:
案例一:我想要吃大盘鸡 面向过程 面向对象 1.自己去买菜 1.委托一个会砍价的人帮忙去买菜 2.自己择菜 2.委托一个临时工帮忙择菜 3.自己做菜 3.委托一个厨师帮忙做菜 4.自己开始吃 4.自己开始吃
案例二:小明是一个电脑小白,想要配一台电脑,买完零件后需要运到家里,组装完成后打开电脑玩游戏 面向过程 面向对象 1.小明补充电脑知识 1.委托一个懂电脑的朋友(老王)去帮忙买零件 2.小明去买零件 2.委托一个能跑腿的人将零件运送到家里 3.小明把零件带回家里 3.委托一个会组装电脑的人帮小明组装电脑 4.小明组装电脑 4.小明自己打开电脑,开始玩游戏 5.小明开机玩电脑
面向过程和面向对象
面向过程
在生活案例中:
一种看待问题的思维方式,在思考问题的时候,着眼于问题是怎样一步一步解决的,然后亲力亲为的去解决问题
在程序中:
1》代码从上而下顺序执行
2》每一模块内部均是由顺序、选择和循环三种基本结构组成
3》程序流程在写程序时就已决定
面向对象
在生活案例中:
也是一种看待问题的思维方式,着眼于找到【一个具有特殊功能的具体个体,然后委托这个个体去做某件事情】,我们把这个个体就叫做对象,一切皆对象
是一种更符合人类思考习惯的思想【懒人思想】,可以将复杂的事情简单化,将程序员从执行者角度转换成了指挥者角度
在程序中:
把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象
1》对同类对象抽象出其共性,形成类
2》类中的大多数数据,只能用本类的方法进行处理
3》程序流程由用户在使用中决定
4》使用面向对象进行开发,先要去找具有所需功能的对象,如果该对象不存在,那么创建一个具有该功能的对象
注意:面向对象只是一种思想,不是一门编程语言,也不会绑定编程语言
面向过程和面向对象 的优缺点【面试题】
面向过程:
优点:性能较高,比如单片机,嵌入式开发等一般采用的是面向过程的方式,因为性能是最重要的因素
缺点:没有面向对象易于维护,易于复用,易于扩展,开销比较大,比较消耗资源
面向对象:
优点:易于维护,易于复用,易于扩展,因为面向对象有封装、继承和多态的特征,可以涉及出低耦合的系统,使得系统更加灵活。
缺点:性能较低
使用面向对象解决问题,其中的核心内容:
类和对象
类和对象【重点掌握】
概念
类:一个具有特殊功能的实体的集合【群体】,是抽象的概念
如:学生
对象:在一个类中,一个具有特殊功能的实体,能够帮忙解决特定的问题【对象也被称为实例】,是具体的存在
如:小明同学
两者之间的关系:
类用于描述某一类对象的共同特征,而对象则是类的具体存在
问题:先有对象还是先有类?
先有对象,再有类-----》将多个具有共同特征的对象,抽取一个类出来
先有类,再有对象----》在代码中,一般都是先定义类,通过类创建对象,
实际应用
举例:
帮助理解:类也是一种数据类型,只不过是自定义的,用于描述生活中的一些事物,且Python中并没有提供这些类型,跟学过的intAct,float,str。。。。类似,用类创建对象则相当于定义一个类的变量
类的定义
格式:
class 类名(): 类体
# ()可以省略
说明:
a.Python中使用class关键字定义类
b.类名只要是一个合法的标识符即可,但是要求:遵循大驼峰命名法【每个单词的首字母大写】 ,如:KeyError,ValueError,NameError,IndexError…….
c.尽量使用单个或多个有意义的单词连接而成,类名中一般不使用下划线
类的声明
d.通过缩进来体现类体的存在
e.类体一般包含两部分内容:对类的特征描述和行为描述
类的实现
f.类的包含两部分:类的声明和类的实现
定义完成,即加载
a.类和函数相比,函数必须调用才能执行其中的代码,但是类只要定义完毕,其中的内容就会被加载一遍
b. 在同一个py文件中,可以定义多个类, 但是,如果要实现的需求较为复杂,一般会结合模块使用, 在一个模块中定义一个类
类体
特征描述:变量
类中的属性【重点掌握】
行为描述:函数
类中的函数【重点掌握】
关于self
a.self不是关键字,本质上可以是一个任意的标识符,但是使用self表示自己【self在Java中是关键字】
b.类中的函数,默认的情况下,形参列表的第一个参数都是self
是类中方法与函数的特殊区别。
c.self表示当前对象,哪个对象调用该函数,则self表示哪个对象
d.当调用函数的时候,self无需手动传参,会自动将当前对象传参给self,只需要注意自己定义的参数的传参即可
用类创建对象
语法:
变量 = 类名()
实例化
obj = MyClass()
此处的()不能省略
自动调用构造方法。
说明
e.同一个类,默认情况下,可以创建无数个对象,每个对象都会被分配不同的地址
f. 直接输出对象,默认的情况下,会得到一个地址
通过所创建的对象 访问类的特征和行为
对变量进行访问和修改
对象.属性
亦可在外部定义属性并赋值
对象属性的动态绑定
对函数进行调用
对象.函数(实参)
类的设计
只需要关心 3个要素
事物名称(类名):人类(Person)
特征:身高(height)、年龄(age)—————》名词———》变量
行为:跑(run)、打架(fight)———————》动词————》函数
初期学习,通过提炼动名词进行类的提取
类中的属性【重点掌握】
Property
类属性【类的字段】
定义
类属性直接定义在类中
变量赋值
在内存中出现的时机
类属性随着类的加载而出现
使用场景
多个对象共享的数据
访问
访问方式
类属性可以通过类名或对象访问
Person.place
self.place
访问优先级
低
重名时,类属性被实例属性覆盖。
可以用类名访问。
实例属性 【对象属性,对象的字段】
定义
在__init__中或在类的外面直接动态绑定定义
对象.属性 = 值
在内存中出现的时机
对象创建完毕之后才会出现
使用场景
每个对象特有的数据
访问
访问方式
实例属性只能通过对象访问
self.name
访问优先级
高
重名时,实例属性优先访问。
属性的动态绑定 和限制绑定
class Doctor: __slots__ = ('name', 'age', 'kind', 'id') def __init__(self, name, age): self.name = name self.age = age doc = Doctor('张大富', 35) doc.kind = '外科' doc0 = Doctor('cj', 65) doc0.id = 250 print(doc.age, doc.age, doc.kind) print(doc0.age, doc0.age, doc0.id)
对象属性的动态绑定
doc = Doctor('张大富', 35) doc.kind = '外科' # 类中没有此属性
只要是 对象.属性 = 值 类似这样的语法,都是给对象动态绑定属性, 在默认情况下,对于属性的绑定没有任何限制
限制对象属性的动态绑定
__slots__ = ('name', 'age', 'kind')
用__slots__限制对象属性的动态绑定,定义一个元组,将属性名以字符串的形式书写在元组中, 一般是结合实际需求或实际情况确定
【面试题】简述类属性【类的字段】和 实例属性【对象属性,对象的字段】的区别
1.定义位置不同:类属性直接定义在类中,只要是动态绑定的属性都是实例属性【在__init__中或在类的外面直接动态绑定定义】
2.访问方式不同:类属性可以通过类名或对象访问,而实例属性只能通过对象访问
3.访问优先级不同:当类属性和实例属性重名时,通过对象访问,优先访问的是实例属性
4.在内存中出现的时机不同:类属性优先于实例属性出现在内存中,类属性随着类的加载而出现,实例属性是对象创建完毕之后才会出现
5.使用场景不同:类属性用于表示多个对象共享的数据,实例属性表示每个对象特有的数据
类中的函数【重点掌握】
构造函数
构造函数的工作原理
class Person(): def __new__(cls, *args, **kwargs): # 1. 创建对象并返回 print('new running') print(cls) return super().__new__(cls) # def __init__(self): # 2. 将对象初始化 print('init running') print(self) p = Person()
构造函数:包括__new__和__init__
在Python中,以__xxx__方式命名的函数,被称为魔术函数/魔术方法, 该类函数都是在特定的场景下被自动调用的,无需手动调用
__new__:从无到有的过程,表示真正意义上创建对象
__init__:初始化的过程,表示将__new__创建出来的对象进行初始化
代码执行顺序: 当x = 类名()创建对象的时候,
首先会自动调用__new__,创建出来一个对象,且将该对象返回,
同时自动将该对象传递给__init__,对该对象完成初始化
构造函数常用的形式
一般在创建对象的时候,倾向于将对象创建完有初始状态的【初始状态:创建对象的同时进行特征的描述,或者定义属性】
class Person(): def __init__(self, name, age): self.name = name # 将传入的参数赋值给对象的相应的属性 self.age = age def show(self): print(self.name, self.age) p0 = Person('cj', 18) # 传入初始化所需要的参数 p0.show() p1 = Person('mom', 45) p1.show()
# 注意:当类中定义了__init__,创建对象的时候,一定要注意参数保持一致
实例函数
定义:第一个形参是self,self表示当前对象
调用
只能通过对象调用
内部可以调用其他函数
类函数
定义:用@classmethod装饰器装饰,第一个形参是cls,cls是class的缩写,表示当前类
调用
类名或对象调用
内部可以调用其他函数
调用实例函数时,须将cls实例化
静态函数
定义:用@staticmethod装饰器装饰,参数没有特别之处
调用
类名或对象调用
内部无法调用其他函数
函数类型
class Book: __slots__ = ('book_name', 'author') # 定义 # 1. 实例函数 def __init__(self, book_name, author): self.book_name = book_name self.author = author # 实例函数内部调用 self.show() # 调用实例函数 self.f0() # 调用类函数 self.st_method() # 调用静态函数 print('构造函数运行完毕'.center(50, '*')) def show(self): print('实例函数', self.book_name, self.author) print('实例函数show运行完毕'.center(50, '*')) # 2. 类函数 @classmethod def class_method(cls): print('类函数开始运行', cls) # 类函数内部调用 cls.f0() # 调用类函数 cls('类书名', 'cj').show() # 先实例化,再调用实例函数 cls.st_method() # 调用静态函数 print('类函数class_method运行完毕'.center(50, '*')) @classmethod def f0(cls): print('类函数f0', cls) # 3. 静态函数 @staticmethod def st_method(): print('静态函数') b = Book('逆流成河', 'cj') # 调用 # 1. 调用实例函数 b.show() # 2. 调用类函数 Book.class_method() # 不需要实例化 b.class_method() # 3. 调用静态函数 Book.st_method() # 不需要实例化 b.st_method()
析构函数
析构函数:__del__, 对象被销毁的时候会自动调用的函数,
对象被销毁的时机
a.程序执行完毕(全局变量),对象的声明周期完成(局部变量)
b.手动销毁对象,语法:del xxx
面向对象编程进阶
面向对象 三大特征
面向对象的三大特征:封装,继承和多态
1.封装
广义的封装:
函数的定义和类的定义
狭义的封装:
一个类中的某些属性,如果不希望被外界直接访问,则可以将该属性私有化,该属性只能在当前类中被直接访问,如果在类的外面需要访问【获取或修改】,则可以通过暴露给外界的函数间接访问
封装的本质:
将类中的属性进行私有化
【面试题】解释下面不同形式 的变量出现在类中的意义
a:
普通属性,也被称为公开属性,在类的外面可以直接访问 ****
_a:
在类的外面可以直接访问,但是不建议使用,容易和私有属性混淆
__a:
私有属性,只能在类的内部被直接访问。类的外面可以通过暴露给外界的函数访问 *****
属性私有化
class Animal(): def __init__(self, name, age, information='癌症'): self.name = name self.age = age self.__information = information # 属性私有化(封装) def show(self): print(self.name, self.age) # 内部访问公开属性 print(self.__information) # 内部访问私有属性 return self.__information def update(self, infor): self.__information = infor # 对私有属性进行修改 a = Animal('大黄', 8) print(a.name, a.age) # 外部访问公开属性,私有属性无法直接访问 p = a.show() # 通过函数间接访问私有属性 print(p) a.update('正常') # 从外部完成对私有属性的修改 print(a.show())
__a__:
在类的外面可以直接访问,但是不建议自定义使用,因为系统属性和魔术方法都是这种形式的命名,
如:__slots__ __init__ __new__ __del__,__name__,__add__,__sub__,__mul__等
使用装饰器 访问私有属性
@property
装饰获取私有化属性的函数,并将函数处理成属性使用(数据类型为str)。
建议被修饰函数名为私有化的属性名
@xxx.setter
xxx: 一定得是被@property装饰的函数名
@information.setter
用来装饰修改私有化属性的函数,将该函数处理成属性使用
建议命名为被私有化的属性的属性名
示例
class Animal(): def __init__(self, name, age, infor='癌症'): self.name = name self.age = age self.__infor = infor # 属性私有化(封装) @property # 装饰获取私有化属性的函数,并将函数处理成属性使用(数据类型为str)。 def infor(self): # 建议被修饰函数名为私有化的属性名 return self.__infor @infor.setter # 用来装饰修改私有化属性的函数,将该函数处理成属性使用 def infor(self, infor): # 建议被修饰函数名为私有化的属性名 self.__infor = infor # 对私有属性进行修改 a = Animal('大黄', 3) print(a.infor, type(a.infor)) # 癌症 <class 'str'> a.infor = '正常' # 以赋值的方式修改 print(a.infor, type(a.infor)) # 正常 <class 'str'>
2.继承
概念
如果两个或者两个以上的类具有相同的属性和方法,我们可以抽取一个类出来,在抽取出来的类中声明各个类公共的部分
被抽取出来的类——父类【father class】 / 超类【super class】/ 基类【base class】
两个或两个以上的类——子类 / 派生类
他们之间的关系——子类 继承自 父类 或者 父类 派生了 子类
简单来说,
一个子类只有一个父类,被称为单继承
一个子类有多个父类,被称为多继承
注意:object是Python中所有类的根类
定义
单继承
class 子类类名(父类类名): 类体
多继承
class 子类类名(父类类名1, 父类类名2.......): 类体
子类构造函数调用 父类的构造函数
方式一:
super(当前类,self).__init__(参数列表)
super(Doctor,self).__init__(name,age)
方式二:
super().__init__(参数列表)
super().__init__(name,age)
方式三:
多继承适用
父类类名.__init__(self,参数列表)
Person.__init__(self, name, age)
创建对象
单继承
没有自定义构造函数,则调用父类的构造函数
自定义构造函数添加实例属性
方式二:
多继承
没有自定义构造函数,则调用第一个父类的构造函数
自定义构造函数添加实例属性
方式三:
示例
# 1. 单继承 class Person(): def __init__(self, name, age): self.name = name self.age = age def show(self): print(self) class Student(Person): def study(self): print('study') class Teacher(Person): def __init__(self, name, age: int, subject): # 自己定义构造函数 # super().__init__(name, age) # 子类构造函数调用父类的构造函数,并将共同的参数传递给它 # print(super()) # <super: <class 'Teacher'>, <Teacher object>> Person.__init__(self, name, age) self.subject = subject s = Student('bob', 12) # 调用父类的构造函数 # s.study() t = Teacher('cj', 25, '数学') # 调用自己定义的构造函数 print(t.name, t.age) t.show() # 2. 多继承============================================================================================================== class Flyable: def __init__(self, a, b): self.a = a self.b = b def fly(self): print(self, '飞行中') class Walkable: def __init__(self, c, d): self.d = d self.c = c def walk(self): print(self, '行走中') class Bird(Flyable, Walkable): def __init__(self, a, b, c, d, e, f): Flyable.__init__(self, a, b) Walkable.__init__(self, c, d) self.e = e self.f = f def show(self): print(self.a, self.b, self.c, self.d, self.e, self.f) bi = Bird(1, 2, 3, 4, 5, 6) bi.fly() bi.walk() bi.show()
3. 多态
多态的前提:继承
体现1:同一种事物的多种体现形式,如:动物有很多种
体现2:
在定义的过程,无法确定变量的类型以及调用哪个函数
只有当程序正常运行的时候,才会确定该变量是什么类型 ,调用哪个函数
示例
# 同一事物的多种体现形式 class Animal(): pass class Cat(Animal): pass class SmallCat(Cat): pass sc = SmallCat() # sc既是小猫又是猫又是动物 print(type(sc)) # <class '__main__.SmallCat'> print(isinstance(sc, SmallCat)) print(isinstance(sc, Cat)) print(isinstance(sc, Animal)) print(isinstance(sc, object)) # 体现2:在定义的过程无法确定变量的类型,只有当程序正常运行的时候才会确定该变量是什么类型,调用哪个函数 class Animal(): def movement_mode(self): print(self, '行走') class Cat(Animal): pass class Dog(Animal): pass class Pig(Animal): pass class Bird(Animal): def movement_mode(self): print(self, '飞行') class Fish(Animal): def movement_mode(self): print(self, '游泳') def f(ani): # ani有多种类型的体现形式,所以此处是多态的体现 ani.movement_mode() # 无法确定变量的类型,不清楚会调用哪个函数 cat = Cat() f(cat) fish = Fish() f(fish)
重写与重载
函数重写【重点掌握】
重写:override,
在继承的前提下,子类中重新实现了父类中的函数
注意:
1.什么时候需要重写函数
如果一个类有很多子类,大多数子类可以直接使用父类中实现的功能
但是,如果父类中实现的需求满足不了部分子类的使用,则需要在子类中重写函数
2.重写需要注意的事项
保留函数的声明部分:def xxx(self,形参列表)
重新实现函数的实现部分【函数体】
自定义函数的重写
系统函数的重写
当输出对象时
默认会调用魔术函数__str__, __str__是object中的函数,该函数默认返回当前对象在计算机中的地址
当重写__str__时,返回值必须是一个字符串,表示一个对象的字符串描述信息,一般是和当前对象相关的属性信息
当将对象作为元素存储在列表等容器中时, 输出列表,元素仍然以地址的形式呈现,
则重写__repr__
方式一:只重写__repr__
def __repr__(self): return f'name:{self.name},age:{self.age}'
方式二:
def __str__(self): return f'name:{self.name},age:{self.age}' __repr__ = __str__
当子类中重写了父类中的函数,创建子类对象调用函数,优先调用的是子类中的函数
子类函数中调用父类中的函数
子类构造函数调用 父类的构造函数
用法相同
运算符重载
运算符
重载:overload
不支持指定的运算,通过重载让支持运算
+
除了数字之外,其他数据类型但凡支持+的运算,底层都是调用了__add__
如果一个类不支持+,则可以在类中重载__add__
其他运算符对应的魔术函数
子主题
比较运算符
> ---->__gt__ greater than < ----> __lt__ less than == ---> __eq__ equal != ---> __ne__ not equal >= ---> __ge__ <= --->__le__
子主题
对象的内置内容
1.内置属性
__slots__: 限制对象属性的动态绑定
__dict__: 获取类或对象的所有信息【属性和函数】,返回一个字典 ****
__module__;获取指定对象属于哪个模块,
如果时当前模块,则结果为__main__,
如果是其他模块,则结果为模块名
__name__: 可以用来判断正在执行的是否是当前文件
若为当前文件值为'__main__'
如果是模块名则表示运行的是其他文件
__class__:类似于type(),获取指定对象的数据类型
2.内置函数
id(): 获取一个对象的内存地址
type(): 获取一个对象的数据类型
精准匹配,不考虑继承关系
isinstance(): 判断一个对象的数据类型是否是指定类型
包括继承关系
issubclass(类1,类2):判断类1和类2直接是否具有继承关系
程序执行的入口
run
类之外的代码
默认情况下,在其他模块中导入该模块,类之外的代码也会一起执行。
main
# 适用的场景: 当作代码的规范,认为是程序执行的入口 if __name__ == '__main__': print(__name__) # 类之外的代码 pass
此时若程序不在该模块执行,则里面的代码不会执行
单例设计模式
概念
什么是设计模式?
设计模式是经过总结、优化的,对我们经常会碰到的一些编程问题的可重用解决方案
设计模式更为高级,它是一种必须在特定情形下实现的一种方法模板。设计模式不会绑定具体的编程语言
23种设计模式,其中比较常用的是单例设计模式,工厂设计模式,代理模式,装饰者模式等等
什么是单例设计模式?
单例:单个实例/单个对象,一个类只能创建一个对象,只能创建出一个对象的类被称为单例类
程序运行过程中,确保某一个类只有一个实例【对象】,不管在哪个模块获取这个类的对象,获取到的都是同一个对象。例如:一个国家只有一个主席,不管他在哪
单例设计模式的核心:一个类有且仅有一个实例,并且这个实例需要应用于整个程序中,该类被称为单例类
问题:验证两个变量中是否存储的是同一个对象
解决:地址
方式一:x1 is x2
方式二:id(x1) == id(x2)
应用场景
应用程序中描述当前使用用户对应的类 ———> 当前用户对于该应用程序的操作而言是唯一的——> 所以一般将该对象设计为单例
实际应用:数据库连接池操作 ——> 应用程序中多处地方连接到数据库 ———> 连接数据库时的连接池只需一个就行,没有必要在每个地方都创建一个新的连接池,这种也是浪费资源 ————> 解决方案也是单例
实现
步骤
定义私有变量用于存储该单一实例
变量为空时,创建一个对象并赋值,其他情况下不创建对象
将变量返回
实现单例类方式一
异常和错误
异常和错误
1.概念
Python有两种错误很容易辨认:语法错误和异常
语法错误
Python 的语法错误或者称之为解析错误,是初学者经常碰到的,比如缺少冒号等
异常
一般情况下,当程序运行的时候出现的错误被称为异常【Exception】
在程序运行过程中, 总会遇到各种各样的错误,
有的错误是程序编写有问题造成的,这种错误我们通常称之为bug,bug是必须修复的;
有的错误是用户输入造成的,这种错误可以通过检查用户输入来做相应的处理;
还有一类错误是完全无法在程序运行过程中预测的,比如写入文件的时候,磁盘满了,写不进去了,或者从网络抓取数据,网络突然断掉了,这类错误被称为异常,
注意:
b.异常一般使用类表示,比如:IndexError表示索引越界的异常类【列表,元组,字符串】
c.所有异常类的父类是BaseException或者Exception
d.异常的特点:
当程序运行的过程中遇到了异常,而且该异常未被处理,程序会被终止在异常的代码处,后面的代码将没有执行的机会
e.在Python中,处理异常的思想:
暂时跳过异常的代码,让后面的代码有执行的机会
f.异常在代码中的出现按只是一种可能性
2.常见异常【面试题】
SyntaxError
Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
AttributeError
试图访问一个对象没有的属性,比如foo.x,但是foo没有属性x
IOError 输入/输出异常;基本上是无法打开文件
ImportError
无法引入模块或包;基本上是路径问题或名称错误
IndentationError 语法错误(的子类) ;代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 试图访问字典里不存在的键
NameError
使用一个还未被赋予对象的变量
TypeError
传入对象类型与要求的不符合
UnboundLocalError
试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它
ValueError
传入一个调用者不期望的值,即使值的类型是正确的
MoudleNotFoundError
导入模块的时候,路径有误
FileNotFoundError
文件路径不存在
KeyboardInterrupt
进程被强制中断
3.异常处理方式
处理异常的本质:没有从根本上解决问题【修改代码】,只是将异常忽略,可以让后面的代码继续执行
try-except-finally/else 捕获【重点掌握】
try: 代码1 except 异常码 as e: 代码2 finally: 最终执行代码3
try: print(next(it)) except StopIteration: break
注意:
a.将可能存在异常的代码书写在 【代码1】处,监测起来
b.如果【代码1】出现异常,肯定会匹配执行相应的【代码2】, 如果【代码1】没有异常,则所有的except会被跳过
c.不管【代码1】是否有异常,【代码3】永远都会执行
d.except出现一次,且明确了异常码,则只能处理一种异常
e.当try中的代码出现异常,同样,try中的代码会终止在异常处,后面的代码无法执行
不同的书写形式
a.try-except
try: num = int(input('请输入一个数字:')) print(num) except ValueError as e: # 此处的e是一个对象,该对象对应的类是ValueError,当代码出现了异常,则异常的对象就会被自动创建出来 print(ValueError, e, type(e), sep='\n') # 在异常类中,已经重写了__str__,所以当输出对象的时候,不是默认的地址,而是错误描述信息
b.try-except-else
try-except中的else执行的时机:只有当try中的代码没有异常的时候,else代码块才会被执行
c.try-except-finally
d.try-finally
finally
try-except中的finally执行的时机:不管try中的代码是否有异常,finally代码块都会被执行
try或except代码块中出现了return,finally语句仍然会正常执行
相似地,else中的语句不会执行
finally只能写在最后
e.try-except-except......
类似于if的多分支,如果try中的代码出现异常,会从上往下依次匹配相应的except语句
try中如果存在多种异常,都会处理其中的一个异常
如果try中代码不确定异常的类型
可以直接使用父类捕获,任何类型的异常都可以捕获
print('start~~~') try: list1 = [4, 5, 7, 8] num = int(input('请输入一个数字:')) print(list1[num]) except BaseException as e: # 父类 print('BaseException:', e) except ValueError as e: print('ValueError:', e) except IndexError as e: print('IndexError:', e) print('end~~~~~~')
省略异常的类型
print('start~~~') try: list1 = [4, 5, 7, 8] num = int(input('请输入一个数字:')) print(list1[num]) except: print('出现异常,异常信息无法获得') print('end~~~~~~')
raise 抛出
将异常对象抛出
注意:raise常用于自定义异常中
程序执行到此会主动报错。
语法:raise 异常类('描述信息')
raise IndexError('索引越界问题。')
assert 断言
表达式为False,报错
语法:assert 表达式, 异常描述信息
工作原理:
如果表达式成立,则代码正常执行,
但是如果表达式不成立,则会出现AssertionError
会产生异常
自定义异常
为了解决实际生活中的特定场景,系统的异常类满足不了需求,则需要自定义异常
定义步骤
自定义一个类,继承自BaseException或Exception
按需要添加实例属性
# 2.书写构造函数,在其中定义实例属性 # def __init__(self, msg): # # 调用父类的构造函数,主要是为了使用异常类的机制 # super().__init__() # self.msg = msg # # # 3.重写__str__函数 # def __str__(self): # return self.msg
定义一个实例函数,用来解决出现的问题
主题
属性和方法
类型
函数、方法
变量、方法参数
命名空间与作用域
命名空间 ——模块
变量名、函数名、类名
官方
特性
不同命名空间相互独立,没有关系,因此可以重名。
通过导入使空间相连,此时不能重名。
三种命名空间
内置名称
builtins.py
全局名称
模块中定义的名称,记录模块变量
本模块以及导入模块中的类、全局变量和方法
局部名称
函数中定义的名称,记录函数变量。
函数的参数、局部变量和嵌套方法名
作用域
LEGB规则
内置B(内建作用域,Built-in)》 模块创建G(全局作用域,Global)》 类/闭包创建E(Enclosing)》 方法创建L(局部作用域,Local)》*
相同作用域不能重名,作用域不同可以重名。 作用域不同的属性和方法不能直接访问,需要特殊的调用方法。
生命周期
程序运行期间
函数调用期间
实例存在期间