Python

运算符

以下假设变量: a=10,b=20:

算术运算

运算符 描述 实例
+ 加 - 两个对象相加 a + b 输出结果 30
- 减 - 得到负数或是一个数减去另一个数 a - b 输出结果 -10
* 乘 - 两个数相乘或是返回一个被重复若干次的字符串 a * b 输出结果 200
/ 除 - x除以y b / a 输出结果 2
% 取模 - 返回除法的余数 b % a 输出结果 0
** 幂 - 返回x的y次幂 a**b 为10的20次方, 输出结果 100000000000000000000
// 取整除 - 返回商的整数部分(向下取整 >>> 9//2 4 >>> -9//2 -5

比较运算符

运算符 描述 实例
+ 加 - 两个对象相加 a + b 输出结果 30
- 减 - 得到负数或是一个数减去另一个数 a - b 输出结果 -10
* 乘 - 两个数相乘或是返回一个被重复若干次的字符串 a * b 输出结果 200
/ 除 - x除以y b / a 输出结果 2
% 取模 - 返回除法的余数 b % a 输出结果 0
** 幂 - 返回x的y次幂 a**b 为10的20次方, 输出结果 100000000000000000000
// 取整除 - 返回商的整数部分(向下取整 >>> 9//2 4 >>> -9//2 -5

Python赋值运算符

运算符 描述 实例
= 简单的赋值运算符 c = a + b 将 a + b 的运算结果赋值为 c
+= 加法赋值运算符 c += a 等效于 c = c + a
-= 减法赋值运算符 c -= a 等效于 c = c - a
*= 乘法赋值运算符 c *= a 等效于 c = c * a
/= 除法赋值运算符 c /= a 等效于 c = c / a
%= 取模赋值运算符 c %= a 等效于 c = c % a
**= 幂赋值运算符 c **= a 等效于 c = c ** a
//= 取整除赋值运算符 c //= a 等效于 c = c // a

Python位运算符

按位运算符是把数字看作二进制来进行计算的。Python中的按位运算法则如下:

下表中变量 a 为 60,b 为 13,二进制格式如下:

运算符 描述 实例
& 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 (a & b) 输出结果 12 ,二进制解释: 0000 1100
| 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。 (a | b) 输出结果 61 ,二进制解释: 0011 1101
^ 按位异或运算符:当两对应的二进位相异时,结果为1 (a ^ b) 输出结果 49 ,二进制解释: 0011 0001
~ 按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1 。~x 类似于 -x-1 (~a ) 输出结果 -61 ,二进制解释: 1100 0011,在一个有符号二进制数的补码形式。
<< 左移动运算符:运算数的各二进位全部左移若干位,由 << 右边的数字指定了移动的位数,高位丢弃,低位补0。 a << 2 输出结果 240 ,二进制解释: 1111 0000
>> 右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,>> 右边的数字指定了移动的位数 a >> 2 输出结果 15 ,二进制解释: 0000 1111

Python逻辑运算符

Python语言支持逻辑运算符,以下假设变量 a 为 10, b为 20:

运算符 逻辑表达式 描述 实例
and x and y 布尔"与" - 如果 x 为 False,x and y 返回 False,否则它返回 y 的计算值。 (a and b) 返回 20。
or x or y 布尔"或" - 如果 x 是非 0,它返回 x 的计算值,否则它返回 y 的计算值。 (a or b) 返回 10。
not not x 布尔"非" - 如果 x 为 True,返回 False 。如果 x 为 False,它返回 True。 not(a and b) 返回 False

Python成员运算符

除了以上的一些运算符之外,Python还支持成员运算符,测试实例中包含了一系列的成员,包括字符串,列表或元组。

运算符 描述 实例
in 如果在指定的序列中找到值返回 True,否则返回 False。 x 在 y 序列中 , 如果 x 在 y 序列中返回 True。
not in 如果在指定的序列中没有找到值返回 True,否则返回 False。 x 不在 y 序列中 , 如果 x 不在 y 序列中返回 True。

Python身份运算符

身份运算符用于比较两个对象的存储单元

运算符 描述 实例
is is 是判断两个标识符是不是引用自一个对象 x is y, 类似 id(x) == id(y) , 如果引用的是同一个对象则返回 True,否则返回 False
is not is not 是判断两个标识符是不是引用自不同对象 x is not y , 类似 id(a) != id(b)。如果引用的不是同一个对象则返回结果 True,否则返回 False。

Python运算符优先级

以下表格列出了从最高到最低优先级的所有运算符:

运算符 描述
** 指数 (最高优先级)
~ + - 按位翻转, 一元加号和减号 (最后两个的方法名为 +@ 和 -@)
* / % // 乘,除,取模和取整除
+ - 加法减法
>> << 右移,左移运算符
& 位 'AND'
^ | 位运算符
<= < > >= 比较运算符
<> == != 等于运算符
= %= /= //= -= += *= **= 赋值运算符
is is not 身份运算符
in not in 成员运算符
not and or 逻辑运算符

阶段一

第一部分 1-3 章 环境搭建语法入门

常用:

格式化输出:
print(a,b,sep="") # sep=""意思为两个数中间没有空格
print("a:%s,b:%s"%(a,b))  # 格式化输出

字符串的操作:
str='python'
str.split('a') # 以a来对字符串进行切割
str.replace('py','PY'[,max=x])  # 把py替换为PY max为可选项,表示最多替换几次
str.upper() # 返回大写
str.lower() # 返回小写
print(''.join['life','is','short']) # join拼接字符串 ''是为拼接的间隔,''就是没有间隔, 'A'就是一个a间隔:lifeAisAshort
第一阶段总结
# 唐僧大战白骨精:

# 显示欢迎信息
print('-' * 20, '欢迎光临<唐僧大战白骨精>', '-' * 20)
# 显示选择你的身份
print('选择你的身份:')
print('\t1.唐僧')
print('\t2.白骨精')
player_choose = input("请选择[1-2]:")

# 打印分割线:
print('-' * 64)

if player_choose == '1':
    # 选择1
    print('你已经选择了1,你将以->唐僧<-的身份来进行游戏')
elif player_choose == '2':
    # 选择2
    print('你竟然选择了白骨精,系统将自动分配身份:你将以->唐僧<-的身份来进行游戏')
else:
    # 选择3
    print('你输入了错误的参数,系统将自动分配身份:你将以->唐僧<-的身份来进行游戏')

# 进入游戏
# 创建变量 来保存玩家的生命值和攻击里
player_life = 5.0  # 生命值
player_attack = 3.0  # 攻击力
# 创建 boss的生命值和攻击力
boss_life = 100.0
boss_attack = 5.0
# 打印分割线:
print('-' * 64)
print(f"唐僧,你的生命值是:{player_life},你的攻击力是:{player_attack}")
# 打印分割线:
print('-' * 64)

# 定义boss或玩家死亡的die
# 由于游戏的选项是需要反复显示的,所以必须将其编写到一个循环里
die = True
while die:
    print('您可以进行以下操作:')
    print('\t1.练级')
    print('\t2.打boss')
    print('\t3.逃跑')
    game_choose = input('请选择要做的操作[1-3]:\n')
    # 打印分割线:
    print('-' * 64)
    if game_choose == '1':
        # 选择1
        print('你已经选择了1,你将进行练级,练级会使你增长生命值和攻击力')
        player_life += 5.0
        player_attack += 3.0
        print(f'恭喜你升级了, 你现在的生命值是:{player_life}, 你现在的攻击力是: {player_attack}')
        # 打印分割线:
        print('-' * 64)
        continue
    elif game_choose == '2':

        # 选择2 打boss
        # 玩家攻击boss
        # boss反击玩家
        # 减去的生命值,应该等于玩家的攻击力

        print('你已经选择了2,你将开始大战白骨精')
        while die:
            print('唐僧攻击了白骨精')
            boss_life -= (player_attack * 0.4)
            real = player_attack * 0.4
            # 控制
            print('因为白骨精是boss,减免40%的伤害,唐僧打掉了白骨精', format(real, '.1f'), '点血')
            # 打印分割线:
            print('-' * 64)
            if boss_life <= 0:
                print(f'白骨精受到了{player_attack}点伤害,重伤不治狗带了')
                print('恭喜您击杀了boss!!!游戏结束了')
                # 直接击杀,退出游戏
                die = False
                break
            print('白骨精攻击了唐僧')
            player_life -= boss_attack
            print(f'白骨精打掉了唐僧{boss_attack}点血')
            # 打印分割线:
            print('-' * 64)
            if player_life <= 0:
                print('您被打败了,游戏结束了')
                die = False
                break

    elif game_choose == '3':
        # 选择3
        print('唐僧一听说要打白骨精,吓得扭头就走,游戏结束')
        break
    else:
        print('您输入了错误的数字,请重新输入')
input("按任意键退出")

第二部分 4章 列表,元组,字典,集合

第四章 序列

# 第四章 序列
## 列表(list)
	- 列表是python中的一个对象
	- 对象就是内存中存储数据的一块数据
	- 之前我们学习的对象,像数值,他只能保存一个单一的数据
	- 列表中可以保存多个有序的数据
	- 列表时存储对象的对象
	- 列表的使用:
		1.列表的创建
		2.操作列表中的数据

## 序列(sequence)
	- 序列时python中最基本的数据结构
	- 数据结构指计算机中数据存储的方式
	- 序列用于保存一组有序的数据,所有的数据在序列但终都有一个唯一的位置(索引)
		并且序列中的数据会按照添加的顺序来分配索引
	- 序列的分类:
		可变序列(序列中的元素可以改变):
			> 列表 (list)
		不可变序列(序列中的元素不可以改变):
			> 字符串 (str)
			> 元组 (tuple)
		- 刚刚将的所有操作都时序列的通用操作 也就是切片 in,not in 等

## EMS( Employee Manager System 员工管理系统) 练习
	- 做命令行版本的命令行管理系统
	- 功能:
		四个:
			1. 查询
				- 显示当前系统的所有员工
			2. 添加
				- 将员工添加到当前系统中
			3. 删除
				- 将员工从系统中删除
			4. 退出
				- 退出系统
	- 员工信息要保存到哪里? 列表,在系统中应该有一个列表,专门用来保存所有员工信息的

## 可变对象
	- 每个对象中都保存了三个数据:
		id(标识)
		type(类型)
		value(值) 可变
	列表就是换一个可变对象
		a=[1,2,3]

	- a[0]=10 (改对象)
		- 这个操作是在同故宫变量去修改对象的值
		- 这种操作不会改变变量所指向的独享
		- 当我们去修改对象时,如果有其他对象也指向了该对象,则修改也会在其他变量中体现
		- 会影响所有指向该对象的变量

	a=[4,5,6] (改变量)
		- 这个操作是在给变量重新赋值
		- 这种操作会改变变量所指向的对象
		- 为一个变量重新赋值时,不会影响其他变量
	- 一般只有在为变量赋值时,才是赋值变量,其余都是修改对象

## 字典(dict)
	- 字典属于一种新的数据结构,成为映射,(mapping)
	- 字典的作用和列表类似,都是用来存储对象的容器
	- 列表存储数据的性能很好,查询性能的性能很差
	- 在字典中每一个元素都有一个唯一的名字,通过这个唯一的名字可以快速查找到指定的元素
	- 在查询元素时,字典的小路是非常快的
	- 在字典中可以保存多个对象,每个对象都会有一个唯一的名字
		这个唯一的名字我们叫做键(key)
		这个对象我们称之为值(value)
		所以字典,我们亦可以称为键值对(key-value)结构
		所以每个字典中都可以有多个键值对,而每一个键值对我们称其为一项(item)
		
## 集合(set)
	- 集合和列表非常相似
	- 不同点:
		1.集合中只能存储不可变对象
		2.集合中存储的对象是无序的(不是按照元素的插入顺序保存的)
		3.集合中不能出现重复的元素

01.列表的简介

01.列表的简介.py
# 创建列表,通过[]创建列表
my_list = []  # 创建了一个空列表
# print(my_list, type(my_list)) #类型是list

# 列表中存储的数据,我们称为元素
# 一个列表中可以存储多个元素,也可以在创建列表时,来指定列表中的元素
my_list = [10]  # 创建一个只有一个包含一个元素的列表

# 当向列表中添加元素时,多个元素之间使用,隔开
my_list = [10, 20, 30, 40, 50]  # 创建一个包含五个元素的列表

# 列表中可以保存任意对象
my_list = [10, 'hello', True, None, [1, 2, 3], print]

# 列表中的对象都会按照插入的顺序存储到列表中,
#   第一个插入的对象保存到第一个位置,第二个插入的保存到第二个位置
# 我们可以通过索引(index)来获取列表中的元素
#   索引是元素在列表中的位置,列表中的每一个元素都有一个索引
#   索引时从0开始的证书,第一个位置为0,第二个1,以此类推
my_list = [10, 20, 30, 40, 50]


# 通过索引获取列表总的元素
# 语法:my_list[索引] my_list[]
# print(my_list[4])
# 如果使用的索引超过最大范围会抛出异常
# print(my_list[5]) IndexError: list index out of range

# 获取列表的长度,列表中元素的个数
# len()函数,通过该函数可以获取列表的长度
# 获取到的长度,时列表的最大索引+1
print(len(my_list)) # 5

02.切片


02.切片.py
# 切片
# 切片指从现有列表中,获取一个子列表
# 创建一个列表,一般创建列表时,变量的名字会使用复数
stus = ['孙悟空', '猪八戒', '沙和尚', '唐僧', '蜘蛛精', '白骨精']

# 列表的索引可以时负数
# 如果索引时负数,则从后向前获取元素,-1代表倒数第一个,-2代表第二个
print(stus[-1])

# 通过切片获取指定的元素
# 列表 : 列表[起始:结束]
#  通过切片获取元素时,会包括起始位置的元素,不会包括结束位置的元素
#  做切片操作时,总会返回一个新的列表
#  起始和结束位置的索引都可以省略不写
#  如果省略结束位置,则会一直截取到最后
#  如果省略开始位置,则会从第一个元素开始截取
# 也可以传负值,也就是从后往前数
# 开始位置和结束位置则相当于创建了一个列表的副本
print(stus[-3:])
print(stus[1:3])
# 这俩不是一个 先当与复制了一个
print(stus[:])
print(stus)

# 语法: 列表[起始:结束:步长]
# 步长表示,每次获取元素的间隔,默认是1
# 步长可以是负数,但是不能是0
print(stus[0:5:2])
# 如果是负数,则从列表后部向前面取元素
print(stus[::-1])

03.通用操作

03.通用操作.py
# + 和 *

# + 可以把两个列表拼接为一个列表
my_list = [1, 2, 3] + [4, 5, 6]

# * 可以把列表重复指定的次数  可以创建定长列表
my_list = [1, 2, 3] * 2
# 定长列表的另一种方法
my_list=[0 for i in range(100)]   #创建长为100全为0的列表

