Python学习

一、绪论

1.windows的命令行

①命令行就是文本交互界面,通过命令行可以使用一个一个的指令来操作计算机,任何的计算机的操作系统中都包含有命令行(windows、linux、macOS)命令行有多个不同的名字:命令行、命令行窗口、DOS窗口、命令提示符、CMD窗口、Shell、终端、Terminal

1.如何进入到命令行
    win键 + R 出现运行窗口,输入cmd,然后回车
2.命令行的结构
    - 版本及版权声明(一般没有什么用)
        Microsoft Windows [版本 10.0.16299.431]
        (c) 2017 Microsoft Corporation。保留所有权利。
    - 命令提示符
        C:\Users\lilichao>  
            C:  
                - 当前所在的磁盘根目录
                - 可以通过 x: 来切换盘符(x表示你的盘符)
            \Users\lilichao
                - 所在磁盘的路径,当前所在的文件夹
                - cd 文件名--来切换目录
            >
                - 命令提示符,在大于号后边可以直接输入指令
3.常用的dos命令
    dir 查看当前目录下的所有文件(夹)
    cd 进入到指定的目录
        . 表示当前目录
        .. 表示上一级目录
    md 创建一个目录
    rd 删除一个目录    
    del 删除一个文件
    cls 清除屏幕
    命令的语法
        命令 [参数] [选项]
4.小技巧
    - 方向键上下,查看命令的历史记录
    - tab键自动补全命令        

2.环境变量(environment variable)

环境变量指的就是操作系统当中的一些变量。可以通过修改环境变量,来对计算机进行配置(主要是来配置一些路径的)
1.查看环境变量
    > 右键 计算机(此电脑),选择属性
    > 系统界面左侧选择 高级系统设置
    > 选择环境变量
    环境变量界面分成了两个部分,上边是用户环境变量,下边是系统环境变量
        建议只修改用户的环境变量,不要修改系统的环境变量
2.添加环境变量
    > 通过新建按钮添加环境变量
    > 一个环境变量可以由多个值,值与值之间使用;(英文)隔开
3.修改环境变量
    > 通过编辑按钮来修改环境变量
4.删除环境变量
    > 通过删除按钮来删除环境变量  

3.path环境变量

path环境变量中保存的是一个一个的路径。
当我们在命令行中输入一个命令(或访问一个文件时),
    系统会首先在当前目录下寻找,如果找到了则直接执行或打开
    如果没有找到,则会依次去path环境变量的路径中去寻找,直到找到为止
    如果path环境变量中的路径都没有找到,则报错
        'xxx' 不是内部或外部命令,也不是可运行的程序或批处理文件。
我们可以将一些经常需要访问到的文件会程序的路径,添加到path环境变量中,
    这样我们就可以在任意的位置访问到这些文件了

注意事项:
    1.如果环境变量中没有path,可以手动添加
    2.path环境变量不区分大小写 PATH Path path   
    3.修改完环境变量必须重新启动命令行窗口
    4.多个路径之间使用;隔开            

4.进制

- 十进制(最常用的进制)
    - 十进制就是满十进一的进制
    - 十进制当中一共有10个数字
        0 1 2 3 4 5 6 7 8 9
    - 十进制如何计数
        0 1 2 3 4 5 6 7 8 9 10 11 12 ... 19 20 ...29 30
- 二进制(计算机底层使用的进制)
    - 满二进一
    - 二进制中一共有2个数字
        0 1
    - 二进制如何计数
        0 1 10 11 100 101 110 111 1000
    - 所有的数据在计算机底层都是以二进制的形式保存的,计算机只认二进制
    - 可以将内存想象为一个一个的小格子,小格子中可以存储一个0或一个1
    - 内存中的每一个小格子,我们称为1bit(位)    
        bit是计算机中的最小的单位
        byte是我们最小的可操作的单位
        8bit = 1byte(字节)    
        1024byte = 1kb(千字节) 
        1024kb = 1mb(兆字节)
        1024mb = 1gb(吉字节)
        1024gb = 1tb(太字节)
        ...
- 八进制(一般不用)
    - 满八进一
    - 八进制中一共有8个数字
        0 1 2 3 4 5 6 7
    - 八进制如何计数
        0 1 2 3 4 5 6 7 10 11 ... 17 20 ...27 30
- 十六进制
    - 满十六进一
    - 十六进制中一共有16个数字
        由于十六进制是满16才进位,
            所以十六进制中引入了a b c d e f来表示 10 11 12 13 14 15   
        0 1 2 3 4 5 6 7 8 9 a b c d e f 
    - 十六进制如何计数    
        0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12... 1a 1b 1c 1d 1e 1f
        20 21 22 ... 2a 2b 2c 2d 2e 2f 30    
    - 我们在查看二进制数据时,一般会以十六进制的形式显示

5.文本文件和字符集

- 文本分成两种,一种叫做纯文本,还有一种叫做富文本
- 纯文本中只能保存单一的文本内容,无法保存内容无关的东西(字体、颜色、图片。。。)
- 富文本中可以保存文本以外的内容(word文档)
- 在开发时,编写程序使用的全都是纯文本!
- 纯文本在计算机底层也会转换为二进制保存,
    将字符转换为二进制码的过程,我们称为编码
    将二进制码转换为字符的过程,我们称为解码
    编码和解码时所采用的规则,我们称为字符集
- 常见的字符集:
    ASCII
        - 美国人编码,使用7位来对美国常用的字符进行编码
        - 包含128个字符
    ISO-8859-1
        - 欧洲的编码,使用8- 包含256个字符
    GB2312
    GBK
        国标码,中国的编码
    Unicode   
        万国码,包含世界上所有的语言和符号,编写程序时一般都会使用Unicode编码
        Unicode编码有多种实现,UTF-8 UTF-16 UTF-32
        最常用的就是UTF-8
- 乱码
    编写程序时,如果发现程序代码出现乱码的情况,就要马上去检查字符集是否正确        

二、Python基础语法

1.几个概念

1.表达式
    表达式就是一个类似于数学公式的东西
    比如:10 + 5   8 - 4
    表达式一般仅仅用了计算一些结果,不会对程序产生实质性的影响
    如果在交互模式中输入一个表达式,解释器会自动将表达式的结果输出
2.语句
    在程序中语句一般需要完成某种功能,比如打印信息、获取信息、为变量赋值...
    比如:
        print()
        input()
        a = 10
    语句的执行一般会对程序产生一定的影响
    在交互模式中不一定会输出语句的执行结果  
3.程序(program)
    程序就是由一条一条的语句和一条一条的表达式构成的。
4.函数(function)
    函数就是一种语句,函数专门用来完成特定的功能
    函数长的形如:xxx()          
    函数的分类:
        内置函数
            - 由Python解释器提供的函数,可以在Python中直接使用
        自定义函数   
            - 由程序员自主的创建的函数
    当我们需要完成某个功能时,就可以去调用内置函数,或者自定义函数 
    函数的两个要素:
        参数
            - ()中的内容就是函数的参数
            - 函数中可以没有参数,也可以有多个参数,多个参数之间使用,隔开
        返回值        
            - 返回值是函数的返回结果,不是所有的函数都有返回值

2.基本语法

1.在Python中严格区分大小写
2.Python中的每一行就是一条语句,每条语句以换行结束
3.Python中每一行语句不要过长(规范中建议每行不要超过80个字符)
    "rulers":[80],
4.一条语句可以分多行编写,多行编写时语句后边以\结尾  
5.Python是缩进严格的语言,所以在Python中不要随便写缩进  
6.在Python中使用#来表示注释,#后的内容都属于注释,注释的内容将会被解释器所忽略
    我们可以通过注释来对程序进行解释说明,一定要养成良好的编写注释的习惯
    注释要求简单明了,一般习惯上#后边会跟着一个空格

3.字面量和变量

字面量就是一个一个的值,比如:1,2,3,4,5,6,‘HELLO’
    字面量所表示的意思就是它的字面的值,在程序中可以直接使用字面量
变量(variable)变量可以用来保存字面量,并且变量中保存的字面量是不定的
    变量本身没有任何意思,它会根据不同的字面量表示不同的意思
    python中变量不需要声明可以直接赋值和使用,但是不可以使用没有赋值的变量
    python是一个动态类型语言,可以为变量赋值任何类型的值,也可以任意修改变量的值
一般我们在开发时,很少直接使用字面量,都是将字面量保存到变量中,通过变量来引用字面量

4.变量和标识符

①标识符:字母数字下划线,数字不能打头,不可以是python中的关键字和保留字以及函数名(函数名会在编译时报错)

②命名规范:下划线命名法(max_length)或大驼峰命名法(MaxLength)

5.数据类型

1.整型

①没有大小限制,所有的整数都是int型,如果数过大可以使用_作为分隔符如c=123_456_789,输出仍为123456789

②十进制数字不可以以0开头,如a=0123456,会报错

③二进制:以0b开头,a=0b10,输出2

④八进制:以0o开头,a=0o17

⑤十六进制:以0x开头,a=0x1F

⑥无论用什么形式输入,输出是默认用十进制输出

2.浮点型

①python中所有的小数都是浮点数(float)如a=1.23,b=4.56

②对浮点数进行运算时,可能会得到一个不精确的结果如0.3+0.4=0.300004

3.字符串

