python基础
python
- 在ubantu下使用./xxx.py运行py
- python官方中文文档
一、py基础知识点
1.在py文件前面加上
#!/user/bin/env python |
2.python2执行py出现ASCII码问题
在文件开头加
# encoding=utf-8 |
或者
# coding=utf-8 |
或者(官方推荐)
# _*_coding:utf-8_*_ |
3.py关键字
import keyword |
4.常用数据类型
- 查看类型
# 输出<class 'bool'> |
4.1 Numbers(数值类型)
int
1)二进制:0b+二进制数(注意是数字0不是字母o)
2)八进制:0/0o+八进制数
3)十进制:十进制数
4)十六进制:0x+十六进制数
long(python2的类型,python3可以通过int自动调整)
float
1)由整数和小数组成,如155.66
2)也可以使用科学计数法1.5566e2
complex(复数)
1)a+bj
2)a,b均为浮点数,complex(a, b)
4.2 bool(布尔类型)
- True
- False
- 可以当做int类型参加数学运算,使用issubclass(bool, int)判断bool是否是int子类
4.3 string(字符串str)
1)非原始字符串(\加上\或者字母表示转义,仅仅\表示续行符)
‘string’ |
“string” |
"""string""" |
'''string''' |
2)原始字符串(转义字符不生效)
r‘string’ |
r“string” |
r"""string""" |
r'''string''' |
3)字符串打印与多行关系
# 多行打印(也可以作为多行注释) |
4)字符串拼接
# 方式一:直接+拼接 |
5)字符串切片
概念:获取一个字符串的某个片段
# 获取一个字符 下标法获取 注意不能越界,i是字符串下标 |
注:步长大于0,从左到右,小于0,从右到左,不能从头部跳到尾部,从尾部跳到头部,即起始值与结束值要与方向保持一致
6)字符串方法
# 字符串查找的方法 |
4.4 List(列表)
1)概念:有序可变的集合
2)定义:
列表变量名 = [元素1, 元素2, …],列表的嵌套,即列表的元素可以为列表,但注意不能相互嵌套
列表生成式:
range(stop) : [0, 1, 2, …, stop-1];
range(start, stop[, step]) : [start, start+2*step, …],step步长默认为1
注:由于防止生成的列表没有被使用,python3做了一些改变,不会立即生成列表
列表推导式:从一个list推导出另一个list;映射解析:一对一变更;过滤:从多到少
语法:[表达式 for 变量 in 列表] 或 [表达式 for 变量 in 列表 if条件],其中for遍历可以有多个,表示多层循环遍历
# 列表推导式
nums = [1, 2, 3, 4, 5]
# resList = [num ** 2 for num in nums if num % 2 != 0]
# resList = [num ** 2 for num in nums for num2 in nums]
resList = list(num ** 2 for num in nums for num2 in nums)
print(resList)列表常规方法操作
# 列表添加操作
'''
append
作用:往列表最后追加一个元素
语法:list.append(object)
参数:object为添加的元素
返回值:None
注意:会修改原列表
'''
nums = [1, 2, 3, 4]
print(nums)
print(nums.append(5))
print(nums)
'''
insert
作用:往列表指定索引处追加一个元素
语法:list.insert(index, object)
参数:index为索引(下标),object为添加的元素
返回值:None
注意:会修改原列表
'''
nums = [1, 2, 3, 4]
print(nums)
print(nums.insert(1, 5))
print(nums)
'''
extend
作用:往列表拓展另一个可迭代序列
语法:list.extend(iterable)
参数:iterable为可迭代集合
字符串、列表、元组、等等
返回值:None
注意:会修改原列表,和append区别是两个集合的拼接
'''
nums = [1, 2, 3, 4]
nums2 = ["a", "b", "c"]
print(nums)
print(nums.extend(nums2))
print(nums)
# 乘法运算
nums = [1, 2, 3, 4]
print(nums * 3)
# 加法运算
nums1 = [1, 2, 3, 4]
nums2 = ["a", "b", "c"]
# nums3 = "abf" 不能拼接字符串
print(nums1 + nums2)
# 列表删除操作
'''
del
作用:可以删除一个指定元素或对象
语法:del 指定元素
参数:无
返回值:无
注意:可以删除整个列表、某个元素、一个变量
'''
nums = [1, 2, 3, 4, 5]
# 删除某个元素
del nums[0]
# 删除整个列表
del nums
print(nums)
# 删除一个变量
num = 666
del num
print(num)
'''
pop
作用:移除并返回列表中指定索引对应元素
语法:list.pop(index = -1)
参数:index默认是-1,即最后一个元素
返回值:被删除元素
注意:会直接修改原数组,注意越界
'''
nums = [1, 2, 3, 4, 5]
print(nums.pop(2))
print(nums)
'''
remove
作用:移除列表中指定元素
语法:list.remove(object)
参数:object是待移除的元素
返回值:None
注意:会直接修改原数组,如果不存在,报错,存在多个,删除最左边一个,注意循环内删除列表元素的坑
'''
nums = [1, 2, 2, 3, 4, 5]
print(nums.remove(2))
print(nums)
nums2 = [1, 2, 2, 3, 4, 5, 2]
for num in nums2:
print(num)
if num == 2:
nums2.remove(2)
print(nums2)
# 修改列表
'''
通过下标修改指定列表的指定索引处的值
'''
nums = [1, 2, 3, 4, 5]
nums[2] = 6
# 列表查询
# 获取单个,元素通过下标(索引)查询
nums = [1, 2, 3, 4, 5, 5, 2, 6, 6]
print(nums[2])
# 获取元素索引 list.index(value,start,stop)
print(nums.index(5, 5, 6))
# 获取指定元素个数 count()
print(nums.count(5))
# 切片与字符串一样
print(nums[1:4:1])
print(nums[-1:-4:-2])列表查询遍历操作:
1) 根据元素进行遍历
# for item in list:
values = ['a', 'b', 'c', 'd']
for v in values:
currentIndex = 0
print(v)
print(values.index(v, currentIndex))
currentIndex += 12) 根据索引进行遍历
# for index in range(len(list)):
values = ['a', 'b', 'c', 'd', 'e']
for index in range(len(values)):
print(index, values[index])3) 根据枚举对象进行遍历(了解)
values = ['a', 'b', 'c', 'd', 'e']
# 1.根据列表创建枚举对象
# 语法:enumerate(list, [start=0])
# print(list(enumerate(values)))
# 2.遍历枚举对象,可以直接遍历
for index, value in enumerate(values):
# for tupleValue in enumerate(values):
# print(tupleValue)
# print(tupleValue[0])
# print(tupleValue[1])
# index, value = tupleValue
print(index)
print(value)4) 根据迭代器进行遍历(了解)
l = [1, 2, 3, 5, 6]
# 创建迭代器对象
it = iter(l)
# 内部自动调整指针,指向下一个索引
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
# 迭代完成,继续迭代会报错StopIteration
print(next(it))
for v in it:
print(v)
print("===========")
# 不能多次迭代,只能重新创建迭代器对象
for v in it:
print(v)5) 迭代器
可迭代对象:能够被迭代的对象
判断方法:
from collections.abc import Iterable
isinstance(obj, Iterable)判断依据:能否作用于for in
迭代器是可以记录遍历位置的对象,从第一个元素开始,往后只能通过next()函数进行遍历,只能往前,不能往后
判断方法:
from collections.abc import Iterator
isinstance(obj, Iterator)判断依据:能否作用于next()函数
注意:迭代器也是可迭代对象,也可以使用for in
迭代器产生原因:
仅仅迭代到某个元素时才会处理该元素: 在此之前,元素可以不存在,在此之后,元素可以销毁,适用于无限大集合,例如斐波那契数列
提供了一个统一的访问集合的接口: 可以将所有可迭代对象转换成迭代器使用iter(Iterable)
iter(str)
iter(list)
iter(tuple)
iter(dict)
...注:迭代完成,继续迭代会报错StopIteration,使用for in,不能多次迭代,只能重新创建迭代器对象
6) 列表其他操作
# 判断元素是否在集合
# in 判断元素是否在列表中(对其他集合和字符串通用)
# not in 判断元素是否在列表中(对其他集合和字符串通用)
# 比较
'''
cmp(val1,val2)
内建函数
如果比较字符串,列表等集合,从左往右逐个比较
val1 > val2 1
val1 = val2 0
val1 < val2 -1
python3.x不支持
python3使用比较运算符比较
'''
# python3
print([1, 2] > [1, 3])
# 排序方式一:内建函数,返回一个列表,原来列表不变
# s = "happynewYear"
s = [1, 2, 5, 6, 8, 9]
# 升序,返回一个列表
res = sorted(s)
print(res)
# print(s)
# 降序
res1 = sorted(s, reverse=True)
print(res1)
s1 = [("dd1", 18), ("dd5", 16), ("dd2", 28), ("dd1", 15)]
# 第一个值的比较排序
print(sorted(s1))
def getKey(x):
return x[1]
# 第二个值来排序
print(sorted(s1,key=getKey, reverse=True))
# 排序方式二:更改对象本身,返回值为None
val = [11, 2, 45, 56]
print(val.sort(), val)
s = [("dd1", 18), ("dd5", 16), ("dd2", 28), ("dd1", 15)]
print(s.sort())
def getKey(x):
return x[1]
# 第二个元素降序排序
print(s.sort(key=getKey, reverse=True), s)
'''
乱序
import random
random.shuffle(list)
返回值 :None
'''
import random
l = [1, 5, 98, 6, 2]
res = random.shuffle(l)
print(res, l)
'''
反转
list.reverse()
返回值 :None
'''
l = [1, 5, 98, 6, 2]
res = l.reverse()
print(res, l)
# 切片反转,返回值反转,不会改变本身
res1 = l[::-1]
print(res1, l)
4.5 set(集合)
1)概念:无序的,不可随机访问的,不可重复的元素集合
- 可变集合set:可以增删改
- 不可变集合frozenset:不能增删改
2)可变集合定义:
集合变量名 = {元素1, 元素2, …};
集合变量名 = set(iterable), 即可以转换字符串、列表、元组、字典为set集合,字典时,只生成key的集合;
集合的推导式:集合变量名={推导式}或者set(推导式)
# 集合推导式
# set5 = set(x for x in range(10) if x % 2 == 0)
set5 = {x for x in range(10) if x % 2 == 0}
print(set5, type(set5))集合的嵌套,即元组的元素可以为元组,但注意不能相互嵌套
3) 不可变集合定义:
集合变量名 = frozenset(iterable), 即可以转换字符串、列表、元组、字典为set集合,字典时,只生成key的集合;
集合的推导式:集合变量名=frozenset(推导式)
fs = frozenset(x for x in range(10) if x % 2 == 0)
print(fs, type(fs))
4) 注意:
创建一个空集合时需要使用set()或者frozenset(),不能使用是s={},会被识别为dict字典
集合中的元素必须是可哈希的值:
如果一对象在自己的生命周期中有一哈希值(hash value),是不可变的,那么它是可哈希的(hashable)到,暂时认为是不可变类型
比如字典、列表是可变类型,元组、字符串是不可变类型
如果集合中的元素出现重复,则会被合并为一个
5) 集合常规操作方法:
a)可变集合:(元素为不可变类型,不能修改)
# 集合常用操作方法 |
b) 不可变集合常用方法:(不能增删改)
# 不可变集合查询 |
c) 集合之间交并差集判定操作:(可变集合与不可变集合做混合运算,返回值类型以运算符左侧类型为主)
s1 = {1, 2, 3, 5, 6, 9} |
4.6 Tuple(元组)
1)概念:有序不可变的集合
2)定义:
- 元组变量名 = (元素1, 元素2, …);
- 元组变量名 = (元素1, )只有一个元素时,需要写逗号;
- 元组变量名 = 元素1, 元素2, …;
- 元组变量名 = tuple(list);
- 元组的嵌套,即元组的元素可以为元组,但注意不能相互嵌套
3) 元组常规操作方法:
元组不能增加、删除、修改元素。
''' |
4.7 Dictory(字典)
1)概念:无序的可变的键值对集合
2)定义:
- 字典变量名 = {key1: value1, key2: value2, …};
- 类调用fromkeys,字典变量名 = dict.fromkeys(S, V=None),值不写默认为None,S可以是字符串,列表元组
- 字典对象调用fromkeys,字典变量名 = dictory.fromkeys(S, V=None),值不写默认为None,S可以是字符串,列表元组,dictory为字典实例化对象
- 字典的嵌套,即字典的元素可以为字典,但注意不能相互嵌套
- 字典的key不能重复,如果重复,后值覆盖前值;key必须是任意不可变类型,value可以是任意可变与不可变类型
3)字典常规操作方法:
# 字典常规操作 |
4.8 NoneType(空类型)
5.数据类型转换(强类型语言)
#转换为整数 |
6.py运算符
- 算术运算符(不同类型运算,自动精度提升,即类型上转换)
+ - * /(不像c++、java,除数为非整数,有小数部分) |
- 比较运算符
> < != <>(python2不等于) >= <= == is(比对唯一标识) x1 <= x <= x3(链式运算符) |
- 逻辑运算符
not and or # 非零非空为真,返回结果不一定是bool |
7.py输入输出
7.1 输入
- python2
res = raw_input('请输入:') # 将内容当做字符串 |
- python3
res = input('提示信息') # 等同于python2的raw_input('请输入:') |
7.2 输出
- python2
print value1 |
- python3
print(value1) |
格式控制符
%[(name)][flags][width][.precision]typecode # []表示可选
1) (name) : 选择指定的名称对应的值
englishSc = 100
mathSC = 60
print('数学成绩 :%(ms)d, 英语成绩:%(es)d' % ({'es': englishSc, 'ms': mathSC}))2) flags :
- 空:表示右对齐
- -:-表示左对齐
- 空格:‘ ’表示一个空格,在正数左侧填充一个空格,与负数对齐
- 0:表示用0填充
3) width:表示显示宽度
4) .precision: 表示小数点后精度
5) typecode
数值
| 格式符 | 含义 |
| :——: | :—————————————————————————————: |
| i/d | 将整数、浮点数转化成十进制 |
| o | 将整数转化成八进制 |
| x | 将整数转化成十六进制 |
| e | 将整数、浮点数转换成科学计数法 |
| E | 将整数、浮点数转换成科学计数法 |
| f | 将整数、浮点数转换成科学计数法(默认保留小数点后6位) |
| F | 将整数、浮点数转换成科学计数法(默认保留小数点后6位) |
| g | 自动调整将整数、浮点数转换成浮点型或科学计数法(超过6位数用科学计数法) |
| G | 自动调整将整数、浮点数转换成浮点型或科学计数法(超过6位数用科学计数法) |字符串
| 格式符 | 含义 |
| :——: | :—————————————————————————————: |
| s | 获取传入对象的_str_方法的返回值 |
| r | 获取传入对象的_repr_方法的返回值 |
| c | 整数:将数字转换成unicode对应的值 字符:将字符添加到指定为位置 |
注:没有将整数转为二进制,即%b; %%表示打印%
8.py分支
8.1 if分支
# 单分支 |
注:python没有switch语句
9.循环
while
while 条件体:
条件满足的代码块
# 没有dowhile,而且有break语句else就不会执行
else:
条件不满足代码块
for
# 与java增强for循环相同
# 变量不用先前声明
for 变量名 in 列表、字符串等:
条件满足循环体
# 没有dowhile,而且有break语句else就不会执行
else:
条件不满足循环体break: 打断本次循环,退出循环
- continue : 结束本次循环,继续进入下一次循环
- pass: 占位语句,空语句,保持程序结构完整性
注:rang(a, b)函数 —》[1,b-1]
10.时间日历
import time |
- 日期时间格式符
符号 | 说明 |
---|---|
%a | 当前区域设置下星期简写,如星期二Tue。 |
%A | 当前区域设置下星期全名,如星期二Tuesday。 |
%b | 当前区域设置下月份简写,如九月Sep。 |
%B | 当前区域设置下月份全名,如九月September。 |
%c | 当前区域设置下的日期和时间表示。 |
%C | 世纪,如2019年,21世纪,则返回20。 |
%d | 十进制数字表示的月份的某一天,范围[01,31]。 |
%D | 日期,等效于“%m/%d/%y”(美国格式),如“09/03/19” |
%e | 十进制数字表示的月份的某一天,范围[1,31]。如果小时10,则数字前用一个填充一个空格。 |
%F | ISO 8601格式的完整日期,等效于“%Y-%m-%d”,如“2019-09-03”。 |
%g | ISO周数对应的不包含世纪的年份,等效于“%y”,除非ISO周数属于前一年或后一年,则使用前一年或后一年,范围[00,99]。 |
%G | ISO周数对应的年份,等效于“%Y”,除非ISO周数属于前一年或后一年,则使用前一年或后一年,范围[0000,9999]。 |
%h | 等效于“%b”。 |
%j | 十进制表示的在一天中的天数,范围[001,366]。 |
%m | 十进制表示的月份,范围[01,12]。 |
%u | 十进制表示的星期,范围[1-7]。周一为一周的第一天。周一为1,依次递增。 |
%U | 十进制表示的一年中的周数,[00,53]。星期日为一周的第一天,新年第一个星期日之前的所有日子都视为第0周。 |
%V | 十进制表示的一年中的ISO周数,[01,53]。星期一为一周的第一天,如果包含1月1日的一周在新的一年里有四天或四天以上,则认为这周是第一周,否则就是前一年的第53周,下一周是新年的第一周。 |
%w | 十进制表示的星期,范围[0-6]。周日为一周的第一天。周日为0,依次递增。 |
%W | 十进制表示的一年中的周数,[00,53]。星期一为一周的第一天,新年第一个星期一之前的所有日子都视为第0周。 |
%x | 按当前区域设置下的日期格式,如“09/03/19”。 |
%y | 年份的后两位,范围[00,99]。 |
%Y | 年份,范围[0000,9999]。 |
%H | 十进制数字表示的小时(24小时制),范围[00,23]。 |
%I(大写i) | 十进制数字表示的小时(12小时制),范围[01,12]。 |
%M | 分钟,范围[00,59]。 |
%p | 本地区域设置下等价于“AM”或“PM”,在许多地区是空字符串。中午视为“PM”,午夜视为“AM”。 |
%r | 本地区域设置下12小时制时间,如02:15:11 PM。 |
%R | 24小时制的时和分,等效于“%H%M”,如“14:16”。 |
%S | 十进制数字表示的秒,范围[00,61]。60在表示闰秒的时间戳中有效,61是出于历史原因而支持的。 |
%T | 24小时制的时分秒,等效于“%H:%M:%S”。 |
%X | 按当前区域设置下的日期格式,如“02:20:24 PM”。 |
%z | 表示与UTC/GMT的正或负时差的时区偏移量,格式为+HHMM或-HHMM,其中H表示小时数,M表示分钟数,范围是[-23:59,+23:59]。 |
%Z | 时区名。没有时区则返回空字符。 |
11.函数
调用:先定义后调用,类似c语言,不同于java(调用的代码可以在方法前)
作用:方便代码重用;分解任务,简化程序逻辑;使代码更加模块化
定义:
'''
函数定义方式一:
def 函数名(形式参数名列表):
代码块
注意,参数列表不是数据类型的列表
函数调用:
方式一:
函数名(实际参数列表)
实际参数列表的参数与形式参数的列表一一对应
此时的参数列表可以写一个类似元组去掉括号,例如 函数名(参数1, 参数2, 参数3, 参数4, 参数5,...)
方式二:
函数名(实际参数名=参数的值,...) 也可叫关键字参数:键值对
实际参数列表的参数与形式参数的列表不需要位置上一一对应,只需要参数名对应即可,例如mysum(num2=5, num1=2)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
函数定义方式二: *args代表的接收一个元组
def 函数名(*形式参数名)
代码块
函数调用:
函数名(实际参数列表)
实际参数列表的参数与形式参数的列表一一对应
此时的参数列表可以写一个元组,此时元组括号必须省略 ,例如 函数名(参数1, 参数2, 参数3, 参数4, 参数5)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
函数定义方式三: **args代表的接收一个字典
def 函数名(**形式参数名)
代码块
函数调用:
方式一:
函数名(实际参数列表)
实际参数列表的参数与形式参数的列表一一对应
此时的参数列表是类似字典,此时字典{}必须省略,冒号用等号代替,参数名称没有引号,例如 函数名(参数名称1=参数1, 参数名称2=参数2, 参数名称3=参数3, ...)
'''参数拆包:
def mySum(a, b, c, d):
print(a + b + c + d)
def myFun(*args):
print(args)
# 拆包
print(*args)
mySum(*args)
mySum(args[0], args[1], args[2], args[3])
# 此时只有a能接收值,且为元组,报错
mySum(args)
myFun(1, 2, 3, 4)
def mySum(a, b):
print(a + b)
def test(**kwargs):
print(kwargs, type(kwargs))
# 拆包操作
# 无法打印,报错
# print(**kwargs)
# 同理一个字典传参,b没有接收值报错
# mySum(kwargs)
mySum(**kwargs)
#必须传a,b不然不错,即传递对应的名称才能正确的拆包传参
test(a=15, b=12)
'''
参数装包:把传递进来的参数包装成一个集合
参数拆包:把集合参数再次分解成单独的个体
'''缺省参数:
1) 场景:使用一个函数,使用某个数据的一个固定值或者主功能之外的小功能实现
2) 定义:def 函数名(变量名1=默认值1, 变量名2=默认值2, …):
函数体中即使没有传参,也可以使用,但是使用的是默认值
3) 使用:函数名(变量1, 变量2, …) 缺省参数可以不写,为默认值
参数传递:
1)值传递:只传递值的副本,不传递地址(引用),修改值的副本对原参数值没有影响
2)引用传递(地址传递):通过传递过来的地址可以操作原参数
注意:python只有引用传递,如果数据类型是可变类型,可以改变原件,如果数据类型是不可变类型,不能改变原件!
函数返回值:
1) 场景:使用一个函数处理数据,需要获取数据的处理结果
2) 定义:
def 函数名(参数列表):
函数体
return 返回值
只能返回一次,因为return后函数执行完毕,不会向下执行
如果返回多个数据,包装成一个集合返回,可以返回列表、字典、元祖等等函数的使用描述:
1) 场景:编写三方函数,方便别人使用,添加函数的功能和使用方式等描述在函数体最上面
2) 定义:使用双引号引起来注释
def 函数名(参数列表):
"""
帮助信息
"""
函数体3)一般函数的使用描述
- 函数的功能
- 参数的含义、类型、是否可以省略、默认值
- 返回值含义、类型
4) 查看函数帮助文档
help(函数名)
# 注意函数名后面没有括号
11.1 偏函数(有点想java的重载)
1) 场景:编写较多参数的函数时,如果某些参数是一个固定值,为了简化使用,可以创建一个新函数,指定使用的函数的某个参数为某个固定值,这个新函数就是偏函数。
2) 语法:
方式一:自己写一个新的
def mySum(a, b, c, d=1):
print(a + b + c + d)
# 偏函数
def mySum1(a, b, c=1, d=2):
mySum(a, b, c, d)
mySum1(1, 2)方式二:借助functools模块的partial函数
import functools
def mySum(a, b, c, d=1):
print(a + b + c + d)
# 偏函数
newFunc = functools.partial(mySum, c=5)
print(newFunc, type(newFunc))
# 利用functools.partial(mySum, c=5)生成的偏函数不能多写参数,会报错
# newFunc(1, 2, 3, 4)
newFunc(1, 2)
# 场景
numStr = "1000010"
# 二进制转换成十进制
print(int(numStr, base=2))
# 使用偏函数,不用重复写base参数
int2 = functools.partial(int, base=2)
print(int2(numStr))
11.2 高阶函数
1) 概念:当一个函数A的参数,接收的又是另一个函数时,则把这个函数A称为是高阶函数。例如sorted函数
2) 语法:
# 高阶函数 |
11.3 返回函数
1) 概念:一个函数的内部,它的返回值数据是另一个函数,把这样的操作称为返回函数。
2) 语法:
# 返回函数 |
11.4 匿名函数(lambda函数)
1) 概念:没有名字的函数。
2) 语法:lambda 参数1, 参数2, …: 表达式
3) 限制:只能写一个表达式,不能直接return;表达式的结果就是返回值;只适用于简单的操作处理。
# 匿名函数 |
11.5 闭包(Closure)
1) 概念:在函数嵌套的前提下,内层函数引用了外层函数的变量(包括参数),外层函数又把内层函数当作返回值返回,这个内层函数+所引用的外层变量,称为闭包。
2) 语法:
# 闭包函数 |
3) 应用场景:外层函数,根据不同的参数生成不同功能的内层函数
# 案例:生成分割线 |
4) 注意事项
闭包中如果想要修改引用的外层变量,需要使用nonlocal关键字修饰变量,不能同时赋值,先修饰然后赋值,没有加关键字,就是闭包内新定义的变量
# external and internal
def external_func():
num = 10
def internal_func():
# nonlocal关键字 非局部的
nonlocal num
num = 666
print(num)
# 没有加nonlocal 10
print(num)
internal_func()
# 没有加nonlocal 10
print(num)
return internal_func
func = external_func()
func()闭包内引用了一个后期会发生变化的变量是需要注意
# 闭包注意事项二
def external_func():
num = 10
def internal_func():
print(num)
num = 100
return internal_func
func = external_func()
# 函数被调用时才确定内部变量标识对应的值
# 100
func()
def external_func1():
funcs = []
for i in range(1, 4):
def internal_func1():
print(i)
funcs.append(internal_func1)
return funcs
func_ = external_func1()
print(func_, type(func_))
# 全是3
func_[0]()
func_[1]()
func_[2]()
def external_func2():
funcs = []
for i in range(1, 4):
def internal_func2(num):
# 增加内层函数,保持num = i = 1、2、3
def inner():
print(num)
return inner
funcs.append(internal_func2(i))
return funcs
func2 = external_func2()
print(func2, type(func2))
# 1、2、3
func2[0]()
func2[1]()
func2[2]()
11.6 装饰器(设计模式)
1) 作用:在函数名以及函数体不改变的前提下,给一个函数附加一些额外代码
2) 案例:
优化前
# 装饰器
# 开放封闭原则:已经写好的代码,尽可能不要修改,如果想新增功能,在原来代码基础上,单独进行扩展
# 单一职责原则:单一功能
def publish_novels():
# print("login_check")
login_check()
print("发说说")
def publish_pictures():
# print("login_check")
login_check()
print("发图片")
def login_check():
print("login_check")
# 业务逻辑代码
btn = 2
if btn == 1:
# print("login_check")
publish_novels()
else:
# print("login_check")
publish_pictures()
# 方式一:在业务逻辑代码添加登录验证代码,代码冗余度大,维护性能差
# 方式二:在功能函数添加登录验证代码,减少代码重复度,复用性好
使用装饰器优化后
def login_check(func):
def inner():
print("login_check")
func()
return inner
# 使用@login_check立马执行login_check方法
def publish_novels():
# print("login_check")
# login_check()
print("发说说")
# publish_novels = login_check(publish_novels)相当于
# publish_novels = def inner():
# print("login_check")
# func()
# 利用python语法题@函数名代替下式
# publish_novels = login_check(publish_novels)
# print(publish_novels)
def publish_pictures():
# print("login_check")
# login_check()
print("发图片")
# 利用python语法题@函数名代替下式
# publish_pictures = login_check(publish_pictures)
# print(publish_pictures)
# 业务逻辑代码
btn = 1
if btn == 1:
# print("login_check")
publish_novels()
# login_check(publish_novels)
else:
# print("login_check")
publish_pictures()
# login_check(publish_pictures)
装饰器的叠加使用(使用多个注解)
# decorator 装饰器叠加使用,加@decorator方法的注解,从上往下执行
def decorator_line(func):
def inner():
print("-" * 100)
func()
return inner
def decorator_star(func):
def inner():
print("*" * 100)
func()
return inner
# content = decorator_line(content)
# content = decorator_star(content)
def content():
print("社会我小谢,人狠话不多")
content()对有参函数进行装饰
# 对有参函数进行装饰
def decorator(func):
def inner(*args, **kwargs):
print("*" * 50)
print(args, kwargs)
# 拆包
func(*args, **kwargs)
return inner
def print_number(num1, num2, num3):
print(num1, num2, num3)
def print_number2(num1):
print(num1)
print_number(123, 666, num3=666)
print_number2(123)对有返回值的函数进行装饰(无论什么场景下,保证函数返回值一致)
# 对有返回值的函数进行装饰
def decorator(func):
def inner(*args, **kwargs):
print("*" * 50)
print(args, kwargs)
# 拆包
res = func(*args, **kwargs)
return res
return inner
def print_number(num1, num2, num3):
print(num1, num2, num3)
return num1 + num2 + num3
def print_number2(num1):
print(num1)
# inner函数必须要有返回值,且与被装饰者返回值一致
res1 = print_number(123, 666, num3=666)
res2 = print_number2(123)
print(res1, res2)通过装饰器的注解加参数获取不同的装饰器
# 获取装饰器,通过char控制获取不同装饰器
def getDecorator(char):
def decorator(func):
def inner():
print(char * 50)
func()
return inner
return decorator
def fp():
print("中华人民共和国万岁")
fp()
11.7 生成器
1) 概念:是一个特殊的迭代器(迭代器的抽象层次更高)
拥有迭代器特性:惰性计算数据,节省内存;能够记录状态,通过next()函数访问下一个状态;具备可迭代特性;
也可以自己实现迭代器
2) 创建方式一:生成器变量名 = (推导式),即列表推导式的[]改为()
3) 创建方式二:生成器函数,函数中包含yield语句,函数执行结果就是生成器
# yield,可以阻断当前函数执行,当使用next(generator)或者generator.__next__()函数,让函数继续执行,当执行下一个yield时,又阻断 |
4) send()方法:
send方法有一个参数,指定的是上一次被挂起的yield语句的返回值 |
5) 关闭生成器
g.close() |
11.8 递归函数
1) 概念:函数A内部继续调用A
2)案例:
# 递归求n! |
11.9 按位置传参数函数
概念:只能位置传参,不能键值对传参,最后一个是正斜杠/,该参数不用传
# 保证按位置传参且只能传一个 |
11.10 函数作用域
1) 变量作用域
- 变量的作用范围(可操作范围):python是静态作用域,变量的作用域源于它在代码中的位置,在不同的位置,可能有不同的命名空间。
2) 命名空间
- 是作用域的体现形式
- 不同的具体操作范围
3) python-LEGB
- L-Local:函数内的命名空间;作用范围:当前整个函数体范围
- E-Enclosing function locals:外部嵌套函数的命名空间;作用范围:闭包函数
- G-Global:全局命名空间;作用范围:当前模块(文件)
- B-Builtin内建模块命名空间;作用范围:所有模块(文件)
- 注意:python没有块级作用域,比如while、for、if后面的代码块只要执行了,代码块外面可以访问,与javascript类似
- LEGB规则:按照L—>E–->G–>B 的顺序进行查找
- 基于命名空间的常见变量类型
- 局部变量:在一个函数内部定义的变量;作用域为函数内部;查看局部变量locals()
- nonlocal关键字仅仅适用于闭包里面函数与外层函数的变量相同的变量操作,单个函数体内使用global关键字,即可修改全局变量的值
- 全局变量:在函数外部,文件最外层定义的变量,作用域为整个文件内部;查看全局变量globals()
- 注意点 :
- 结构规范:访问原则从内到外
- 全局变量与局部变量重名:获取,就近原则;修改,加global关键字修饰变量再修改
- 命名:全局变量一般g_变量名命名
12.python文件I/O操作
文件概念:数据存放的容器
文件的作用:持久性的存储数据内容
文件组成:
- 文件名:同级目录下,不允许同名文件存在
- 扩展名:.jpg、.avi、.doc、.java等等
- 注意:一般不同的扩展名,对应不同的文件格式;不同的文件格式,有着不同的存储约定,方便程序处理
文本内容
- 文本文件:.doc、.xls、.txt等等
- 二进制文件:图片、视频、音乐等等
文件使用流程
打开:open(“文件”, “模式”),文件指定文件名称,模式控制操作模式,返回文件对象
模式:
| 模式 | 含义 |
| :—-: | :—————————————————————————————- |
| r | 以只读方式打开文件(默认文件打开模式),文件的指针放在文件的开头,注意:文件不存在报错 |
| w | 以只写方式打开文件,文件的指针放在文件的开头,所以写入新的内容会覆盖原文件的所有内容,注意:文件不存在会自动创建文件 |
| a | 以追加方式(只写)打开文件,文件的指针放在文件的结尾,所以写入新的内容会新增原文件的结尾,注意:文件不存在会自动创建文件 |
| 增加b | rb、wb、ab,如果是二进制文件,则选择此项;如图片、视频、音频等等(b为binary) |
| 增加+ | r+、w+、a+、wb+、rb+、ab+,都是以读写模式打开,其他特性基本和+前面的模式一致,但部分操作,有细节区别 |
| r+ | 可读可写打开文件,没有文件 No such file or directory报错;可以读操作,指向文件头,读取全部内容;可以写入,指针指向文件头部,覆盖写入的内容长度,不会全部覆盖;读写同时进行,先读后写,指针先移到读完后的位置,再写入;读写同时进行,先写后读(会等读语句执行后写入),指针移到写完后的位置,若原文件空,读写不到数据,非空,则读到指针后面剩下内容 |
| w+ | 可读可写打开文件,文件不存在,创建文件;打开文件就清空文件,指针指向头部,所以读操作,指向文件头,返回空;可以写入,指针指向文件头部,覆盖原文件全部内容;读写同时进行,先读后写(打开文件就清空了内容),不管先读后写,还是先写后读(执行完写语句不会写入内容,执行完读操作(缓冲区内容到磁盘)才会写入成功),不会读到内容,总结不可读,但是使用读操作不报错 |
| a+ | 可读可写打开文件,文件不存在,创建文件;可以读操作,不管是否存在内容,读取不到内容,返回空;可以写入,指针指向文件尾,追加写入;读写同时进行,不管先读后读(写入到缓冲区,执行完close语句写入磁盘),还是先写后读(执行完读语句写入),不会读到内容,总结不可读,但是使用读操作不报错 |
| rb+ | 可读可写打开文件,文件不存在,创建文件;以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。 |
| wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
| ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |读写:
a)定位:
# 偏移
f.seek(偏移量[, 0, 1, 2])
# 查看文件指针
f.tell()
# 通过命令行查看seek帮助文档
f = open("py文件操作/a.txt", "r")
help(f.seek)
'''
seek的第二个参数
0.文件指针在文件头,偏移量为正
1.文件指针在文件中,表示当前位置,有可能偏移量可以为负
2.文件指针在文件尾部,偏移量为负
注意:文本文件不带"b"seek第二参数只能写0,二进制文件(加b)操作模式下可以为1或2
'''b)读:
'''
f.read(字节数)
字节数默认是文本内容长度,下标自动移动
* f.readline([limit])
读取一行数据,limit限制最大读取字节数
f.readlines()
自动将文件按照换行符处理,将处理好的每一行数据组成列表返回
* for in (动态加载内存)
1.遍历行列表
2.遍历文件本身(文件对象就是迭代器)
判断文件是否可读(容错处理)
file对象.readable()
注意:
文件比较大时使用readline方法,按行加载,可节省内存,但相对于其他两个读取方法,性能较低
其他两个方法一次读取全部内容,虽然占用内存,但处理性能比较高
'''c)写:
'''
file对象.write()
返回写入字节的长度
判断文件是否可写(容错处理)
file对象.writable()
'''
关闭:
'''
file对象.close()
关闭一个打开的文件
可以释放系统资源,会立即清空缓冲区的数据内容到磁盘文件
file对象.flush()
将缓冲区的数据内容刷新到磁盘文件中
'''
文件常用操作
import os
# 重命名文件 os.rename(原, 新), 没有文件或目录报错
# os.rename("b.txt", "one.txt")
# 重命名目录
# os.rename("first", "one")
# 同时修改目录名和文件名 renames()
# os.renames("one/one.txt", "two/two.txt")
# 删除文件,不存在报错 remove()
# os.remove("m52.jpg")
# 删除目录,文件夹非空报错 rmdir()
# os.rmdir("one")
# 只删除two目录
# os.rmdir("one/two")
# 递归删除目录,文件夹非空报错 removedirs()
# os.removedirs("one/two")
# 创建一级目录 mkdir()
# os.mkdir("one")
# 无法递归创建多级目录,报错
# os.mkdir("a/b/c")
os.mkdir("a", 0o777)
'''
数字权限模式,windows忽略,可以参照linux的文件权限(读r:4、写w:2、可执行x:1)
文件拥有者 八进制第一位数(读r:4、写w:2、可执行x:1)
同组用户 八进制第二位数(读r:4、写w:2、可执行x:1)
其他用户 八进制第三位数(读r:4、写w:2、可执行x:1)
'''
# 切换默认目录,os.chdir(target目录),目标目录不存在会报错
# os.chdir("one")
# open("ddd.txt", "w")
# 获取当前目录 os.getcwd()
# print(os.getcwd())
# 获取目录内容列表 os.listdir(path) 目录默认是当前目录下("./") 返回一个列表
print(os.listdir("../"))案例:
# 文件复制案例
import os
os.chdir("../file/")
# 1.以只读模式打开要复制的文件
# 以追加模式打开副本文件
# 两个文件编码一致,不会乱码
source_file = open("poem.txt", "r", encoding="utf-8")
target_file = open("poem_copy.txt", "a", encoding="utf-8")
# 2.从原文件中读取内容
# 目标文件写内容
# if source_file.readable():
# content = source_file.read()
# if target_file.writable():
# target_file.write(content)
while True:
content = source_file.read(1024)
if len(content) == 0:
break
target_file.write(content)
# 刷新流
target_file.flush()
# 3.关闭流
source_file.close()
target_file.close()
'''--------------------------------------------'''
# 创建于扩展名相同目录并移动到该目录下,进行分类
# 获取文件名称列表
import os
import shutil
path = "../file"
if not os.path.exists(path):
exit()
os.chdir(path)
file_list = os.listdir("./")
# print(file_list)
# 1 遍历所有文件
for file_name in file_list:
# print(file_name)
# 2 分解文件后缀名
# 2.1获取最后一个.的索引位置
index = file_name.rfind(".")
# print(index)
# 2.2根据这个索引开始截取后续内容
extension = file_name[index + 1:]
# print(extension)
if extension == -1:
continue
# 3 查看一下, 是否存在同名的目录
# 4 如果不存在这样的目录,创建该名称的目录
if not os.path.exists(extension):
# 创建
os.mkdir(extension)
# 移动
# 5 目录存在后,移动对应文件
shutil.move(file_name, extension)
'''------------------------------------'''
# 打印文件目录文件夹及文件清单
# 获取文件名称列表
import os
import shutil
# path = "../file"
# if not os.path.exists(path):
# exit()
# listdir = os.listdir(path)
# print(listdir)
def listFiles(dir, f):
file_list = os.listdir(dir)
# print(file_list)
for file_name in file_list:
new_file_name = dir + "/" + file_name
# 判断是不是目录
if os.path.isdir(new_file_name):
# print(new_file_name)
f.write(new_file_name + "\n")
# 是目录,递归
listFiles(new_file_name, f)
else:
# print("\t" + file_name)
f.write("\t" + file_name + "\n")
# print("")
f.write("\n")
f = open("list.txt", "a")
listFiles("../file", f)
二、py核心知识点(oop)
- python面向对象(Object-oriented programming (OOP) )
1.面向对象基本理论
- 面向对象:解决问题时关注的是需要解决问题所需要的的对象,根本是不同具体对象面向过程的封装
- 面向过程:解决问题时关注解决问题的每一个步骤(过程)
- 类:某一个对象的特征行为的抽象,包特征(属性)和行为(方法)
- 实例化对象:拥有某个类的属性和方法,且属性方法得到具体实现
2.面向对象在python中实现
2.1 定义一个类(类也是对象,万物皆对象)
class 类名: |
2.2 实例化对象
对象名 = 类名() |
2.3 属性:某个对象的特性,通过实例化对象来调;而变量是可以改变的量值
''' |
2.4 方法:描述一个目标的行为动作
''' |
2.5 实例方法:可以使用实例属性和类属性 self.属性
''' |
2.6 类方法:可以使用类属性 cls.类属性(没有传实例对象参数前提)
''' |
2.7 静态方法:可以使用类属性,类名.属性(没有传实例对象参数前提)
''' |
2.8 元类:创建类对象的类(type类,继承于object)
''' |
2.9 类创建流程
''' |
2.10 类的描述
- 三个双引号对“”“类、方法描述”“”,属性的描述写在类描述里面,具体参考官方文档
''' |
2.11 属性相关补充
2.11.1 私有化属性
''' |
2.11.2 只读属性
''' |
# 在python3.x环境下 |
2.11.3 内置特殊属性
# 内置特殊属性 |
2.12 私有化方法
''' |
2.13 内置特殊方法
2.13.1 生命周期方法
2.13 .2 其他内置方法
# ----------------信息格式化操作:__str__方法------------------------- |
2.14 浅拷贝与深拷贝
变量的赋值操作:只是形成两个变量,实际上还是指向同一个对象
浅拷贝:python拷贝一般都是浅拷贝,使用copy模块的copy函数拷贝时,对象包含的子对象内容不拷贝,因此源对象与拷贝对象会引用同一个子对象
深拷贝:使用copy模块的deepcopy函数,递归拷贝对象中包含的子对象,源对象与拷贝对象的所有子对象也不同
# -----------------------深拷贝-----------------------------------
class Cpu:
def __init__(self, value):
self.cpu = value
class Disk:
def __init__(self, value):
self.disk = value
class Computer:
def __init__(self, cpu, disk):
self.cpu = cpu
self.disk = disk
# print("--------------变量赋值---------------")
# num1 = 12
# num2 = num1
# 同时指向一块内存空间
# print(id(num1), id(num2))
# print("--------------实例变量赋值---------------")
# cpu = Cpu("英特尔")
# disk = Disk("联想")
# computer = Computer(cpu, disk)
# 你要是认为是computer2没有实例化才会一模一样,你也可以实例化,发现就是一样的
# computer2 = computer
# print(computer, computer.disk, computer.cpu)
# print(computer2, computer2.disk, computer2.cpu)
print("--------------实例浅拷贝---------------")
# import copy
# cpu = Cpu("英特尔")
# disk = Disk("联想")
# computer = Computer(cpu, disk)
# computer2 = copy.copy(computer)
# # 只有子对象相同,指明子对象没有拷贝,对象本身拷贝了
# print(computer, computer.disk, computer.cpu)
# print(computer2, computer2.disk, computer2.cpu)
print("--------------实例深拷贝---------------")
import copy
cpu = Cpu("英特尔")
disk = Disk("联想")
computer = Computer(cpu, disk)
computer2 = copy.deepcopy(computer)
# 对象本身与他的子对象都拷贝了
print(computer, computer.disk, computer.cpu)
print(computer2, computer2.disk, computer2.cpu)
3.python对象的生命周期,以及周期方法
3.1 生命周期概念
- 指的是一个对象, 从诞生到消亡的过程
- 当一个对象被创建时, 会在内存中分配相应的内存空间进行存储
- 当这个对象不再使用, 为了节约内存, 就会把这个对象释放
3.2 涉及问题
- 如何监听一个对象的生命过程?
- Python是如何掌控一个对象的生命?
3.3 监听对象生命周期
''' |
3.4 内存管理机制-存储方面
''' |
3.5 内存管理机制-垃圾回收方面
引用计数器机制
- 概念
一个对象, 会记录着自身被引用的个数,每增加一个引用, 这个对象的引用计数会自动+1,每减少一个引用, 这个对象的引用计数会自动-1
- 概念
举例
- 引用计数+1场景
- 对象被创建
p1 = Person()
- 对象被引用
p2 = p1
- 对象被作为参数,传入到一个函数中
log(p1) 这里注意会+2, 因为内部有两个属性引用着这个参数
- 对象作为一个元素,存储在容器中
l = [p1]
- 对象被创建
- 引用计数-1场景
- 对象的别名被显式销毁
del p1
- 对象的别名被赋予新的对象
p1 = 123
- 一个对象离开它的作用域
a.一个函数执行完毕时; b.内部的局部变量关联的对象, 它的引用计数就会-1;
- 对象所在的容器被销毁,或从容器中删除对象
- 对象的别名被显式销毁
- 引用计数+1场景
查看引用计数
import sys |
垃圾回收机制
主要作用:从经历过”引用计数器机制”仍未被释放的对象中, 找到”循环引用”, 干掉相关对象
底层机制(了解&难)
怎样找到”循环引用”?
1. 收集所有的"容器对象", 通过一个双向链表进行引用 容器对象 可以引用其他对象的对象 列表 元组 字典 自定义类对象 ... 非容器对象 不能引用其他对象的对象 数值 字符串 布尔 ... 注意: 针对于这些对象的内存, 有其他的管理机制 2. 针对于每一个"容器对象", 通过一个变量gc_refs来记录当前对应的引用计数 3. 对于每个"容器对象",找到它引用的"容器对象", 并将这个"容器对象"的引用计数 -1 4. 经过步骤3之后, 如果一个"容器对象"的引用计数为0, 就代表这玩意可以被回收了, 肯定是"循环引用"导致它活到现在的
如何提升查找”循环引用”的性能?
如果程序当中创建了很多个对象, 而针对于每一个对象都要参与”检测”过程; 则会非常的耗费性能
所以, 基于这个问题, 产生了一种假设:
a.越命大的对象, 越长寿 b.假设一个对象10次检测都没给它干掉, 那认定这个对象一定很长寿, 就减少这货的"检测频率"
基于这种假设, 设计了一套机制
1.分代回收机制
a.默认一个对象被创建出来后, 属于 0 代
b.如果经历过这一代”垃圾回收”后, 依然存活, 则划分到下一代
c.”垃圾回收”的周期顺序为
1)0代”垃圾回收”一定次数, 会触发 0代和1代回收
2)1代”垃圾回收”一定次数, 会触发0代, 1代和2代回收 2.查看和设置相关参数
import gc
print(gc.get_threshold())
gc.set_threshold(700, 10, 5)
垃圾回收器当中, 新增的对象个数-消亡的对象个数 , 达到一定的阈值时, 才会触发, 垃圾检测
垃圾回收时机(掌握&简单)
1. 自动回收 - 触发条件 开启垃圾回收机制 gc.enable() 开启垃圾回收机制(默认开启) gc.disable() 关闭垃圾回收机制 gc.isenabled() 判定是否开启 并且达到了垃圾回收的阈值 垃圾回收器中, 新增的对象个数和释放的对象个数之差到达某个阈值 涉及方法 gc.get_threshold() 获取自动回收阈值 gc.set_threshold() 设置自动回收阈值 2. 手动回收 - 触发条件 gc.collect() 执行一次垃圾回收(开关状态无效)
测量对象的引用个数
辅助工具objgraph http://mg.pov.lt/objgraph/ xdot graphviz
3.6 面向综合案例
# 私有装饰器 |
4.面向对象三大特性
4.1 封装
- 封装概念:将一些属性和相关方法封装在一个对象中对外隐藏内部具体实现细节内部实现, 外界不需要关心,外界只需要根据”内部提供的接口”去使用就可以
- 好处:
1. 使用起来更加方便 - 因为已经把很多相关的功能, 封装成一个整体 - 类似于像外界提供一个工具箱 - 针对于不同的场景, 使用不同的工具箱就可以 2. 保证数据的安全 - 针对于安全级别高的数据, 可以设置成"私有"; - 可以控制数据为只读,外界无法修改 - 也可以拦截数据的写操作,进行数据校验和过滤 3. 利于代码维护 - 如果后期, 功能代码需要维护, 则直接修改这个类内部代码即可; - 只要保证接口名称不变; 外界不需要做任何代码修改
- 案例
4.2 继承
概念:
- 现实中的”继承:”子女继承父母的”财产资源”
- 编程中的”继承”:一个类”拥有”另外一个类的”资源”的方式之一
- “拥有”:并不是资源的复制, 变成双份资源,而是, 资源的”使用权”
- “资源”:指”非私有的”属性和方法
- 例子:Dog类继承自Animal类
Dog:**子类、派生类** Animal:**父类、基类、超类**
- 目的:方便资源重用
4.2.1 继承的分类
单继承
概念:仅仅继承了一个父类
语法:
class Dog(Animal):
pass
多继承
概念:继承了多个父类
语法
class child(Father, Mather):
pass
4.2.2 type与object区分
type和object:
新式类:直接或者间接继承自object的类(python3.x没有显式继承object,但实际继承了object)
- 经典类:没有直接或者间接继承自object的类(python2.x没有显式继承object,需要手动继承了object)
4.2.3 继承的影响
资源的继承
- 明确:
在Python中, 继承是指, 资源的使用权;所以, 测试某个资源能否被继承, 其实就是测试在子类当中, 能不能访问到父类当中的这个资源
- 结论:
除下私有的属性和私有的方法, 其他的基本都能继承 公有属性/方法 受保护属性/方法 内置方法
- 明确:
资源的使
继承的几种形态
单继承链:一个子类只有一个父类
无重叠的多继承链:继承链无交叉, 无公共父类
有重叠的多继承链:继承链有交叉, 有公共父类
几种形态应该遵循的标准原则
单继承:遵循”从下到上的原则”,自己身上没有这个资源, 就到父类里面去找, 父类里面没有再往上找
无重叠的多继承:遵循”单调原则”,按照继承的先后顺序, 优先调用左侧继承链上的资源
有重叠的多继承:遵循”从下到上的原则”,简单理解就是A继承B继承C,B重写了C类的方法, 那么A优先使用B类的方法
针对于几种标准原则的方案演化
查看顺序:
import inspect
# 查看资源顺序三种方式
print(inspect.getmro(A))
print(A.mro())
print(A.__mro__)
资源的覆盖(属性覆盖,方法重写)
原理:
在MRO的资源检索链当中,优先级比较高的类写了一个和优先级比较低的类一样的一个资源(属性或方法),到时候, 再去获取相关资源, 就会优先选择优先级比较高的资源;,而摒弃优先级比较低的资源; 造成”覆盖”的假象。
注意事项:当调用优先级比较高的资源时, 注意self的变化
资源的累加:在一个类的基础之上, 增加一些额外的资源
子类相比于父类, 多一些自己特有的资源,直接添加
在被覆盖的方法基础之上, 新增内容:
方案一:在高优先级类的方法中, 通过”类名”调用低优先级类的方法,弊端是代码维护性差、容易产生重复调用;
方案二:在低优先级类的方法中, 通过”super”调用高优先级类的方法
概念:一个类,在新式类中有效
作用:
- 起着代理的作用, 帮我们完成以下任务
- 沿着MRO链条, 找到下一级节点, 去调用对应的方法
问题:沿着谁的MRO链条? 找谁的下一个节点? 如何应对类方法, 静态方法以及实例方法的传参问题?
语法原理:
super(参数1[, 参数2]) 参数1:当前类,参数2为当前类获实例
工作原理:
def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls) + 1]问题解决:
- 沿着参数2的MRO链条;
- 找参数1的下一个节点;
- 如何应对类方法, 静态方法以及实例方法的传参问题,使用参数2调用
常用具体语法形式:
# python2.2+
super(type, obj) -> bound super object;
# 需要链条的类与实例
requires isinstance(obj, type)
super(type, type2) -> bound super object;
# 需要链条的类
requires issubclass(type2, type)
# python3+
super()注意:
- super 和父类(超类)没有实质性的关联,仅仅是沿着MRO链条, 找到下一级节点;
- 保证调用形式的统一:要是类名调用, 全是类名调用;要是super调用, 全是super调用
- 参数不要使用self.class,可能会死循环
4.3 多态
- 一个类, 所延伸的多种形态
- 在继承的前提下; 使用不同的子类, 调用父类的同一个方法, 产生不同的功能
- 调用时的多种形态
- 多态在Python中的体现:关注点在于对象的”行为和属性”; 而非对象的”类型”,与其他语言不同的是python的动态类型语言,只要对象有该方法,就可以调用,所以python没有真正意义上多态。
4.4 抽象类、抽象方法
- 抽象类:抽象出来的类,没有具体的实现,不能创建实例,否则会报错
- 抽象方法:抽象出来的方法,没有具体实现,不能直接被调用,需要子类实现(子类不实现会报错)
python中实现抽象:
- 无法直接实现,需要导入模块 import abc
- 设置类的元类为 abc.ABCMeta
- 使用装饰器修饰抽象方法 @abc.abstractmethod
- 抽象类方法@classmethod + @abc.abstractmethod等等
4.5 面向对象三大特性综合案例
class Animal(object): |
5.py异常处理
5.1 异常和错误的概念
错误:没法通过其他的代码进行处理的问题
语法错误
- 比如定义函数写成了 dfe xxx()
- 这种错误, 可以直接通过IDE或者解释器给出的提示进行修改
逻辑错误
语法层面没有问题, 但是自己设计的逻辑出现问题
例如
用户输入年龄, 判定是否成年
if age < 18:
print("已经成年")
这种错误, IDE或者解释器无法帮我们检测出, 只有我们通过代码测试进行排查
异常:多指程序在执行的过程中, 出现的未知错误; 语法和逻辑都是正确的; 可以通过其他代码进行处理修复
- 接收输入整型但输入了字符串,还强制转换,抛异常
- 0作为除数,抛异常
- 列表或字典,索引或键的异常等等
5.2 常见的系统异常
''' |
系统异常类继承树:
BaseException 所有内建的异常的基类:
- SystemExit:由sys.exit()函数引发。当它不处理时,Python 解释器退出
- KeyboardInterrupt: 当用户点击中断键 (通常ctrl + C) 时引发
- GeneratorExit: 当调用一种generator的close()方法时引发。
- Exception:所有内置的、 非系统退出异常是从该类派生的。应该从该类派生所有用户定义的异常。
5.3 解决异常
系统一开始已经内置了一些特定的应用场景; 当我们写代码的过程当中, 一旦触发了这个场景, 系统内部就会自动的向外界抛出这个问题, 也就是我们所谓的异常;不做处理,程序被终止执行; 软件的崩溃。
5.3.1 预防异常:添加容错代码
弊端:容错代码不属于我们主要的业务逻辑; 如果容错代码过多, 会造成代码混乱, 主业务不清晰
5.3.2 解决异常
- 方式一处理异常
方式一处理异常 |
方式二处理异常
作用:适用于执行某一段代码A之前, 进行预处理, 执行代码A结束后, 进行清理操作;不管出现了什么异常, 最终都要执行一些清理操作
语法:
with context_expression [as target(s)]:
with-body
语法图解:
示例:
try:
f = open("m5.jpg", "r")
f.readlines()
except Exception as e:
print(e)
finally:
print("ccc")
f.close()
# 但是以上写法过于繁琐, 于是有了这个方案
# 但是如果产生了异常, 依然会报错; 并没有进行异常处理操作
with open("m5.jpg", "r") as f:
f.readlines()3.自定义上下文管理器
import traceback
class Test(object):
def __enter__(self):
print("enter")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(self, exc_type, exc_val, exc_tb)
print(traceback.extract_tb(exc_tb))
print("exit")
# True 不显示异常信息, False:显示异常信息
return True
with Test() as x:
# print("body", x)
1 / 0
'''
定义的类, 实现"上下文管理协议"
__enter__
做一些预处理操作
__exit__
做一些清理操作
接收抛出的异常
如果返回True, 则忽略异常; 如果返回的False, 则再次抛出异常
'''
contextlib 模块
```python
import contextlib
@contextlib.contextmanager
def test():
# enter方法的具体实现
print(1)
# exit方法的具体实现
yield “kkkk”
print(2)
#
#
with test() as x:
print(3, x)
@contextlib.contextmanager
def ze():
try:
yield
except Exception as e:
print(“error”, e)
num1 = 1
num2 = 0
with ze():
num1 /num2
try:
num1 / num2
except Exception as e:
print(“666”)
a = 10
b = 0
with ze():
a / b
class Test:
def t(self):
print(“tttt”)
# 必须写
def close(self):
print("资源释放")
# 使用contextlib.closing取代
# def __enter__(self):
# return self
#
# def __exit__(self, exc_type, exc_val, exc_tb):
# self.close()
with contextlib.closing(Test()) as test:
test.t()
python2.7
with open(“m5.jpg”, “rb”) as from_file:
with open(“m52.jpg”, “wb”) as to_file:
contents = from_file.read()
to_file.write(contents)
python3.x, 以后写法
with open(“m5.jpg”, “rb”) as from_file, open(“m52.jpg”, “wb”) as to_file:
contents = from_file.read()
to_file.write(contents)
python2.7之前
with contextlib.nested(open(“m5.jpg”, “rb”), open(“m52.jpg”, “wb”)) as (from_file, to_file):
contents = from_file.read()
to_file.write(contents)
‘’’
@contextlib.contextmanager
使用装饰器, 让一个生成器变成一个”上下文管理器”
contextlib.closing
这个函数, 让一个拥有close方法但不是上下文管理器的对象变成”上下文管理器”
contextlib.nested
python2.7之前: 完成多个上下文管理器的嵌套
‘’’
#### 5.4 抛出异常
根据严重程度是抛出异常还是处理解决异常,通过 raise 语句直接抛出相关类型的异常
```python
def setAge(age):
if age <= 0 or age > 200:
raise ValueError("值错误")
else:
print(age)
try:
setAge(-2)
except Exception as e:
print(e)
5.5 自定义异常(继承Exception, 或者BaseException)
class MyException(Exception): |
6.包和模块
- 模块:将某一组相关功能的代码写入一个单独的.py文件中,这个.py文件就称为一个模块。
- 包:包是一个有层次的目录结构,它定义了由n个模块和n个子包(目录下含有init.py文件的,还有模块和其他子包)
- 库:具有一定功能的集合,可以是包或模块
- 框架:从库的角度来看,解决一个开放性问题而设计的具有一定约束性的支撑结构
- 作用:有效的对程序分解,方便维护和管理,供给他人使用;防止同一模块下重名的问题
6.1 包和模块的分类
6.1.2 标准包/模块
- 安装python后自动安装好一些模块,可以直接导入使用
- 特殊:内建包/模块,python自动帮我们导入导入的模块 builtins,当我们使用模块的内容时不需要手动导入模块,直接使用就行
6.1.3 三方包/模块
一些开发人员直接写的包,供给其他人下载使用
6.1.4 自定义包/模块
自己写的包/模块,发布出去就是第三方包/模块
6.2 包/模块的操作
6.2.1 包/模块的创建
- 创建模块:直接写一个.py文件就行
- 创建包:创建一个文件夹,含有init.py文件,该文件在python3.3后不用创建也可以,一般版本兼容都创建(第一次导入包是,自动执行init.py文件)
- 创建多级包:嵌套就行
6.2.2 包/模块的基本信息
包/模块的名称:模块名称为去掉.py的文件名,包名为文件夹名
包和模块存放的位置:
import os
# 打印当前文件路径
print(os.path)
# 打印os模块存放位置路径
print(os.__file__)查看包/模块的里面的内容
print(dir(包名/模块名))
6.2.3 导入包/模块
常规导入(import语句导入)
'''
以下的包模块可以使用点语法定位
1.导入单个模块
import 同级目录的模块名/包名.模块名(注意包名可以嵌套,但是自己定义的模块与包使用这种方式导入只能导入当前py文件目录下的,不然报错找不到)
2.导入多个模块
import 同级目录的模块名/包名.模块名, 同级目录的模块名/包名.模块名...
3.导入单个/多个模块并起别名(使用as关键字,与sql语法不同的是as不能省略)
import 同级目录的模块名/包名.模块名 as 别名, 同级目录的模块名/包名.模块名 as 别名 ...
注意:
1.使用时,需要指定资源的模块名称,即写全,例如p1.tool_module.name
2.如果仅仅导入一个包,在__init__.py再次导入相关模块,一般使用from... import...方式导入
'''2.from… import语句导入
'''
语法:
from A import B[ as 别名]
A的资源必须包含B,即A顺序在前
资源排序:
包 > 模块 > 模块资源
注意面向关系:
包只能看到模块,看不到模块资源
模块只能看到模块资源
最终组合:
1.从包中导入导入模块(包是可能有层级关系,模块没有层级,但可以多个,可以起别名)
2.从模块中导入导入模块资源(模块是可能有层级关系(包有层级的),模块资源没有层级,但可以多个,可以起别名)
注意:
保持import后面最简化,即所有层级关系放在from后面;
注意导入模块,模块是当前文件目录下的或文件目录的上级目录下的等等,需要写全包的路径名,即类似idea的全类名去掉类
特例:
1.from 模块 import *
"*"代表所有非下划线_开头资源导入到当前位置(定义__all__看列表的,这个规定需要看列表有没有写入来判断)
与在引入模块声明的__all__ = ["资源1", "资源2", ...]配合使用,代表"*"匹配到的资源,注意列表内都是字符串
注意:慎用,避免当前位置与导入资源的变量冲突
2.from 包 import *
与在引入包的__init__.py中声明的__all__ = ["资源1", "资源2", ...]配合使用,代表"*"匹配到的模块,注意列表内都是字符串
必须写__all__,没有什么都不导入
'''导入模块具体发生的事情:
- 第一次导入:
- 在自己当下的命名空间中执行所有代码
- 创建一个模块对象,并将模块内所有顶级变量以属性的形式绑定在模块对象上
- 在import的位置,引入import后面的变量名称到当前命名空间
- 第二次导入:
- 直接执行上述第3步
- 结论:
- 注意:上述两种导入方式都大致执行以上步骤
- 多次导入该模块,该模块不会执行多次
- 两种导入方式没有区别是否节省内存,而是区别在于哪一部分内容拿到当前位置来用
- 第一次导入:
从什么位置找到需要导入的模块:
第一从导入:
第一级:内置模块
第二级:
sys.path构成:
当前目录:
import os
print(os.getcwd())环境变量中PYTHONPATH(这个的路径是按照自己定义的)中指定的路径列表
特定路径下的.pth文件中的文件路径列表
查看特定路径
# 查看特定路径
import site
print(site.getsitepackages())后缀名.pth,名字随意,一个路径占一行
在python安装路径的lib库中搜索
追加路径的方式:
直接追加到sys.path列表,只作用本次
修改环境变量
- 修改PYTHONPATH,把需要的模块路径定义到环境变量(不用添加到path),仅仅在shell起作用
- 在pycharm需要另外一种设置方式,设置里面的python Interpreter –> show ALL –>修改Interpreter path后点击ok,apply
- 添加.pth文件(在特定路径下创建该文件,并且文件内容的路径不能为中文)
第二次导入:
从已经加载过的模块去找
查看已加载文件
# 查看已加载文件
import sys
print(sys.modules)
导入模块的常见场景:
- 局部导入:
- 在某个局部范围内导入模块,在其他范围无法使用
- 如果想要全局范围都能使用,在文件顶部导入相关模块
- 使用场景:该模块使用不频繁,而且导入时间过长时
- 覆盖导入:
- 场景一:
- 自定义模块与非内置的标准模块重名,根据前者存储位置,有可能会覆盖后者
- 结论是自定义模块命名不要与后者重名
- 场景二:
- 自定义模块与内置模块重名,内置肯定覆盖自定义
- 使用from 绝对路径 import…导入,覆盖内置,但还是不建议重名
- 场景一:
循环导入:
- 假设有两个模块A、B,A模块导入了B模块,B模块导入了A模块,执行A模块或B模块都会造成循环导入(尽量避免使用,容易出现问题)
可选导入
- 概念:两个功能相似的包,根据需求优先导入选择其中一个导入
- 场景:有两个A、B包,都可以实现相同功能,想优先使用A,而且需要做到没有A的情况下,B作为备选
- 实现:使用try:… except …:…实现
包内导入:python相对导入与绝对导入是基于包内导入而言的,包内导入就是包内导入包内部的模块
绝对导入:
参照sys.path路径进行检索
例如指明包名或模块名
import a
from a import b注意:以上结论是基于python3.x以上
相对导入:
使用.来指明相对路径,.是根据模块名称获取的当前目录,..是根据模块名称所获取的上层目录
例如:
from . import a
from .. import a注意:解释器是根据模块名称而确定层级关系,而不是存放目录,通过下划线下划线name下划线下划线查看模块名称。
结论:
- 包内导入:使用相对导入
- 包外导入:使用绝对导入
# __name__
# 如果一个py文件直接以脚本形式执行 python file,它的名称就是__main__
# 如果一个py文件使用模块的形式,进行加载的,那么它的名称由加载路径决定的,包名(顶级名称).子包名.模块名
# import tool2
# import tool2 python2.x 隐式相对导入等同于 from . import tool2
# import tool2
# 使用from __future__ import absolute_import禁用隐式相对导入
from __future__ import absolute_import
import tool2
- 局部导入:
6.3 三方包的安装与升级
7.虚拟环境
8.python反射
具体含义看源码
import(name, globals=None, locals=None, fromlist=(), level=0):动态加载模块(import前后有双下划线)
hasattr(object, name, default=None):判断实例obj是否存在字符串name对应的属性
getattr(object, name, default=None):获取字符串name对应的属性
setattr(x, y, v):设置属性到实例
delattr(x, y):删除实例中字符串对应的属性,等同del x.y
反射案例:
# 案例一
class User(object):
def add_user(self):
print("添加用户")
def delete_user(self):
print("删除用户")
def update_user(self):
print("更新用户")
def select_user(self):
print("查找用户")
def before_process(self, method):
if method == "add":
self.add_user()
elif method == "delete":
self.delete_user()
elif method == "update":
self.update_user()
elif method == "select":
self.select_user()
else:
print("无效调用")
def after_process(self, method):
"""
去除ifelse的繁琐,类似理由转发
"""
# p判断是否存在该方法
if hasattr(self, method):
# 获取方法
user_method = getattr(self, method)
user_method()
else:
print("无效调用")
if __name__ == "__main__":
# 没有处理,好多if
User().before_process("delete")
# 处理后
User().after_process("delete_user")
# 案例二
# 保证按位置传参且只能传一个
def new_method(self, /):
print("我是新来的,给我一个实例")
def main():
"""
动态加载模块,创建类实例执行其中方法
"""
# 动态导入模块Func
func_module = __import__("Func", fromlist=True)
print(func_module)
# 判断是否找到对应类
if hasattr(func_module, "Func"):
# 通过模块对象实例获取类对象实例
func_class = getattr(func_module, "Func")
# print(type(func_class))
print(func_class)
# 判断是否存在方法
if hasattr(func_class, "process"):
# 通过类对象获取成员方法
process_method = getattr(func_class, "process")
# print(type(process_method))
print(process_method)
# 执行该方法
process_method(func_class)
else:
print("没有找到当前方法")
# 向类添加方法
setattr(func_class, "newFunc", new_method)
# 是否成功添加方法
if hasattr(func_class, "newFunc"):
# 获取该方法
new_func = getattr(func_class, "newFunc")
# 执行方法
new_func(func_class)
else:
print("添加失败")
# 删除Func类中法
delattr(func_class, "newFunc")
if not hasattr(func_class, "newFunc"):
print("删除成功")
else:
print("没有找到该类")
# 测试代码
if __name__ == "__main__":
main()