# print(my_list)
stus = ['孙悟空', '猪八戒', '沙和尚', '唐僧', '蜘蛛精', '白骨精', '沙和尚']

# in 和 not in
# in用来检查指定元素是否存在于列表中
#  如果存在返回 true,否则返回false
# not in 来检查指定元素是否不在列表中
#  如果不在返回 false,否则返回true
print("沙和尚" in stus)
print("牛魔王" in stus)

# len() 获取列表中元素的个数
# min() 获取列表中的最小值
# max() 获取列表中的最大值
arr = [10, 1, 2, 5, 100, 77]
print(min(arr), max(arr))


# 两个方法(method) , 方法和函数基本是一样的,方法必须通过对象.方法()的形式调用
# xxx.print() 方法实际上就是和对象关系紧密的函数
# s.index() 获取指定元素在列表中第一次出现时的位置
print(stus.index('沙和尚'))
# index的第二个参数传从第几个位置开始查找,第三个参数表时查找的结束位置
print(stus.index('沙和尚', 3, 7))
# 如果没有 则会报错ValueError: '牛魔王' is not in list
# print(stus.index('牛魔王'))

# s.count()
# 查看指定的元素在列表出现的次数
print(stus.count('沙和尚'))

04.修改元素


04.修改元素.py
stus = ['孙悟空', '猪八戒', '沙和尚', '唐僧', '蜘蛛精', '白骨精']

# 修改列表中的元素
# 直接通过索引来修改元素
print('修改前', stus)
stus[0] = 'sunwukong'
print('修改后', stus)

# 通过del删除元素
del stus[0]  # 删除索引为0的元素


stus = ['孙悟空', '猪八戒', '沙和尚', '唐僧', '蜘蛛精', '白骨精']
print('修改前', stus)
# 通过切片来修改列表
# 再给切片赋值时,只能使用序列
# stus[0:2]=123 # 错
# stus[0:2] = ['红孩儿', '牛魔王','二郎神']
stus[0:0] = ['牛魔王']  # 向索引为零的位置插入元素
# 当我们设置了步长时,序列中的个数必须和切片中元素的个数一致
stus[::2] = ['牛魔王', '红孩儿', '二郎神', '黑熊精']
print('修改前', stus)
# 通过切片来删除元素
del stus[0:2]
print('修改后', stus)
del stus[::2]
print('修改后', stus)


stus = ['孙悟空', '猪八戒', '沙和尚', '唐僧', '蜘蛛精', '白骨精']
stus[0:3] = []
print('修改后', stus)

# 以上操作仅适用于可变序列
s = 'hello'
print(s[::2])
# s[1]='a' # 错 # 不可变序列,无法通过索引来修改
# 可以通过list()函数将其他序列转换为list
s = list(s)
print(s)

05.列表的方法

05.列表的方法.py
# 列表的方法
stus = ['孙悟空', '猪八戒', '沙和尚']
print('源列表', stus)


# append() 向列表末尾添加一个元素
stus.append('唐僧')
print('修改后', stus)

# insert() 向列表指定的位置传入一个元素
# 参数:
#  1.要插入的位置
#  2.要插入的元素
stus.insert(2, '唐僧')
print('修改后', stus)

# extend()
# 使用新的序列来扩展当前序列
# 需要一个序列作为参数,它会将该序列中的元素添加到当前序列
stus.extend(['tangseng', '白骨精'])
# 等同于 stus+=['tangseng', '白骨精']
print('修改后', stus)

# clear()
# 情况序列
stus.clear()
print('修改后', stus)


stus = ['孙悟空', '猪八戒', '沙和尚']

# pop()
# 根据索引删除并返回指定元素,并返回删除的元素
result = stus.pop(2)  # 删除索引为2的元素,并返回删除的元素 不写参数就删最后一个
print('修改后', stus, '\n', result)


# remove()
# 删除指定值的元素,如果相同值只会删除第一个
stus.remove('猪八戒')
print('修改后', stus)


stus = ['孙悟空', '猪八戒', '沙和尚']
# reverse()
# 反转列表
stus.reverse()
print('修改后', stus)


# sort()
# 用来对列表中的元素进行排序
# 默认升序排序
my_list = list('asdfasdfasdf')
print('修改前', my_list)
my_list.sort()
print('修改后', my_list)
# 降序排列
my_list.sort(reverse=True)
print('修改后', my_list)

06.遍历列表


06.遍历列表.py
# 遍历列表,指的就是将列表中的所有元素取出来
# 创建列表
stus = ['孙悟空', '猪八戒', '沙和尚', '唐僧', '白骨精', '蜘蛛精']

# 遍历列表

# 渣渣
print(stus[0])
print(stus[1])
print(stus[2])

print('-' * 20 + '分割线' + '-' * 20)
# 通过while遍历循环
# 也不用
i = 0
while i < len(stus):
    print(stus[i])
    i += 1

print('-' * 20 + '分割线' + '-' * 20)
# 通过for循环来遍历列表
# 常用
# 语法:
#   for 变量 in 序列:
#	代码块
# for循环的代码块会执行多次,序列中有几个元素就会执行几次
#  每执行一次就会将序列中的一个元素赋值给变量
for s in stus:
    print(s)

#enumerate()将一个可遍历的数据对象组合为一个索引序列,同时标出数据和下标,一般用在for循环当中
for i in enumerate(stus):
    print(i)
输出:
(0, '孙悟空')
(1, '猪八戒')
(2, '沙和尚')
(3, '唐僧')
(4, '白骨精')
(5, '蜘蛛精')

# 列表推导式 批量生成符合规则的元素组成的列表
squares=[i*2 for i in stus]
print('squares')

07.EMS 练习

07.EMS练习.py
(Employee Manager System 员工管理系统)  
# 显示系统欢迎信息
print('=' * 20 + '欢迎使用员工管理系统' + '=' * 20)

# 创建一个列表,来保存员工的信息,员工的信息以字符串的形式同一保存到列表中
emps = ['孙悟空\t18\t男\t花果山', '猪八戒\t28\t男\t高老庄']
# 创建一个死循环
while True:
    # 显示用户选项
    print('请选择要做的操作:')
    print('\t 1.显示员工')
    print('\t 2.添加员工')
    print('\t 3.删除员工')
    print('\t 4.退出系统')
    user_choose = input('请选择要做的操作[1-4]:')
    print('-' * 58)
    # 根据用户的选择做相关的操作
    if user_choose == '1':
        # 查询员工
        # 打印表头
        print('\t序号\t姓名\t年龄\t性别\t住址')
        # 创建一个变量,来表示员工的序号
        n = 1
        # 显示员工信息
        for emp in emps:
            print(f'\t{n}\t{emp}')
            n += 1

    elif user_choose == '2':
        # 添加员工
        # 获取要添加员工的信息,姓名,年龄,性别,住址
        emp_name = input('请输入员工姓名:')
        emp_age = input('请输入员工年龄:')
        emp_gender = input('请输入员工性别:')
        emp_address = input('请输入员工住址:')

        # 创建一个员工信息
        emp = f'{emp_name}\t{emp_age}\t{emp_gender}\t{emp_address}'
        # 显示一个提示信息
        # input('员工:' + emp + '将会被添加到系统中,是否确认该操作[Y/N]:')
        print('以下员工将会被添加到系统中')
        print('-' * 58)
        print('姓名\t年龄\t性别\t住址')
        print(emp)
        print('-' * 58)
        user_confirm = input('是否确认该操作[Y/N]:')

        # 判断
        if user_confirm == 'y' or user_confirm == 'Y' or user_confirm == 'yes':
            # 确认
            emps.append(emp)
            # 显示提示信息
            # 插入成功

        else:
            # 取消操作
            print('添加以取消')
            pass

    elif user_choose == '3':
        # 删除员工 根据员工的序号来删除员工
        # 要获取要删除员工的序号
        del_num = int(input('请输入要删除员工的序号:'))

        # 判断序号是否有效
        if 0 < del_num <= len(emps):
            # 输入合法,根据序号来获取索引
            del_i = del_num - 1
            # 显示提示
            print('以下员工将会被删除')
            print('-' * 58)
            print('\t序号\t姓名\t年龄\t性别\t住址')
            print(f'\t{del_num}\t{emps[del_i]}')
            print('-' * 58)
            user_confirm = input('该操作不可恢复,是否确认该操作[Y/N]:')
            # 判断
            if user_confirm == 'y' or user_confirm == 'Y':
                # 删除元素
                emps.pop(del_i)
                # 显示提示
                print('员工已被删除')
            else:
                # 操作取消
                print
        else:
            # 输入有误
            print('输入有误,请重新输入')

    elif user_choose == '4':
        # 退出系统
        print('-' * 58)
        print('欢迎使用!再见!')
        input('点击回车键退出')
        break
    else:
        print('您的输入有误,请重新选择!')
    # 打印分割线
    print('-' * 58)

08.range

# range() 是一个函数,可以用来生成一个自然数的序列
r = range(10)  # 生成了这样的序列[0,1,2,3,4]
print(r)
print(list(r))
# 该函数需要三个参数
#	1.起始位置(可以省略,默认是0)
#	2.结束位置
#	3.步长(可以省略,默认是1)

# 通过range()可以创建一个执行指定次数的for循环
# for()循环除了创建方式意外,
# 其余都和while一样,包括else,break,continue都可以在for循环中使用
# 并且for循环使用也更加简单
for i in range(20):
    print(i)


for s in 'hello':
    print(s)

09.元组

09.元组.py
# 元组就是不可变序列
# tuple
# 他的操作方式基本上和列表是一样的
# 索引在操作元组时,就把元组当成一个不可变的列表就ok了
# 一般当我们希望数据不改变时,就是用元组,其余情况都使用列表

# 创建元组
# 使用()来创建元组
my_tuple = ()  # 创建了一个空元组
print(my_tuple, type(my_tuple))


my_tuple = (1, 2, 3, 4, 5)  # 创建了一个五个元素的元组
# 元组是不可变对象,不能为尝试为元组中的元素重新赋值
# my_tuple[3] = 10 # TypeError: 'tuple' object does not support item assignment
# 但是元组可以修改元组内的可变对象如:
my_tuple=(1, 2, 3, 4,[1,2])
my_tuple[4][0]=0
print(my_tuple) 
输出为:(1, 2, 3, 4, [0, 2])

# 当我们的元组不是空元组时,括号可以省略
# 如果元组不是空元组,它里面至少要有一个',' 不管加不加()
my_tuple = 10, 20, 30, 40
print(my_tuple, type(my_tuple))

# 元组的解包
# 解包就是将元组的每一个元素赋值给一个变量
my_tuple = 10, 20, 30, 40
print(my_tuple)
a, b, c, d = my_tuple
print(' a=', a, '\n', 'b=', b, '\n', 'c=', c, '\n', 'd=', d, '\n')
# a= 10
# b= 20
# c= 30
# d= 40

a = 100
b = 300
print(a, b)

# 交换 a和b的值,这是就可以利用元组的解包
a, b = b, a

print(a, b)


# 在对一个元组进行解包时,变量的数量必须和元组中的元素的数量是一致的
# 也可以在变量前边添加一个'*',这样变量将会获取元组中的所有剩余的元素生成列表
# 最后
my_tuple = 10, 20, 30, 40
a, b, *c = my_tuple
print(a, b, c)

# 中间
a, *b, c = my_tuple
print(a, b, c)

# 开头
*a, b, c = my_tuple
print(a, b, c)

# 不能同时出现两个及两个以上'*'
# *a, *b, c = my_tuple #SyntaxError: two starred expressions in assignment
# print(a, b, c)

a, b, *c = "hello,word"
print(a, b, c)

10.可变对象

10.可变对象.py
# 可变对象
a = [1, 2, 3]
print('修改前:', a, id(a), id(a[0]))

# 通过索引修改列表
a[0] = 9
print('修改后:', a, id(a), id(a[0]))

# 为变量重新赋值
a = [4, 5, 6]
print('修改后:', a, id(a), id(a[0]))

a = [1, 2, 3]
b = a
b[0] = 10
print('a', a, id(a))
print('b', b, id(b))

a = [1, 2, 3]
b = a
b = 10, 20, 30
print('a', a, id(a))
print('b', b, id(b))

# == != is is not
# == != 比较的时对象的值是否相等
# is is not 比较的是对象的id是否相等(比较两个对象是否是一个对象)
a = [1, 2, 3]
b = [1, 2, 3]
print(a, b)
print(id(a), id(b))
print(a == b)  # a和b的值相等,使用== 会返回true
print(a is b)  # a和b不是一个对象,内存地址不同,使用is会返回false

11.字典

# 字典
# 使用{}来创建字典
d = {}  # c创建了一个空字典

# 创建一个保存有数据的字典
# 语法:
d={key:value,key:value,key:value}
d=dict(key=value,key=value,key=value)
d=([("key","value"),("key","value")])
# 字典的值可以是任意对象
# 字典的键可以是任意的不可变对象(int,str,bool,tuplu ...)
d = {'name': '孙悟空', 'age': '18', 'gender': '男'}
print(d, type(d))
# 	字典的键是不能重复的,如果出现重复,后面会替换前面的
d = {
    'name': '孙悟空',
    'age': '18',
    'gender': '男',
    'name': 'sunwukong'
}
print(d, type(d))

# 需要根须键来获取值
print(d['name'], d['age'], d['gender'])

# 如果使用字典中不存在的键,会报错
print(d['hello'])


12.字典的使用

# 字典
# 使用{}来创建字典
# 语法:
d={key:value,key:value,key:value}
d=dict(key=value,key=value,key=value)
d=([("key","value"),("key","value")])
# 使用dict()函数来创建字典
# 每一个参数都是一个键值对,参数名就是键,参数名就是值(这种方式创建的字典,key都是字符串)
# 这种函数会给键自动加''
d = dict(name='孙悟空', age=18, gender='男')
print(d, type(d))

# 也可以将一个包含有双值子序列的序列转换为字典
# 双值序列,序列中只有两个值 : [1,2](a,3) 'ab'
# 子序列,如果序列中的元素也是序列那么我们就称这个元素为子序列
# (1,2),(3,5)]
# 特殊情况会用
d = dict([('name', '孙悟饭'), ('age', 18)])

print(d, type(d))


# len() 获取字典中键值对的个数
d = dict(name='孙悟空', age=18, gender='男')
print(len(d))

# in 检查字典中是否包含指定的键
# not in 检查字典中是否不包含指定的键
print('name' in d)
print('hello' in d)


# 获取字典中的值,根据键来获取值
# 语法: d[key]
print(d['name'])
# print(d[name])  # 不加引号当变量使用报错 NameError: name 'name' is not defined

# 使用变量时不要加''
n = 'name'
print(d[n])