①用引号引起来(可以是双引号也可以是单引号,但是不要混着用如a='abc123")

②相同的引号不可以嵌套,不同的引号可以嵌套,如想要输出一句带双引号的话可以写成a=‘“学而时习之不亦说乎”’

③单引号双引号不可以跨行使用,如需要让文本换行要用长字符串(三重引号:三个单引号’‘’ ‘’'或三个双引号"“”“”"),三重引号可以换行且保留字符串中的格式

print('123''123')
输出:123123

print('''123
123
123''')
输出:
123
123
123

print('''123\
123\
123''')
输出:123123123--在每行后面加了\下一行会被看作是接着写

a='hello'
b='world'
c=a+b
d='abc'+'def'
print(c,d)
输出:
hellow abcdef

④两个字符串相加相当于将两个字符串拼接成一个,字符串只可以和字符串进行拼接,如果与其他类型拼接则会报错。因此常用一下三种方式来进行字符串的拼接

#不常用方式
print('a='+a)
输出:
a=hellow
报错,字符串与整形拼接会报错
a=123
print('a='+a)
#常用方式1
f=123
print('f=',f)
输出:
f= 123
#常用方式2
a=123
print('a=%s'%a)
输出:
a=123
#常用方式3:格式化字符串
a=123
print(f'a={a}')

⑤%s:字符串占位符,数字和字符串作为填充内容,%s字符串’%3s(‘填充内容1’,填充内容2,…),填充内容可以任意类型,字符串加引号;仅一个参数可以省略括号;有几个%s就要有几个填充字符串,并按顺序填充;n可以任意数字,来表示填充字符串最小长度(多了全要,少了左补空格,%-3s右补空格),%3.5s最小三个最多5个(多去少补)。不足,当填充内容为小数且想要保证精度是该方法无法做到

⑥%f:表示任意小数,只可以以数字位填充内容,%4.5f表示最少4位且保留5位小数,最后一位四舍五入,整数位不够补空格,小数位不够补0

⑦%d:整数占位符,%d对小数部分四舍五入,%.nd:要n位整数部分不够补0

⑧格式化字符串:通过在字符串前加一个f来创建一个格式化字符串,在格式化字符串中可以直接嵌入变量,变量用{变量}表示,该变量会被该变量的值所替代,{}中的变量必须被赋值(被用过)

#%s
a='%s123'%'孙悟空'
b='%s123%s123'%('Tom','孙悟空')
#%f
h='%5.2fabc'%7.7
print(h)--   7.70abc
#%d
i='%.5dabc'%12
print(i)--00012abc
#格式化字符串
a=123
b=345
c=f'abc{a}{b}'
d='468'
print(c)
print(f'a={a}')
print(f'{d}={a}+{b}')

⑨字符串与数字相乘表示将字符串赋值n次

a='abc'
a=a*2
print(a)
输出:
abcabc
4.转义字符

①可以使用\作为转义字符,在符号前加\会原样输出。

②单双右+转义字符

③\uxxxx:表示unicode编码,xxxx为4位数字编码,每个编码代表一个字符

print("\"学而时习之\"")输出:"学而时习之"
print('%')--%
print('?')--?
print('\\')--\
print('\u0040')--@
print('\u1040')--o
5.布尔类型

①主要用来做逻辑判断

②布尔类型相当于整形,True:真(1);False:假(0)

b=False
c=True
print(f'b={b}')
print(f'c={c}')
print(c+1)
print(b+1)
6.空值None

①None表示不存在

b=None
print(b)
7.类型检查

①利用type()函数来检查变量对应值的类型

②type(变量或常量):返回变量或常量的数据类型

b=True
c=type(b)
print(c)
print(type(123))

6.对象(object)

1.对象的说明

①Python是一门面向对象的语言

②一切皆对象!

③程序运行当中,所有的数据都是存储到内存当中然后再运行的

④对象就是内存中专门用来存储指定数据的一块区域

⑤对象实际上就是一个容器,专门用来存储数据

⑥像我们之前学习的数值、字符串、布尔值、None都是对象

2.对象的结构
- 每个对象中都要保存三种数据
    - id(标识)
        > id用来标识对象的唯一性,每一个对象都有唯一的id
        > 对象的id就相当于人的身份证号一样
        > 可以通过id()函数来查看对象的id
        > id是由解析器生成的,在CPython中,id就是对象的内存地址
          print(id(123))
        > 对象一旦创建,则它的id永远不能再改变
        > id是根据值来区分的,变量同值不同id不一样,变量不同值相同,id一样
    - type(类型)
        > 类型用来标识当前对象所属的类型
        > 比如:int str float bool 。。。
        > 类型决定了对象有哪些功能
        > 通过type()函数来查看对象的类型
        > Python是一门强类型的语言,对象一旦创建类型便不能修改
    - value(值)
        > 值就是对象中存储的具体的数据
        > 对于有些对象值是可以改变的
        > 对象分成两大类,可变对象 不可变对象
            可变对象的值可以改变
            不可变对象的值不能改变,之前学习的对象都是不可变对象
3.变量和对象

①对象并没有直接存储到变量中,在Python中变量更像是给对象起了一个别名

②变量中存储的不是对象的值,而是对象的id(内存地址)

③当我们使用变量时,实际上就是在通过对象id在查找对象

④变量中保存的对象,只有在为变量重新赋值时才会改变

⑤变量和变量之间是相互独立的,修改一个变量不会影响另一个变量

4.类型转换

①所谓的类型转换,将一个类型的对象转换为其他对象

②类型转换不是改变对象本身的类型,而是根据当前对象的值创建一个新对象

③类型转换的四个函数:int()、float()、str()、bool()

​ int():用来将其他对象的类型转换为整型,作为返回值返回。True转化为1,False转化为0;浮点数不四舍五入直接取整数部分;只有整数字符串可以用int函数转换为整形,其他类型的字符串会报错;None类型不可以转为int;

​ float():将其他对象的类型转化为浮点数类型,用法与整数相似,整数格式字符串也可以使用

​ str():将其他对象的类型转化为字符串,任何类型都可以使用

​ bool():将其他对象的类型转化为布尔类型,任何类型都可以使用。对于表示空的对象都会转换为False,其他的都会转换为True;0、None、''等都表示空,注意字符串‘0’不属于假值

#int()
a=True
a=int(a)
#float()
b='123'
b=float(b)
#str()
c=None
c=str(c)
#可以用它将整型与字符串拼接
a=123
print('a='+str(a))

7.运算符

1.算数运算符
+-
*:字符串与数字做乘法时相当于把这个字符串赋值n次
**:a**b求a的b次幂,特别的,a**0.5则为a开根号,0.5换成其他的小数同理
/:除法时运算结果总会返回一个浮点数。除数不能为0
//:整除,指挥保存计算结果的整数位
%:取模
2.赋值运算符
=+=-+*=**=/=//=%=
4.逻辑运算符
5.条件运算符(三元运算符)    
3.关系运算符

①总会返回True和False,关系成立返回True,不成立返回False

②> >= < <= == !=、is、is not

③可以让数字和布尔类型比,布尔类型相当于T=1和F=0

④两个字符串比较时比较的时逐位比较每个字符的unicode编码,有结果不再比较,相同第二位再比,若都一样则相等。可以用该特性对字符串进行排序,对中文字符串的意义不是很大。如果不希望比较其unicode编码,则需要将其转换为数字然后再比较

print('2'>'11')#True
print(int('2')>int('11'))#False

⑤不同数据类型不可以比较,但布尔和整型可以

⑥==、!=比较的是对象的值而不是id,比较id时用is、is not

a=1
b=1
print(id(a),id(b))
print(a is True)
print(a is b)
print(a is 1)--比较a和1id1id和a一样
print(a is 2)
输出:
1562733600 1562733600
False
True
True
False

⑦Python中独有的用法:关系运算符可以连续使用

#三个值进行比较:用中间的数分别和两边比,如果都是True则为Trur否则为False
b=10<20>15--true
b=(10<20)>15--false:10小于20返回True=11>15返回False
#四个值:10和20比,20和15比,15和10比
a=10<20>15>10--True
4.逻辑运算符

(1)not:①逻辑非,对符号右侧的值进行非运算

​ ②对于布尔值,not会对其进行取反操作,True与False转变

​ ③对于非布尔值,会先对其转换成布尔值(非空即Ture)然后再取反

​ ④结果只有Ture和False,False在前短路了则后面不再看

a=''
a=not a
print(a)

(2)and:①逻辑与,可以对符号两侧的值进行与运算

​ ②两侧有假为假全真为真。遵循短路法则

​ ③bool类型返回真假,其他类型会当作布尔值来运算,最总返回原值(满足短路法则)。

​ 会返回第一个假值(False、0、None),如果没有假值则会返回最后的真值

#bool类型返回真假--True
a=True and True
#其他类型返回and后面的--123.2
b='123' and '123.2'
#短路法则:后面的值不再看
False and print('123')

(3)or:①逻辑或,可以对符号两侧的值进行或运算

​ ②一真为真全假则假

​ ③短路法则:如果前面的值为True则or后面的不再看

​ ④bool类型返回真假,其他类型会当作布尔值来运算,最总返回原值(满足短路法则)。

​ 会返回第一个真值,如果都为假值则返回最后一个假值

True or print('123')--什么也不输出
False or print('123')--输出123
5.条件运算符(三元运算符)

①语法:语句1 if 条件表达式 else 语句2

②执行流程:先对条件表达式进行判断,如果为True则执行语句1,否则执行语句2,并返回执行结果

num1=123
num2=354
print('123') if num1>num2 else print('456') --456
max=num1 if num1>num2 else num2
print(max)--354
#比较三个值的大小
a=123
b=345
c=789
max=a if a>b else b
max=max if max>c else c
max=a if (a>b and a>c) else b if b>c else c
print(max)
6.运算符优先级

①查看Python的文档

②不用刻意去记,开发中如果遇到优先级不清楚的情况给想要先算的表达式加小括号

三、流程控制语句

1.input()函数

①input函数用来获取用户的输入,input函数调用后,程序会立即暂停等待用户的输入,用户输入完程序才会继续向下执行;用户输入的内容会作为该函数的返回值返回

②input的返回值永远是一个字符串

③input(字符串)函数中可以传一个字符串参数,该字符串可以作为提示信息显示

④input()也可以暂停程序的运行

username=input('请输入一个用户名:')
if username=='admin':print('欢迎管理员使用')
else:print('您不是管理员')
#inputde的返回值是字符串类型
age=int(input('请输你的年龄:'))
if age >=18:print('你已经成年了')

2.条件判断语句

1.条件判断语句–if语句

①语法

if 条件表达式 :
    代码块

②执行流程:先判断条件表达式,如果为True则执行if后面的语句,如果为False,则不执行

③默认情况下if语句只会控制紧随其后的一条语句,如果希望if控制多条语句则可以在if后跟着一个代码块

num=20
if num>10 : print('num大于10')

④代码块:代码块中保存者一组代码,同一个代码块中的代码要么都执行要么都补执行,代码块是为代码分组的机制。

​ if中如果要编写代码块,语句就不能紧随在:后边写了,要在:下一行

​ 代码块以缩进开始到代码恢复到缩进前的缩进级别时结束

​ 缩进可以用空格(四个)表示,也可以用Tab表示,建议用空格

⑤可以使用逻辑运算符连接多个条件

if True:
 print('123')
 print('456')
print('不是代码块了')

if num>10 and num<20 or i<=2:
    print('123')
等价为
if 10<num<20 or i<2:
    print('123')
2.条件判断语句–if else语句

①语法

if 条件表达式:
	代码块
else :
    代码块
#案例
age=int(input('请输你的年龄:'))
if age >=18:print('你已经成年了')
else:print('你还未成年')
3.条件判断语句–if elif-else语句

①语法

if 条件表达式1:
	代码块
elif 条件表达式2:
	代码块
elif 条件表达式3:
	代码块
else:
	代码块
#案例
num = 10
if num > 20:
    print('123')
elif num > 15:
    print('456')
elif num > 10:
    print('789')
elif num > 5:
    print('000')
else:
    print(111)

②要避免出现死代码,一般加上显示条件如第一个条件num>20 and num<30,第二个条件为num>30 and num<60,如果第一个判读那不加num<30,则第二个条件判断永远不会执行,因为只要大于20肯定大于30,满足第一个就结束了,第二个永远执行不到

4.练习
练习1:
    编写一个程序,获取一个用户输入的整数。然后通过程序显示这个数是奇数还是偶数。
number = int(input('请输入一个整数:'))
if number == 0:
    print(f'{number}既不是奇数也不是偶数')
elif number % 2 == 0:
    print(f'{number}为偶数')
else:
    print(f'{number}为奇数')

练习2:
    编写一个程序,检查任意一个年份是否是闰年。
    如果一个年份可以被4整除不能被100整除,或者可以被400整除,这个年份就是闰年
year = int(input('请输入一个年份:'))
if year % 4 == 0 and year % 100 != 0 or year % 400 == 0:
    print(f'{year}是闰年')
else:
    print(f'{year}不是闰年')

练习3:
    我家的狗5岁了,5岁的狗相当于多大年龄的人呢?
    其实非常简单,狗的前两年每一年相当于人类的10.5岁,然后每增加一年就增加四岁。
    那么5岁的狗相等于人类的年龄就应该是10.5+10.5+4+4+4 = 33岁 

    编写一个程序,获取用户输入的狗的年龄,然后通过程序显示其相当于人类的年龄。
    如果用户输入负数,请显示一个提示信息
age = int(input('请输入狗的年龄:'))
if age < 2 and age > 0:
    age = age * 10.5
    print(f'狗的年龄为:{age}')
elif age > 2:
    age = 2 * 10.5 + (age - 2) * 4
    print(age)
else:
    print('请输入一个正确的年龄')
    
练习4:
    从键盘输入小明的期末成绩:
        当成绩为100时,'奖励一辆BMW'
        当成绩为[80-99]时,'奖励一台iphone'
        当成绩为[60-79]时,'奖励一本参考书'
        其他时,什么奖励也没有
score = int(input('请输入小明的期末成绩:'))
if score == 100:
    print('奖励一辆BMW')
elif 80 <= score <= 99:
    print('奖励一台iphone')
elif 60 <= score <= 79:
    print('奖励一本参考书')
else:
    print('莫得奖励')
    
练习5:
    大家都知道,男大当婚,女大当嫁。那么女方家长要嫁女儿,当然要提出一定的条件:
        高:180cm以上;:1000万以上;:500以上;
        如果这三个条件同时满足,则:'我一定要嫁给他'
        如果三个条件有为真的情况,则:'嫁吧,比上不足,比下有余。'
        如果三个条件都不满足,则:'不嫁!'
heigh = int(input('请输入身高:'))
property = int(input('请输入财产:'))
cool = int(input('请输入颜值:'))
if heigh >= 180 and property >= 1000 and cool >= 500:
    print('我一定要嫁给他')
elif heigh > 180 or property > 1000 or cool > 500:
    print('嫁吧,比上不足,比下有余。')
else:
    print('不嫁')

3.循环语句

1.循环语句–while循环

①语法

while	条件表达式:
    代码块1
else:
    代码块2
满足条件是先执行代码块1,当不满足条件表达式时执行一次代码块2

②while语句的练习

练习1:
    求100以内所有的奇数之和
sum = 0
i = 0
while i <= 100:
    if i % 2 != 0
    	sum = sum + i
    	i += 2
print(f'100以内奇数之和为{sum}')

练习2:
    求100以内所有7的倍数之和,以及个数
sum = 0
i = 1
count = 0
while i <= 100:
    if i % 7 == 0:
        count = count + 1
        sum = sum + i
        i = i + 1
    else:
        i = i + 1
print(f'100以内7的倍数之和为{sum},个数为{count}')

练习3: 
    水仙花数是指一个 n 位数(n≥3 ),它的每个位上的数字的 n 次幂之和等于它本身(例如:1**3 + 5**3 + 3**3 = 153)。
    求1000以内所有的水仙花数
i = 100
while i < 1000:
    a = i % 10
    b = i // 10 % 10
    c = i // 100
    if a ** 3 + b ** 3 + c ** 3 == i:
        print(i)
    i+=1
2.循环语句–for循环

①语法

for 变量 in 序列:
	代码块

②for循环的代码块会执行多次,序列中有几个元素就会执行几次,每执行一次就会将序列中的一个元素赋值给变量,因此看可以通过变量来获取列表中的元素

list = list('asjxfksdufkashjfascs213')
for s in list:
    print(s)

③通过range()可以创建一个执行指定次数的for循环

④for循环除了创建方式以外其余都和while一样,包括else、break、continue都可以在for循环中使用

#利用range函数循环20次。else在跳出循环时执行
for i in range(20):
    print(i)
else:print(123)
3.break和continue

①break可以用来立即退出循环语句,包括对应while的else

②continue可以用来跳过当此循环,不会跳过对应while的else

④break和else都只对离他最近的循环起作用

⑤pass:用来在if或while中占位的,没有实际意义

#break
i = 2
while i < 10000:
    j = 2

    while j < i:
        if i % j == 0:
            break
        j += 1
    if j >= i:
        print(i)
    i += 1
#continue
i = 1
while i < 10:
    if i == 2:
        i += 1
        continue
    print(i)
    i += 1
else:print('循环结束')
#pass
if False:
    pass--如果改为True则什么也不输出,只是用来占位的
else:print('123')--123
4.循环的嵌套

①循环嵌套:外层循环每执行一次,内层循环执行一圈

②图形:外部循环控制高度,内部循环控制宽度

#1.打印十行十列的*
i = 10
j = 10
r = 0
while r < 10:
    c = 0
    while c < 10:
        print('* ', end='')
        c = c + 1
    print()
    r = r + 1
    
#2.打印三角形
i = 10
j = 10
r = 0
while r < 10:
    c = 0
    while c <= r:
        print('* ', end='')
        c = c + 1
    print()
    r = r + 1
    
#3.打印下三角
i = 10
j = 10
r = 0
while r < 10:
    c = i
    while c > r:
        print('* ', end='')
        c = c - 1
    print()
    r = r + 1
    
#4.打印99乘法表
    1*1=1
    1*2=2 2*2=4
    1*3=3 2*3=6 3*3=9
    ...                 9*9=81
i = 1
j = 1
while i <= 9:
    j = 1
    while j <= i:
        print(f'{j}*{i}={i * j} ',end='')
        j += 1
    print()
    i += 1
    
#5.求100以内所有的质数
i = 2
while i < 100:
    j = 2
    flag = 1  # 作为退出循环的标志,当作break来用,其中0与1可改为True和False
    while j < i and flag == 1:
        if i % j == 0:
            flag = 0  # 是0更改标志位,使循环在下一次退出
        j += 1
        k = 1
    if j >= i:
        print(i)
    i += 1
5.time模块

①可以使用from time import *来引入time模块

②time():用来获取当前时间,返回的单位是秒

#质数
from time import *
startTime=time()
i = 2
while i < 100:
    j = 2
    flag = 1  # 作为退出循环的标志,当作break来用,其中0与1可改为True和False
    while j < i and flag == 1:
        if i % j == 0:
            flag = 0  # 是0更改标志位,使循环在下一次退出
        j += 1
        k = 1
    if j >= i:
        print(i)
    i += 1
endTime=time()
print(endTime-startTime)
#质数优化2利用标志位与break--0.0029916763305664062 秒
from time import *
startTime = time()
i = 2
while i < 1000:
    j = 2
    flag = True
    while j <= i ** 0.5:
        if i % j == 0:
            flag = False
            break
        j += 1
    if flag:
        print(i)
    i += 1
endTime = time()
print(endTime - startTime, '秒')
#质数优化3只利用break--0.001991748809814453 秒
from time import *
startTime = time()
i = 2
while i < 1000:
    j = 2
    while j <= i ** 0.5:
        if i % j == 0:
            break
        j += 1
    if j > i ** 0.5:
        print(i)
    i += 1
endTime = time()
print(endTime - startTime, '秒')
6.range()函数

①range(起始索引,结束索引,步长):生成一个元素为起始位置到结束位置的自然数序列,有闭右开

​ 起始位置:可以省,略默认为0

​ 结束索引:

​ 步长:可以省略,默认为1

r = range(10, -2, -1)  # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1]
print(r)  # range(10, -2, -1)输出函数信息
print(list(r))  # 想要输出集合[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1],要用list将他转化成集合

②通常与for来配合使用表示循环次数,循环次数:后-前

#循环10次
for s in range(0,10):
    print(s)
7.唐僧大战白骨精
1、身份选择
    ① 显示提示信息
        欢迎光临 xxx 游戏!
        请选择你的身份:
            1.xxx
            2.xxx
        请选择:x
    ② 根据用户选择来分配身份(显示不同的提示消息)  
        1.---
        2.---
        3.---  

2、游戏进行
    ① 显示玩家的基本信息(攻击力 生命值)
    ② 显示玩家可以进行的操作:
        1、练级
            - 提升玩家的攻击力和生命值
        2、打BOSS
            - 玩家对BOSS进行攻击,玩家要攻击BOSS,BOSS对玩家进行反击
            - 计算BOSS是否被玩家消灭,玩家是否被BOSS消灭
            - 游戏结束
        3、逃跑
            - 退出游戏,显示提示信息,游戏结束!
# 打印欢迎信息
boss = '白骨精'
player = '唐僧'
print('-' * 20, f'欢迎光临<<{player}大战{boss}>>', '-' * 20)
# 游戏身份选择
print('请选择您的身份:')
print(f'''\t1.{player}
\t2.{boss}''')
player_choice = input('请选择[1~2]:')
print('-' * 67)
if player_choice == '1':
    print(f'你已经选择了{player},您将以唐僧的身份进行游戏')
elif player_choice == '2':
    print(f'你已经选择了2,您竟然选怎了{boss},系统将自动为您分配{player}的身份')
else:
    print(f'您的选择有误,系统将自动为您分配{player}的身份')
# 显示玩家的攻击力与生命值
player_atk = 2
player_hp = 2
boss_atk = 10
boss_hp = 10
print('-' * 67)
print(f'您的生命值是{player_hp},您的攻击力是{player_atk}')
# 进入游戏
while True:
    print('您可以进行的操作:')
    print('''\t1.练级
    2.打BOSS
    3.逃跑''')
    game_choice = input('请选择您要进行的操作[1~3]:')
    print('-' * 67)
    if game_choice == '1':
        player_hp += 2
        player_atk += 2
        print(f'恭喜您升级了,您的生命值是{player_hp},您的攻击力是{player_atk}')
    elif game_choice == '2':
        # flag判断是否游戏结束
        flag = False
        # 怪物或者是玩家生命值大于0就持续战斗
        while boss_hp > 0 or player_hp > 0:
            boss_hp = boss_hp - player_atk
            print('你攻击了boss')
            if boss_hp <= 0:
                print('boss死亡,您已胜利')
                flag = True
                break
            player_hp = player_hp - boss_atk
            print('你攻击了boss')
            # 玩家死亡会降低攻击力与生命值
            if player_hp <= 0:
                print('你已经死亡,再练练吧')
                player_hp = player_hp - 4
                player_atk = player_atk - 4
                break
        if flag: break
    else:
        print('逃跑成功,您已经退出了游戏')
        break

四、序列

1.序列

1.序列的分类

①序列是Python中最基本的一种数据结构

②数据结构指计算机中数据存储的方式

③序列用于保存一组有序的数据,所有的数据在序列当中都有一个唯一的位置(索引),并且序列中的数据会按照添加的顺序来分配索引

④序列的分类:可变序列(序列中的元素可以改变):列表(list)

​ 不可变序列(序列中的元素不能改变):字符串(str)、元组(tuple)

注:刚刚我们所讲所有操作都是序列的通用操作(切片、通用操作)

2.可变对象
- 每个对象中都保存了三个数据:
    id(标识)
    type(类型)
    value(值)--可变对象可变的是值
- 列表就是一个可变对象
    a = [1,2,3]
- a[0] = 10 (改对象)
    - 这个操作是在通过变量去修改对象的值
    - 这种操作不会改变变量所指向的对象    
    - 当我们去修改对象时,如果有其他变量也指向了该对象,则修改也会在其他的变量中体现
    - id不变,值变了
# ab都变成了10,2,3
a = [1, 2, 3]
b = a
b[0] = 10

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

==!=:比较对象的值是否相等
isis not:比较对象的id是否相等(比较两个对现象是否是一个对象)
a=[1,2,3]
b=[1,2,3]
值相等,id不一样
a=b后全是true

2.列表(list)

1.列表的说明

①列表是Python中的一个对象

②对象(object)就是内存中专门用来存储数据的一块区域

③之前我们学习的对象,像数值,它只能保存一个单一的数据,列表中可以保存多个有序的数据

④列表是用来存储对象的对象

2.列表的创建和使用

①列表的创建:列表中存储的数据称为元素,一个列表中可以有多个元素,可以在创建列表时来指定列表中的元素,列表中的对象都按照插入的顺序存储到列表中,当向列表中添加多个元素时用逗号隔开

②列表中可以保存任意的对象,类型可以混合存储,但是一般都存储同一类型

# 创建了一个空列表
list = []
# 创建一个只含有一个元素10的列表
list = [10]
# 当向列表中添加多个元素时用逗号隔开
list1 = [10, 20, 30]
# 列表中可以保存任意的对象,类型可以混合存储,但是一般都存储同一类型
list2 = [1, '1', True, None, 0.1, [1, 2, 3], print]

③列表的输出:可以全部输出,也可以通过索引获取列表中的元素,列表中的第一个元素索引为0,语法list[索引]

print(list2)
print(list2[0])
# 输出列表以及他的类型
print(list, type(list))

④获取列表中元素的个数:len(),通过该函数可以获取列表长度,获取到的长度是列表的最大索引+1

print(len(list2))
3.切片

①切片指从现有列表中获取一个子列表

②切片语法:如果步长为正则索引开始索引必须小于结束索引,如果步长为负则开始索引必须大于结束索引;列表的索引可以是正数也可以是负数,正数表示从前往后获取,为正时第一个元素下标为0;负数时最后一个元素的索引为-1

列表[开始索引:结束索引:步长],左闭右开(无论正负都是开始闭区间结束开区间)。返回一个新的列表,不会影响原来的列表。

②步长表示每次获取元素的间隔,每次跳步长个元素(包含当前元素数步长个元素,下一个元素即为要获取的元素),默认值为1,步长可以为负数,表示从后往前获取元素

③可以省略开始以及结束索引,表示从开头到指定位置或从指定位置到结束;如果开始位置与结束位置都省略则相当于建立了一个列表的副本

stus = ['孙悟空', '猪八戒', '沙和尚', '白骨精', '蜘蛛精']
print(stus[-1])  # 蜘蛛精
print(stus[-2])  # 白骨精
print(stus[-3])  # 沙和尚
stus1 = stus[0:2]  # ['孙悟空', '猪八戒']
print(stus[0:5:3])  # ['孙悟空', '白骨精']
print(stus[:2])  # ['孙悟空', '猪八戒']
print(stus[1:])  # ['猪八戒', '沙和尚','白骨精','蜘蛛精']
print(stus[:])  # ['孙悟空', '猪八戒', '沙和尚', '白骨精', '蜘蛛精']
print(stus[::2]) # ['孙悟空', '沙和尚', '蜘蛛精']
# 可以输入负值,但是要保证索引从小到大
stus = stus[-2:-1]  # ['白骨精']
#步长为负数则从后往前取,此时索引必须是从后往前写的(开始索引所指向的元素的位置必须在结束索引所指元素的右边)
print(stus[4:0:-2]) # ['蜘蛛精', '沙和尚']
print(stus[-1:-4:-1]) # ['蜘蛛精', '白骨精', '沙和尚']
print(stus[-1:-3]) # []不对,步数为正必须开始索引所指向的元素位置必须再结束索引的左边
4.通用操作

①+:可以将两个列表拼接成一个列表

list = [1, 2, 3] + [4, 5, 6]
print(list)  # [1, 2, 3, 4, 5, 6]

②*:可以将列表重复指定次数

list = [1, 2, 3] * 10
print(list)  # [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

③in:用来检查指定元素是否存在于列表中,在返回True,不在返回False

print(1 in list) # True

④not in:检查指定元素是否不在列表中,不在返回True,在返回False

⑤len():获取列表长度

⑥min():获取列表中的最小值,字符串比unicode码

list = [1, 2, 3] + [4, 5, 6]
print(max(list), min(list)) # 6 1

⑦max():获取列表中的最大值,字符串比unicode码

下面是两个方法,方法和函数基本上是一样的,但是方法必须通过 对象.方法() 的形式调用,函数直接调用如: 方法()

⑧序列.index(查找的元素,起始位置,结束位置):获取指定元素在类表中第一次出现的位置,如果列表中没有该元素会报错。可以指定开始与结束位置(左闭右开),便会从该范围去查找

list = [1, 2, 3] + [4, 5, 6, 1]
print(list.index(1))  # 0
print(list.index(1,2)) # 6
print(list.index(1,2,7)) # 6

⑨序列.count(元素):统计某元素在列表中出现的次数

list = [1, 2, 3] + [4, 5, 6, 1]
print(list.count(1)) # 2
5.通过索引和切片修改可变列表中的元素

①直接通过索引来修改元素

list = ['孙悟空', '猪八戒', '沙和尚', '唐僧']
# 通过索引来修改元素
list[0] = 'sunwukong'
# del 列表[index]--通过索引来删除元素
del list[2]
print(list)

②通过切片来修改列表:在给切片进行赋值时必须使用序列

​ 可以找到序列:切片元组个数大于等于选中的切片个数–替换、添加

​ 切片元组个数少于选中的切片个数–没写值的位置会被删除

​ 找不到序列:在切片开始位置插入该元素,可以是多个元素

​ 切片结束位置为0则在开始位置插入该元素

③切片如果设置了步长,则添加的元素需要和切片中的元素个数一样

④删除:del+列表选中元素

​ 直接给要删除元素赋空值:列表[要删除元素索引]=[]

list = ['孙悟空', '猪八戒', '沙和尚', '唐僧']
 # 切片值个数等于替换元组个数--替换 ['sunwukong', 'zhubajie', '沙和尚', '唐僧']
list[0:2] = ['sunwukong', 'zhubajie'] 
# ['sunwukong', 'zhubajie', 'honghaier', '沙和尚', '唐僧']
list[0:2] = ['sunwukong', 'zhubajie', 'honghaier']  
# 切片元组个数少于选中的切片个数--没写值的位置会被删除,['sunwukong', '沙和尚', '唐僧']
list[0:2] = ['sunwukong']
#如果在该列表中找不到对应索引的切片,则索引开始处插入新的元素。如使用0到0,则说明匹配不到,因此会向0的位置插入新元素。如一共有5个元素(最大下标为4),5,n(n为任意数)则会向下标5处查询这个元素
list[0:0] = ['123', '456']  # ['123', '456', '孙悟空', '猪八戒', '沙和尚', '唐僧']
list[4:-1] = ['213', '456']  # ['孙悟空', '猪八戒', '沙和尚', '唐僧', '213', '456']
# 切片结束位置为0则在开始位置插入该元素
list[2:0] = ['123']  # ['孙悟空', '猪八戒', '123', '沙和尚', '唐僧']
#切片如果设置了步长,则添加的元素需要和切片中的元素个数一样
#['123', '猪八戒', '456', '唐僧']该切片从开始到结束步长为2,所选取的元素为两个(孙和沙)
list[::2]=['123','456']
# ['孙悟空', '456', '沙和尚', '123']
list[-1::-2] = ['123', '456']  
#删除
del list[0:2]  # ['沙和尚', '唐僧']
del list[::2]  # ['猪八戒', '唐僧']
list[1:3] = []  # ['孙悟空', '唐僧']

⑤以上操作仅可变列表使用,如果不可变列表想要用以上操作进行修改,可以用list()函数将其转换为可变列表

#['1', '2', 1, '4', '5', '6']
s='123456'
s=list(s)
s[2]=1
print(s)
6.列表的方法

①增加:append、insert、extend

②删除:clear、pop、remove

③其他:reverse、sort

list = ['1', '2', '3']
# 1.list.append():向列表的最后添加一个元素
list.append('4') # ['1', '2', '3', '4']
# 2.list.insert(要插入的位置,要插入的元素):向指定位置插入指定元素
list.insert(1, '1.5')# ['1', '1.5', '2', '3']
# 3.list.extend(序列):将序列添加到当前序列中,下面四种写法等价,输出结果都为['1', '2', '3', '4', '5', '6']
list.extend(['4', '5', '6'])
list += ['4', '5', '6']
new_list = ['4', '5', '6']
list.extend(new_list)
list+=new_list
# 4.list.clear():清空序列
list.clear() # []
# 5.list.pop(index):根据索引删除并返回指定元素,如果不写index则删除最后一个元素
deleme = list.pop(2) # 3
# 6.list.remove(元素值):根据指定元素值删除元素,如果有多个相同值得元素则删除第一个
# 7.list.reverse():反转列表
list.reverse() # ['3', '2', '1']
# 8.list.sort():对列表中的元素进行排序,默认升序排列
#   list.sort(reverse=True):对列表中的元素进行排序,默认降序排列
mylist = list('asjxfksdufkashjfascs213')
mylist.sort() # ['1', '2', '3', 'a', 'a', 'a', 'c', 'd', 'f', 'f', 'f', 'h', 'j', 'j', 'k', 'k', 's', 's', 's', 's', 's', 'u', 'x']
7.列表的遍历
#方式一:while循环
list = list('asjxfksdufkashjfascs213')
i = 1
while i < len(list):
    print(list[i])
    i += 1
#方式二:for循环
list = list('asjxfksdufkashjfascs213')
for s in list:
    print(s)
8. 练习
EMS(Employee Manager System 员工管理系统)
- 做命令行版本的员工管理系统
- 功能:
    四个:
        1.查询
            - 显示当前系统当中的所有员工
        2.添加
            - 将员工添加到当前系统中
        3.删除
            - 将员工从系统当中删除
        4.退出
            - 退出系统
- 员工信息要保存到哪里? 列表,在系统中应该有一个列表,专门用来保存所有员工信息的
# 显示欢迎信息
print('-' * 20, '欢迎来到员工管理系统', '-' * 20)
emp = ['孙悟空\t1000\t它\t\t花果山', '猪八戒\t5000\t它\t\t高老庄']
# 显示用户选项
while True:
    print('-' * 24, '操作选择界面', '-' * 23)
    print('请选择您要进行的操作:')
    print('\t1.查询员工')
    print('\t2.添加员工')
    print('\t3.删除员工')
    print('\t4.退出系统')
    user_choice = input('请选择您要进行的操作[1~4]:')
    # 根据用户的选择进行相关操作
    if user_choice == '1':
        print('序号\t姓名\t年龄\t性别\t住址')
        n = 1#每次查询的时候来分配序号,有几条数据分配几个序号,这样在添加或删除时不用对序号进行另外的操作
        for emps in emp:
            print(f'{n}\t\t{emps}')
            n += 1
    elif user_choice == '2':
        username = input('请输入您要添加的员工姓名:')
        userage = input('请输入您要添加的员工年龄:')
        usergender = input('请输入您要添加的员工性别:')
        useraddress = input('请输入您要添加的员工地址:')
        flag = input(f'{username}\t{userage}\t{usergender}\t{useraddress}将要被添加到列表中,按Y/N')
        if flag == 'Y' or flag == 'y':
            emp.append(f'{username}\t{userage}\t{usergender}\t\t{useraddress}')
            print('添加成功')
        else:
            print('添加失败')
            continue
    elif user_choice == '3':
        index = int(input('请输入要删除员工的序号:'))
        if 0 < index <= len(emp):
            list_index = index - 1
            del_input = input('您确认要删除该员工吗?[Y/N]')
            if del_input == 'Y' or del_input == 'y':
                emp.pop(list_index)
                print('删除成功')

            else:
                print('您已选择不删除')
                continue
        else:
            print('用户输入有误!')
    elif user_choice == '4':
        print('退出成功')
        break
    else:
        print('您输入的数字范围不正确,请重新输入')
        continue

3.元组(mytulp)

1.元组的说明

①元组是一个不可变序列,操作方式与列表一致,通常把元组当成一个不可变的列表

②元组不可使用列表的那些使列表改变的方法,其他的in、not in等都可以使用

③数据不会改变时用元组,其余情况用列表

2.元组的创建与使用
# 创建一个空的元组
mytulp = ()
# 输出元组以及查看其类型
print(mytulp, type(mytulp))
# 创建有5个元素的元组
mytulp = (1, 2, 3, 4, 5)
# 输出其中的某一个元素
print(mytulp[3])
# 元组可以使用切片(1, 2)
tulp1 = mytulp(1,2)
print(tulp1)
# 当元组非空时,小括号可以省略
mytulp = 1, 2, 3, 4, 5
print(mytulp)
# 如果元组不是空元组时至少要有一个逗号,否侧系统会认为他是整型等其他类型
mytulp = 1,
print(mytulp)  # (1,)
mytulp=1
print(type(mytulp))--int# 元组的解包:将元组中的每一个元素分别赋给一个变量,变量个数要和元组中的元素个数一致;也可以在变量前加一个*,则该变量则获取该元组中剩余的所有元素,返回时是一个列表;不可以出现多个带星号的变量
mytulp = 1, 2, 3, 4, 5
a, b, c, d, e = mytulp
print(a, b, c, d, e)  # abcde分别为12345
a, b, *c = mytulp
print(a, b, c)  # 1 2 [3, 4, 5]
a, *b, c = mytulp
print(a, b, c)  # 1 [2, 3, 4] 5
*a, b, c = mytulp
print(a, b, c)  # [1, 2, 3] 4 5
# 字符串和列表也可以解包
a, b, *c = [1, 2, 3, 4]
a, b, *c = '1234'
# 元组的解包常用于交换两个变量的值
a = 100
b = 300
a, b = b, a  # 后面的b,a相当于元组
print(a, b)  # 300 100

4.字典(dict)

1.字典的说明

①字典属于一种新的数据结构,称为映射(mapping)

②字典的作用和列表类似,都是用来存储对象的容器

③列表存储数据的性能很好,但是查询数据的性能的很差

④在字典中每一个元素都有一个唯一的名字,通过这个唯一的名字可以快速的查找到指定的元素,这个唯一的名字,我们称其为键(key),通过key可以快速的查询value;这个对象,我们称其为值(value),所以字典,我们也称为叫做键值对(key-value)结构,每个字典中都可以有多个键值对,而每一个键值对我们称其为一项(item)

2.字典的创建与使用
# 字典的创建
方式一:直接创建
d = {}
方式二:字典的键可以是任意的不可变对象(int str bool none tuple),值可以是任意的类型或对象,一般都用字符串,语法:变量名={'k':'v','k':'v','k':'v'...}
d = {'name': '孙悟空', 'age': '18', 'gender': '男'}
方式三:使用函数dict(key=value,key=value,...)来创建字典,使用该种方式创建的字典key都是字符串类型
d = dict(name='孙悟空', age=18, gender='男')
方式四:也可以将一个包含双值子序列的序列转化为字典
双值序列:序列中只包含两个值,如[1,2],('a',3),'ab'
子序列:如果序列中的元素也是序列,则这个元素叫做子序列如[(1,2),(3,4)]
d = dict([('name', '孙悟饭'), (['age', 18])])
print(d, type(d))

# 字典中的键是不可以重复的,如果重复了后面的键会替换掉前面重复的键
d = {'name': '孙悟空',
     'age': '18',
     'gender': '男',
     'name': 'sunwukong'}
print(d)  # {'name': 'sunwukong', 'age': '18', 'gender': '男'}

# 获取字典中的值
方式一:根据键来获取值,语法:变量['key'],key一般要加引号,因为键会自动带上引号
n = 'age'
d['age']  # 加引号,定义时如果key没加引号系统也会自动加上引号,因此这里要加引号
d[n]  # 利用变量不用加引号
print(d['name'], d['age'], d['gender'])
如果使用了字典中不存在的键会报错
print(d['hello'])
方式二:字典.get('key','默认值')来获取字典中的值,key不存在时不会报错会返回None,如果设置了默认值且key不存在则返回默认值
print(d.get('name'))
print(d.get('hello')) # None
print(d.get('hello','hello')) # hello

# 修改字典
方式一:d[key]=value,key存在则修改,key不存在则添加
d['name'] = '卡卡罗特'
d['addredss'] = '花果山' # 添加
方式二:字典.setdefault(key,'defalut value')如果key存在,则返回key值,如果key不存在,则向字典中添加key值并设置默认值value并返回
flag = d.setdefault('name', '猪八戒')
print(flag)

# 添加:d1.update(d2):将字典d2添加到字典d1中,如果d1、d2中有相同key,则用d2的值替换
d2 = {'a': 1, 'b': 2, 'name': 3}
d.update(d2)
print(d) # {'name': 3, 'age': 18, 'addredss': '花果山', 'a': 1, 'b': 2}

# len(变量):获取字典中键值对的个数
count = len(d)
print(count)

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

#删除
方式一:使用del 字典[key]来删除字典中的key-value,删除不存在的key则报错
del d['c']
方式二:popitem():随机删除一个字典中的键值对,一般都会删除最后一个键值对,并返回一个含有两个元素的元组,第一个元素是key第二个元素是value,删除空字典时会报错
result = d.popitem()
方式三:字典.pop(key,default):删除指定key,并返回对应的value,删除不存在的key如果设置了默认值则返回默认值,没有设置则抛出异常
print(d.pop('s', '123'))  # 123
方式四:变量.claer():清空字典
d.clear()

# 字典的复制:常用浅复制
字典.copy():用于对字典进行浅复制,复制后的对象与原对象是独立的
d1 = d  # d1指向d的id,改变一个字典中的元素另一个也会改变
d1 = d.copy()  # d与d1打的地址不一样,改变一个字典中的元素另一个不会改变
# 浅复制:只会简单复制对象内部的值,如果值也是一个可变对象,则不会复制(改变其值,d1也会改变)--浅复制只有第一层有效
d = {'a': {'name': '孙悟空', 'age': 18}, 'gender': '男'}
d1 = d.copy()
d1['a']['age'] = 17  # 对字典d1下的a键中的name键的值进行修改
print(d1, d)  # {'a': {'name': '孙悟空', 'age': 17}, 'gender': '男'} {'a': {'name': '孙悟空', 'age': 17}, 'gender': '男'}

# 字典的遍历:key()、values()、items()
方式一:字典.keys()方法返回一个序列,该序列中保存有字典的所有键
for k in d.keys():
    print(k, d[k])
方式二:字典.values()方法返回一个序列,序列中保存着该字典的所有值,缺点看不到key
for v in d.values():
    print(v)
方式三:itesm()方法返回字典中的所有项的序列,序列中包含双值子序列,双值分别是key和value
for k, v in d.items():
    print(f'{k}={v}')

5.集合(set)

1.集合的说明

①集合中只能存储不可变对象

②集合中存储的对象是无序的(不是按照元素的插入顺序保存)

③集合中不能出现重复的元素

2.集合的创建与使用
# 创建集合:集合没有办法通过索引来操作,除非用list()把他转化为集合
方式一:直接使用{}
s={1,2,1,3,4}
print(s,type(s)) # 只输出一个1
s={[1,2,3],[4,5,6]} # 集合中不可以存储可变对象
方式二:使用set()函数来创建集合
s=set() # 创建了一个空集合
s=set([1,2,3,1,2]) # 通过set()可以将序列和字典转化为集合
s=set('hello') # 字符串时特殊的序列
s=set({'a':1,'b':2,'c':3}) # 使用set()将字典转化为集合时,只会包含字典中的键

# 可以使用in或not in来检查集合中的元素
print('a' in s)--True

# 可以使用len()来查看集合中元素的数量,不算重复的元素
s.len()

# add()向集合中添加元素
s.add(10)
s.add(30)
s.add(30) # 重复元素不会重复添加

# update()将一个集合中的元素添加到当前集合中,可以传集合、序列、字典(key)
s1=set('hello')
s.update(s2)

# 删除
方式一:pop()随机删除一个集合中的元素
s.pop()
方式二:remove()删除集合中的指定元素
s.remove(100)
方式三:clear()清空集合
s.clear()

# copy()浅复制
s1=s.copy()
3.结合的运算
# 创建集合
s1 = {1, 2, 3, 4, 5}
s2 = {3, 4, 5, 6, 7}
# &交集运算:不影响原来的集合,返回s1与s2的交集
result = s1 & s2  # {3, 4, 5}s1与s2中都有的元素
# |并集运算:不影响原来的集合,返回s1与s2的并集
result = s1 | s2  # {1, 2, 3, 4, 5, 6, 7}s1和s2元素合起来
# -差集:不影响原来的集合,返回s1与s2的差集
result = s1 - s2  # {1, 2}只在s1中有s2中没有的元素
# ^异或集:不影响原来的集合,返回s1与s2的异或集
result = s1 ^ s2  # {1, 2, 6, 7}取只在一个集合中有的元素
# <=检查一个集合是否是另一个集合的子集。如果一个集合中的元素全部出现在另一个集合中,则这个集合就是另一个集合的子集
a = {1, 2, 3}
b = {1, 2, 3, 4, 5}
result = a <= b  # True,如果a集合元素全部都在b集合中出现,则a是b的子集,b是a的超集
result = {1, 2, 3} <= {1, 2, 3}  # True
# <检查一个集合是否是另一个集合的真子集。b中含有子集a中的所有元素,并且还有a中没有的元素,则b就是a的真超集,a是b的真子集
# 子集可以一样,真子集必须要少
result = {1, 2, 3} < {1, 2, 3}  # False
result = {1, 2, 3, 4} < {1, 2, 3, 4, 5}  # True
# >=检查一个集合是否是另一个集合的超集。
# >检查一个集合是否是另一个集合的真超集。

五、函数

1.函数简介(function)

①函数也是一个对象

②对象是内存中专门用来存储数据的一块区域

③函数可以用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次的调用

2.函数的创建与调用

①函数名必须要符号标识符的规范(可以包含字母、数字、下划线、但是不能以数字开头)

②函数中保存的代码不会立即执行,需要调用函数代码才会执行

③定义函数一般都是要实现某种功能的

#创建函数:
def 函数名([形参1,形参2,...,形参n]) :
    代码块

def fun():
    print('今天天气真不错')
    print('hello')
    print('这是一个函数')
    
# 显示函数信息
print(fun)  # <function fun at 0x0000011F05581E18>
print(type(fun))  # <class 'function'>

# 函数的调用
fun()

3.函数的参数

①在定义函数时,可以在函数名后的()中定义数量不等的形参,多个形参之间使用逗号隔开

②形参(形式参数),定义形参就相当于在函数内部声明了变量,但是并不赋值。

③可以直接给形参设置一个默认值,如果用户传递了实参则没有任何作用,如果没有传递实参则变量值为默认值

def fun(a,b,c=10):
	print(c)
fun(1,2) # a=10

④实参(实际参数):如果函数定义时,指定了形参,那么在调用函数时也必须传递实参,实参将会赋值给对应的形参,简单来说,有几个形参就得传几个实参,如果形参设置了默认值则可以不写该参数的实参

⑤实参的两种传递方式:位置参数、关键字参数

1.位置参数:位置参数就是将对应位置的实参赋值给对应位置的形参
2.关键字参数:可以不按照形参定义的顺序取传递,直接根据参数名取传递参数
def fun(a,b,c)
	代码块
fun(b=1,c=2,a=3) # 会按照变量来传递
3.位置参数和关键字参数可以混合使用,如end就是一个关键字参数。混合使用时必须将位置参数写在前面
print('hello',end='')
fun(1,b=2,c=3) # 混合使用时必须将位置参数写在前面

⑥形参与实参可以同名

⑦函数在调用时解析器不会检查实参的类型,实参可以传递任意类型的对象,甚至可以是序列以及函数

def fun(a, b, c):
    print(f'a={a},b={b},c={c}')
def fun1(a):
    print(a)
fun1(fun) #<function fun at 0x000001C876D71E18>fun的id信息

⑧形参如果是值传递(变量)只在其函数内部有效,且可以重新赋值后不会影响函数外的变量

​ 如果是地址传递(对象)如序列,函数内部与外部共用一个地址,内部对序列元素进行更改外部也会改变

​ 如果传递的是对象且不想让其一起改变,则可以在传递参数时传递实参的副本,利用copy()函数或切片(c[:])

#传变量:不改变值
def fun(a):
    a = 2
    print(a)  # 2

a = 1
fun(a)
print(a)  # 1

#传对象:改变值
def fun(a):
    a['name'] = 2
    print(f'a={a}')  # a={'name': 2}

a = {'name': 1}
fun(a)# fun(a.copy())传递实参副本保证不一起改变,切片方式fun(a[:])从头到尾切,总而言之就是定义不同变量进行传递
print(a)  # {'name': 2}

⑨不定长参数(参数的装包):在定义函数时可以在形参前加上一个*,这样这个形参将会获取到所有实参,它会将所有的实参保存到一个元组中。带星号的值可以和其他参数配合使用,可变参数不是必须写在后面,但是在可变参数之后的实参必须用关键字的形式传递;

*只可以接收位置参数,*之后的参数要用关键字的传递方式,*可接受空参。没有合适的参数可以不接受
②在第一个形参前加一个*,则所有的实参都要用关键字形式传递
③**可以接收除位置变量之外的参数,如可以接受带*的关键字参数,和不匹配的关键字参数,不可以接收位置参数
④如果全是位置实参,形参为一个*和一个**会全都给*,因为**不可以接收位置参数
⑤带星的当作实参传递时也要加上星,带星变量为数字格式,不带星变量为元组格式,但其类型都是元组类型,只是输出的格式不同
#在定义函数时可以在形参前加上一个*,这样这个形参将会获取到所有实参,a会接收所有位置的实参,并且将这些实参统一保存到一个元组中(装包)。带*的形参只能接收位置参数。如果没有合适的值a可以被赋予空值
def fun(*a):
    print(a)
fun()#可传递空参
fun(1,2,3,4,5)
#没有合适的参数可以不接受
def fun(b,c,*a):
    print(f'a={a}')
fun(1,2) # b=1,c=2,a={}
#求和
def sum(*a):
    result=0
    for s in a:
        result+=s
    print(result)
sum(1,2,3)
#带星号的值可以和其他参数配合使用,可变参数不是必须写在后面,但是在可变参数之后的实参必须用关键字的形式传递
def fun(a,b,*c,d):
    print()
fun(1,2,3,4,5,d=6)

def fun(*c,d):
    print()
fun(1,2,3,4,5,d=6)
#在第一个形参前加一个*,则所有的实参都要用关键字形式传递
def fun(*, a, b, c):
    print()
fun(a=1, b=2, c=3)
#带**的形参可以接收其他的关键字参数,他会将这些参数统一保存到一个字典中,字典的key就是参数的名字,字典的值就是参数的值。带**的形参只可以有一个且必须在最后
def fun(**a):
    print(f'a={a}') # a={'b': 1, 'c': 2}
fun(b=1, c=2)
#带**的参数不会接收一一对应的关键字参数,会接收不对应的关键字参数以及带一个*所对应的关键字参数
def fun(*d, **a):
    print(f'a={a},d={d}')  # a={'d': 1, 'w': 100},d=()
fun(d=1,w=100) # d和w都给**了,*只可以接收位置参数

def fun1(b, c, *d, **a):
    print(f'a={a},b={b},c={c}')  # 不加10的输出结果a={'w': 100},b=1,c=2
fun1(1, c=2,10,w=100) # 加上10之后会报错
#如果全是位置实参,形参为一个*和一个**会全都给*,因为**不可以接收位置参数
def fun(*a, **b):
    print(f'a={a},b={b}')  # a=(1, 2),b={}
fun(1, 2)

def fun(*a, c, d, **b):
    print(f'a={a},b={b},c={c},d={d}')  # a=(1,),b={'b': 2},c=3,d=2
fun(1, d=2, c=3, b=2)

⑩参数的解包:传递实参时可以在序列类型的实参前添加星号,他会自动将序列中的元素依次作为参数传递。序列中元素的个数和形参个数一致

def fun(a, b, c):
    print(a) # 10
    print(b) # 20
    print(c) # 30
# 创建一个元组
t = (10, 20, 30)
# 传递元素的元素
fun(t[0], t[1], t[2])
# 序列利用*解包
fun(*t)
# 利用两个星号对字典进行解包,key与形参名要一样
dic = {'a': 10, 'b': 20, 'c': 30}
fun(**dic)

练习:

#练习1:定义一个函数,可以用来求任意三个数的乘积
def fun(a, b, c):
    print(f'三个数的乘机为:{a * b * c}')

a = int(input('请输入第一个数字:'))
b = int(input('请输入第二个数字:'))
c = int(input('请输入第三个数字:'))
fun(a, b, c)

#练习2:定义一个函数,可以根据不同的用户名显示不同的欢迎信息
def fun(username):
    print(f'欢迎光临{username}')

a = input('请输入用户名:')
fun(a)

4.函数的返回值

①return后边跟什么值函数就会返回什么,return后可以跟任意对象,甚至可以是一个函数

②如果不写return或这是return后不写东西相当于return None

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

④fun与fun()的区别:fun是一个函数对象,即一个地址,用于函数的赋值或者是让函数作为参数进行传递

​ fun()是调用对象,即获取函数的返回值,用于传递实际参数完成函数的功能

return后边跟什么值函数就会返回什么,return后可以跟任意对象,甚至可以是一个函数
def fun():
    def fun1():
        print('返回一个函数fun1')
    return fun1
# return {'name': '孙悟空'}
# return [1, 2, 3]
# return 100
# return
a = fun()
a()  # 返回一个函数fun1
print(a)  # <function fun.<locals>.fun1 at 0x000001F3C78EEA60>

#fun与fun()的区别
def fun():
    return 10
fun() # 10
fun # 函数fun的id
例一:
def fun():
    def fun1():
        print('返回一个函数fun1')

    return fun1
a = fun()  # fun返回fun1的id,因此a为fun1的地址,相当于fun1
a()  # a()相当于fun1(),因此调用fun1函数输出:返回一个函数fun1
print(a)  # a为fun1的id
例二:
def fun():
    def fun1():
        print('返回一个函数fun1')
    return fun1()#输出:返回一个函数fun1,fun1本身返回值为None
print(fun())#返回一个函数fun1 None

5.文档字符串

1.help()函数

①help函数是python中的内置函数,通过help可以查询python中的一些函数用法

②语法

help(函数对象)--直接传对象不要加括号
如
help(print)
2.文档字符串

①在定义函数时可以在函数内部编写文档字符串,文档字符串就是函数的说明,当我们编写了文档字符串时我们就可以通过help函数来查看

②一般在函数的第一行直接写一个字符串,通常用‘’‘ ’‘’,因为其可以多行书写

③可以在形参后面使用冒号加上类型,最后的->说明返回值的类型,这仅仅只是一个注释方便阅读

def fun(a: int, b: str, c: bool = True) -> str:
    '''
    这是一个文档字符串
    函数的作用。。。
    参数1.。。
    。。。
    :return:
    '''
    print('文档字符串')
    return 10
help(fun)

6.作用域与命名空间

1.作用域

①作用域是指变量生效的区域

②在python中有两种作用域:全局作用域、函数作用域

1.全局作用域
①全局作用域在程序执行时创建,在程序执行结束时销毁
②所有函数以外的区域的区域都是全局作用域
③在全局作用域中定义的变量都属于全局变量,可以在程序的任意位置访问
2.函数作用域
①函数作用域在函数调用时创建,在调用结束时销毁
②函数每调用一次就会产生一个新的函数的作用域
③在函数作用域中定义的变量都是局部变量,只能在函数内部访问
④内部可以获得外部变量,但是变量名重复时变量的值内部说的算。
⑤从内到外一次找变量,如果找到全局变量还没找到该变量时还没找到该变量则报错
⑥在函数内部进行变量的修改默认修改局部变量,如果想要在函数内部对全局变量进行修改,可以在变量前加global,然后再进行修改
例:
a = 10  # 全局变量
def fun():
    a = 20  # 局部变量
    def fun1():
        a = 30  # 局部变量
fun()
def fun1():  # 此fun1与内部的fun1没关系,该fun1为全局变量fun1,上面的为局部变量fun1
    a = 30 #修改局部变量,如果在gloabl a的两句不注释,则不能有该语句,会报错
    #global a
    #a=300 修改全局变量,全局变量a=10变为a=300,不可以写成global a=300,且global 变量之前不可以给对应变量赋值或使用
fun1()  # 本次在不添加global前提下进行调用,调用全局变量fun1,a=30,如果在fun1中不对a赋值30,则a使用全局变量的10
2.命名空间(namespace)

①命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间中

②每一个作用域都会有一个他对应的命名空间

③全局命名空间用于保存全局变量,函数命名空间用于保存函数变量

④命名空间实际上就是一个专门存储变量的字典

⑤locals()函数用于获取当前作用域的命名空间:如果在全局作用域中调用locals()则获取全局命名空间,如果在函数作用域中调用locals()则获取函数命名空间,返回的是一个字典

⑥globals()可以在任意位置获得全局命名空间(一般用于在函数内部获取全局命名空间),也可以用于获取变量值或创建变量

a = 10
def fun():
    a = 300
    scopeInner = locals()
    scopeInner['b'] = 20
    globalscop=globals()
    print(scopeInner)  # 如果该函数没有局部变量返回{}
    print(globalscop['a'])
fun()
print(a)
scope = locals()
print(scope)
print(a)  # 直接打印a和打印scope['变量']是一样的效果
print(scope['a']) # 获取变量
# 向全局字典中添加key-value就相当于在全局中创建了一个变量
scope['c'] = 1000
print(c)

7.递归

1.递归的说明

①递归式函数:自己调用自己,整体思想是将一个大问题分解为一个个的小问题,直到问题无法分解时再去解决问题

②递归式函数的两要素:

​ 基线条件(递归停止条件):问题可以被分解为的最小问题,当满足基线条件时,递归就不能再执行了

​ 递归条件:将问题继续分解的条件

# 求10的阶乘方式一
num = 1
for i in range(1, 11):
    num = num * i
print(num)
# 求10的阶乘方式二
n = 10
for i in range(1, 10):
    n = n * i
print(n)


# 利用函数求阶乘+
def fun(n):
    num = n
    for i in range(1, n):
        num = num * i
    return num
    
# 利用递归求阶乘
def fun(n):
    if n == 1:
        return 1
    else:
        return n * fun(n - 1)
print(fun(10))
2.递归的练习
#创建一个函数power来为任意数字做幂运算
def power(num, n):
    if n == 1:
        return num
    else:
        return num * power(num, n - 1)
print(power(2, 10))
#创建一个函数,来检查字符串是否是回文字符串,是返回True否则返回False
def hui_wen(s):
    if len(s)<2:
        return True
    elif s[0]!=s[-1]:
        return False
    else:
        return hui_wen(s[1:-1])# 传入去掉第一个与最后一个字符,因为后面的是不包含的因此为-1
print(hui_web('123321'))
方法二:
def hui_web(s):
    if len(s) < 2:
        return True
    return s[0] == s[-1] and hui_web(s[1:-1])  # 如果第一位和最后一位不相等则直接返回false,相等递归判断
print(hui_web('123321'))

8.函数式编程

1.高阶函数
- 在Python中,函数是一等对象
- 一等对象一般都会具有如下特点:
    ① 对象是在运行时创建的
    ② 能赋值给变量或作为数据结构中的元素
    ③ 能作为参数传递
    ④ 能作为返回值返回

- 高阶函数
    - 高阶函数至少要符合以下两个特点中的一个
      ① 接收一个或多个函数作为参数
      ② 将函数作为返回值返回 
      当我们把函数作为参数时,实际上是将对应的函数代码传递进了目标函数
定义多个函数,在调用函数时根据需要将这些函数中的某些函数作为实参进行传递,传递到待使用的函数中,并且在待使用的函数中使用
def fun2(i):
    if i % 2 == 0:
        return True
    else:
        return False
def fun3(i):
    if i > 5:
        return True
    else:
        return False
def fun(func, list):
    new_list = []
    for n in list:
        if func(n):
            new_list.append(n)
    return new_list
list = [1, 2, 3, 4, 5, 6]
print(fun(fun2, list))  # 通过传递不同的函数作为参数可以完成不同的功能,这样就是一个高阶函数
2.匿名函数–lambda表达式

①lambda函数表达式专门用来创建一些简单的函数,他是函数创建的有一种方式

#语法:
lambda 参数列表 : 返回值

lists = [1, 2, 3, 4, 5, 6]
# 使用方式一:给lambda加括号相当于函数名,然后在后面跟参数
result = (lambda a, b: a + b)(10, 20)
print(result)
# 使用方式二:lambda的返回值为函数地址,把他赋予变量,则该变量相当于函数名
funlam = lambda a, b: a + b
print(funlam(10, 20))
# 方式三:结合filter使用,在参数为函数的位置直接写lambda表达式
result = filter(lambda i: i % 5 == 0, lists)
print(list(result))
3.几种常用函数
1.filter()函数–过滤

①filter()过滤器函数可以从序列中过出符合条件的元素并保存到一个新的序列中

②filter(函数,序列)

​ 函数:根据该函数来过滤,如果只需要判断序列中元素的真假可以用None,表示不利用任何函数

​ 序列:需要过滤的序列

​ 返回值:过滤后的新的序列的对象

③不会对原变量产生影响

④其返回值为一个对象,想要输出元素,必须将他的返回值转换成list,否则输出函数信息

def fun2(i):
    if i % 2 == 0:
        return True
    else:
        return False
def fun(func, list):
    new_list = []
    for n in list:
        if func(n):
            new_list.append(n)
    return new_list

lists = [1, 2, 3, 4, 5, 6]
print(fun(fun2, lists))  # [2, 4, 6]
print(filter(fun2, lists))  # <filter object at 0x0000025CC245A4E0>
print(list(filter(fun2, lists)))  # [2, 4, 6]def fun2(i):
    if i % 2 == 0:
        return True
    else:
        return False
def fun(func, list):
    new_list = []
    for n in list:
        if func(n):
            new_list.append(n)
    return new_list

lists = [1, 2, 3, 4, 5, 6]
print(fun(fun2, lists))  # [2, 4, 6]
print(filter(fun2, lists))  # <filter object at 0x0000025CC245A4E0>
print(list(filter(fun2, lists)))  # [2, 4, 6]
2.map()函数–操作

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

lists = [1, 2, 3, 4, 5, 6]
result = map(lambda i: i + 1, lists)
print(list(result))
3.sort()函数–列表排序

①该方法用于对列表中的元素进行排序

②sort函数默认比较列表中元素的大小(除了int与bool外,必须式相同数据类型)

③在sort中可以接收一个 key=函数地址 作为参数,设置了key后,会将列表的每个元素作为实参传递给key的函数,根据函数的反回值进行排序,默认返回的结果从小到大排序;reverse:是否对排序结果进行翻转,即改为从大到小,默认为False

④sort函数会影响原有的列表

lists = ['11', 'ss2', '22', 'aa', '55', '33']
lists.sort()
print(lists)
# 以上排序排的是大小,如果想按长度排序,则需要使用 key=函数id 作为参数
lists.sort(key=len,reverse=False)
print(lists)
# 本列表既有字符串又有数字,不可以进行比较,可以利用int函数将其都转换成整型进行比较(list1并没有真的转换成整型),也可以都转换成字符串
list1 = ['1', 2, '3', 5, '4', 7, '6', 8, '9']
list1.sort(key=int)
print(list1)

# 案例
my_list = [['a', 33], ['b', 34], ['c', 23]]
def choose(e):
    return e[1]
# key传递的函数默认获取list集合的各个元素,然后在choose函数中获取每个元素的下标为1的元素,即数字,并返回,于是sort会根据这些返回值进行排序
my_list.sort(key=choose, reverse=True)
print(my_list)
# 利用lambda表达式
my_list.sort(key=lambda e: e[1], reverse=True)
print(my_list)
4.sorted()函数–任意序列排序

①sorted函数可以对任意序列进行排序,并且不影响原有对象,会返回新的对象

②也支持key参数

lists = ['77', '22', 33, 88, 55, '99', 66]
print(sorted(lists, key=int))
# 不仅可以对列表进行排序,也可以对其他的进行排序,如字符串
str = '12311451234512'
print(sorted(str))
# 字典:对key进行排序
d = dict(name='123', age='456')
print(sorted(d))
4.闭包–函数作为返回值返回

①函数作为返回值返回也是一种高阶函数

②通过闭包可以创建一些只有当前函数能访问的变量,可以将一些私有的数据藏到闭包中

③形成必闭包的条件:

​ 1.有函数嵌套

​ 2.在外层函数中将内测函数作为返回值返回

​ 3.内部函数必须使用到外部函数的变量

# 求多个数的平均值
nums = [10, 20, 30, 40, 50]
print(sum(nums) / len(nums))
# 改进:提供一个全局变量
nums = []
def avg(n):
    nums.append(n)
    return sum(nums) / len(nums)
print(avg(10))
print(avg(20))

# 继续改进:全局变量容易被修改,因此使用闭包。创建一个函数将代码进行包裹,返回值为我们用到的功能函数的id即avg,在调用时我们先对闭包函数进行调用获得功能函数的id保存到变量中,再利用该变量完成功能
def bi_bao():  # 1.有函数的嵌套
    nums = []
    def avg(n):
        nums.append(n)  # 3.内层函数使用到外层函数的变量
        return sum(nums) / len(nums)  # 2.内层函数在外层函数中作为返回值返回
    return avg
fun = bi_bao()
print(fun(10))
print(fun(20))
# 好处,函数外面不可以对nums进行更改
5.装饰器

①像begin_end这样的函数称为装饰器,通过装饰器可以在不修改原来函数的情况下对函数进行扩展,在开发中我们通常用装饰器来扩展函数的功能

def add(a, b):
    return a + b
def mul(a, b):
    return a * b
# 希望函数可以在计算前打印 开始计算 ,在计算结束后打印 计算结束,我们可以在函数中添加代码来完成这个功能,但是有很多问题,比如如果函数过多添加费劲,也不方便后期的维护,此外这样做会违反ocp(开闭原则:程序的设计要求开放对程序的扩展关闭对程序的修改)
# 因此我们进行优化:我们还想要add函数的返回值,因此我们利用r接收add的返回值并返回r
def fun(a, b):
    print('函数开始执行')
    r = add(a, b)
    print('函数结束执行')
    return r
r = fun(10, 20)
print(r)

# 虽然上面函数已经可以在不修改代码的情况下对函数进行扩展了,但上面的方法也有缺点,每次如果有新的函数功能则需要将add换掉或重新定义一个新的函数
# 继续优化:为了解决这个问题,我们创建一个函数,让这个函数可以自动的帮助我们成产函数
def begin_end(old):
    '''
    作用:对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束
    old:要扩展的函数对象
    '''
    # 创建一个新函数
    def new_function(a, b):
        # 调用被扩展的函数
        print('函数开始执行!')
        result = old(a, b)
        print('函数结束执行!')
        return result
    # 返回新函数
    return new_function
f = begin_end(add)  # f相当于new_function函数
print(f(123, 456))
# 仍有不足:如果我的函数不需要两个参数而是没有参数的如fun,则还需要重新写一个没有参数的
def begin_end(old):
    '''
    作用:对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束
    old:要扩展的函数对象
    '''
    # 创建一个新函数
    def new_function(*a, **b):  # 如果全是位置参数,则赋值给a,全是有关键字参数会传给b,这样全都包括了
        # 调用被扩展的函数
        print('函数开始执行!')
        print(a)  # a是元组类型(123, 456)
        print(*a)  # a是元组类型123 456
        result = old(*a, **b)  # 这两种变量作为实参传递时要用带*的格式,带星的传的是数,不加括号
        print('函数结束执行!')
        return result
    # 返回新函数
    return new_function
f = begin_end(add)  # f相当于new_function函数
print(f(123, 456))
f = begin_end(fun)
print(f())

②装饰器开发中典型的用法

​ 1.在定义函数时可以使用 @装饰器 来使用指定的装饰器来装饰当前函数,可以为一个函数指定多个装饰器,这样函数会按照由内到外的顺序进行装饰,如以下顺序时函数先被fun3装饰,然后begin_end装饰被fun3装饰过的。调用时直接调用定义的新函数即可

def begin_end(old):
    def new_function(*a, **b):
        print('函数开始执行!')
        result = old(*a, **b)
        print('函数结束执行!')
        return result
    return new_function


def fun3(old):
    def new_function(*a, **b):
        print('fun3函数开始执行!')
        result = old(*a, **b)
        print('fun3函数结束执行!')
        return result

    return new_function


# 在定义函数时可以使用@装饰器来使用指定的装饰器来装饰当前函数,可以为一个函数指定多个装饰器,这样函数会按照由内到外的顺序进行装饰,如以下顺序时函数先被fun3装饰,然后begin_end装饰被fun3装饰过的
# 已经定义好了装饰器,有新的函数可以直接如下书写并调用
@begin_end  # 说明下面的函数放在begin_end装饰器中,这样就相当于old就是下面定义的函数
@fun3
def sayhello(a, b):
    print('hello')
    return a + b


# 调用时直接调用定义的新函数即可
print(sayhello(10, 20))
# 输出
# 函数开始执行!
# fun3函数开始执行!
# hello
# fun3函数结束执行!
# 函数结束执行!
# 30

六、类(class)

1.类的说明

①像 int() float() bool() str() list() dict() … 这些都是类,如a = int(10) # 创建一个int类的实例 等价于 a = 10

②在类的代码块中,我们可以定义变量和函数

变量会成为该类实例的公共属性,所有的该类实例都可以通过 对象.属性名 的形式访问;

函数会成为该类实例的公共方法,所有该类实例都可以通过 对象.方法名() 的形式调用方法

③方法调用时第一个参数由解析器自动传递,所以定义方法时至少要定义一个形参self,该参数是调用方法的对象本身,即哪一个对象调用的方法则该方法内的self就是哪一个对象的引用。如果p1调用则该参数就是p1(与p1地址一样),可以用self.属性或self.方法调用该对象的属性和方法

④在一个类中可以直接访问该类的属性,self.属性

2.类的创建与创建类的对象的流程

# 类创建对象的流程(如下图)
1.创建一个变量
2.在内存中创建一个新对象
3.将对象的id赋值给变量
#类创建的语法:如果没有标注父类则小括号可以省略不写
class 类名([父类]):
	代码块
    
# 自己定义一个类:使用class关键字来定义类
class MyClass():
    pass

# 创建对象:使用MyClass来创建对象,就像调用函数一样,此时mc就是通过MyClass创建的对象(实例)
mc = MyClass()
mc1 = MyClass() # mc与mc1都是MyClass的实例,都是一类对象
print(mc, type(mc))  # <__main__.MyClass object at 0x00000276DC04A438> <class '__main__.MyClass'>

# isinstance(实例,类)用来检查一个对象是否是一个类的实例,是则参会True,不是则返回False
result = isinstance(mc, MyClass)
print(result)  # True
print(isinstance(mm, MyClass))  # False

  由于MyClass类中没有内容,因此我们通过MyClass这个类创建的对象都是一个空对象,相当于一个空盒子,可以向对象中添加变量,对象中的变量称为属性, 该属性是在每个对象(实例)中添加的
# 给对象中添加属性--语法:对象.属性名=属性值
mc.name = '孙悟空'
mc1.name='猪八戒'
# 获取属性并输出
print(mc.name)

在这里插入图片描述

3.类的属性与方法

1.说明
类中定义的属性和方法都是公共的,任何该类实例都可以访问
    (1)属性和方法查找的流程
        当我们调用一个对象的属性时,解析器会先在当前对象中寻找是否含有该属性,
            如果有,则直接返回当前的对象的属性值,
            如果没有,则去当前对象的类对象中去寻找,如果有则返回类对象的属性值,
            如果类对象中依然没有,则报错!
            如果当前对象没有值,也可以为其添加值!
            如有p1.name='孙悟空',p1中也定义了属性name='sunwukong'则输出孙悟空,因为先在当前对象中找,再在类		  对象中找
    (2)类对象和实例对象中都可以保存属性(方法)
        - 如果这个属性(方法)是所有的实例共享的,则应该将其保存到类对象中
        - 如果这个属性(方法)是某个实例独有,则应该保存到实例对象中     
    (3)一般情况下,属性保存到实例对象中而方法需要保存到类对象中    
2.举例
# 创建一个表示人类的类
class Person:
    # 在类的代码块中,可以定义属性和方法
    # 定义属性,我们在类中定义的属性将会成为所有实例的公共属性,所有实例对象都可以访问这些变量
    name = '孙悟空'
    age = 20

    # 在类中也可以定义函数,在类中定义的函数叫做方法,这些方法可以通过该类的所有实例来访问
    def say_hello(self, a, b):
        # 方法每次被调用,解析器都会自动传递第一个实参,该参数是调用方法的对象本身,即哪一个对象调用的方法,则该方法内的self就是哪一个对象的引用。如果p1调用则该参数就是p1(与p1地址一样),可以用self.属性或self.方法调用该对象的属性和方法
        print(self)  # <__main__.Person object at 0x000001E02BADA438>该参数与p1地址一样
        # 方法中不可以直接使用类中的属性,一般使用self作为第一个参数,并利用self来调用属性
        print(f'输出类中的属性:name={self.name},age={self.age}')
        print(f'我是Person中的方法{a}{b}')
# 创建实例对象
p1 = Person()
p2 = Person()
# 所有的实例对象都可以访问属性变量,利用 对象.属性名 来调用属性
print(p1.name)
print(p2.age)
# 利用 对象.方法名 来调用方法
# 方法调用和函数调用的区别:如果是函数调用则调用时传几个参数就会有几个实参;但是如果是方法调用则会默认传递一个参数,第一个参数由解析器自动传递,所以方法至少要定义一个形参
# 1传a,2传b
p1.say_hello(1, 2)
p2.say_hello(1, 2)
p1.name = '孙库空'
print(p1.name) # 孙库空
print(p2.name) # 孙悟空
# 可以用del p1.name 来删除p2的实例对象的属性值
del p1.name
print(p1.name)  # 孙悟空
# 方法中使用的self的属性值也是按规则赋值的,即方法中p1使用的name为孙库空,p2为孙悟空
p1.name = '孙库空'
p1.say_hello(1, 2)

在这里插入图片描述

4.特殊方法(构造器)

1.init–初始化方法

①特殊方法(魔术方法):以__(双个下划线)开头并且以__(双下划线)结尾的方法

②特殊方法不需要我们自己调用,不要去尝试调用特殊方法,如:p1.init(),特殊方法将会在特殊的时刻自动调用

③特殊方法会在类的对象创建时自动调用,即init会在对象创建以后立刻执行

④init可以用来向新创建的实例对象中初始化属性,通过self向新建的对象中初始化属性

⑤可以在初始化方法中定义属性,让该类默认拥有这些属性。格式:self.age=10

class Person:
    # 开发中,一般不在此处声明属性如:name = 'sbk'
    # 在类中可以定义一些特殊方法(魔术方法):以__(双个下划线)开头并且以__(双下划线)结尾的方法
    # 特殊方法不需要我们自己调用,不要去尝试调用特殊方法,如:p1.init(),特殊方法将会在特殊的时刻自动调用
    def __init__(self):
        print('hello')
        #通过self向新建的对象中初始化属性,相当于给对象设置了属性
        self.name='孙悟空'

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

# 由于定义了需要name的函数,因此对于Person对象来说name是必须的,并且每一个对象的name基本上都不同,而我们现在是将name属性手动添加在各个对象中很容易因忘记添加而出错
# 我们希望在创建对象时必须设置name属性,并且是自动完成的而不是手动完成的
p1 = Person() # 此时调用了特殊方法
p1.name = '孙悟空'
p1.say_hello()
p2 = Person()
p1.name = '猪八戒'
p2.say_hello()
p3.say_hello() # 由于有init方法因此不会报错,可以调用say_hello方法输出孙悟空

p1 = Person()的运行流程
    1.创建一个变量p1
    2.在内存中创建一个新对象
    3.执行类的代码块中的代码(只在类定义的时候执行一次),__init__(self)方法也算是代码块中的代码
    4.将对象的id赋值给变量
2.del–将对象从内存中销毁

①该特殊方法会在在属性被销毁之前自动调用

②只要有某个变量被设置为None且给i变量没有赋值给其他变量则才会被销毁,如有a和b两个实例对象,然后让b=a,a=None,a不会被销毁,只有再设置b=None或只有a=None时才会执行def取销毁a,用del也需要将b删除才会执行del的特殊方法,只删除a不可以,因为a被赋值给了b他就不是垃圾对象

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

    def __del__(self):
        print('%s要被销毁' % self._name)


tom = Cat('Tom')
print(tom._name)
# del关键字可以对一个变量进行删除,因此del会在此时执行。如果此处不写del tom,因为tom实例对象是全局变量要在程序运行后被销毁,因此在分割线下方输出
del tom
print('分割线')

3.str–对象输出方法

①str方法相当于java中的toString,重新设置print(实例对象)输出的内容。如果不加str打印实例对象时输出的是其信息。

②str方法必须返回一个字符串

③当我们打印一个对象时,实际上打印的是对象中特殊方法str的返回值

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

    def __str__(self):
        return f'Person[name={self._name},age={self._age}]'


p1 = Person('孙悟空', 18)
p2 = Person('猪八戒', 20)
print(p1)  # Person[name=孙悟空,age=18],等价于print(str(p1))
print(p2)  # Person[name=猪八戒,age=20]

#案例
class Cat:
    def __init__(self, name):
        self._name = name

    def __del__(self):
        print('%s要被销毁' % self._name)

    def __str__(self):
        return f'我是小猫{self._name}'

tom = Cat('Tom')
print(tom)  # <__main__.Cat object at 0x000001D8E1734780>,加上str之后则输出 我是小猫Tom
print(tom._name)  # Tom
3.repr–交互模式中输出的内容

①对当前函数使用repr函数时调用

②指定对象在’交互模式’中直接输出的效果

③str是用于print的输出结果,而repr是在命令行交互模式时输出的,如有a=‘hello’ a两行代码,该代码会输出repr中的内容

4.关于比较的特殊方法

①会在实例对象做大于或其他比较的时候,该方法的返回值作为比较的返回结果,满足返回True,否则返回False

②self:表示当前对象,other表示和当前对象比较的对象,即p1>p2相当于self>other

1.__lt__(self,other):小于<
2.__le__(self,other):小于等于<=
3.__eq__(self,other):等于==
4.__ne__(self,other):不等于!=
5.__gt__(self,other):大于>
6.__ge__(self,other):大于等于>=
#案例:
class Person(object):
    def __init__(self, name, age):
        self._name = name
        self._age = age

    # 会在在实例对象做大于比较的时候,该方法的返回值作为比较的返回结果
    # self:表示当前对象,other表示和当前对象比较的对象,即p1>p2相当于self>other
    # 其中self表示运算符前的变量,other表示运算符后面的变量
    def __gt__(self, other):
        return self._age > other._age


p1 = Person('孙悟空', 10)
p2 = Person('猪八戒', 20)
print(p1 > p2)  # 10>20--False
5.len–获取对象长度
6.bool–将对象转换为布尔值

①默认情况下将对象转换成布尔值时对象不为空则会True

②一般对象在进行if等真假判断时会自动转换为bool,而bool类型是否为真则根据我们所设置的bool方法来判断

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

    # 通过bool函数设置将对象转换为布尔值时,如果age>17则转换为True
    def __bool__(self):
        return self._age > 17


p1 = Person('孙悟空', 10)
p2 = Person('猪八戒', 20)
print(bool(p1))
# p1在比较时默认转换为bool类型,,怎么转换看bool的方法设置,以下根据p1是否大于17来返回True和False
if p1:
    print('孙悟空已经成年了')
else:
    print('孙悟空还未成年')
7.算术运算相关的特殊方法

①需要让我们的变量做算术运算可以运用以下的方法,当我们做算数运算时就会根据这些方法来判断

1.__add__(self,other)+
2.__sub__(self,other)-
3.__mul__(self,other)*
4.__matmul__(self,other)5.__truediv__(self,other)/
6.__floordiv__(self,other)//
7.__mod__(self,other)%
8.__pow__(self,other)
9.__lshift__(self,other)<<
10.__rshift__(self,other)>>
11.__and__(self,other)&
12.__xor__(self,other)^
13.__or__(self,other)|
def __add__(self,other):
      return self.age + other.age
p1=person('孙悟空',23)
p2=person('猪八戒',34)
print(p1+p1) # 返回age相加的结果

5.类的基本结构

class 类名([父类]) :
    公共的属性... 
    # 对象的初始化方法,相当于java中的构造器
    def __init__(self,...):
        ...
    # 其他的方法    
    def method_1(self,...):
        ...

    def method_2(self,...):
        ... 
        
#创建对象及调用
对象=(参数)
#例子:
class Person:
    def __init__(self, name):
        self.name = name
    def say_hello(self):
        print('大家好,我是%s' % self.name)

# 调用类创建对象时,类后边所有的参数都会依次传到init()中,self是系统自己传的,孙悟空传给name
p1 = Person('孙悟空')
print(p1.name)
# 在创建对象p2时也要重新传递参数
p2 = Person('猪八戒')
print(p2.name)

- 练习:
    尝试自定义一个表示狗的类(Dog)      
        属性:
            name
            age
            gender
            height
            ...
        方法:  
            jiao()
            yao()
            run()
            ...
            
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('跑')
dog = Dog('小黑', 4, '男', 30)
# 我们可以直接通过 对象.属性 的方式来修改属性值,这种方式导致对象中的属性可以随意修改,非常不安全。
dog.name = '毛头'
print(dog.name, dog.age, dog.gender, dog.height)
dog.run()
dog.jiao()
dog.yao()

6.封装性

1.数据的安全性

①属性不可以随意修改(我让你改才能改,不让你改你就不能改)

②属性不能修改为任意值(年龄不可以是负数)

2.封装性说明

①封装是面向对象的三大特征之一

②封装指的是隐藏对象中一些不希望被外部访问到的属性或方法

3.封装性的引入

①如何隐藏对象中的属性:将对象的属性名修改为一个外部不知道的名字

class Dog():
    def __init__(self, name):
    	self.hidden_name=name
	def say_hello(self):
   		print('大家好我是%s'%self.hidden_name)
d=Dog('小黑')
d.name='毛头' # 不会对hidden_name属性进行修改
d.say_hello() # 大家好我是小黑
d.hidden_name='毛头' # 会对hidden_name属性进行修改
d.say_hello() # 大家好我是毛头

②如何获取(修改)对象中的属性:我们需要提供一个getter/setter方法使外部可以访问到属性

​ getter:获取对象中的指定属性,格式:get_属性名(self)

​ setter:设置对象中指定的属性,格式:set_属性名(self,变量)

③使用封装增加了类定义的复杂程度,但也确保了数据的安全性

​ 1.隐藏了属性名使调用者无法随意的修改对象中的属性

​ 2.增加了getter/setter方法可以很好的空值属性是否只读的

​ 如果希望属性是只读的,则可以直接去掉setter方法;

​ 如果希望属性不能被外部访问,则可以直接去掉getter方法

​ 3.使用setter方法设置属性,可以增加数据的验证,确保数据的值是正确的

​ 4.使用getter方法获取属性,使setter方法设置属性,可以在读取属性和修改属性的同时做一些其他处理

​ 5.使用get方法可以表示一些计算的属性

class Dog:
    count=0
    def __init__(self, name, age):
        self.hidden_name = name
        if age > 0:
            self.hidden_age = age
    def jiao(self):
        print(f'{self.hidden_name}叫')
    # 本方法是一个get方法,用来获取对象的name属性
    def get_name(self):
        # 使用getter方法获取属性,使setter方法设置属性,可以在读取属性和修改属性的同时做一些其他处理,如用计数器记录一下商品价格查询次数、弹出警告或输出语句等
        self.count += 1
        print('用户读取了属性')

        return self.hidden_name

    # 本方法是一个set方法,用来设置对象的name属性,set方法中还需要一个参数name,表示要把值设置成什么
    def set_name(self, name):
        print('用户修改了属性')
        self.hidden_name = name

    def get_age(self):
        return self.hidden_age

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


dog = Dog('小黑', 10)  # 如果在构造器中设置条件则在此处设置值时应满足条件,否则会报错
dog.set_name('毛头')
print(dog.get_name())
dog.set_age(-10)  # 由于在setter方法中设置了条件,-10不满足条件,则不会对age进行更改,因此age仍为10。此处值不满足条件时不会报错,只是不会修改
print(dog.get_age())

#使用get方法可以表示一些计算的属性,如下面的get_area方法,可以直接利用前面的长和宽计算出面积,在对象中使用该get方法返回长方形面积
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_width * self.hidden_height

r = Rectangle(2, 5)
r.set_height(10)
r.set_width(20)
print(r.get_area())

4.设置私有属性不常用的方法–隐藏属性

①我们发现尽管使用hidden_name,如果我们在实例对象处对hidden_name进行更改,还是会修改其值的,为此我们可以为对象的属性使用双下划线开头,__属性名,即隐藏属性

②隐藏属性:双下划线开头的属性是对象的隐藏属性,隐藏属性只能在类的内部访问无法通过对象访问,无法通过对象访问

③其实隐藏属性只不过是Python自动为属性改了一个名字,实际上是将名字修改为了 _类名__属性名,使用该名字可以在对象中进行修改和访问

语法:__属性名

class Person:
    def __init__(self, name):
        self.__name = name  # __name实际上被Python改为了_Person__name

    def get_name(self):
        return self.__name

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


p = Person('孙悟空')
p.__name = '猪八戒'  # 可以改但是不会对get/set方法的name进行修改,但是输出p.__name可以输出改后的值
print(p.__name)  # 在没有使用p.__name修改的情况下报错不可访问AttributeError: 'Person' object has no attribute '__name',加上上面的修改此处输出为猪八戒
print(p.get_name())  # 输出孙悟空,因为使用上面的修改方式不会修改实际的值
# 使用_类名__属性名可以在对象中进行修改和访问
p._Person__name = '沙和尚'
print(p.get_name())  # 会输出沙和尚
print(p._Person__name)  # 会输出沙和尚
5.设置私有属性的常用方式

①使用隐藏属性依然可以在类的外部进行访问,因此这种方式一般不用,开发中一般我们会将一些私有属性以 _开头

②一个下划线开头的属性可以在类的外部进行访问,只是一个规范,告诉开发人员这是一个私有属性

③一般情况下,使用_开头的属性都是私有属性,没有特殊需要不要对其进行修改

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('孙悟空')
p.set_name('猪八戒')
print(p.get_name())  # 猪八戒
6.property装饰器

①get方法的装饰器:@property,在方法前面加一个装饰器,@proerty,此装饰器用来将一个get方法转换为对象的属性,添加了@property装饰器以后,就可以像调用属性一样使用get方法,调用时不用写括号

②添加了property装饰器的get/set方法的方法名要与属性名一样(如这个get/set方法是name属性的,则名字就为name)

③set方法的装饰器:@属性名.setter

④有get可以没有set,有set必须有get

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

    # 在方法前面加一个装饰器,proerty,此装饰器用来将一个get方法转换为对象的属性,添加了property装饰器以后,就可以像调用属性一样使用get方法
    @property
    def name(self):
        print('get方法执行了')
        return self._name

    @name.setter
    def name(self, name):
        print('set方法运行了')
        self._name = name

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

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


p = Person('孙悟空', 1000)
p.name = '猪八戒'
# 加了装饰器后可以不加括号
print(p.name)
p.age = 100
print(p.age)
# 输出:
set方法运行了
get方法执行了
猪八戒
100
7.封装性练习
class Cat:
    def __init__(self, name, weight):
        self._name = name
        self._weight = weight

    def __str__(self):
        return f'{self._name}现在{self._weight}斤'

    def eat(self):
        print('吃东西')
        self._weight += 1

    def run(self):
        self._weight -= 2


tom = Cat('Tom', 20)
tom.eat()
tom.run()
print(tom)
# 吃东西
# Tom现在19斤
jack = Cat('jack', 10)
jack.eat()
jack.run()
print(jack)
# 吃东西
# jack现在9斤
两个对象互不影响

7.继承性

1.继承性的说明

①在定义类时,可以在类名后的括号中指定当前类的父类

②通过继承可以直接让子类获取到父类的方法或属性,因此通常需要通过继承来对一个类进行扩展,但是在父类中定义的私有属性__开头的,在子类中不可以被访问

⑤在创建类时如果省略了父类,则默认父类为object类,object类是所有类的父类,所有的类都继承object类

⑥isinstanced(对象a,类b):检查对象a是不是类b的对象,是返回True,否则返回Flase。如果这个类是这个对象的父类则也会返回True。所有的对象和object类进行instanced都会返回True,如instanced(print,object)

⑦issubclass(类a,类b):检查a是不是b的子类,是返回True,否则返回False

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

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


class Dog(Animal):
    def bark(self):
        print('狗汪汪叫')


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


d = Dog()
h = Hashiqi()
d.sleep()
d.bark()
h.sleep()
h.bark()
h.fansha()
print(isinstance(d, Dog))  # True
print(isinstance(d, Animal))  # True
print(issubclass(Dog, Animal))  # True
2.方法的重写(override)

①方法的重写:如果在子类中有和父类方法同名的方法,则通过子类实例去调用方法时会调用子类的方法而不是父类的方法

②当我们调用一个对象的方法,他会优先去当前对象中寻找是否有该方法,如果有则直接调用,没有则取当前对象的父类中寻找有直接调用,如果还没有则向父类的父类中寻找,以此类推,直到找到object后依然没有则会报错

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

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

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

c = C()
c.test()
3.super()关键字

①父类中的所有方法都会被子类继承,包括特殊方法、get/set方法,如果子类的一些属性与父类中不一样,也可以重写特殊方法

②super()来继承init方法:可以用来获取当前类的父类,并且通过super()返回对象调用父类方法时不需要传递self

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

    def fun(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):
        # self_name = name,太麻烦,希望可以调用父类的init来树池话父类中定义的属性name
        # 方式一:Animal.__init__(self, name),弊端:animal是写死的,可以移植性差,不建议使用
        # 方式二:super()可以用来获取当前类的父类,并且通过super()返回对象调用父类方法时不需要传递self
        super.__init__(name)
        self._age = age

    # get/set:name用父类的即可,只需要设置age的
    @property
    def age(self):
        return self._age

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

    def bark(self):
        print('狗汪汪叫')


d = Dog('毛头', 10)
d.name = '小黑'
print(d.name)
print(d.age)

③super().父类方法:通过此方式来调用父类中的方法

④可以通过self.属性(当前类,self) 来访问父类中的属性,可以通过self.本类属性 来访问父类中的属性

⑤子类对象不可以访问父类的私有属性,但是如果父类方法中有使用父类的私有属性,在子类的方法中调用父类的该方法则父类的私有属性也可以在该方法中使用

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

    def fun(self):
        print('A的方法')


class B(A):
    # 通过 类名.方法名 来调用A的方法,并把self作为参数
    def funB(self):
        # 可以针对子类特有的需求编写代码
        print('B的新功能')
        # 使用super().方法 的方式调用父类的方法
        super(B, self).fun()
        # Python2.0时使用 类名.方法名(self) 来调用父类的方法,不退按使用
        A.fun(self)
        # 注意如果使用B(本来)来调用B类的方法(本方法)会出现死循环,B调用fun不是死循环,因为该语句没写在fun方法中
        B.funB(self)
        # 可以通过self.属性(当前类,self) 来访问父类中的属性
        print(self.name)
        # 可以通过self.本类属性 来访问父类中的属性
        self.age = 11
        print(self.age)  # 11

    def fun(self):
        super(B, self).fun()
        print('新的代码')


b = B('毛头')
b.funB()
b.fun()
4.多重继承

①在Python中支持多继承,即一个类可以同时指定多个父类

②可以在类型名的括号中添加多个类来实现多重继承

③多重继承会使子类同时拥有多个父类,并且会获取到所有父类中的方法

④在多个父类中拥有比较多个同名的方法时应该避免使用多继承

⑤如果本类中有该方法的重写,则会直接调用本类中的,如果本类中没有重写且多个父类中有同名的方法,则会在括号中第一个父类A中寻找,并且如果A中没有则会在A的父类中找,A的父类全都没有则在括号的B类即他的父类中找,如果B中没有且B的父类就是A的父类,这是不会再找已经找过的类会从括号的下一个类中找直到找到为止否则报错,即前面的会覆盖后面的。

1.类名.__bases__:这个属性可以获取当前类的所有父类,返回一个元组
print(C.__base__)  # <class 'object'>
2.
class A(object):
    def test(self):
        print('AAA')


class B(object):
    def test(self):
        print('B的Test')

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


# 如果多个父类中有同名的方法,则会在括号中第一个父类中寻找,第一个没有再在后面的类中找,即前面的会覆盖后面的,即现在A中招再在B中找;先写B再写A则相反
class C(A, B):
    pass
print(C.__bases__)  # (<class '__main__.A'>, <class '__main__.B'>)
c = C()
c.test()  # AAA
c.test2()  # BBB
5.MRO方法搜索顺序
输出C方法的方法搜索顺序 
class A:
    pass
class B:
    pass
class C(A, B):
    pass
print(C.mro())  # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

8.多态性

1.多态的说明

①不同的子类对象调用相同的父类方法产生不同的执行结果

②多态的前提:继承并重写父类的方法

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

    def __len__(self):
        return 10

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

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


class C:
    pass

a = A('孙悟空')
b = B('猪八戒')


# 多态:只需要符合某个特征,不需要考虑具体类型,因此在开发中isinstanced这样的函数一般不会使用
# 对于该函数来说,只要对象中有name属性就可以作为参数传递,该函数并不会考虑对象的类型只要有name属性即可
def say_hello(obj):
    print(f'你好{obj.name}')


# 在该函数中我们设置一个类型检查,只有obj是A的实例对象时才可以使用,其他类型的对象无法使用该函数,这样的函数就违反了多态。违反了多态的函数只适用于一种类型的对象,无法处理其他类型对象,这样导致函数的适用性非常差
def say_hello2(obj):
    if isinstance(obj, A):
        print(f'你好{obj.name}')


say_hello(b)

# 之所以一个对象能通过len()来获取长度,是因为对象中具有一个特殊方法__len__,换句话说只要对象中具有__len__特殊方法,就可以通过len()来获取它的长度
2.案例
class Dog(object):
    def __init__(self, name):
        self._name = name

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

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

    def game(self):
        print(f'{self.name}蹦蹦跳跳地玩耍')


class XiaoTianquan(Dog):
    def game(self):
        print(f'{self.name}飞到天上去玩刷')


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

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

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

    def game_with_dog(self, dog):
        print(f'{self.name}{dog.name}快乐的玩耍')
        dog.game()


dog = Dog('小黑')
xiaotianquan = XiaoTianquan('哮天犬')
xiaoming = Person('小明')
xiaoming.game_with_dog(dog)
xiaoming.game_with_dog(xiaotianquan)
#输出
小明和小黑快乐的玩耍
小黑蹦蹦跳跳地玩耍
小明和哮天犬快乐的玩耍
哮天犬飞到天上去玩刷

9.类中的属性和方法

1.属性

①类属性:直接在类中定义的属性,可以通过 类.属性名实例.属性名 访问到;只可以通过 类.属性名 修改。(通过实例进行修改不会影响类属性只是添加或修改了变量属性)

②实例属性:通过实例对象添加的属性(如a.count = 10)或者是通过init方法设置的属性,只可以通过实例对象访问和修改,可以在其他方法中或者是继承的类中通过 self.变量名 来使用

2.方法

①实例方法:在类中定义并以self作为第一个参数。实例方法可以通过 类.方法名实例.方法名 调用,在通过实例的方式调用实例方法时会在调用时将实例对象作为第一个参数self自动传入,通过类来调用时不会自动传递self,需要手动设置一个实例对象作为实参

②类方法:在类的内部使用 @classmethod 来修饰的方法属于类方法,类方法的第一个参数不是self而是cls,也会自动传递,传递的时当前的类对象(类方法在哪个类中定义的cls就是谁),也可以通过cls操作类属性。类方法可以通过类去调用也可以通过实例去调用,两种调用方式没有区别。

③静态方法:在类中使用 @staticmethod 修饰的方法属于静态方法,不需要指定任何的参数,可以通过 类.方法实例.方法 调用,两种调用方式没有区别。静态方法是一个和当前类无关的方法,只是一个保存到当前类中的函数,一般都是一些和当前类无关的工具方法(不一定完全无关,某个方法不知道写在哪时可以写在静态方法中)

class A(object):
    # 类属性:直接在类中定义的属性,可以通过 类.属性名 或类的实例访问到;只可以通过 类.属性名修改。(通过实例进行修改不会影响类属性只是添加或修改了实例属性)
    count = 0

    # 实例属性
    def __init__(self):
        self.name = '孙悟空'

    # 实例方法:在类中定义并以self作为第一个参数。实例方法可以通过 类.方法名 或实例调用,在通过实例的方式调用实例方法时会在调用时将实例对象作为第一个参数self自动传入,通过类来调用时不会自动传递self,需要手动设置一个实例对象作为实参
    def fun(self):
        self.name = '卡卡罗特'
        print(self.name)
        print('这是一个fun方法')

    # 类方法:cls传递的时当前类A
    @classmethod
    def fun2(cls):
        print(f'这是一个类方法,cls={cls}')  # 这是一个类方法,cls=<class '__main__.A'>
        # 可以通过cls操作类属性
        print(cls.count)  # 0

    @staticmethod
    def fun3():
        print('fun3是一个静态方法')


# 类方法和类变量关于继承的使用可以用类.变量名或实例.变量名访问、super().变量名、self.变量名访问
class B(A):
    def funB(self, a):
        print(A.count)
        print(a.count)
        A.fun2()
        a.fun2()
        print(super().count)  # 0
        print(self.count)  # 0
        print(self.name)  # 孙悟空


# 访问类属性
# 通过 类.属性名 访问
print(A.count)  # 0
# 通过实例访问:在没有设置对象属性时会去找类属性
a = A()
# 实例属性:通过实例对象添加的属性(如a.count = 10)或者是通过init方法设置的属性,只可以通过实例对象访问和修改,可以在其他方法中或者是继承的类中通过self.变量名来使用
a.count = 10
print(a.count)  # 10
# 实例对象方式会自动传递self
a.fun()  # 卡卡罗特
# 类的方式不会自动传递self,需要手动设置传递哪个实例
A.fun(a)
# 类方法可以通过类去调用也可以通过实例去调用,两种调用方式没有区别。
A.fun2()
a.fun2()
b = B()
b.funB(a)
# 可以通过 类.方法 或 实例.方法调用,两种调用方式没有区别
a.fun3()
A.fun3()

七、模块(module)

1.模块的说明

①模块化:将一个完整的程序分解为一个一个小的模块,通过将模块组合,来搭建出一个完整的程序

②不采用模块化统一将所有代码编写到一个文件中;采用模块化将程序分别编写到多个文件中

③模块化的有点:方便开发、方便维护、模块可以复用

④在Python中一个py文件就是一个模块,创建模块就是创建一个Python文件,模块名要符合标识符的规范

2.引用整个模块

1.说明

①我们可以引用同一个模块多次,但是模块的实例只会创建一次

②import语句可以在文件的任何位置调用,一般情况下都写到开头

③在每一个模块内部都有一个__name__的属性,通过这个属性可以获取到该模块的名字

④__main__为主模块,一个程序中只会有一个主模块就是直接通过python执行的模块(直接执行谁谁就是主模块)

1.如何在一个模块中引入外部模块
方式一:import 模块名 as 别名	#模块名为Python文件的名字不要后缀py。别名可以有也可以不起,如果起了则只可以用文件的别名

#案例:
# 先创建一个名字为test01.py的文件作为另一个模块
# 在主模块中引入后test01中的代码会直接执行
import test01 as test

# 直接打印另一个文件名可以输出其文件路径
print(test)  # <module 'test01' from 'E:\\Python_learn\\Code\\test01.py'>
# 在每一个模块内部都有一个__name__的属性,通过这个属性可以获取到该模块的名字
print(test.__name__)  # test01
print(__name__)  # __main__
2.案例
语法:import 模块名 as 别名
#主文件test.py
import test01 as test

print(test.a)
print(test.b)
test.test(10)
test.test2()
p = test.Person()
# 拿到实例变量以后调用其他模块中的函数和方法的格式与在本模块调用自己的属性和方法是一样的
# 调用其他模块中类的变量
print(p.name)
# 调用其他模块中的函数
p.fun()

#另一个模块test01.python

# 定义两个变量,在模块中定义的两个变量在模块引入其他模块后其他模块可以直接使用,访问格式 模块名.变量名
a = 10
b = 20

# 可以在模块中定义函数,同样可以通过模块访问到,访问格式:模块名.方法名(参数)
def test(a):
    print(f'test{a}')

def test2():
    print('test2')
#可以在模块中定义类
class Person(object):
    def __init__(self):
        self.name=10
        print(self.name)
    def fun(self):
        print('123')

3.引入部分变量

①引入某个模块中所有的变量,相当于把该模块内容直接复制到当前模块,这样其他模块中的变量的使用方式就和这些变量定义在本文件中是一样的,但是不建议引用所有内容

②两个模块中都有test2方法,后变得会覆盖前面的。(import写在主模块的test2之前则输出本模块的,之后则输出其他模块的)

③为了对②中提到的情况做区分,可以给引入的变量去一个别名

④模块内部变量:添加了单下划线的变量或函数只能在模块内部访问。该变量通过import *引入时不会被引入(一般不用引入指明变量名的方式引用这种变量),但是可以通过引用整个模块来获取。

⑤测试代码:这部分代码只有当当前文件作为主模块时才执行,而当模块被其他模块引用时则不希望他执行,我们可以检查当前模块是否是主模块,是主模块再执行。判断语句:if name == ‘main’:

#1.引用其他模块的部分变量,必要时可以设置别名
from 模块名 import 变量 as 别名,变量 as 别名...
#2.引入该模块中所有的变量
from 模块名 import *

#3.案例:
主模块test.py
from test01 import Person, a, test2 as t, test
from test01 import *

# 引入某个模块中所有的内容,相当于把该模块内容直接复制到当前模块,这样其他模块中的变量的使用方式就和这些变量定义在本文件中是一样的,但是不建议引用所有内容
def test2():
    print('主模块中的test2')
# 引用时可以直接引用变量名(和这些变量实在主模块中定义的使用方式一样)
p = Person()
print(a)
test(150)
# 两个模块中都有test2方法,后变得会覆盖前面的。(import写在主模块的test2之前则输出本模块的,之后则输出其他模块的)
test2()
# 给引入的test2起了一个别名之后,使用别名来使用其他模块的test2
t()
# 通过引入变量的方法引用模块内部变量时,该变量不会给引用,函数也一样,因此以下代码会报错
主模块中输出c或_c、tt()或_tt()都不行,在其他模块中可以以_c的形式输出,方法也是以_t输出
print(c)
tt()

其他模块:test01.py
# 定义两个变量,在模块中定义的两个变量在模块引入其他模块后其他模块可以直接使用,访问格式 模块名.变量名
a = 100
b = 20
_c = 50
# 可以在模块中定义函数,同样可以通过模块访问到,访问格式:模块名.方法名(参数)
def test(a):
    print(f'test{a}')
def _tt():
    print('tt')
def test2():
    print('test2')
# 可以在模块中定义类
class Person(object):
    def __init__(self):
        self.name = 10
        print(self.name)
    def fun(self):
        print('123')
# 测试代码
if __name__ == '__main__':
    p = Person()
    p.tt()

4.包(package)

①python文件包也是一个模块,当我们模块中代码过多时或者需要被分解为多个模块时我们可以用包

②普通模块是一个py文件而包是一个文件夹

③每一个包中都需要有一个__init__.py的文件,这个文件中可以包含有包中的主要内容

④可以用包名.变量名来调用init文件的变量

⑤无论包在什么位置,只要在一个项目下就可以直接引用包名,且调用方式一样

1.导入包中的__init__.py文件
improt 包名 as 别名
调用:
别名.变量名

2.导入包中其他的py文件
from 包名 import 文件名 as 别名,文件名 as 别名
调用:
别名.变量名

主文件
import hello as hh
from hello import a as apy, b as bpy
#无论包在什么位置,只要在一个项目下就可以直接引用包名,且调用方式一样
import pyfile
from pyfile import aaa

print(hh.a)
hh.test()
print(apy.ac)
print(bpy.bc)

包中__init__文件
aa = 10
bb = 20

def test():
    print('test')

包中文件a
ac=10
包中文件b
bc=40

5.python标准库

①python提供了很强大的保准库,并随着python的安装自动安装

②在官方文档的首页:Global Module Index中寻找并查看

1.sys模块

①它里面提供了一些变量和函数,是我们可以获取到python解析器的信息或者通过函数来操作python解析器

1.引入sys模块
improt sys
2.sys.argv:获取执行代码时命令行中做包含的参数并以列表的形式返回
print(sys.argv) # ['E:/Python_learn/Code/test.py']
3.sys.modules:获取当前程序中引入的所有模块并以字典的形式返回,key为模块名,value为模块本身
print(sys.modules)
4.sys.path:以列表的形式返回模块的搜索路径
pprint.print(sys.path)
5.sys.platforom:获取当前python运行的平台
print(sys.plarfrom) # win32
6.sys.exit(参数):函数exit()用来退出程序,后面代码不再执行,可以有一个参数来提示
sys.exit('程序出现异常结束')
2.pprint模块
1.pprint():该方法可以来对打印的数据做一个简单的格式化
#sys模块中直接使用print打印modules会比较乱,我们可以使用pprint()打印
pprint.pprint(sys.modules)
3.os模块–操作系统模块

①os模块让我们可以对操作系统进行访问

1.os.environ['环境变量名']:获取系统的环境变量,可以加上[]在括号中输入指定的环境变量名
print(os.environ) # 输出全部环境变量
print(os.environ['path']) # 输出指定环境变量
2.os.system('命令'):可以用来执行操作系统的名字,来执行控制台相关的命令
os.system('dir') # 查看所有文件夹
os.system('notepad') # 打开记事本
3.os.listdir('path'):不写参数即获取当前目录结构,可以使用参数来获取指定目录的结构,参数可以是绝对路径也可以是相对路径,返回一个列表,目录结构中的每一个文件或文件夹的名字都作为该列表的元素
os.listdir(..) # 相对路径获取上一级文件夹下的目录结构
4.os.chdir('path'):切换当前所在目录,作用相当于cd,可以是绝对路径也可以是相对路径
os.chdir('..') # 切换到上一级目录
5.os.getcwd():获取当前所在目录
6.os.mkdir('目录名'):在当前目录下创建一个目录
os.mkdir('aaa'):在当前目录下创建一个名为aaa的目录
7.os.rmdir('文件名'):删除当前目录下的目录(67配合切换使用,先切换再创建或删除)
8.os.remove('文件名'):删除文件
9.os.rename('就名字','path/新名字'):对文件重命名,如果新名字前加上路径也可以用来移动一个文件
4.案例
import sys
import pprint
import os

print(sys.argv)
pprint.pprint(sys.modules)
print(sys.path)
print(sys.platform)
os.system('notepad')

八、异常

1.异常的说明

①程序在运行过程当中,不可避免的会出现一些错误,比如使用了没有赋值过的变量、使用了不存在的索引、除0等等,这些错误在程序中,我们称其为异常。

②程序运行过程中,一旦出现异常将会导致程序立即终止,异常以后的代码全部都不会执行。

③程序运行时出现异常,目的并不是让我们的程序直接终止,Python是希望在出现异常时,我们可以编写代码来对异常进行处理。

2.异常的处理

①异常处理语法中try是必须的,else语句有没有都行,except和finally至少有一个(只写try except、try finally都可以,但是只写try else不可以)

②如果excpt后不设置任何错误,则他会捕捉所有的异常(任何异常都执行该except代码块中的代码),如果except后跟了一个异常的类型,那么此时他只会捕获该类型异常,有其他被捕获的异常则会在控制台显示错误信息。可以写了几个指定异常后在最后加上一个什么都不加的except或者是异常类型为Exception的异常。可以在异常后使用as设置一个一场名,输出该异常名可以查看异常的说明,type(e)输出查看该异常类型

③异常类型:当python解释器抛出异常时,最后一行错误信息的第一个单词就是错误类型

④Exception异常:是所有异常的父类,except后跟的是exctption则该except也会捕获所有的异常。

⑤当程序运行过程中出现异常以后,所有的异常信息会被保存一个专门的异常对象中,而异常传播时,实际上就是异常对象抛给了调用处比如 :ZeroDivisionError类的对象专门用来表示除0的异常、NameError类的对象专门用来处理变量错误的异常。一般一个错误信息多次输出,最后一次输出提示的行数是该异常的主要原因

⑥可能出错的代码放入到try的代码块中,如果代码没有错误,则会正常执行且如果有else其代码块也会执行;如果出现错误,则会执行expect子句中的代码,这样我们就可以通过代码来处理异常避免因为一个异常导致整个程序的终止

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

⑧finally字句:无论是否出现异常,finally都会执行

    try:
        代码块(可能出现错误的语句)
    except 异常类型 as 异常名:
        代码块(出现错误以后的处理方式)
    except 异常类型 as 异常名:
        代码块(出现错误以后的处理方式)
    except 异常类型 as 异常名:
        代码块(出现错误以后的处理方式)
    else:
        代码块(没出错时要执行的语句)    
    finally:
        代码块(该代码块总会执行)
#案例1    
print('程序开始执行')
try:
    print(10 / 1)
except ZeroDivisionError as zd:#这里只会捕获除0异常其他异常不会捕获,因此会在控制台显示错误
    print('哈哈哈出错了')
    print(zd) # 如果除零了则输出division by zero
else:
    print('程序正常执行')
print('程序执行结束')
#输出
程序开始执行
10.0
程序正常执行
程序执行结束

#案例2:fun的输出语句出现了异常,如果不处理,则异常会向它的调用者fun1传播,可以在fun1中做处理使其不再传播,如果不做处理则会向主函数的调用处fun1()传播,可以在此处进行异常的处理
def fun():
    a=int(input())
    print(10 / a)
def fun1():
    try:
        fun()
    except ZeroDivisionError:#用户输入0,除零异常,这里只会捕获除0异常其他异常不会捕获,因此会在控制台显示错误
        print(10 / 2)
    except ValueError as ve:#用户输入字母,值错误异常
        print(10 / 1)
        print(ve, type(ve))  # invalid literal for int() with base 10: 'a' <class 'ValueError'>
    except Exception as e:
        print(10/2)
        print(e,type(e))
    else:
        print('没有异常')
    finally:
        print('继续执行')
fun1()

3.抛出异常

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

②抛出异常的目的是告诉调用者这里出现里问题需要自己处理一下

③Exception的括号中可以跟一些错误提示信息

def add(a, b):
    if a < 0 or b < 0:
        raise Exception('两个参数中不可以有负数') # Exception的括号中可以跟一些错误提示信息
    r = a + b
    return r
print(add(-1, 1))

4.自定义异常类

①创建一个类继承Exception类即可,代码块可以什么也不写,也可以补充一些其他的代码

②自定义异常的括号中可以跟一些错误提示信息

class MyException(Exception):
    pass
def add(a, b):
    if a < 0 or b < 0:
        raise MyException('自定义异常类') # 自定义异常的括号中可以跟一些错误提示信息
    r = a + b
    return r
print(add(-1, 1))

九、文件

1.文件的说明

①通过Python程序来对计算机中的各种文件进行增删改查的操作

②I/O(Input / Output)

③操作文件的步骤:① 打开文件;② 对文件进行各种操作(读、写),然后保存;③ 关闭文件

2.打开文件

①open(文件名):打开文件,返回一个对象,该对象就是当前打开的文件,如果文件目标和当前文件在同一目录下则直接使用文件名即可;不在同一目录下则要加上文件目录

②在windos系统使用路径时,可以用/代替\,或者使用两个\来表示一个\,或者使用原始字符串如:r’字符串’,加上r后字符串是什么就是表示什么

③表示路径是可以用…/来返回上一级目录

④如果文件在桌面上或者是离当前文件比较远,可以使用绝对路径

⑤可以将文件分为纯文本文件(utf-8等编码)和二进制文件(图片、mp3、ppt等文件),调用open()来打开一个文件时默认是以纯文本文件来打开的,默认的编码为None。因此在处理文本文件时要指定文件编码,语法:encoding=‘utf-8’,规定utf-8后中文编码就可以正常读取了

⑤使用open()打开文件时必须要指定打开所要做的操作(读、写、追加),如果不指定类型则默认为读,而读取文件时是不能向文件中写入的。

​ mode=‘r’:表示只读,mode可以省略,只写’r’

​ mode=‘w’:表示只写,mode可以省略,只写’w’

​ mode=‘a’:表示追加内容,mode可以省略,只写’a’,追加模式下使用write方法写入数据时如果文件已存在,不会删除原来的内容,直接在后面追加新的内容;如果没有文件则会创建该文件

​ mode=‘r+’:即可读又可写,但是文件不存在会报错

​ ‘w+’、‘a+’:即可读又可写

​ mode=‘x’:用于新建文件,文件不存在则创建,存在则报错

​ mode=‘t’:读取文件,如果只写一个r或w等一个不设置b,则它默认为rt、wt

​ mode=‘b’:读取二进制文件,想要设置读取或写入二进制文件可以加一个b,如rb、wb、ab等,尽量不要一次读出来

file_name = 'demo.txt'
#文件和py文件在一起
file_obj = open(file_name,'r',encoding='utf-8')
#文件和py文件在不同目录下
file_obj1 = open('hello/hello.txt')
print(file_obj)

3.文件操作

1.文件的读取

①对象.read(size):读取文件并返回文件内容的字符串。

②如果直接调用read()它会将文本文件的所有内容都读出来,如果文件较大的话会一次性将文件的所有内容加载到内存中容易导致泄露,所以对于较大的文件不要直接调用open(),可以通过③来解决

③size参数

​ 1.read中可以传递一个参数size,该参数用来指定要读取字符的数量,默认值为-1(读取文件中的所有内容)

​ 2.可以为size设置一个值,这样read()会读取指定数量的字符,每一次都是从上次读取到的位置开始读取,如果字符数量少于size则剩余多少就读多少,如果已经读到了最后则会返回空串。

​ 3.空格、回车等符号也算一个字符

​ 4.在读取文本文件时size以字符为单位,在读取二进制文件时size以字节

fileName = 'demo.txt'
try:
    with open(fileName, encoding='utf-8') as fileObj:
        size = 100
        fileContent = ''
        # 创建一个循环来读入内容
        while True:
            content = fileObj.read(size)
            # 检查是否读取到了内容
            if not content:
                # 内容读取完毕
                break
            # 输出内容,并保存内容
            print(content, end='')
            fileContent += content
except FileNotFoundError:
    print('文件不存在')

④readline():该方法可以用来读取一行内容

fileName = 'demo.txt'
try:
    with open(fileName, encoding='utf-8') as fileObj:
        while True:
            content = fileObj.readline()
            if not content:
                break
            print(content, end='')
except FileNotFoundError:
    print('文件不存在')

⑤readlines():该方法用于一行的读取内容,他会一次性将读取到的内容封装到一个列表中返回,可以使用索引返回第i行的内容

import pprint

fileName = 'demo.txt'
try:
    with open(fileName, encoding='utf-8') as fileObj:
        while True:
            content = fileObj.readlines()
            if not content:
                break
            pprint.pprint(content)
            # 可以使用索引返回第i行的内容
            pprint.pprint(content[0])
except FileNotFoundError:
    print('文件不存在')

⑥可以使用for循环直接对对象进行输出,他也会一行一行的输出

fileName = 'demo.txt'
try:
    with open(fileName, encoding='utf-8') as fileObj:
        for t in fileObj:
            print(t, end='')
except FileNotFoundError:
    print('文件不存在')
2.文件的写入

①write()方法

​ 1.来向文件中写入内容,如果文件不存在则会创建文件,如果文件存在则会删除文件中的所有内容并写入新的内容。

​ 2.如果操作的是一个文件的话,则write()需要传递一个字符串作为参数(其他类型可以用str()转换成字符串在传入)。

​ 3.该方法在一次程序执行可以分多次向文件中写入内容,但是再次执行程序时会删除原来内容写入新的内容。

​ 4.本方法不会自动换行,如需换行可以在要换行的位置加入\n

​ 5.本方法会返回本次write()方法写入字符的个数

fileName = 'demo.txt'
with open(fileName, 'w+', encoding='utf-8') as fileObj:
    fileObj.write('hello,how are you 123!\n')
    fileObj.write(str(True) + '\n')
    count = fileObj.write('hello,how are you 7789!')
    print(count)  # 23
3.文件复制–二进制文件常用
fileName = 'F:/图片/[Y4ZC1JYKEJR8DDX7M}`[}Y.jpg'
with open(fileName, 'rb') as fileObj:
    newFile = 'aab.jpg'
    with open(newFile, 'wb') as newObj:
        # 设置读取长度
        size = 1024 * 1024
        while True:
            # 从旧的对象中读取数据
            content = fileObj.read(size)
            # 内容读完则退出循环
            if not content:
                break
            # 将数据写入新对象中
            newObj.write(content)
4.读取文件的位置
1.对二进制文件

①tell():查看当前读取的位置

②seek(index,way):修改当前读取的位置

​ index:表示要切换的位置,设置读取到index处,下次从index的下一个位置开始读

​ way:表示计算位置的方式,0:从头计算(默认值),1:从当前位置开始计算,2:从最后位置开始计算(从后计算如果index时整数是没有意义的,因为后面没内容了,但是可以把index设置为负数,表示从后计算往前数index个)

with open('demo.txt', 'rb') as fileObj:
    print(fileObj.read(55))  # 等价于fileObj.seek(55,0)从头计算数55个
    fileObj.seek(10, 1)  # 从当前位置(56)开始计算数10个
    print(fileObj.tell())  # 65
    fileObj.seek(-10, 2)  # 从后往前数10个
    print(fileObj.tell())  # 430
2.对文本文件

①utf-8中文字占3个字节,如果将这三个字节分开读取会报错,因此要进行判断

4.关闭文件

①文件对象.close():关闭文件

fileName = 'demo.txt'
fileObj = open(fileName)
# 当我们获取到文件对象之后,所有的文件操作都有文件对象来完成
# 文件对象.read():用来读取文件中的内容,将内容全部保存为一个字符串返回
content = fileObj.read()
print(content)
# 文件对象.close():关闭文件
fileObj.close()

②with open(文件名) as 对象变量名: 代码块–打开文件,只可以在代码块中可以对文件进行操作,with结束自动关闭文件

# 可以使用with open(文件名) as 对象变量名: 代码块   的格式来打开文件,只可以在代码块中可以对文件进行操作,with结束自动关闭文件
with open(fileName,encoding='utf-8') as fileObj:
    fileObj.read()
    
# 添加try/except来捕获异常
fileName = 'demo1.txt'
try:
    with open(fileName,encoding='utf-8') as fileObj:
        print(fileObj.read())
except FileNotFoundError:
    print(f'{fileName}不存在')  # 如果文件不存在则提示--demo1.txt不存在

直接调用read()它会将文本文件的所有内容都读出来,如果文件较大的话会一次性将文件的所有内容加载到内存中容易导致泄露,所以对于较大的文件不要直接调用open(),可以通过③来解决

③size参数

​ 1.read中可以传递一个参数size,该参数用来指定要读取字符的数量,默认值为-1(读取文件中的所有内容)

​ 2.可以为size设置一个值,这样read()会读取指定数量的字符,每一次都是从上次读取到的位置开始读取,如果字符数量少于size则剩余多少就读多少,如果已经读到了最后则会返回空串。

​ 3.空格、回车等符号也算一个字符

​ 4.在读取文本文件时size以字符为单位,在读取二进制文件时size以字节

fileName = 'demo.txt'
try:
    with open(fileName, encoding='utf-8') as fileObj:
        size = 100
        fileContent = ''
        # 创建一个循环来读入内容
        while True:
            content = fileObj.read(size)
            # 检查是否读取到了内容
            if not content:
                # 内容读取完毕
                break
            # 输出内容,并保存内容
            print(content, end='')
            fileContent += content
except FileNotFoundError:
    print('文件不存在')

④readline():该方法可以用来读取一行内容

fileName = 'demo.txt'
try:
    with open(fileName, encoding='utf-8') as fileObj:
        while True:
            content = fileObj.readline()
            if not content:
                break
            print(content, end='')
except FileNotFoundError:
    print('文件不存在')

⑤readlines():该方法用于一行的读取内容,他会一次性将读取到的内容封装到一个列表中返回,可以使用索引返回第i行的内容

import pprint

fileName = 'demo.txt'
try:
    with open(fileName, encoding='utf-8') as fileObj:
        while True:
            content = fileObj.readlines()
            if not content:
                break
            pprint.pprint(content)
            # 可以使用索引返回第i行的内容
            pprint.pprint(content[0])
except FileNotFoundError:
    print('文件不存在')

⑥可以使用for循环直接对对象进行输出,他也会一行一行的输出

fileName = 'demo.txt'
try:
    with open(fileName, encoding='utf-8') as fileObj:
        for t in fileObj:
            print(t, end='')
except FileNotFoundError:
    print('文件不存在')
2.文件的写入

①write()方法

​ 1.来向文件中写入内容,如果文件不存在则会创建文件,如果文件存在则会删除文件中的所有内容并写入新的内容。

​ 2.如果操作的是一个文件的话,则write()需要传递一个字符串作为参数(其他类型可以用str()转换成字符串在传入)。

​ 3.该方法在一次程序执行可以分多次向文件中写入内容,但是再次执行程序时会删除原来内容写入新的内容。

​ 4.本方法不会自动换行,如需换行可以在要换行的位置加入\n

​ 5.本方法会返回本次write()方法写入字符的个数

fileName = 'demo.txt'
with open(fileName, 'w+', encoding='utf-8') as fileObj:
    fileObj.write('hello,how are you 123!\n')
    fileObj.write(str(True) + '\n')
    count = fileObj.write('hello,how are you 7789!')
    print(count)  # 23
3.文件复制–二进制文件常用
fileName = 'F:/图片/[Y4ZC1JYKEJR8DDX7M}`[}Y.jpg'
with open(fileName, 'rb') as fileObj:
    newFile = 'aab.jpg'
    with open(newFile, 'wb') as newObj:
        # 设置读取长度
        size = 1024 * 1024
        while True:
            # 从旧的对象中读取数据
            content = fileObj.read(size)
            # 内容读完则退出循环
            if not content:
                break
            # 将数据写入新对象中
            newObj.write(content)
4.读取文件的位置
1.对二进制文件

①tell():查看当前读取的位置

②seek(index,way):修改当前读取的位置

​ index:表示要切换的位置,设置读取到index处,下次从index的下一个位置开始读

​ way:表示计算位置的方式,0:从头计算(默认值),1:从当前位置开始计算,2:从最后位置开始计算(从后计算如果index时整数是没有意义的,因为后面没内容了,但是可以把index设置为负数,表示从后计算往前数index个)

with open('demo.txt', 'rb') as fileObj:
    print(fileObj.read(55))  # 等价于fileObj.seek(55,0)从头计算数55个
    fileObj.seek(10, 1)  # 从当前位置(56)开始计算数10个
    print(fileObj.tell())  # 65
    fileObj.seek(-10, 2)  # 从后往前数10个
    print(fileObj.tell())  # 430
2.对文本文件

①utf-8中文字占3个字节,如果将这三个字节分开读取会报错,因此要进行判断

4.关闭文件

①文件对象.close():关闭文件

fileName = 'demo.txt'
fileObj = open(fileName)
# 当我们获取到文件对象之后,所有的文件操作都有文件对象来完成
# 文件对象.read():用来读取文件中的内容,将内容全部保存为一个字符串返回
content = fileObj.read()
print(content)
# 文件对象.close():关闭文件
fileObj.close()

②with open(文件名) as 对象变量名: 代码块–打开文件,只可以在代码块中可以对文件进行操作,with结束自动关闭文件

# 可以使用with open(文件名) as 对象变量名: 代码块   的格式来打开文件,只可以在代码块中可以对文件进行操作,with结束自动关闭文件
with open(fileName,encoding='utf-8') as fileObj:
    fileObj.read()
    
# 添加try/except来捕获异常
fileName = 'demo1.txt'
try:
    with open(fileName,encoding='utf-8') as fileObj:
        print(fileObj.read())
except FileNotFoundError:
    print(f'{fileName}不存在')  # 如果文件不存在则提示--demo1.txt不存在

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值