# 通过[]来获取值时,如果键不存在,会抛出异常 KeyError
# get(key[,default])
# 使用get()没有的话会返回None
print(d.get('name'))
# 也可以指定一个默认值,来作为第二个参数,这样获取不到值时会返回默认值
print(d.get('hello', '默认值'))


# 修改字典
# d[key]=value 如果key存在则覆盖,不存在则添加
d['name'] = 'sunwukong'
# 添加字典 默认在最后
d['address'] = '花果山'
print(d)


# setdefault(key[,default]) 可以向字典添加key-value
# 如果字典存在键 key ,返回它的值。
# 如果不存在,插入值为 default 的键 key ,并返回 default 。 default 默认为 None。
result = d.setdefault('name', '猪八戒')
print('result:', result)
result = d.setdefault('hello', '猪八戒')
print('result:', result)
print(d)


# update([other])
# 将其他字典的key-value添加到当前字典中
# 如果有重复,后面会替换当前的
d = {'a': 1, 'b': 2, 'c': 3}
d2 = {'d': 4, 'e': 5, 'f': 6, 'a': 7}
d.update(d2)
print(d)

# 删除字典
# del来删除字典中的key-value
# 删除不存在的key-value也会报错
print(d)
del d['a']
print(d)
del d['b']
print(d)

# popitem()
# 随机删除字典中的一个键值对,一般都会删除最后一个键值对
# 	删除之后,他会将删除的键值对作为返回值放回
# 	返回的是一个元组,元组有两个元素,一个是字典删除的key一个是删除的value
# 删除一个空字典也会报错
print(d)
result = d.popitem()
print(d, result)


# pop(key[, default])
# 根据key删除字典中的key-value
# 会将被删除的value返回
result = d.pop('d')
# 删除不存在的key也会抛出异常
# 如果指定默认值,在删除不存在的key则不会报错,会直接返回默认值
result = d.pop('z', '这是默认值')
print(d, result)

# clear()
# 删除所有的项
d = {'a': 1, 'b': 2, 'c': 3}
print(d)
d.clear()
print(d)


# copy()
# 该方法用于对字典[浅复制]
# 复制以后的对象,和源对象是独立,修改一个不会影响其他的
# 此方法也用于序列
d = {'a': 1, 'b': 2, 'c': 3}
# 直接 d=d2 指向的都是一个对象
# id一致
d2 = d

# id不一致
d2 = d.copy()
print(id(d), id(d2))

# 注意,浅复制指挥简单的复制对象内部的值,如果值也是一个可变对象,这个可变对象不会被复制
# 深复制性能较差,常用浅赋值
d = {'a': {'name': '孙悟空', 'age': 18}, 'b': 2, 'c': 3}
print(d)
d2 = d.copy()
d2['a']['name'] = '猪八戒'
print(d)
print(d2)

13.遍历字典

# 遍历字典
# keys() 该方法会返回所有的key
# 该方法会返回一个序列,序列中保存有字典的所有的键 key
d = {'name': '孙悟空', 'age': 18, 'gender': '男'}
print(d.keys())
# 通过遍历keys()来获取所有的键
for k in d.keys():
    print(d[k])

#  分割线
print('-' * 90)


# values()
# 该方法会返回一个序列,序列中保存有字典中所有的value 值
for v in d.values():
    print(v)


#  分割线
print('-' * 90)

# items()
# 该方法会返回字典中所有的项
# 他会返回一个序列,序列中包含有双值子序列
# 双值分别是字典中的key和value
print(d.items())
for k, v in d.items():
    print(k, '=', v)

14.集合

# 集合
# 使用{}来创建 集合
s = {10, 3, 5, 1, 2, 1}  # 只有一个1
# s = {[1, 2, 3], [4, 5, 6]} TypeError: unhashable type: 'list'
print(s, type(s))


# 使用set()来使用集合
# 不能直接s={}这样是字典
s = set()  # 这是空集合
# 可以通过set()来将序列和字典转换为集合
s = set([1, 2, 3, 4, 5, 1, 2, 1, 3, 4, 5])
# {1, 2, 3, 5, 10} <class 'set'>
s = set('hello')
# {'e', 'l', 'h', 'o'} <class 'set'>
s = set({'a': 1, 'b': 2, 'c': 3})  # 使用set()将字典转换为集合是,指挥包含字典中的键
# {'c', 'b', 'a'} <class 'set'>
print(s, type(s))

# 不可变集合
notchangeset=frozenset(s)

# 创建集合
s = {'a', 'b', 1, 2, 3}
# print(s[0]) TypeError: 'set' object does not support indexing
# 不能使用索引若想使用需要转换为list
print(list(s)[0])  # b

# 也可以使用in和not in来检查
print('a' in s)

# 使用len()获取集合中元素的数量
print(len(s))

# add()向集合中添加元素
s.add(10)
s.add(30)
print(s)

# update()将集合中的元素添加到当前集合中
s2 = set('hello')
s.update(s2)
s.update((100, 200, 300))
# update()可以传递序列活字典作为参数,字典纸绘使用键 key
s.update({1000: 'ab', 10000: 'cd'})
print(s)


# pop()随机删除集合中的元素
result = s.pop()
print(s, result)

# remove()删除集合中的指定元素
s.remove(100)
print(s)

# clear()清空集合
s.clear()
print(s)

# copy()对集合进行浅复制
s = set('hello')
s1 = s.copy()
print(s1, id(s1), s, id(s))

深拷贝与浅拷贝与赋值

import copy  # 调用copy模块
dict1={'name':'lilei','age':89,'num':[1,2,8]}
dict_copy = dict1.copy()  # 浅拷贝
dict_dcopy=copy.deepcopy(dict1)  # 深拷贝
dict2=dict1  # 浅拷贝,直接赋值对象
dict1['num'][1]=6 # 修改源数据中嵌套列表的值
dict1['age']=50
print('dict1:'+str(dict1)+'\n','dict_copy:'+str(dict_copy)+'\n','dict_dcopy:'+str(dict_dcopy)+'\n','dict2:'+str(dict2)+'\n',sep="")
结果:
dict1:{'name': 'lilei', 'age': 50, 'num': [1, 6, 8]}
dict_copy:{'name': 'lilei', 'age': 89, 'num': [1, 6, 8]}
dict_dcopy:{'name': 'lilei', 'age': 89, 'num': [1, 2, 8]}
dict2:{'name': 'lilei', 'age': 50, 'num': [1, 6, 8]}

总结:
浅拷贝对外层进行复制一份新的,但是深层的嵌套是引用,所以age还是89,但是2变为了6
深拷贝是全部复制一份新的
赋值是直接引用dict1,所以dict2全部改变,如果使用id()查看两个是一样的

15.集合的运算

# 创建两个集合
# 对集合做运算时,不会影响原来的集合,而是返回一个运算的结果
s = {1, 2, 3, 4, 5}
s2 = {3, 4, 5, 6, 7}

# & 交集运算
print(s, s2)
result = s & s2
print(result)  # {3, 4, 5}

# | 并集运算
result = s | s2
print(result)  # {1, 2, 3, 4, 5, 6, 7}

# - 差集运算
result = s - s2
print(result)  # {1, 2}  只在a集合有的b集合没有的

# ^ 异或集运算
result = s ^ s2
print(result)  # {1, 2, 6, 7} 两个集合中不相交的值

# <= 检查一个集合是否是另一个集合的子集
# 如果a集合中的元素全部都在b集合中出现,那么a集合就是b集合的子集,b集合就是a集合的超集
a = {1, 2, 3}
b = {1, 2, 3, 4, 5}
result = a <= b  # true
result = {1, 2, 3} <= {1, 2, 3}  # true
result = {1, 2, 3, 4} <= {1, 2, 3}  # false
print(result)
# 如果两个集合一样也是子集

# < 检查一个集合是否是另一个集合的真子集
# 如果超集b中含有子集a中的所有元素,并且b中还有a中没有的元素,则b就是a的真超集,a是b的真子集
result = {1, 2, 3} < {1, 2, 3}  # false  # 子集但不是真子集
result = {1, 2, 3} < {1, 2, 3,4,5} # true  # 真子集
print(result)

# >= 检查a集合是否是b集合的超集
# > 检查a集合是否是b集合的真超集

第三部分 5- 章 函数,面向对象,异常处理

第五章 函数

# 第五章 函数

## 函数简介(function)
	- 函数也是一个对象
	- 对象是内存中专门用来存储数据的一块区域
	- 函数可以用来保存一些可执行的代码,且可以在需要时,对这些语句进行多次的调用
	- 创建函数:
		def 函数名([形参1],[形参2],.....,[形参n]):
			代码块
		- 函数名必须要符合标识符的规范
			(可以包含数字字母下划线,数字不能开头)
	- 函数中保存的代码不会立即执行,调用后才会执行
	- 调用函数
		函数对象()
	- 实现函数都是要实现某种功能

## 函数的参数
	- 在定义函数时,可以在函数名后()中定义数量不等的形参
		多个形参中用','隔开
	- 形参(形式参数),定义形参就相当于在函数定义了变量但没赋值
	- 实参(实际参数)
		- 如果函数定义了形参,那么在调用时也必须传递实参,
			实参将会赋值给对应的形参,有几个形参,就得传几个实参

## 函数式编程
	- 在python中,函数是一等对象
	- 一等对象一般会具有以下条件:
		① 对象是在运行时创建的
		② 能赋值给变量或者作为数据结构中的元素
		③ 能作为参数传递
		④ 能作为返回值返回
	- 高阶函数
		- 高阶函数至少要符合以下两个特点中的一个
			① 接收一个或多个函数作为参数
			② 将函数作为返回值返回
	- 装饰器

01.函数的简介

# 比如有如下三行代码,这三行代码是一个完整的功能
# print('hello')
# print('你好')
# print('再见')


# 定义一个函数
def fn():
    print('这是我的第一个函数!')
    print('hello')
    print('今天天气真不错')
    print('再见')


print(fn, type(fn))
# <function fn at 0x0000026293BA2E18> <class 'function'>

# fn时函数对象,fn()时调用函数
# print时函数对象,print()调用对象
fn()

# 定义一个函数,可以用来求任意两个数的和

# 定义函数时定义形参


def sum(a, b):
    print(a, '+', b, '=', a + b)


# 调用函数时,来传递实参
sum(123, 456)

02.函数的参数

# 求任意三个数的乘积
def mul(a, b, c):
    print(a * b * c)

# 根须不同的用户名显示不同的欢迎信息


def welome(username):
    print('欢迎', username, '光临')


mul(1, 2, 3)
welome('孙悟空')

# 定义一个函数
# 定义形参是,可以为形参指定默认值
# 指定了默认值以后,如果用户传递了参数则不会生效
# 如果用户没有传递,则默认值生效


def fn(a=1, b=2, c=20):
    print('a=', a)
    print('b=', b)
    print('c=', c)


fn(1, 2)

# 实参的传递方式
# 位置参数
# 位置参数就是将对应位置的实参赋值给对应位置的形参
# 第一个实参赋值给第一个形参,第二个赋值给第二个形参 ...
fn(1, 2, 3)

# 关键字参数
# 关键字参数可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数
fn(b=1, a=3, c=2)
print('hello', end='' + '\n')
# 位置参数和关键字可以混合使用
# 混合使用关键字和位置参数时,必须将位置参数写道前面
fn(1, c=20)
# fn(1, a=10)  # erro


def fn2(a):
    print('a=', a)


# 函数在调用时,解析器不会检查实参的类型
# 实参可以传递任意类型的对象
b = 123
b = True
b = [1, 2, 3]
fn2(b)

fn2(fn)  # a= <function fn at 0x000002666CF89B70>


def fn3(a, b):
    print(a + b)

# fn3(123, '456')
# TypeError: unsupported operand type(s) for +: 'int' and 'str'


def fn4(a):
    # 在函数中对形参进行重新赋值,不会影响其他变量
    # a = 20
    # a是一个列表,尝试修改列表中的元素
    # 如果形参指向的是一个对象,当我们通过形参去修改对象是
    # 	会修改所有指向的该对象的变量
    a[0] = 30
    print('a=', a, id(a))


c = 10
c = [1, 2, 3]
fn4(c)
print(c, id(c))


# 传副本
def fn4(a):
    a[0] = 30
    print('a=', a, id(a))


c = [1, 2, 3]
fn4(c.copy())
# or fn(c[:])
print('c=', c, id(c))

03.不定长参数

# 不定长参数
# 定义一个函数,可以求任意数的和
def sum(*nums):
    # 定义一个变量来保存结果
    result = 0
    # 遍历元组,并将元组中的数进行累加
    for n in nums:
        result += n
    print(result)


sum(1, 2, 12, 345, 3245, 3245)

print('*' * 60)  # 分割线

# 在定义函数时,可以在形参前面加上一个*,这样这个形参将会获取到所有的实参
# 他将会将所有的实参保存到一个元组中
# a,b,*c=(1,2,3,4,5,6)
# *a会接受所有的位置实参,并且会将这些实参同一保存到一个元组中(装包)


def fn(*a):
    print('a=', a, type(a))


fn(1, 2, 3, 4, 5)
# 带* 的参数只能有一个
# 带* 的参数可以和其他参数配合使用

print('*' * 60)  # 分割线


def fn2(a, b, *c):
    print('a=', a)
    print('b=', b)
    print('c=', c)


fn2(1, 2, 3, 4, 5)
# 第一个参数给a,第二个给b,其他给c

# 可变参数不是必须写到最后,带*的参数后的所有参数,必须以关键字形式传递
print('*' * 60)  # 分割线


def fn2(a, *b, c):
    print('a=', a)
    print('b=', b)
    print('c=', c)


fn2(1, 2, 3, 4, c=5)
# 第一个参数给a,其他给b ,c必须用关键字传递

print('*' * 60)  # 分割线


def fn2(*a, b, c):
    print('a=', a)
    print('b=', b)
    print('c=', c)


fn2(1, 2, 3, b=4, c=5)
# 所有位置参数给a,b和c必须用关键字传递

print('*' * 60)  # 分割线


def fn2(*, a, b, c):
    print('a=', a)
    print('b=', b)
    print('c=', c)


fn2(a=3, b=4, c=5)
# 如果在形参开头直接写 *, 则要求我们所有的参数必须以关键字来传递

print('*' * 60)  # 分割线

# *形参只能接受位置参数,不能接收关键字参数


# def fn3(*a):
#     print('a=', a)


# fn3(b=1, d=2, c=3)


def fn3(b, c, **a):
    print('b=', b)
    print('c=', c)
    print('a=', a, type(a))


fn3(b=1, d=2, c=3, e=10, f=20)
# **形参可以接收其他的没有定义的关键字参数,会将这些参数同意保存到一个字典中
#	字典的key就是参数的名字,value就是参数的值
# **形参只能有一个,并且必须写在所有参数的最后


# 参数的解包(拆包)
def fn4(a, b, c):
    print('a=', a)
    print('b=', b)
    print('c=', c)


print('*' * 60)  # 分割线

# 创建一个元组
t = (10, 20, 30)
# 传递实参时,也可以在序列类型的参数前添加* ,这样他会自动将序列中的元素依次作为参数传递
# 要求序列中元素的个数必须和形参的个数一致
fn4(*t)

print('*' * 60)  # 分割线

# 创建一个字典
d = {'a': 100, 'b': 200, 'c': 300}
# **对字典解包
fn4(**d)

04.返回值

# 返回值就是函数执行以后返回的结构
# 使用return来指定函数的返回值
# 可以直接使用函数的返回值,也可以通过一个变量来接收函数的返回值

def sum(*nums):
    # 定义一个变量来保存结果
    result = 0
    # 遍历元组,并将元组中的数进行累加
    for n in nums:
        result += n
    return result


result = sum(123, 356, 789)
print(result)

print('*' * 60)  # 分割线


def fn():
    # return [1, 3, 4, 5, 5]
    # return{'name': 123}
    def fn2():
        print('hello')
    return fn2  # 返回值也可以是一个函数


r = fn()  # 这个函数的执行结果就是他的返回值
print(r)
r()
# return后面跟什么值,函数就会返回什么值
# return可以跟任意对象,返回值甚至可以时一个函数

print('*' * 60)  # 分割线


def fn2():
    a = 10


r = fn2()
print(r)

# 如果仅仅写一个return 或者 不写return 则相当于return None

print('*' * 60)  # 分割线


def fn3():
    print('hello')
    return
    print('abc')


r = fn3()
print(r)

# return 在函数中return后的代码都不会执行,return一旦执行,函数自动结束

print('*' * 60)  # 分割线


def fn4():
    for i in range(5):
        if i == 3:
            # break  # 用来退出当前循环
            # continue # 用来跳出当次循环
            return  # 用来结束当前函数
        print(i)
    print('循环执行完毕')


fn4()

print('*' * 60)  # 分割线


def fn5():
    return 10


# fn5和fn5()的区别
print(fn5)  # fn5是函数对象,打印fn5实际是在打印函数对象 <function fn5 at 0x000001A491CA9D08>
print(fn5())  # fn5()是在调用函数,打印fn5()实际上是在打印fn5()函数的返回值 10

05.文档字符串

# 文档字符串
# help()是Python中的内置函数
# 通过help()函数可以查询python中的函数用法
# 语法:help(函数对象)
# help(print)  # 查看print函数的使用说明

# 文档字符串(doc str)
# 在定义函数时,可以在函数内部编写文档字符串,文档字符串就是函数的说明
#	当我们编写了文档字符串是,就可以通过help()来查看函数的说明
# 	文档字符串非常简单,启示直接在函数的地一样写一个字符串就是文档字符串


def fn(a: int, b: bool, c: str) -> int:
    '''
    这是一个文档字符传的示例
    尽可能使用英文,但是why?对外项目就用英文吧,国内还是中文

    函数的作用
    函数的参数:
        a,作用:类型....
        b,作用:类型....
        c,作用:类型....
    '''
    return 10


help(fn)

06.作用域于命名空间

# 作用域(scope)
# 作用域指的是变量生效的区域
b = 20


def fn():
    a = 10  # a定义在了函数内部,所以他的作用域就是海曙内部,函数外部无法访问
    print('函数内部:', 'a=', a)
    print('函数内部:', 'b=', b)


fn()

# print('函数外部:', 'a=', a)  # NameError: name 'a' is not defined
print('函数外部:', 'b=', b)

print('-' * 60)  # 分割线

# 在python中一共有两种作用域,
# 全局作用域
#  	- 全局作用域在程序执行时创建,在程序执行结束时销毁
#	- 所有函数以外的都是全局作用域
#	- 在全局作用域中定义的变量都属于全局变量,全局变量可以在程序的任意位置被访问
#
# 函数作用域
# 	- 函数作用域在函数调用时创建,在调用结束时销毁
# 	- 函数每调用一次就会产生一个新的函数作用域
# 	- 在函数作用域中定义的变量,都是局部变量,他只能在函数内部被访问
#
# 变量的查找
#	- 当我们使用一个变量时,会优先在当前作用域中寻找变量,如果有则使用
# 		如果没有,则继续去上一级作用域寻找
# 		如果依然没有则则继续去上一级作用域寻找.......以此类推
#		只到找到全局作用域依然没有找到,则会抛出异常


def fn2():
    a = 30
    print('fn2中', 'a=', a)

    def fn3():
        a = 40
        print('fn3中', 'a=', a)
    fn3()


fn2()  # fn3中 a= 30 可以从内往外,不能从外往内

print('-' * 60)  # 分割线

a = 20


def fn3():
    # a = 10  # a= 10 在函数中为变量赋值时,默认都是为局部变量赋值
    # 如果需要函数内部修改全局变量,则需要使用global关键字,来声明变量
    global a  # 声明在函数内部中使用的a时全局变量,此时再去修改a时,就是在修改全局变量
    a = 10
    print('函数内部:', 'a=', a)


fn3()
print('函数外部:', 'a=', a)

print('-' * 60)  # 分割线

# 命名空间(namespace)
# 命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间中
# 每一个作用于都会有一个它对应的命名空间
# 全局命名空间用来保存全局变量,函数命名空间用来保存函数中的变量
# 命名空间实际上就是一个字典,是一个专门用来存放变量的字典

# locals()用来获取当前作用域的命名空间
# 如果在全局作用域中调用locals()则获取全局命名空间,如果在函数作用域中调用locals()则获取函数命名空间
scope = locals()
print(scope, type(scope))
print(a)
print(scope['a'])

print('-' * 60)  # 分割线

# print(c)  # NameError: name 'c' is not defined

# 向scope中添加一个key-value
scope['c'] = 1000  # 向字典中田间key-value就相当于在全局中创建了一个变量,但是不建议这么做
print(c)

print('-' * 60)  # 分割线


def fn4():
    a = 10
    # scope = locals()  # 在函数内部调用locals()会获取到函数的命名空间
    # scope['b'] = 20 # 可以通过scope来操作函数的命名空间,但是不建议这么做

    # global()函数可以用来在任意位置获取全局命名空间
    global_scope = globals()
    print(scope)
    global_scope['a'] = 30
    print(b)


fn4()
print('函数外部:', 'a=', a)

07.递归

# 尝试求10的阶乘(10!)
# 创建一个函数,求任意数的阶乘
# 1!=1
# 2!=1*2=2
# 3!=1*2*3=6
# 4!=1*2*3*4=24

# 创建一个变量保存结果
n = 10
for i in range(1, 10):
    n *= i
    print(n)

print('-' * 60)  # 分割线

# 创建一个函数,用来求任意数的阶乘


def factorial(n: int):
    '''
        该函数求任意数的阶乘

        参数:
            n 要求阶乘的数字
    '''
    # 创建一个变量来保存结果
    result = n
    for i in range(1, 10):
        result *= i
    return result


print(factorial(10))

print('-' * 60)  # 分割线

# 递归式的函数
# 从前有座山,山里有座庙,庙里有个老和尚讲故事,讲的什么呢?
#   从前有座山,山里有座庙,庙里有个老和尚讲故事,讲的什么呢?....
# 递归简单理解就是自己去引用自己
# 递归式函数,在函数中自己调用自己

# 无穷递归,如果这个函数被调用,程序的内存会溢出,效果类似于死循环
# def fn():
#     fn()

# fn()

# 递归是我们解决问题的一种方式,他和循环很像
#   他的整体思想是,将一个大问题,分解为一个一个小问题,直到问题无法分解时,再去解决问题

# 递归式函数的两个要件
#   1.基线条件
#       - 问题可以分解为的最小的问题,当满足基线条件时,递归就不执行了
#   2.递归条件
#       - 将问题继续分解的条件
# 递归和循环类似,基本上可以相互代替的
#   循环编写起来比较容易,但是阅读稍难
#   递归编写起来难,但是方便阅读
# 10! = 10*9!
# 9!=9*8!
# 8!=8*7!
# ...
# 1!=1


def factorial(n):
    '''
        该函数求任意数的阶乘

        参数:
            n 要求阶乘的数字
    '''
    # 创建一个变量来保存结果

    # 基线条件 判断n是否为1,如果为1,则不能继续递归
    if n == 1:
        # 1的阶乘就是1,直接返回1
        return 1
    # 递归条件
    return n * factorial(n - 1)


print(factorial(10))

print('-' * 60)  # 分割线

# 练习
#   创建一个函数power来求任意数字的幂运算 n**i
# 10 ** 5 =10*10**4
# 10 ** 4 =10*10**3


def power(n, i):
    '''
    用来为任意的数字做幂运算
    参数:
        n 要做幂运算的数字
        i 要做幂运算的次数
    '''
    # 基线条件
    if i == 1:
        # 求1次幂
        return n
    # 递归条件
    return n * power(n, i - 1)


print(power(8, 6))
print(8**6)


print('-' * 60)  # 分割线
#   创建一个函数,用来检查一个任意的字符串是否时回文字符串,如果是返回true,否则返回false
#   abcba
#   abcdefgfedcba
#   先检查第一个和最后一个字符是否一致,如果不一致,则不是回文
#   如果一致,则看甚于的部分是否是回文
#   先检查abcdefgfedcba 是不是回文
#   检查bcdefgfedcb 是不是回文
#   检查cdefgfedc 是不是回文
#   检查defgfed 是不是回文
#   检查efgfe 是不是回文
#   检查fgf 是不是回文
#   检查g 是不是回文


def palindrome(s: str):
    '''
        该函数用来检查指定的字符串是否是回文字符串,如果是返回true,否则返回false

        参数: 
            s: 就是要检查的字符串

    '''
    # 基线条件
    if len(s) < 2:
        # 字符串的长度小于2,则字符串一定是回文
        return True
    elif s[0] != s[-1]:
        # 第一个和最后一个不相等,不相等,不是回文字符串
        return False
    # 递归条件
    return palindrome(s[1:-1])


print(palindrome('abcdefgfedcba'))

08.高阶函数

# 高阶函数
# 接收函数作为参数,或者将函数作为返回值的函数是高阶函数
# 当我们使用一个函数作为参数时,实际上是将指定的代码传递进了目标函数
# 创建一个列表
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 定义一个函数
# 可以将指定列表中的所有的偶数,保存到一个新的列表中返回

# 定义一个函数,用来检测一个任意的数字是否是偶数


def fn2(i):
    if i % 2 == 0:
        return True

    return False

# 定义一个函数大于五的数


def fn3(i):
    if i > 5:
        return True
    return False

# 三的倍数函数


def fn4(i):
    if i % 3 == 0:
        return True
    return False


def fn(func, lst):
    '''
        fn()函数可以将指定列表中的所有偶数获取出来,并保存到一个新列表中返回
        参数:
            lst:要进行筛选的列表
    '''

    # 创建一个新列表
    new_list = []

    # 对列表进行筛选
    for n in lst:
        # 判断n的奇偶
        if func(n):
            new_list.append(n)
        # if n > 5:
        #     new_list.append(n)

        # 返回新列表
    return new_list


print(fn(fn4, l))
print(l)

print('-' * 60)

# filter()  # 过滤器
# 可以从序列中过滤出符合条件的元素,保存到一个新的序列中
# 参数:
#   1. 函数,根据该函数过滤序列(可迭代)
#   2. 需要过滤的序列(可迭代)
# 返回值:
#   过滤后的新序列(可迭代的结构)

print(list(filter(fn4, l)))

print('-' * 60)

# fn4时作为参数传递进filter()函数中 不要加()
#   而fn4实际上只有一个作用,就是作为filter()的参数
#   filter()调用完以后,fn4就已经没用
r = filter(fn4, l)
print(list(l))

print('-' * 60)

# 匿名函数 lambda 函数表达式 (语法糖)
#   lambda函数表达式专门用来创建一些简单的函数,他是函数创建的又一种方式
# 语法 lamdba 参数列表:返回值
# 匿名函数一般都是作为参数使用,其他地方不会使用


def fn5(a, b):
    return a + b


print(fn5(5, 5))

print('-' * 60)

print(lambda a, b: a + b)

(lambda a, b: a + b)(10, 20)

# 一般不会这么用:
print((lambda a, b: a + b)(10, 20))

print('-' * 60)

# 也可以将匿名函数赋值给变量
# fn6=lambda a, b: a + b  # 这个编译器直接给我转成了函数
def fn6(a, b): return a + b


lambda i: i % 3 == 0

r = filter(lambda i: i > 5, l)
print(list(r))

print('-' * 60)

# map()
# map()函数可以对可迭代对象中的所有元素做指定的操作,然后将其添加到一个新的对象返回

l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

r = map(lambda i: i ** 2, l)

print(list(r))

print('-' * 60)

# sort()
# 该方法用来对列表中的元素进行排序
# sort()方法默认时直接比较列表中的元素的大小
# 在我们的sort()中可以接收一个关键字参数,key
#   key需要一个函数作为参数,当我们设置了函数作为参数
#   每次都会以列表中的一个元素,作为参数来调用函数,并且使用函数的返回值来比较元素的大小
l = ['bb', 'aaaa', 'c', 'dddddd', 'fff']
l.sort(key=len)
print(l)


l = [2, 5, '1', 3, '6', '4']
l.sort(key=int)
print(l)

print('-' * 60)

# sorted()
# 这个函数和sort()用法基本一致,但是sorted()可以对任意的序列进行排序
#   兵器使用sorted()排序不会影响原来的对象,而是返回一个新的对象
l = [2, 5, '1', 3, '6', '4']
print('排序前', l)
print(sorted(l, key=int))
print('排序后', l)

print('-' * 60)

l = '12312313432532467'
print('排序前', l)
print(sorted(l, key=int))
print('排序后', l)

09.闭包

# 闭包
# 将函数作为返回值返回,也是一种高阶函数
# 这种高阶函数我们就叫做闭包,通过闭包可以创建一些只有当前函数能访问的变量
#   可以将一个私有的数据藏到闭包里

def fn():
    a = 10
    # 函数内部在定义一个函数

    def inner():
        print('我是fn2', a)
    return inner


print(fn())

# r 是一个函数,是调用fn()后返回的函数
# 这个函数是在fn()内部定义的,并不是全局函数
# 所以这个函数总是能访问到fn()内部的变量

r = fn()

r()  # 我是fn2 10

print('-' * 60)

# 求多个数的平均值  最简单方法
nums = [50, 30, 20, 10, 77]

# sum()用来求一个列表中的所有的和
print(sum(nums) / len(nums))

# 创建一个列表,来保存数值
nums = []

print('-' * 60)

# 创建一个函数,用来计算平均值 全局函数方法


def averager(n):
    # 将n添加到列表中
    nums.append(n)

    # 求平均值
    return sum(nums) / len(nums)


print(averager(10))
print(averager(20))
print(averager(30))
print(nums)

print('-' * 60)


# 闭包方法
# 形成闭包的要件
#   ① 函数嵌套
#   ② 将内部函数作为返回值返回
#   ③ 内部函数必须要用外部函数的变量
def make_averager():
    # in_nums = []

    def averager(n):
        in_nums = []
        # 将n添加到列表中
        in_nums.append(n)

        # 求平均值
        return sum(in_nums) / len(in_nums)
    return(averager)


averager = make_averager()
print(averager(10))
print(averager(20))
print(averager(30))
# print(in_nums) NameError: name 'in_nums' is not defined

10.装饰器

# 创建几个函数

def add(a, b):
    '''
        求任意两个数的和
    '''
    r = a + b
    return r


def mul(a, b):
    '''
        求任意两个数的和
    '''
    r = a * b
    return r
# 希望函数可以在计算前,打印开始计算,计算结束后打印计算完毕
#   我们可以直接通过修改函数中的代码来完成这个需求,但是会产生一下问题:
#       ① 如果要修改的函数过多,修改起来会比较麻烦
#       ② 并且不方便后期维护
#       ③ 这样会违反开闭原则()
#           程序的设计,要求开发对程序的扩展,要关闭对程序的修改
#


r = add(123, 456)

print(r)

print('-' * 60)  # 分割线

# 我们希望在不修改原函数的的情况下,来对函数进行扩展


def fn():
    print('我是fn函数.........')

# 只需要根据现有的函数,来创建一个新的函数


def fn2():
    print('函数执行开始...')
    fn()
    print('函数执行结束')


fn2()

print('-' * 60)  # 分割线

# 需要参数的情况  但是这种不能通用


def new_add(a, b):
    print('函数执行开始...')
    r = add(a, b)
    print('函数执行结束')
    return r


r = new_add(123, 456)
print(r)

print('-' * 60)  # 分割线

# 上面的方式,已经可以在不修改源代码的情况下,对函数进行扩展了
#   但是,这种方式要求我们每扩展一个函数就要手动创建一个新的函数,实在是太麻烦了
#   为了解决这个问题,我们创建一个函数,让这个函数可以自动地帮助我们生产函数


def begin_end(old):
    '''
        用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结果\

        参数:
        old 要扩展的函数对象
    '''
    # 创建一个新函数
    def new_function(*args, **kwargs):  # 装包
        print('函数执行开始...')
        # 调用被扩展的函数
        result = old(*args, **kwargs)  # 拆包
        print('函数执行结束...')
        # 返回函数的执行结果
        return result
    # 返回新函数
    return new_function


f = begin_end(add)
result = f(1, 2)
print('result=', result)

print('-' * 60)  # 分割线

f2 = begin_end(mul)
result = f2(3, 2)
print('result=', result)

print('-' * 60)  # 分割线

f3 = begin_end(fn)
f3()

print('-' * 60)  # 分割线

# 像begin_end()这种函数我们就称他为装饰器
#   通过装饰器, 可以在不修改原来函数的情况下对函数进行扩展
#   在开发中,我们都是通过装饰器来扩展函数的功能的
# 在定义函数时,可以通过@装饰器,来使用指定的装饰器,来装饰当前函数
#   可以同时为一个函数指定多个装饰器,这样函数将会按照由内向外的装饰


def fn3(old):
    '''
        用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结果\

        参数:
        old 要扩展的函数对象
    '''
    # 创建一个新函数
    def new_function(*args, **kwargs):  # 装包
        print('fn3--函数执行开始...')
        # 调用被扩展的函数
        result = old(*args, **kwargs)  # 拆包
        print('fn3-函数执行结束...')
        # 返回函数的执行结果
        return result
    # 返回新函数
    return new_function


@begin_end
@fn3
def say_hello():
    print('大家好')


say_hello()

第六章 对象

# 第六章 对象 (Object)

## 什么是对象?
	- 对象是内存中专门用来存储数据的一块区域
	- 对象中可以存放各种数据(比如:数字,布尔值,代码)
	- 对象中保存由三部分组成:
		1. 对象的标识(id)
		2. 对象的类型(type)
		3. 对象的值(value)

## 面向对象(oop)
	- Python是一门面向对象的编程语言
	- 所谓的面向对象的语言,简单来说就是语言中的所有操作都是通过对象来进行的
	- 面向过程的编程语言
		- 面向过程指的是将我们的程序的逻辑分解为一个一个的步骤,
			通过对每个步骤的抽象,来完成程序
		- 例子:
			- 孩子上学 
				1. 妈妈起床
				2. 妈妈上厕所
				3. 妈妈洗漱
				4. 妈妈做早饭
				5. 叫孩子起床
				6. 孩子上厕所
				7. 孩子要洗漱
				8. 孩子吃饭
				9. 孩子背着书包上学校
		
		- 面向过程的编程思想将一个功能分解为一个一个小的步骤
			我们通过完成一个一个的小的步骤来完成一个程序
		- 这种编程方式,符合我们人类的思维,编写起来相对比较简单
		- 但是这种方式编写代码的王婉只适用于一个功能,
			如果要实现别的功能,即使功能相差极小,也往往要重新编写代码
			所以可复用行比较低,并且难以维护

	- 面向对象的编程语言
		- 面向对象的编程语言,关注的是对象,而不关注过程
		- 对于面向对象的语言来说,一切都是对象
		- 例子:
			1. 孩他妈起床叫孩子上学
		- 面向对象的编程思想,将所有的功能同一保存到对应的对象中
			比如,妈妈功能保存到妈妈的对象中,孩子的功能保存在孩子的对象中
			要使用某个功能,直接找到对应的对象即可
		- 这种方式白那些的代码,比较容易越多,容易维护,容易复用
		- 但是这种方式编写, 不太符合常规的思维,所以编写起来稍微麻烦一点

	- 简单归纳一下,面向对象的思想
		1. 找对象
		2. 搞对象

## 类(class)
	- 我们目前所学习的对象,都是python所内置的对象
	- 但是内置对象比那个不能满足所有的需求,所以我们在开发中经常需要自定义一些对象
	- 类,简单理解它就相当于一个图纸,在程序中我们需要根据类来创建对象
	- 类就是对象的图纸!
	- 我们也称对象是类的实例(instance)
	- 如果多个对象是通过一个类创建的, 我们称这些对象是一类对象
	- 像 int(),float(),bool(),str(),list(),dict()...这些都是类
	- a= int(10) #创建一个int类的实例,等价于a=10
	- 我们自定义的类都需要使用大写字母开头,使用大驼峰命名法(帕斯卡命名法)来对类命名

	- 类也是一个对象!
	- 类就是一个用来创建对象的对象!
	- 类是type类型的对象,定义类实际上就是定义了一个type类型的对象

## 使用类创建对象的流程
	- 创建一个变量mc
	- 在内存中创建一个新对象
	- 将对象的id赋值给变量

## 类的定义
	- 类和对象都是对现实生活中的事务,或者程序中内容的抽象
	- 实际上所有的事务都由两部分构成:
		1. 数据(属性)
		2. 行为(方法)
	- 在类的代码块中,我们可以定义变量和函数
		变量会成为该类实例的公共属性,所有该类实例都可以通过 对象.属性名 的形式访问
		函数会成为该类实例的公共方法,所有该类实力都可以同通过 对象.方法名() 的形式调用方法

	- 注意: 
		方法调用时,第一个参数由解析器自动传递,所以定义方法时,至少要定义一个形参!!!

	- 实例为什么能访问到类中的属性和方法
		类中定义的属性和方法都是公共的,任何该类实例都可以访问

		- 属性和方法查找的流程
			当我们调用一个对象的属性时,解析器会先在当前对象中寻找是否含有该属性,
				如果有,则直接返回当前的对象的属性值,
				如果没有,则去当前对象的类对象中去寻找,
					如果有则返回类对象的属性值
					如果没有则报错
		- 类对象和实例对象都可以保存属性(方法)
			- 如果这个属性(方法)是所有实例共享的,则应该将其保存到类对象中
			- 如果这个属性(方法)是某个实例独有,则应该保存到实例对象中

		- 一般情况下,属性保存到实例对象中,
			方法保存到类对象中

## 创建对象的流程
    最先执行执行类的代码块中的代码(只在类定义的时候执行一次)
    p1= Person()的运行流程
    	1. 创建一个变量mc
    	2. 在内存中创建一个新对象
    	3. __init__(self)方法执行
    	4. 将对象的id赋值给变量

## 类的基本结构
	class 类名([父类]) :
		[公共属性.....]

		# 对象的初始化方法
		def __init__(self,....):
			....
		# 其他方法
		def method_1(self,....):
			....
		def method_2(self,....):
			....
		....

	- 练习:
		尝试自定义一个表示狗的类(Dog)
			属性:
				name
				age
				gender
				height
				...			
			方法:
				jiao()
				yao()
				run()
				...

01.类的简介

a = int(10)  # 创建一个int类的实例
b = str('hello')  # 创建一个str类的实例
print(a, type(a))  # 10 <class 'int'>
print(b, type(b))  # hello <class 'str'>

print('-' * 60)  # 分割线

# 定义一个简单的类
# 使用class关键字来定义类,语法和函数很像!
# class 类名([父类]):
#   代码块


class MyClass():
    pass


print(MyClass)  # <class '__main__.MyClass'>

print('-' * 60)  # 分割线

# 使用MyClass创建一个对象
# 使用类来创建对象,就像调用一个函数一样
mc = MyClass()  # mc就是通过MyClass创建的对象,mc是MyClass的实例
mc_2 = MyClass()
mc_3 = MyClass()
mc_4 = MyClass()
# mc* 都是MyClass的实例,他们收拾一类对象

print(mc, type(mc))  # <__main__.MyClass object at 0x0000019D6D0286A0> <class '__main__.MyClass'>

print('-' * 60)  # 分割线

# isinstance()用来检查是一个对象是否是一个类的实例
result = isinstance(mc, MyClass)
print(result)

print('-' * 60)  # 分割线

print(id(MyClass), type(MyClass))  # 2512224225576 <class 'type'>

print('-' * 60)  # 分割线

# 现在我们通过MyClass这个类创建的对象都是一个空对象
# 也就是对象中实际什么都没有,就相当于是一个空的盒子
# 可以向对象中添加变量,对象中的变量称为属性
# 语法: 对象.属性名=属性值

mc.name = '孙悟空'
print(mc.name)

mc_2.name = '猪八戒'
print(mc_2.name)

02.定义类

# 尝试定义一个表示人的类
class Person:
    # 再类的代码块中,我们可以定义变量和函数
    # 在类中我们所定义的变量,将会称为所有的实例的公共属性
    # 所有实例都可以访问这些变量

    name = 'swk'  # 公共属性,所有实例都可以访问

    # 在类中也可以定义函数,类中定义的函数,我们称之为方法
    # 这些方法可以通过该类的所有实例来访问

    def say_hello(self):
        # 方法每次被调用,我们的解析器都会自动传递第一个实参
        # 第一个参数,就是调用方法对象本身
        # 如果是p1调用的,则第一个参数就是p1本事,以此类推
        # 一般我们都会将这个参数命名为self

        # sqy_hello()这个方法,可以显示如下格式的数据:
        #   你好! 我是 xxx
        # 在方法中不能直接访问类中的属性
        print("你好!! 我是%s" % self.name)

# 创建Person的实例


p1 = Person()
p2 = Person()

print(p1.name, p2.name)

print('-' * 60)  # 分割线

# 调用方法: 对象.方法名()
# 方法调用和函数调用的区别
# 如果是函数调用,则调用是传几个参数,就会有几个实参
# 但是如果是方法调用,默认传递一个参数,所以方法中至少要定义一个形参
p1.say_hello()
p2.say_hello()
# 你好!!
# 你好!!

print('-' * 60)  # 分割线

# 修改p1的name属性
p1.name = '猪八戒'
p2.name = '沙和尚'
# 删除p2的name属性
# del p2.name

print(p1.name, p2.name)  # 猪八戒 沙和尚 # 猪八戒 swk

print('-' * 60)  # 分割线

p1.say_hello()  # 你好!! 我是猪八戒
p2.say_hello()  # 你好!! 我是沙和尚

03.对象的初始化

class Person:
    # print('Person代码块中的代码')
    # 在类中可以定义一些特殊方法(魔术方法)
    # 特殊方法都是以__开头,__结尾的方法
    # 特殊方法不需要我们自己调用,不要尝试去调用特殊方法
    # 特殊方法将会在特殊的时刻自动调用
    # 学习特殊方法:
    #   1. 特殊方法什么时候调用
    #   2. 特殊方法有什么作用
    # 创建对象的流程
    # 最先执行执行类的代码块中的代码(只在类定义的时候执行一次)
    # p1= Person()的运行流程
    # 1. 创建一个变量mc
    # 2. 在内存中创建一个新对象
    # 3. __init__(self)方法执行
    # 4. 将对象的id赋值给变量

    # init会在对象创建以后立即执行  init(初始化)
    # init可以用来向新创建的对象中初始化属性
    # 调用类创建对象时,类后边的所有参数都会依次传递到init()中
    def __init__(self, name: str):
        # print(self)
        # 可以通过self向新建的对象中初始化属性
        self.name = name

    def say_hello(self):
        print('大家好,我是%s' % self.name)


# 目前来讲,对于Person类来说name是碧血的,并且每一个对象中的name属性基本上都是不同的
# 而我们现在是将name属性在定义为对象以后,手动添加到对象中,这种方式很容易出现错误
# 我们希望,在创建对象时,必须设置name属性,如果不设置对象将无法创建
#   并且属性的创建应该时自动完成的,而不是在创建对象有以后手动完成


# # 麻烦方法
# p1 = Person()
# # 手动向对象添加name属性
# p1.name = '孙悟空'

# p2 = Person()
# p2.name = '猪八戒'

# p3 = Person()
# p3.name = '沙和尚'

# p3.say_hello()  # AttributeError: 'Person' object has no attribute 'name'

# print('-' * 60)

# 新方法
# p1.__init__()  # 不要这么做
p1 = Person('孙悟空')
p2 = Person('猪八戒')
p3 = Person('沙和尚')
p4 = Person('唐僧')
p1.say_hello()
p2.say_hello()
p3.say_hello()
p4.say_hello()

04.练习

class Dog:
    '''
        表示狗的类
    '''

    def __init__(self, name, age, gender, height):
        self.name = name
        self.age = age
        self.gender = gender
        self.height = height

    def jiao(self):
        '''狗叫的方法'''
        print('汪汪汪')

    def yao(self):
        '''狗咬的方法'''
        print('咬你')

    def run(self):
        '''狗跑的方法'''
        print('%s拜拜了您嘞' % self.name)


d = Dog('旺财', 8, 'male', 30)
print(d.name, d.age, d.gender, d.height)

d.jiao()
d.yao()
d.run()

# 目前我们可以直接通过 对象.属性的方式来修改属性的值,这种方式导致对象中的属性可以随意修改
# 非常不安全,值可以任意修改,不论对错
# 需要一种方式来增强数据的安全性
#   1. 数据不能随便修改(我让你改才能改,不让你改你就不能改)
#   2. 属性不能修改为任意的值(年龄不能修改为负数)
d.name = '阿黄'
d.age = -10
d.run()

05.修改属性名封装

# 封装是面向对象的三大特性之一
# 封装指的是隐藏对象中不希望被外部所访问的属性或方法
# 如何隐藏一个对象中的属性
# 最基本的封装:
#  - 将对象的属性名,修改为一个外部不知道的名字
# 如何获取(修改)对象中的属性?
#   - 需要提供一个getter和setter方法使外部可以访问到属性
#   - getter 获取对象中的指定属性(get_属性名)
#   - setter 用来设置对象的指定属性(set_属性名)
# 使用封装确实增加了类的定义的复杂程度,但是它也确保了数据的安全性
#   1.隐藏了属性名,使调用者无法随意的修改对象中的属性
#   2.增加了getter和setter方法,很好的控制了属性是否是只读的
#       如果希望属性是只读的,则可以直接去掉setter方法
#       如果希望属性不能被外部访问,则可以直接去掉getter方法
#   3.使用setter方法设置属性,可以增加数据的验证,确保数据的值是正确的
#   4.使用getter方法获取属性,使用setter方法设置属性
#       可以在读取属性和修改属性的同时做一些其他的处理
class Dog:
    '''
        表示狗的类
    '''

    def __init__(self, name, age):
        self.hidden_name = name
        
        self.hidden_age = age

    def say_hello(self):
        print('大家好,我是%s' % self.hidden_name)

    def get_name(self):
        '''
            get_name()用来获取对象的name属性
        '''
        print('用户读取了属性')
        return self.hidden_name

    def set_name(self, name):
        print('用户修改了属性')
        self.hidden_name = name

    def get_age(self):
        '''
            get_name()用来获取对象的name属性
        '''
        return self.hidden_age

    def set_age(self, age):
        if age > 0:
            self.hidden_age = age


d = Dog('小黑', 10)

d.say_hello()


print(d.get_name())

# 调用setter方法来修改name属性
d.set_name('旺财')
print(d.get_name())
d.say_hello()

06.python自带封装

class Rectangle:
    '''
        表示矩形的类
    '''

    def __init__(self, width, height):
        self.hidden_width = width
        self.hidden_height = height

    def get_width(self):
        return self.hidden_width

    def get_height(self):
        return self.hidden_height

    def set_width(self, width):
        self.hidden_width = width

    def set_height(self, height):
        self.hidden_height = height

    def get_area(self):
        return self.hidden_height * self.hidden_width


# r = Rectangle(5, 2)
# r.set_height(10)
# r.set_width(3)
# print(r.get_height())
# print(r.get_area())


# 可以为对象的属性使用双__开头,__xxx
# 双下划线开头的属性,是对象的隐藏属性,隐藏属性只能在类的内部访问,无法通过对象访问
# 其实隐藏属性只不过是python自动为属性改了一个名字
#   实际上是将名字改为了 _类名__属性名 比如 __name -> _Person__name
# 防君子不防小人

# class Person:
#     def __init__(self, name):
#         self.__name = name

#     def get_name(self):
#         return self.__name

#     def set_name(self, name):
#         self.__name = name


# p = Person('孙悟空')
# # __开头的属性是隐藏属性,无法通过对象访问
# # print(p.__name)  # AttributeError: 'Person' object has no attribute '__name'
# # p.__name = '猪八戒'  # AttributeError: 'Person' object has no attribute '__name'
# print(p.get_name())
# print('查看隐藏', p._Person__name)
# p._Person__name = '猪八戒'  # 修改隐藏
# print('查看隐藏 且修改为猪八戒', p._Person__name)

# 使用__开头的属性,实际上依然可以发在外部访问,所以这种方式我们一般不用
#   一般我们会将一些私有属性(不希望被外部访问的属性)以_开头
#   一般情况下,使用_开头的属性都是私有属性,没有特殊需要不要修改私有属性
class Person:
    def __init__(self, name):
        self._name = name

    def get_name(self):
        return self._name

    def set_name(self, name):
        self._name = name


p = Person('孙悟空')
print(p._name)

07.封装中的'装饰器'

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age

    # property装饰器,用来将一个get方法转换为属性
    # 添加为property装饰器后,我们就可以像调用属性一样使用get方法
    # 使用property装饰的方法,必须和属性名是一样的

    @property
    def name(self):
        print('get方法执行了')
        return self._name

    # setter方法的装饰器:@属性名.setter
    # 且使用setter装饰器是必须有property装饰器

    @name.setter
    def name(self, name):
        print('setter方法调用了')
        self._name = name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age


p = Person('猪八戒', 10)

p.name = '孙悟空'
p.age = '18'
print(p.name, p.age)

08.继承

# 继承

# 定义一个类 Animal(动物)
#   这个类中需要两个方法:run() sleep()


class Animal:
    def run(self):
        print('动物会跑')

    def sleep(self):
        print('动物睡觉')


# 定义一个列 Dog (狗)
# 这个类中需要三个方法:run() sleep() bark()
# 有一个类,能够实现我们需要大部分功能,但是不能实现全部功能
# 如何能让这个类,来实现全部的功能呢 ?
#   ① 直接修改这个类,在这个类中添加我们需要的功能
#       - 修改起来麻烦,并且会违反OCP原则
#   ② 直接创建一个新的类
#       - 创建一个新的类比较麻烦,并且需要大量的进行复制粘贴,会出现大量的重复代码
#   ③ 直接从Animal类中来继承它的属性和方法
#       - 继承是面向对象的三大特征之一
#       - 通过继承我们可以使一个类获取到其他类中的属性和方法
#       - 在定义类时,可以在类名后的括号中指定当前类的父类(超类,基类,super)
#           子类(衍生类)可以直接继承父类中的所有的属性和方法
# 通过继承可以直接让子类获取到父类的方法或属性,避免白那些重复性代码,并且也符合OCP原则
#   所以我们经常需要通过继承来对一个类进行扩展

class Dog(Animal):
    def bark(self):
        print('动物嚎叫')
    # 重写
    def run(self):
        print('狗在跑')


class HaShiQi(Dog):
    def fansha(self):
        print('我是一只傻傻的哈士奇')


d = Dog()
d.run()
d.sleep()
d.bark()

# isinstance() 用来检查一个对象是否是一个类的实例
# 如果这个类是这个对象的父类,也会返回True
# 所有的对象都是object的实例
r = isinstance(d, Dog)
r = isinstance(d, Animal)

print(r)


# 在创建类时,如果省略了父类,则默认父类为object
#   object是所有类的父类,所有类都继承自object
# 检查一个类是否是另一个类的子类 issubclass()

print(issubclass(Dog, Animal))

09.重写

# 重写
# 如果在子类中有和父类同名的方法,则通过子类实例去调用方法时,
#   会调用子类的方法而不是父类的方法,这个特点我们成为叫做方法的重写(覆盖,override)


class Animal:
    def run(self):
        print('动物会跑')

    def sleep(self):
        print('动物睡觉')


class Dog(Animal):
    def bark(self):
        print('动物嚎叫')
    # 重写

    def run(self):
        print('狗在跑')


#
d = Dog()
d.run()
d.sleep()
d.bark()

print('-' * 60)

# 当我们调用一个对象的方法时,
#   会优先取当前对象中寻找是否具有该方法,如果有则直接调用
#   如果没有,则取当前对象的父类中勋章,如果父类中有则直接调用父类中的方法,
#   如果没有,则去父类的父类寻找,依次类推,只到找到object ,如果依然没有,则报错 


class A:
    def test(self):
        print('AAAAA')


class B(A):
    def test(self):
        print('BBBBB')


class C(B):
    def test(self):
        print('CCCCC')


c = C()
c.test()

10.特殊方法继承

class Animal:
    def __init__(self, name):
        self._name = name

    def run(self):
        print('动物会跑')

    def sleep(self):
        print('动物睡觉')

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name

# 父类中的所有方法都会被子类继承,包括特殊方法,也可以重写特殊方法


class Dog(Animal):
    def __init__(self, name, age):
        # 希望直接调用父类的__init__来初始化父类中定义的属性
        # super()可以用来获取当前类的父类,并且通过super()返回对象调用父类方法时,不用传递self
        super().__init__(name)
        # self._name = name
        self._age = age

    def bark(self):
        print('汪汪汪')
    # 重写

    def run(self):
        print('狗在跑')

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age


# d = Dog() #SyntaxError: invalid syntax

d = Dog('旺财', 18)
d.name = '小黑'
print(d.name)
print(d.age)

11.多重继承

class A(object):
    def test(self):
        print('AAAAA')


class B(object):
    def test(self):
        print('b中的test方法~~~')

    def test2(self):
        print('BBBBB')

# 在python中式支持多重继承的,也就是我们可以为一个类同时指定多个父类
#   可以在类名的()后面添加多个类,来实现多重继承
#   多重继承,会使子类同时拥有多个父类,并且会获取到所有父类中的方法
# 在开发中没有特殊情况,应该尽量避免使用多重继承,因为多重继承会让我们的代码过于复杂
# 如果多个父类中有同名方法,则会在第一个父类中寻找,然后找第二个,然后找第三个....
# 前面的父类方法会覆盖后边的父类方法


class C(A, B):  # B,A时调用的时b中的test方法 b中的test方法~~~
    pass
    # __bases__ 这个属性可以用来获取当前类的所有父类


print(A.__bases__)  # (<class 'object'>,)
print(B.__bases__)  # (<class 'object'>,)
print(C.__bases__)  # (<class '__main__.B'>,)

c = C()
c.test()
c.test2()

12.多态

# 多态时面向对象的三大特征之一
# 堕胎从字面上理解是多种形态
# 狗(狼狗,藏獒,哈士奇,古牧....)
# 一个对象以不同的形态去呈现

# 定义两个类
class A:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name


class B:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name

    def __len__(self):
        return 10


class C:
    pass


a = A('孙悟空')
b = B('猪八戒')
c = C()
# 定义一个函数
# 对于say_hello()这个函数来说,只要对象中含有name属性,他就可以作为参数传递
#   这个函数并不会考虑对象的类型,只要有name属性即可


def say_hello(obj):
    print('你好 %s' % obj.name)


# say_hello(c)  # AttributeError: 'C' object has no attribute 'name'

# 在say_hello2中我们做了一个类型检查,也就是只有obj时a类型的对象时,才可以正常使用
#   其他类型的对象都无法使用该函数,这个函数就违反了多态
# 违反了多态的对象,只适用于一种类型的对象,无法处理其他类型对象,这样导致函数的适应性非常的差
# 注意 像ininstance()这种函数,在开发中一般是不会使用的!!!


def say_hello2(obj):
    if isinstance(obj, A):
        print('你好 %s' % obj.name)


say_hello2(a)


# 鸭子类型
# 如果一个东西走路像鸭子,叫声像鸭子,那么他就是鸭子

# len()
# 之所以一个对象能通过len()来获取,是因为对象中具有一个特殊方法 __len__
# 换句话说,只要对象中具有__len__特殊方法,就可以通过len()来获取它的长度
l = [1, 2, 3]
s = 'hello'
print(len(s))
print(len(a))  # TypeError: object of type 'A' has no len()
print(len(b))
print(len(c))  # TypeError: object of type 'C' has no len()


# 面向对象的三大特征
#   封装
#       - 确保对象中的数据安全
#   继承
#       - 保证了对象的可扩展性
#   多态
#       - 保证了程序的灵活性

13.类中的属性和方法

# 定义一个类
class A(object):
    # 类属性
    # 实例属性
    # 类方法
    # 实例方法
    # 静态方法



    # 类属性,直接在类中定义的属性时类属性
    # 类属性可以通过类或类的实例访问到
    # 但是类属性之恶能通过类对象来修改,无法通过实例对象修改
    count = 0

    def __int__(self):
        # 实例属性,通过实例对象添加的属性属于实例属性
        #   实例属性只能通过实例对象来访问和修改,类对象无法访问修改
        self.name = '孙悟空'

    # 实例方法
    # 在类中定义,以self为第一参数的方法都是实例方法
    # 实例方法在调用时,python会将调用对象作为self传入
    # 实例方法可以通过实例和类去调用
    #   当通过实例调用是,会自动将当前调用对象作为self传入
    #   当通过类调用时,不会自动传递self,此时我们必须手动传递self
    def test(self):
        print('这是test方法', self)


    # 类方法
    # 在类内部使用 @classmethod 来修饰的方法属于类方法
    # 类方法的第一个参数时cls,也会被自动传递,cls就是当前的类对象
    #   类方法和实例方法的区别,实例方法的第一个参数时self,而类方法的第一个参数是cls
    #   类方法可以通过类去调用,也可以通过实例调用,没有区别
    @classmethod
    def test_2(cls):
        print('这是test_2方法,他是一个类方法~~~~~', cls)


    # 静态方法
    # 在类中使用 @staticmethod 来修饰的方法属于静态方法
    # 静态方法不需要指定任何的默认参数,静态方法可以通过类和实例去调用
    # 静态方法,基本上是一个和当前类无关的方法,他只是一个保存到当前类中的函数
    # 静态方法一般都是一些工具方法,和当前类无关
    @staticmethod
    def test_3():
        print('test_3执行了!!!')



# 实例
a = A()

# 实例属性,通过实例对象添加的属性属于实例属性
a.count = 10

# 类属性
A.count = 100

print('A', A.count)
print('a', a.count)
# A 100
# a 10

# 实例方法
a.test()
# a.test() 等价于 A.test(a)
A.test(a)  # TypeError: test() missing 1 required positional argument: 'self'

# 类方法
# A.test2() 等价于 a.test2()
A.test_2()


# 静态方法
# 在类中使用 @staticmethod 来修饰的方法属于静态方法
def test_3():
    print('test_3执行了!!!')

14.垃圾回收

# 垃圾回收
# 就像生活中会产生垃圾一样,程序在运行过程当中,也会产生垃圾
# 程序运行过程中产生的垃圾,会影响到程序的运行性能,所以这些垃圾必须被及时清理
# 没用的东西就是垃圾
# 在程序中没有被引用的对象就是垃圾,这种垃圾过多之后会影响到程序的运行性能
#   所以我们必须进行及时垃圾回收,所谓的垃圾回收就是将垃圾对象从内存中删除
# 在python中有自动的垃圾回收机制,它会自动将这些没有被引用的对象删除
#   所以我们不用手动处理垃圾回收


class A:
    def __init__(self):
        self.name = 'A类'
    # del是一个特殊方法,他会在对象被垃圾回收前调用
    # 一般不会用,通过它知道对象被删除
    def __del__(self):
        print('A()对象被删除了~~~~', self)


a = A()
print(a.name)
b = a  # 又使用一个变量b,来引用a对应的对象
a = None  # 将a设置为了None,此时没有任何的变量对A()对象进行引用,他就变成了垃圾

# del a, b # A()对象被删除了~~~~ <__main__.A object at 0x000001AB3FF584A8>
input('按回车键退出.......')

15.特殊方法

# 特殊方法也成为魔术方法
# 特殊方法都是使用__开头和结尾的
# 特殊方法不许要手动调用,需要在一些特殊情况下自动执行
# 特殊方法用来配合多态
# 定义一个Person类
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 这个特殊方法会在禅师将对象转换为字符串的时候调用
    # 它的作用可以用来指定对象转换为字符串的结果 (print()函数)
    def __str__(self):
        return 'hello Person name=%s,age=%d' % (self.name, self.age)
    # hello Person name=孙悟空,age=18
    # hello Person name=猪八戒,age=28

    # 这个特殊方法会在对当前对象使用repr()函数时调用
    # 它的作用是指定对象在 '交互模式' 中直接输出的结果

    def __repr__(self):
        return 'hello'

    # 运算方法
    # object.__add__(self, other) +
    # object.__sub__(self, other) -
    # object.__mul__(self, other) *
    # object.__matmul__(self, other)
    # object.__truediv__(self, other)
    # object.__floordiv__(self, other)
    # object.__mod__(self, other)
    # object.__divmod__(self, other)
    # object.__pow__(self, other[, modulo])
    # object.__lshift__(self, other)
    # object.__rshift__(self, other)
    # object.__and__(self, other)
    # object.__xor__(self, other)
    # object.__or__(self, other)

    # object.__lt__(self, other) 小于 <
    # object.__le__(self, other) 小于等于 <=
    # object.__eq__(self, other) 等于 ==
    # object.__ne__(self, other) 不能于 !=
    # object.__gt__(self, other) 大于 >
    # object.__ge__(self, other) 大于等于 >=

    # __gt__()会在对象在做大于比较的时候调用,该方法的返回值将会作为比较结果
    # 需要两个参数,一个self表示当前对象,other表示和当前对象比较的对象
    def __gt__(self, other):
        return self.age > other.age

    # __len__() 获取对象的长度

    # __bool__(self)
    # 可以通过bool来指定对象转换为布尔值的情况
    def __bool__(self):
        return self.age > 17


# 创建两个Person类的实例
p1 = Person('孙悟空', 8)
p2 = Person('猪八戒', 28)

# 打印p1
# 当我们打印一个对象时,实际上打印的是对象中的特殊方法 __str__() 的返回值
# print(p1)  # <__main__.Person object at 0x000002295F746C88>
print(p1)  # hello Person
print(p2)  # hello Person

# 添加__repr__()特殊方法后
print(repr(p1))  # hello


# print(p1 > p2)  # TypeError: '>' not supported between instances of 'Person' and 'Person'
# 添加__gt__()特殊方法后
print(p1 > p2)  # False

# __bool__()特殊方法
# 改前
# print(bool(p1)) # True 只要p1不为空就是True
# 改后 实例的年龄属性是否大于17
print(bool(p1))  # False
# 不建议这么做:
if p1:
    print(p1.name, '成年了')
else:
    print(p1.name, '未成年')

16.模块->test_module

# 模块 (module)
# 模块化,模块化指将以一个完整的程序分解为一个一个小的模块
#   通过将模块组合,来搭建出一个完整的程序
# 不采用模块化,统一将所有的代码编写到一个代码中
# 采用模块化,将程序分别编写到多个文件中
#   模块化的优点:
#       ① 方便开发
#       ② 方便维护
#       ③ 模块可以复用
#       ④

# 在pyton中一个py文件就是一个模块,要想创建模块,实际上就是创建python文件
# 注意:模块名要符合标识符的规范

# 在一个模块中引入外部模块
# ① import 模块名 (模块名,就是python文件的名字,注意不要py)
# ② import 模块名 as 模块别名 (起别名)
#   - 可以引入同一个模块多次,但是模块的实例只会创建一个
#   - import可以在程序的任意位置调用,但是一般情况下,import语句都会统一写在程序开头
#   - 在每一个模块内部都有一个__name__ , 通过这个属性可以获取到模块的名字
#   - __name__属性值为 __main__的模块是主模块,一个程序中只会有一个主模块


# ①
# import test_module  # 我是一个test_module模块
# # import test_module
# # import test_module
# print(test_module)
# # <module 'test_module' from 'D:\\python练习\\lesson_06\\code\\test_module.py'>

# ②
import test_module as test
print(test)
# <module 'test_module' from 'D:\\python练习\\lesson_06\\code\\test_module.py'>

# __name__
print(test.__name__)


test_module.py
print('我是一个test_module模块')

17.模块->m.py

# import xxx
# import xxx as yyy
# from xxx import yyy,zzz,fff
# from xxx import *
# from xxx import yyy as zz


# # 直接import xxx 打印需要 xxx.变量(函数/类)
# import m
# print(m.a, m.b)  # 10 20
# print(m.test(), m.test2())  # m.test m.test2
# p = m.Person()
# print(p.name)


# # 选择引入函数,变量,类
# from m import test  # 引入单个
# from m import Person, test2  # 引入多个,也可以添加变量 ,a,b
# from m import *  # 引入到模块中的所有内容,一般不会使用

# test()  # m.test
# p1 = Person()
# print(p1.name)  # 孙悟空
# test2()  # m.test2

# print(a, b)   # 引入所有后可以使用 模块中的变量 # NameError: name 'c' is not defined
# # print(_c)  # NameError: name '_c' is not defined # 因为在模块中加入了 _c 是隐藏的


# # 也可以为引入的变量使用别名
# # 语法: form 模块名 import 变量 as 别名
# from m import test2 as new_test2
# # test2()  # NameError: name 'test2' is not defined
# new_test2() # m.test2

# from m import *
# print(_c) # NameError: name '_c' is not defined





m.py
# 可以在模块中定义变量,在模块中定义的变量,在引入模块后,就可以直接使用了
a = 10
b = 20

# 添加了_的变量,只能在模块内部访问,在同故宫import * 引入式,不会引入_开头的变量
_c = 0

# 可以在模块中定义函数,同样可以通过模块访问到


def test():
    print('m.test')


def test2():
    print('m.test2')

# 也可以定义类


class Person:
    def __init__(self):
        self.name = '孙悟空'

# 编写测试代码,这部分代码,只要当 当前文件作为主模块的时候才需要执行
#   而当模块被其它模块引用时,不需要执行,此时我们就必须要检查当前模块是否是主模块

18.包

# 包 Package
# 包也是一个模块
# 当我们模块中的代码过多时,或者一个模块需要被分解为多个模块时,这时就需要使用到包
# 普通的模块就是一个py文件,而包是一个文件夹
# 包中必须要有一个__init__.py这个文件,这个文件中可以包含有包中的主要内容


# import hello
# print(hello)  # <module 'hello' (namespace)>
# print(hello.init_a, hello.init_b)  # 10 20 # 实际是引用的 hello/__init__中的变量
# hello.test()  # hello.init.test


# from hello import a, b
# print(a.c, b.d)


# __pycache__ 是模块的缓存文件
# py代码在执行前,需要被解析器先转换为机器码,然后再执行
#   所以在使用模块(包)时,也需要将模块的代码先转换为机器码然后再交给计算机执行
#   而为了提高程序运行的性能,python会在编译过一次以后,将代码保存到一个缓存文件中
#   这样在下次加载这个模块(包)时,就可以不再重新编译而是直接加载韩村中编译好的代码

19.python标准库

# 开箱即用
# 为了实现开箱即用思想,python中为我们提供了一个模块的标准库
# 在这个标准库中,有很多很强大的模块我们可以直接使用
#   并且标准库会随python的安装一同安装
# sys模块,它里面提供了一些变量和函数,使我们可以获取到Python解析器的信息
#   或者通过函数来操作Python解析器

# pprint模块 它给我们提供了一个方法pprint()该方法可以用来对打印的数据做简单的格式化
import pprint


# 引入sys模块
import sys

# print(sys)  # <module 'sys' (built-in)>

# sys.argv
# 获取执行代码时,命令行中所包含的参数
# 该属性是一个列表,列表中保存了当前命令的列表
# print(sys.argv)

# sys.modules
# 获取当前程序中引入的所有模块
# modules 是一个字典, key是模块的名字,value是模块对象
# print(sys.modules)
# pprint.pprint(sys.modules)

# sys.path
# 他是一个列表,列表中保存的是模块的搜索路径
# pprint.pprint(sys.path)
# ['D:\\python练习\\lesson_06\\code',
#  'C:\\Program Files\\python\\python36.zip',
#  'C:\\Program Files\\python\\DLLs',
#  'C:\\Program Files\\python\\lib',
#  'C:\\Program Files\\python',
#  'C:\\Program Files\\python\\lib\\site-packages',
#  'C:\\Program Files\\python\\lib\\site-packages\\win32',
#  'C:\\Program Files\\python\\lib\\site-packages\\win32\\lib',
#  'C:\\Program Files\\python\\lib\\site-packages\\Pythonwin']

# sys.platfrom
# 当前python运行的平台
# print(sys.platform) # win32


# sys.exit()
# 函数用来退出程序
# sys.exit('程序出现异常,结束')
# print(sys.platform) # 出不来了

# os 模块让我们可以对操作系统进行访问
import os
print(os)
# <module 'os' from 'C:\\Program Files\\python\\lib\\os.py'>


# os.environ
# 通过这个属性可以获取到系统的环境变量
pprint.pprint(os.environ['path'])


# os.system()
# 可以用来执行操作系统的命令
os.system('dir')
os.system('notepad')# 记事本

第七章 异常和文件

# 第七章 异常和文件
	
## 异常
	- 程序在运行过程当中,不可避免的会出现一些错误,比如
		使用了没有赋值过的变量,
		使用来不存在的索引
		除0
		...
	- 这些错误在程序中,我们称之为异常
	- 程序运行过程中,一旦出现异常将会导致程序立即终止,异常以后的代码全部都不会执行


## 处理异常
	- 程序运行时出现异常,目的并不是让我们的程序直接终止!!!
	- python是希望再出现异常时,我们可以编写代买来对异常进行处理

	- try语句
		try:
			代码块 (可能出现错误的语句)

		except 异常类型 as 异常名:
			代码块 (出现错误以后的处理方式)
		except 异常类型 as 异常名:
			代码块 (出现错误以后的处理方式)
		except 异常类型 as 异常名:
			代码块 (出现错误以后的处理方式)

		else:
			代码块 (没出错时要执行的语句)
		finally:
			代码块 (该代码块总会执行)

		- try是必须的,else语句有没有都行
		  except和finally至少有一个
		- 可以将可能出错的代码放入try语句,这样如果代码没有错误,则会正常执行
			如果出现错误,则会执行expect子句中的代码,这样我们就可以通过代码来处理异常
			避免因为一个异常导致程序终止

## 异常的传播 (抛出异常)
	- 当在函数中出现异常时,如果在函数中对异常进行 了处理,则异常不会继续传播
		如果函数中没有对异常进行处理,则异常则会继续向函数的调用处传播
		如果函数调用处处理了异常,则不再传播,如果没有处理则继续向调用处传播
		直到传递到全局作用域(主模块) 如果依然没有处理,则程序终止,并且显示异常信息

	- 当程序运行过程中出现异常以后,所有的异常信息会被保存到一个专门的异常对象中,
		而异常传播时,实际上就是异常对象抛给了调用处
		比如:
			ZeroDivisionError 类的对象专门用来表示除0的异常
			NameError  		  类的对象专门用来处理变量错误的异常
			....
	- 在python为我们提供了多个异常对象

## 抛出异常
	- 可以使用raise 语句来抛出异常,
		raise语句后需要跟一个异常类 或 异常的实例

## 文件(file)
	- 通过python程序来对计算机中的各种文件进行增删改查的操作
	- I/O (Input/Output) 
	- 操作文件的步骤:
		① 打开文件
		② 对文件进行各种操作(读/写),改完保存
		③ 关闭文件

01.异常

# # print(a)  # NameError: name 'a' is not defined
# # print(10 / 0)  # ZeroDivisionError: division by zero
# print('hello')
# try:
#     # 有可能出现错误的代码
#     print(10 / 2)  # 10/0就出错
# except:
#     # 出错之后的处理方式
#     print('哈哈哈,出错了')
# else:
#     print('程序正常执行,没有错误')  # 5.0 程序正常执行,没有错误

# print('你好')

# print(10 / 0)


def fn():
    print('hello fn')
    print(10 / 0)
    # try:
    #     print(10 / 0)
    # except:
    #     pass


def fn2():
    print('hello fn2')
    fn()


def fn3():
    print('hello fn3')
    fn2()

# try:
#     fn()
# except:
#     pass


# fn3()

print(a)


# Traceback (most recent call last):
#   File "01.异常.py", line 42, in <module>
#     fn3()
#   File "01.异常.py", line 34, in fn3
#     fn2()
#   File "01.异常.py", line 29, in fn2
#     fn()
#   File "01.异常.py", line 20, in fn
#     print(10 / 0)
# ZeroDivisionError: division by zero

02.异常对象

print('异常出现前')
l = []
try:
    print(c)
    print(10 / 0)
    l[10]
    1 + 'hello'
except NameError:
    # 如果except后不跟任何内容,则此时他会捕获到所有的异常
    # 如果在except后跟着一个异常类型,那么此时他只会捕捉该类型的异常
    print('出现 NameError 异常')
except ZeroDivisionError:

    print('出现 ZeroDivisionError 异常')
except IndexError:
    print('出现 IndexError 异常')
except Exception as e:
    # exception是所有异常的父类,所以如果except后跟的是exception也会捕获到所有的异常
    # 可以在异常类跟着一个 as xx 此时xx就是异常对象
    print('出现未知异常', e, type(e)) # unsupported operand type(s) for +: 'int' and 'str' <class 'TypeError'>
finally:
    print('无论是否出现异常该子局都会使用')


print('异常出现后')

03.抛出异常

# 也可以自定义异常类,只需要创建一个类继承exception即可
class MyError(Exception):
    pass


def add(a, b):
    # 如果a和b中有负数,就像调用处抛出异常
    if a < 0 or b < 0:
        # raise 用于向外部抛出异常,后边可以跟一个异常类,或异常类的实例
        # 抛出异常的目的,告诉调用者这里调用时出现问题,希望你处理一下
        # 也可以通过if else 来替代参数的异常
        raise MyError('两个参数中不能有负数')
    r = a + b
    return r


print(add(-123, 456))

04.打开文件

# open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
# 使用open函数来打开一个文件
# 参数:
#   file 要打开的文件的名字(路径)
# 返回值:
#   返回一个对象,这个对象就代表了当前打开的文件

# 创建一个变量,来保存文件的名字
# 如果目标文件和当前文件在同一目录下,则直接使用文件名即可
# file_name = 'demo.txt'

# 在win系统下使用路径时,可以使用/来代替\
# 或者可以使用\\来代替\
# 或者也可以使用原市字符串 加r
# file_name = 'hello/demo.txt'

# 表示路径时,可以使用..来返回一级目录
# file_name = '../hello/demo.txt'

# 如果目标文件距离当前文件比较远,此时可以使用绝对路径
# 绝对路径应该从磁盘的根目录开始书写
file_name = 'D:/python练习/lesson_07/hello/demo.txt'

file_obj = open(file_name)  # 打开file_name对应的文件
print(file_obj)

05.关闭文件&with语句

# # 打开文件
file_name = 'demo.txt'

# # 调用open()来打开文件
# file_obj = open(file_name)


# # 当我们获取了文件对象以后,所有的对文件的操作都应该通过对象来进行
# # 读取文件中的内容
# # read()方法,用来对去文件中的内容,它会将全部内容保存为一个字符串返回
# content = file_obj.read()
# print(content)

# # 关闭文件
# # 调用close()来关闭文件
# file_obj.close()

# with ... as 语句

# with open(file_name) as file_obj:
#     # 代码块
#     # 在with语句中可以直接使用file_obj来做文件操作,相当于赋值给了file_obj
#     # 此时这个文件只能在with中使用,一旦with'结束则文件会自动close()
#     print(file_obj.read())

file_name = 'demo.txt'
try:
    with open(file_name) as file_obj:
        print(file_obj.read())
except FileNotFoundError: 
    print(f'{file_name} 文件不存在')


# file_obj.read() #ValueError: I/O operation on closed file.

06.文件的读取

file_name = 'demo2.txt'


try:
    # 调用open()来打开一个文件,可以将文件分成两种类型
    # 一种,时纯文本文件(使用utf-8等编码编写的文本文件)
    # 一种, 是二进制文件(图片,Mp3,ppt等这些文件)
    # open()打开文件时,默认是以文本文件的形式打开的,但是open()默认的编码默认为None
    # 所以处理文本文件时,必须要指定文件的编码
    with open(file_name, encoding='utf-8') as file_obj:
        # 通过read()来读取文件
        # 如果直接调用read()它会将文本文件的所有内容全部都读取出来
        #   如果要读取的文件比较大的话,会一次性将文件的内容加载到内存中,容易导致内存泄露
        #   所以对较大的文件,不要直接调用read()
        # help(file_obj.read)
        # read()可以接收一个size作为参数,该参数用来指定要读取的字符的数量
        #   默认值-1,他会读取文件中所有的字符
        #   可以为size指定一个值,这样read()会读取指定数量的字符,
        #       每一次读取都是从上次读取到的位置开始读取的
        #       如果字符的数量小于size,则会读取剩余所有的
        #       如果已经读取到了文件的最后了,则会返回''空串
        content = file_obj.read(6)
        content = file_obj.read(6)
        print(content)  # UnicodeDecodeError: 'gbk' codec can't decode byte 0xa6 in position 4: illegal multibyte sequence
        print(len(content))
except FileNotFoundError:
    print(f'{file_name}不存在!')


# 读取大文件的方式


file_name = 'demo.txt'


try:
    with open(file_name, encoding='utf-8') as file_obj:
        # 定义一个变量,来保存文件的内容
        file_content = ''

        # 定义一个变量,来指定每次读取的大小
        chunk = 100
        # 创建一个循环来读取文件的内容
        while True:
            # 读取chunk大小的内容
            content = file_obj.read(chunk)

            # 检查是否读取到了内容
            if not content:
                # 内容读取完毕
                break

            # 输入内容
            # print(content,end='')
            file_content += content


except FileNotFoundError:
    print(f'{file_name}不存在!')

print(file_content)

07.文件的读取 readline

import pprint
file_name = 'demo.txt'

with open(file_name, encoding='utf-8') as file_obj:
    # readline()
    # 该方法可以读一行的内容
    # print(file_obj.readline(), end='')
    # print(file_obj.readline(), end='')

    # readlines()
    # 该方法用于一行一行的读取内容,他会一次性将读取到的内容封装到一个列表中返回
    # r = file_obj.readlines()
    # pprint.pprint(r[0])
    for t in file_obj:
        print(t)

08.文件的写入

file_name = 'demo5.txt'

# 使用open()打开文件时,必须要指定打开文件所要做的操作(读,写,追加)
# 如果不指定操作类型,则默认时读取文件,而读取文件时,是不能向文件中写入的
# r 表示只读的
# w 表示是可写的 ,使用w写入文件时,如果文件不存在会创建文件,如果文件存在则会截断文件
#       截断文件是指删除原来文件中的所有内容
# a 表示追加内容,,如果文件不存在会创建文件,如果文件存在则会向文件中追加内容
# x 用来新建文件,如果文件不存在则创建,存在则报错
# + 为操作符增加功能 
#   r+ 即可读又可写,如果文件不存在不会会创建文件
#   w+ 即可读又可写
#   a+ 即可读又可写
# with open(file_name, 'w', encoding='utf-8') as file_obj:
# with open(file_name, 'r+', encoding='utf-8') as file_obj:
with open(file_name, 'x', encoding='utf-8') as file_obj:
    # write()来向文件中写入内容
    # 如果操作的是一个文本文件的话,则write()需要传递一个字符串作为参数
    # 该方法可以分多次向文件中写入内容
    # 该方法完成以后,该方法会返回写入的字符的个数
    file_obj.write('hello hello \n')
    file_obj.write('hello hello \n')
    file_obj.write('hello hello \n')
    file_obj.write(str(123))
    r = file_obj.write(str(123) + '123123\n')
    r = file_obj.write('今天天气不错')
    print(r)
    # io.UnsupportedOperation: not writable

09.二进制文件

file_name = '不为谁而作的歌.flac'

# 读取模式
# t 读取文本文件(默认值)
# b 读取二进制文件

with open(file_name, 'rb') as file_obj:
    # 读取文本文件时,size是以字符为单位的
    # 读取二进制文件时,size是以字节为单位的
    # print(file_obj.read(100))

    # 将读取到的内容写出来
    # 定义一个新的文件
    new_name = 'aa.flac'
    with open(new_name, 'wb') as new_obj:
        # 定义每次读取的大小
        chunk = 1024 * 100
        while True:
            # 从已有的对象中读取数据
            content = file_obj.read(chunk)
            # 内容读取完毕后,结束循环
            if not content:
                break
            # 读取的数据写入到新对象中
            new_obj.write(content)

10.读取文件位置

#  seek读取二进制文件

# with open('demo.txt', 'rb') as file_obj:
#     # print(file_obj.read(100))
#     # print(file_obj.read(10))

#     # seek() 可以修改当前读取位置
#     file_obj.seek(55)
#     file_obj.seek(80, 0)
#     file_obj.seek(70, 1)
#     file_obj.seek(-10, 2) # b'o hello \r\n'
#     # seek()需要两个参数
#     #   第一个 是要切换到的位置
#     #   第二个 计算位置方式
#     #       可选值:
#     #           0 从头计算,默认值
#     #           1 从当前位置开始计算
#     #           2 从最后位置开始计算

#     print(file_obj.read())

#     # tell()用来查看当前读取到的位置
#     print('当前读取到-->', file_obj.tell())  # 当前读取到--> 110


# 读取文本文件

with open('demo2.txt', 'rt', encoding='utf-8') as file_obj:
    # print(file_obj.read(100))
    # print(file_obj.read(10))

    # seek() 可以修改当前读取位置
    file_obj.seek(2) # UnicodeDecodeError: 'utf-8' codec can't decode byte 0x84 in position 0: invalid start byte
    # 中文是三个字节一个字符 
    # seek()需要两个参数
    #   第一个 是要切换到的位置
    #   第二个 计算位置方式
    #       可选值:
    #           0 从头计算,默认值
    #           1 从当前位置开始计算
    #           2 从最后位置开始计算

    print(file_obj.read())

    # tell()用来查看当前读取到的位置
    print('当前读取到-->', file_obj.tell())  # 当前读取到--> 110

11.文件的其他操作

import os
from pprint import pprint


# os.listdir获取指定目录机构
# 需要一个路径作为参数,会获取到该路径下的目录结构,默认路径为 .  当前目录
# 该方法会返回一个列表,目录中的每一个文件(夹)的名字都是列表中的一个元素
r = os.listdir('c:')


# getcwd() 获取当前位置
r = os.getcwd()

# os.chdir()切换当前所在的目录 作用相当于cd
# os.chdir('c:')
# r = os.getcwd()

# 创建目录 os.mkdir()
# os.mkdir('aaa')

# 删除目录 os.rmdir()
# os.rmdir('aaa')

# 删除文件 os.remove()
open('aa.txt', 'w')
# os.remove('aa.flac')

# 重命名 os.rename()
# 可以对一个文件重命名,也可以用来移动一个文件
os.rename('aa.txt','bb.txt')

pprint(r)

正则

e.match函数

re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match() 就返回 none。

函数语法

re.match(pattern, string, flags=0)

函数参数说明:

参数 描述
pattern 匹配的正则表达式
string 要匹配的字符串。
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志

匹配成功 re.match 方法返回一个匹配的对象,否则返回 None。

我们可以使用 group(num) 或 groups() 匹配对象函数来获取匹配表达式。

匹配对象方法 描述
group(num=0) 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。
groups() 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。

正则表达式修饰符_可选标志

正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:

修饰符 描述
re.I 使匹配对大小写不敏感
re.L 做本地化识别(locale-aware)匹配
re.M 多行匹配,影响 ^ 和 $
re.S 使 . 匹配包括换行在内的所有字符
re.U 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.
re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。

正则表达式模式

模式字符串使用特殊的语法来表示一个正则表达式:

字母和数字表示他们自身。一个正则表达式模式中的字母和数字匹配同样的字符串。

多数字母和数字前加一个反斜杠时会拥有不同的含义。

标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。

反斜杠本身需要使用反斜杠转义。

由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r'\t',等价于 '\\t')匹配相应的特殊字符。

下表列出了正则表达式模式语法中的特殊元素。如果你使用模式的同时提供了可选的标志参数,某些模式元素的含义会改变。

模式 描述
^ 匹配字符串的开头
$ 匹配字符串的末尾。
. 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
[...] 用来表示一组字符,单独列出:[amk] 匹配 'a','m'或'k'
[^...] 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。
re* 匹配0个或多个的表达式。
re+ 匹配1个或多个的表达式。
re? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
re{ n} 精确匹配 n 个前面表达式。例如, o{2} 不能匹配 "Bob" 中的 "o",但是能匹配 "food" 中的两个 o。
re{ n,} 匹配 n 个前面表达式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。"o{1,}" 等价于 "o+"。"o{0,}" 则等价于 "o*"。
re{ n, m} 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
a| b 匹配a或b
(re) 对正则表达式分组并记住匹配的文本
(?imx) 正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。
(?-imx) 正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。
(?: re) 类似 (...), 但是不表示一个组
(?imx: re) 在括号中使用i, m, 或 x 可选标志
(?-imx: re) 在括号中不使用i, m, 或 x 可选标志
(?#...) 注释.
(?= re) 前向肯定界定符。如果所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。
(?! re) 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功
(?> re) 匹配的独立模式,省去回溯。
\w 匹配字母数字及下划线
\W 匹配非字母数字及下划线
\s 匹配任意空白字符,等价于 [ \t\n\r\f]
\S 匹配任意非空字符
\d 匹配任意数字,等价于 [0-9].
\D 匹配任意非数字
\A 匹配字符串开始
\Z 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
\z 匹配字符串结束
\G 匹配最后匹配完成的位置。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
\n, \t, 等. 匹配一个换行符。匹配一个制表符。等
\1...\9 匹配第n个分组的内容。
\10 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。

正则表达式实例

字符匹配

实例 描述
python 匹配 "python".

字符类

实例 描述
[Pp]ython 匹配 "Python" 或 "python"
rub[ye] 匹配 "ruby" 或 "rube"
[aeiou] 匹配中括号内的任意一个字母
[0-9] 匹配任何数字。类似于 [0123456789]
[a-z] 匹配任何小写字母
[A-Z] 匹配任何大写字母
[a-zA-Z0-9] 匹配任何字母及数字
[^aeiou] 除了aeiou字母以外的所有字符
[^0-9] 匹配除了数字外的字符

特殊字符类

实例 描述
. 匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用象 '[.\n]' 的模式。
\d 匹配一个数字字符。等价于 [0-9]。
\D 匹配一个非数字字符。等价于 [^0-9]。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\w 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。
\W 匹配任何非单词字符。等价于 '[^A-Za-z0-9_]'。

阶段二 网络编程

跟之前连不到一块去,一脸懵逼

22.3.27: 找到一本python书, 是的,过了两年我又重新搞python了,

socket

#server
import socket
host = "localhost"
port = 23333
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind((host,port))
s.listen(5)
print('服务器正在监听....\r\n')
sock,addr=s.accept()
print('已连接到客户端')
print("**提示:退出请输入q后回车.\r\n")
info = sock.recv(1024).decode()
while info != 'q':
    if info :
        print("客户端说:",info)
    send_data = input("服务器说:")
    sock.send(send_data.encode())
    if send_data=='q':
        break
    info = sock.recv(1024).decode()
sock.close()
s.close()


client
import socket
s=socket.socket()
host="localhost"
port=23333
s.connect((host,port))
print('已连接到服务器')
print("**提示:退出请输入q后回车.\r\n")
info = ''
while info !='q':
    send_data=input('客户端说:')
    s.send(send_data.encode())
    if send_data=='q':
        break
    info=s.recv(1024).decode()
    print("服务器说:"+info)
s.close()

SSH

本地无公钥

import paramiko

client = paramiko.SSHClient() # 看文档都是paramiko.client.SSHClient() 奇怪
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 设置不知道公钥,自动获取
client.connect('192.168.1.248',22,'root','123456') # 连接 地址, 端口, 用户名, 密码
stdin,stdout,stderr=client.exec_command('ls -l') # 执行命令

# 输出命令
res,err=stdout.read(),stderr.read()
result=res if res else err
print(result.decode('utf-8'))

client.close()  # 关闭连接

本地有公钥

test

 

 

Pyinstaller

pyinstaller -w -F 文件名
-w 隐藏命令窗口(仅windows)
-F 强制为单文件

 

阅读剩余
THE END