- 第一章 计算机基础知识
- 第二章 Python 语言的语法
- 第三章 流程控制语句
- 第四章 序列(sequence)
- 第五章 函数(Function)
- 第六章 对象(Object)
- 第七章 异常和文件
第一章 计算机基础知识
Python 解释器
python 的解释器分类:
- CPython(官方):用 C 语言编写的 Python 解释器。
- PyPy : 用 Python 语言编写的 Python 解释器,需要用到CPython。
- IronPython :用 .net 编写的Python 解释器。
- Jython :用 Java 编写的 Python 解释器。
Python 3 和 Python 2 两个版本的解释器不兼容。
函数
在 Python 中,函数主要分为:
- 内建函数(Built-in Functions): 可以参考《Python3.6.4 Documentation》离线手册,安装了 python 以后就自带的。
- 自定义函数: 由程序员自主创建的函数。
代码块
代码块中保存着一组代码:
- 同一个代码块中的代码,要么都执行要么都不执行。
- 代码块就是一种为代码分组的机制。
- 如果要编写代码块,语句就不能紧随在:后边,而是要写在下一行。
- 代码块以缩减开始,直到代码恢复到之前的缩减级别时结束。
num = 20
if num > 10 :
print('123')
print('456')
print('Hello')
上面通过 回车缩减,print('123') 和 print('456') 语句组成一个代码块。而 print('Hello') 不属于前面两个 print() 那个代码块的。关于 缩进 :
- 一种是使用 Tab 键,一种是使用空格(四个)
- Python 的官方文档中,推荐我们使用空格来缩进。
- Python 代码中使用的缩进方式必须统一,要么都用 Tab 键,要么都用空格。
作用域
在 python 中一共有两种作用域:全局作用域和函数作用域。
全局作用域
全局作用域有以下特点:
- 全局作用域在程序执行时创建,在程序执行结束时销毁。
- 所有函数以外的区域都是全局作用域。
- 在全局作用域中定义的变量,都属于全局变量,全局变量可以再程序的任意位置被访问。
函数作用域
函数作用域有以下特点:
- 函数作用域在函数调用时创建,在调用结束是销毁。
- 函数每调用一次就会产生一个新的函数作用域。
- 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问。
变量查找
当我们使用变量时,变量查找顺序:
- 优先在当前作用域中寻找该变量,如果有则使用。
- 如果没有,就从上一级作用域中寻找。
修改作用域
如果希望在函数内部修改全局变量,则需要使用 global 关键字,来声明变量。如:
a = 10
def fn():
global a
a = 0
print('函数内部:a =', a)
fn()
print('函数外部:a =', a)
上面,在函数内部,使用 global a 来说明,函数内部使用的这个 a 是全局变量 a ,而不是重新定义一个变量a。打印结果如下:
函数内部:a = 0
函数外部:a = 0
命名空间
命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中。
- 每一个作用域都会有一个它对应的命名空间。
- 全局命名空间,用来保存全局变量。
- 函数命名空间,用来保存函数中的变量。
- 命名空间实际上就是一个字典,是一个专门用来存储变量的字典。
获取当前命名空间
可以通过 locals() 函数,读取当前命名空间变量情况:
scope = locals()
print(scope)
print(type(scope))
locals() 返回一个字典,所有命名空间的变量、函数名等都保存在一个字典中,打印结果如下:
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000000000240B160>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '05-03-locals.py', '__cached__': None, 'scope': {...}}
<class 'dict'>
可以打印出一个函数内的命名空间的内容:
def fn4():
a = 10
scope = locals()
print(scope)
fn4()
当前函数的命名空间内,只有一个变量a,打印结果如下:
{'a': 10}
获取全局命名空间
可以通过 globals() 函数,可以获取全局命名空间的内容。例如:
def fn4():
a = 10
print(globals())
fn4()
上面代码,获取到了全局命名空间的内容,打印结果如下:
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000000000248B160>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '05-03-locals.py', '__cached__': None, 'fn4': <function fn4 at 0x00000000023E1F28>}
开闭原则(OCP)
程序的设计,要求开发对程序的扩展,要关闭对程序的修改。我们希望在不修改原函数的情况下,对函数进行功能扩展。
python 文件
python 源文件,是以 .py 结尾的。
第二章 Python 语言的语法
基本语法
语句结束的表示方式
换行 表示一个语句结束,除了带 ( ) 的语句。例如:
print('hello') # 这就表示语句结束了
print('这是新的一行') # 和上面一个语句,通过回车换行来区分不是一个语句
print('参数1',
'参数2') # 通过括号,虽然有回车换行,但也是同一个语句
缩进严格
不可以随意缩进,缩进会分隔代码块。在 python 中,缩进必须统一使用 Tab 键或者 4 个空格,使用其中的一种,推荐都使用 4 个空格。比如:
def fn():
print('hello')
fn()
上面代码,fn() 函数和 print() 通过缩进不一样,区分了它两不在一个代码块内。
注释
# 这是单行注释
"""
这是多行注释。
"""
对于代码中第一行的注释,要注意,它会被解析器读取:
# coding=utf-8
上面这行注释,如果放在代码文件的首行,解析器会将其读取,coding=utf-8 表示编码方式为 utf-8 。
变量
变量不需要声明,直接赋值使用即可。不能使用没有进行过赋值的变量:
a = 10
print(a)
下面是不可以的,因为下面的变量 b 没有赋值:
print(b)
python 是动态类型的语言,变量类型是动态的,可以为变量赋任意类型的值:
a = 1 # 这时,变量a是数值型
print(a)
a = 'hello' # 这时,变量a变成了字符型
print(a)
标识符命名
标识符中可以含有 字母、下划线、数字,不能以数字开头,不能是关键字和保留字。也不建议使用 python 中的函数名作为标志符,因为这样会导致内建函数被覆盖,比如:
print = 123
print(print) # 这时就会报错,print 被覆盖成一个变量名,而不是原来的内建函数名。
数值
在 python 中,数值分为三种:整数、浮点数(小数)、复数 。
整数
python 中,整数有以下特点:
- 所有的整数都是 int 类型。
- python 中的整数没有大小限制,可以是一个无限大的整数。
- 数字过长时,可以所有下划线,方便阅读,比如
b = 123_456_789,相当于b = 123456789。
整数可以使用 不同进制 来表示,常见进制如下:
# 二进制,0b 开头
c = 0b10
# 八进制,0o 开头
c = 0o10
# 十六进制,0x 开头
c = 0x10
浮点数
浮点数,都是 float 类型。
c = 1.23
注意:对浮点数进行计算时,可能会得到一个不精确的结果 。比如
c = 0.1 + 0.2
print(c)
得到的结果不是 0.3 ,而是 0.30000000000000004。这是需要注意的,它只能得到一个估算值,如果需要精确的计算,需要用到专用的计算模块。
布尔值
布尔值,表示真(True)和假(False):
a = True
print(a)
a = False
print(a)
其实,布尔值也是整型数,True 相当于 1 ,False 相当于 0 。
空值
空值 None 表示不存在、值为空:
b = None
print(b)
字符串
字符串变量
字符串用单引号或者双引号引起来:
s = 'hello world!'
或者
s = "hello world!"
字符串换行
字符串太长是,一行写不下,使用反斜杠 \ 进行换行:
s = '锄禾日当午,\
汗滴禾下土,\
谁知盘中餐,\
粒粒皆辛苦'
print(s)
长字符串
使用 '''长文本''' 或者 """长文本""",可以书写长字符串,而且可以保留格式:
s = '''锄禾日当午,
汗滴禾下土,
谁知盘中餐,
粒粒皆辛苦'''
print(s)
上面,格式上还保留了换行。
转义字符
比如,下面情况,我们会用到转义字符(反斜杠 \ 表示转义字符):
s = " 字符串中包含双引号 \" 和单引号 \' "
print(s)
对象基础
对象的结构中,有:
id (标识)
type (类型)
value(值)
在 python 中,一切皆对象。一个简单的数值,比如 1 ,也是一个对象,它有 ID(内存地址),有 type (数据类型),有 value (值,值的大小就是1)。对象和变量之处的不同在于:
变量,保存的是对象的内存地址值,即 id 值,比如 ```a = 123``` ,那么,a 这个变量实际保存的是 ```123``` 的 id 值,也就是内存地址。
对象的标识(id)
- id 用来标识对象的唯一性。
- 可以通过
id()函数来获取对象 id ,比如 :id(1)。 - id 对象由解析器生成,在 CPython 中,id 就是对象的内存地址。
- 对象一旦创建,id 将不可改变。
对象的类型(type)
- 表示当前对象所属类型,比如 int、float、str、bool等。
- 类型决定了对象有哪些功能。
- 通过
type()函数获取。如type(123)。 - python 是一门强类型的语言,对象一旦创建类型便不能修改了。
对象的值(value)
- 对象中存储的具体的数据,比如
123,那么它的值 value 就是 123 。 - 有些值是可变的,有些值是不可变的。
对象的类型转换
一个类型的对象,转换成另一个类型的对象。对象类型转换不是改变对象本身的类型,而是 根据当前对象的值创建一个新对象。类型转换有函数有 int()、float()、str()、bool() 。比如:
a = True
a = int(a)
print(a)
打印结果为:
1
上面是将 bool 变量 True 的 value 值,转换为 int 整数类型,同时创建一个新的 int 对象,将转变后的 value 值赋给新对象的 value ,也就是 True -> value=true 到 1 -> value=1 。a 一开始保存的是 True 的地址,后来保存的是 1 的内存地址。int() 函数转换规则:
- 布尔值:```True -> 1``` , ```False -> 0``` 。
- 浮点数:直接取整,省略小数点后面的内容。
- 字符串:合法的整数字符串,则保存为整数,比如 a = ```123``` ,这是可以的。如果是 a = ```12.3``` ,则会报错。
- 对于其他不可转换为整型的对象,直接抛出异常,比如 ```a = None```。
float() 函数转换规则:
和 int() 函数基本一致,只是整型改为浮点数。
str() 函数转换规则,转换为字符串类型。比如:
a = 123
print('hello' + a) # 这样是错误的,会抛出异常
print('hello' + str(a)) # 这是正确的
bool() 函数转换规则:
- 任何对象都可以转换为布尔值。
- 所有表示 **空** 的特性的,都会转换成 False ,比如 ```a = ''```、```0```、```None``` 等。
字符串的运算
字符串运算,其实就是字符串拼接在一起。其中有 拼接、多个参数、占位符、格式化字符串 等方式进行拼接打印。
name = '魏文应'
# 拼接
print('欢迎 ' + name + ' 光临!')
# 多个参数
print('欢迎', name, '光临!')
# 占位符
print('欢迎 %s 光临!' %name)
# 格式化字符串
print(f'欢迎 {name} 光临!')
字符串的相加
相加,就是将两个字符串拼接在一起:
s = 'abc' + '123' + '哈哈!'
print(s)
这样打印出的结果就是:
abc123哈哈!
字符串的占位符
可以为字符串指定 占位符 ,在 %s 填入后面指定的值:
b = 'Hello %s 你好 %s' %('tom', '孙悟空')
print(b)
% 是占位符,s 表示这是字符串。上面代码,打印结果为:
Hello tom 你好 孙悟空
占位符的长度
下面代码,通过 %5s, 指定占位符所在位置,最少要填充5个字符,不够的话左侧用空格补够:
a = 'Hello %5s' %'bc'
print(a)
上面 bc 只有两个字符,但规定最少要有5个,则在 bc 前填充三个空格。输出结果如下:
Hello bc
下面代码,通过 %.7s , 指定占位符所在位置,最多只能填充7个字符,超过部分后面被舍弃:
a = 'Hello %.7s' % 'bcdefghijk'
print(a)
只保留了 bcdefgh,ijk 被舍弃。打印结果如下:
Hello bcdefgh
此外,可以通过 %5.7s ,指定占位符所在位置,字符数 5 <= 字符数 <= 7 :
a = 'Hello %5.7s' % 'bcdefghijk'
print(a)
字符串的占位符小数点后的位数
有时,要输出一个浮点数,你可能只想保留小数点后的2位数:
a = 'Hello %.2f' %123.456
print(a)
还会自动四舍五入,打印结果如下:
Hello 123.46
此外:
-
舍弃小数: 使用
%d,直接舍弃小数点后的数字,比如123.99,打印结果为123。
字符串的格式化
实现的效果,也是将几个字符串拼接起来:
a = 'Hello'
b = '呵呵'
c = f'小明{a}{b}'
print(c)
这时打印的结果为:
小明Hello呵呵
运算符(操作符)
算术运算符
算法运算符有: +、-、 * 、 / (加减乘除),还有 // (整除)、**(幂运算,如 10 ** 5 就是10的5次方)、% (取模,也就是求余数,如 10 % 3 得到 1)。
赋值运算符
赋值运算符就是 = ,如 a = 10 ,也就是右侧的值,赋值给了左侧的变量。组合起来就有 +=、-= 、*= 、**=、/=、//= 、%= ,比如 a += 5 就相当于 a = a + 5 。
关系运算符
关系运算符有: >(大于)、<(小于)、>=(大于等于)、<=(小于等于)、==(等于号)、!=(不等于)。
比较两个字符串的 Unicode 编码时,是逐位进行比较的。比如
'ac' > 'b',a 和 b 进行比较,其后 c 就没有比较了。
上面运算符比较的对象的 value 值,如果要比较对象的 id ,则使用 is (id 相同)、is not(id 不相同)。
逻辑运算符
逻辑运算符有: not(逻辑非)、and(逻辑与)、or(逻辑或)。逻辑非:
# not 可以对非布尔值进行运算。如果是非布尔值,先将其转换为布尔值,再进行运算:
a = ''
a = not a
print(a)
逻辑与:逻辑与判断时,如果第一个值是 Flase ,第二个值就不看了,直接返回,比如:
# 这个 print() 不会执行
False and print('False:你猜我出来吗?')
# 这个 print() 会执行
True and print('True:你猜我出来吗?')
逻辑或:逻辑或是,如果第一个值是 True,则不再看第二个值,直接返回,比如:
# 打印语句 print() 不执行
True or print(你猜我出来吗?)
非布尔值的逻辑运算
当我们对非布尔值进行与或运算时, Python 会将其当做布尔值运算,最终 返回原值 。比如:
# 结果打印 0
result = 0 and 7
print(result)
# 结果打印 7
result = 1 and 7
print(result)
上面是 逻辑与 的结果,下面是 逻辑或 的结果:
# 这个结果打印 3
result = 3 or 4
print(result)
# 这个结果打印 4
result = 0 or 4
print(result)
条件运算符(三元运算符)
语法: 语句1 if 条件表达式 else 语句2
执行流程,先对条件表达式进行判断:
- 如果判断结果为 True,执行语句 1,并返回执行结果。
- 如果判断结果为 False,执行语句 2,并返回执行结果。
比如:
print('你好!') if True else print('Hello!')
运算符优先级
可以通过官方文档 《python 3.6.4 Documentation》中,The Python Language Reference -> Expretions -> Operater precedence :
| 操作符 | 描述 |
|---|---|
lambda | Lambda 表达式 |
if else
| 三目运算符 |
or | 逻辑或 |
and | Boolean AND |
not x
| Boolean NOT |
in, not in, is, is not, <, <=, >, >=, !=, ==
| Comparisons, including membership tests and identity tests |
| | Bitwise OR |
^ | Bitwise XOR |
& | Bitwise AND |
<<, >>
| Shifts |
+, -
| Addition and subtraction |
*, @, /, //, %
| Multiplication, matrix multiplication, division, floor division, remainder |
+x, -x, ~x
| Positive, negative, bitwise NOT |
** | Exponentiation |
await x
| Await expression |
x[index], x[index:index], x(arguments...), x.attribute
| Subscription, slicing, call, attribute reference |
(expressions...), [expressions...], {key: value...}, {expressions...}
| Binding or tuple display, list display, dictionary display, set display |
下面的优先级比上面的优先级高。如果开发中,遇到优先级不清楚,可以加个括号来限定优先级。
符号 == 和 is 的区别
符号 == 比较的是对象的 value 值,如果相等返回 True ,不相等返回 False。而 is 比较的是对象的 id 值,也就是比较的是两个变量,指向的是不是同一个对象,如果是同一个对象,返回 True,不是同一个对象 False 。例如:
a = [1, 2, 3]
b = [1, 2, 3]
print(id(a))
print(id(b))
print(a == b)
print(a is b)
上面, a 和 b 的 value 值相等,但不是同一个对象,打印结果如下:
74438664
74438536
True
False
但是,下面情况是同一个对象:
a = 'hello'
b = 'hello'
print(id(a))
print(id(b))
print(a == b)
print(a is b)
value 值和 id 值都相等,打印结果如下:
70648864
70648864
True
True
数据类型检查
类型检查,通过 type() 内建函数来实现变量类型检查:
a = 123
print(a)
print(type(a))
print(type(1))
print(type(1.5))
print(type(True))
print(type('hello'))
print(type(None))
打印结果如下:
123
<class 'int'>
<class 'int'>
<class 'float'>
<class 'bool'>
<class 'str'>
<class 'NoneType'>
第三章 流程控制语句
条件判断语句(if 语句)
if 语法
if 条件表达式 : 语句
if 条件判断语句,在执行时,会先对条件表达式进行求值判断:
- 如果为 True,则执行 if 后的语句。
- 如果为 False,则不执行。
比如:
num = 20
if num > 10 : print('num比10大!')
if 语句中使用逻辑运算符
num = 8
# 也可以这么写 if 10 < num < 20 :
if num > 10 and num < 20 :
print('限定num的大小')
if else 语句
如果不满足 if 后面的判断语句,则执行 else 后面的语句:
if 条件表达式:
代码块
else:
代码块
age = int(input('请输入你的年龄:'))
if age >= 18:
print('你已经成年了!')
else:
print('你还没有成年!')
if elif else 语句
if-elif-else 语句在执行时,会自上向下依次对条件表达式进行求值判断:
- 如果表达式结果为 True,则执行当前代码块,然后语句结束。
- 如果表达式的结果为 False,则继续向下判断,直到找到 True 为止。
- 如果所有的表达式都是 False,则执行 else 后的代码块。
比如:
age = int(input('请输入你的年龄:'))
if age >= 18:
print('你已经成年了!')
elif age < 5:
print('小朋友你好!')
else:
print('你还没有成年!')
循环语句(while 语句)
while 语句的语法如下:
while 条件表达式:
代码块
执行流程:
- while 语句在执行时,会先对 while 后的条件表达式进行求值判断。
- 如果判断结果为 True,则执行循环体(代码块);
- 循环体执行完毕,继续对条件表达式进行求值判断,以此类推。
- 直到判断结果为 False,则循环终止。
i = 0
while i < 5:
print('Hello!')
i += 1
上面代码,print('Hello') 和 i += 1 执行 5 遍以后退出。
循环语句(for 语句)
for 语句的语法如下:
for 变量 in 序列 :
代码块
for 循环的代码块执行多次,序列中有多少个元素,就循环多少次,每执行一次,就会将序列中的一个元素赋值给变量。 例如:
stus = [1, 2, 3, 4]
for s in stus:
print(s)
上面的 1, 2, 3, 4 依次赋值给变量 s ,打印结果如下:
1
2
3
4
for 循环的序列有多种,只要是序列就行。比如,字符串也是序列,那么:
for i in 'Hello':
print(i)
字符串 Hello 其实就是一个类似于 ['H', 'e', 'l', 'l', 'o'] 的序列,所以上面的打印结果如下:
H
e
l
l
o
还可以使用 [ ],这样来循环:
[print('-----') for i in range(3)]
打印结果如下:
-----
-----
-----
break 关键字
结束当前循环,比如:
i = 0
while i < 5:
i += 1
if i == 3:
break
print(i)
上面代码,循环执行 2 次就结束了。对应循环嵌套的情况,break 只对离它最近的 while 循环起作用。
continue 关键字
continue 使得当前循环后面的内容不再执行,直接进入下一次循环:
i = 0
while i < 5:
i += 1
if i == 3:
continue
print(i)
输出结果中,i == 3 的循环, print(i) 这个语句没有得到执行,跳过了。
第四章 序列(sequence)
序列是 python 中最基本的一种 数据结构:
数据结构指计算机中数据存储的方式。
序列用于保存一组有序的数据,所有的数据在序列当中都有一个唯一的位置(索引)。序列的分类有:
- 可变序列(序列中的元素可以改变): 列表(list)。
- 不可变序列(序列中的元素不能改变):字符串(str)、元组(tuple)
列表(list)
什么是列表?列表是用于存储一些有序数据对象。
创建列表
创建列表,通过中括号 [ ] 来创建列表:
my_list = []
print(type(my_list))
创建列表时,为列表指定元素:
my_list = [10, 2, 4, 7, 9]
print(my_list)
可以同一个列表存不同类型对象:
my_list = [10, None, 'hello', 1.2, True]
print(my_list)
列表索引
索引是从 0 开始的整数,列表第一个位置索引为 0,第二个位置索引为1 :
my_list = [10, 2, 4, 7, 9]
print(my_list[0])
上面就是打印第一个元素 10 。可以通过 len() 函数获取列表的索引:
my_list = [10, 2, 4, 7, 9]
print(len(my_list))
负数索引
负数表示倒数第几个,比如 -2 负2,表示倒数第 2 个元素:
students = ['天龙八部', '倚天屠龙记', '侠客岛']
print(students[-2])
切片
切片,就是从现有列表中,获取一个子列表,比如:
students = ['天龙八部', '倚天屠龙记', '侠客岛', '鹿鼎记', '雪山飞狐']
print(students[1:4])
打印结果如下:
['倚天屠龙记', '侠客岛', '鹿鼎记']
通过切片获取指定的元素:
- 做切片操作是,总会返回一个新的列表,不会影响原来的列表。
- 通过切片获取元素时,包括起始位置的元素,不会包括结束位置 的元素。
省略参数
下面,是从索引1的位置,一直到最后:
students = ['天龙八部', '倚天屠龙记', '侠客岛', '鹿鼎记', '雪山飞狐']
print(students[1:])
下面则是,从开始的位置,一直到索引值是 3 的位置(不包括 3):
students = ['天龙八部', '倚天屠龙记', '侠客岛', '鹿鼎记', '雪山飞狐']
print(students[:3])
添加步长
语法:列表[起始:结束:步长]
比如:
students = ['天龙八部', '倚天屠龙记', '侠客岛', '鹿鼎记', '雪山飞狐']
print(students[0:5:2])
打印结果为:
['天龙八部', '侠客岛', '雪山飞狐']
也就是隔一个步长取一个。步长不能是 0 ,但 步长可以是负数,则从列表的后部往前倒着取:
students = ['天龙八部', '倚天屠龙记', '侠客岛', '鹿鼎记', '雪山飞狐']
print(students[::-1])
注意:负数时,如果有范围,则要这么写:白骨精
students = ['天龙八部', '倚天屠龙记', '侠客岛', '鹿鼎记', '雪山飞狐']
print(students[4:0:-1])
为什么要把 4 放在前面,0 放在后面呢?这是因为 [start:end:step] ,也就是start是起始位置,你倒着取,start当然是原来的最后一个参数啦。
列表拼接
通过加号 + 实现:
my_list = [1, 2, 3] + [4, 5, 6]
print(my_list)
打印结果如下:
[1, 2, 3, 4, 5, 6]
列表重复
通过乘号 * 实现:
my_list = [1, 2, 3] * 3
print(my_list)
打印结果如下:
[1, 2, 3, 1, 2, 3, 1, 2, 3]
元素检测
查看元素是否存在于列表中,使用 in 关键字:
my_list = ['天龙八部', '倚天屠龙记', '侠客岛', '鹿鼎记', '雪山飞狐']
print('天龙八部' in my_list)
上面检测 '天龙八部' 这个元素是否在 my_list 这列表中,打印结果如下:
True
如果是检查某个元素是否 不存在 于列表中,使用 not in 关键字:
my_list = ['天龙八部', '倚天屠龙记', '侠客岛', '鹿鼎记', '雪山飞狐']
print('天龙八部' not in my_list)
发现 '天龙八部' 在列表中,打印结果如下:
False
查看元素最大值和最小值
对于数值型列表,我们经常要找到其中的最大值或者最小值,例如:
arr = [1, 2, 3, 4, 5, 6, 7]
print(min(arr), max(arr))
函数 min() 取最小值, 函数 max() 取最大值,打印结果如下:
1 7
获取 index
获取索引值,例如:
my_list = ['天龙八部', '倚天屠龙记', '侠客岛', '鹿鼎记', '雪山飞狐']
print(my_list.index('天龙八部'))
这样,就会打印出元素 天龙八部 的索引值,打印结果如下:
0
如果元素在列表中有重复,它查找返回的是 首先索引到的索引值 。还可以指定索引范围:
my_list = ['天龙八部', '倚天屠龙记', '侠客岛', '鹿鼎记', '雪山飞狐', '天龙八部', '天龙八部']
print(my_list.index('天龙八部', 3, 7))
上面是在索引值为 3 到 7(不包括7)中的元素查找。
计算元素数目
统计指定元素在指定列表中出现的次数:
my_list = ['天龙八部', '倚天屠龙记', '侠客岛', '鹿鼎记', '雪山飞狐', '天龙八部', '天龙八部']
print(my_list.count('天龙八部'))
上面代码,统计了 天龙八部 这个元素个数,打印结果如下:
3
修改列表中的元素
修改列表中的某个元素:
stus = ['孙悟空', '沙和尚' ,'猪八戒', '唐僧', '蜘蛛精', '白骨精']
stus[0] = 'sunwukong'
print(stus)
将元素 孙悟空 修改为了 sunwukong ,打印结果如下:
['sunwukong', '沙和尚', '猪八戒', '唐僧', '蜘蛛精', '白骨精']
删除元素,删除列表中的某个元素:
stus = ['孙悟空', '沙和尚' ,'猪八戒', '唐僧', '蜘蛛精', '白骨精']
del stus[0]
print(stus)
上面代码,删除了元素 孙悟空,打印结果如下:
['沙和尚', '猪八戒', '唐僧', '蜘蛛精', '白骨精']
可以同时修改列表的多个元素,通过 切片 实现,这时,给切片进行赋值时,只能使用序列 :
stus = ['孙悟空', '沙和尚', '猪八戒', '唐僧', '蜘蛛精', '白骨精']
stus[0:2] = ['红孩儿', '牛魔王']
print(stus)
同时修改了索引值为 0 和 1 的元素值,打印结果如下:
['红孩儿', '牛魔王', '猪八戒', '唐僧', '蜘蛛精', '白骨精']
末尾添加 (append)
通过 append() 方法在列表的末尾添加一个元素:
stus = ['孙悟空', '猪八戒', '沙和尚']
print('原列表:', stus)
stus.append('唐僧')
print('append后的列表:', stus)
这样,唐僧 这个元素就会被添加到 stus 这个列表的后面,打印结果如下:
原列表: ['孙悟空', '猪八戒', '沙和尚']
append后的列表: ['孙悟空', '猪八戒', '沙和尚', '唐僧']
插入元素(insert)
往列表中插入一个元素,通过 insert() 方法:
stus = ['孙悟空', '猪八戒', '沙和尚']
print('原列表:', stus)
stus.insert(1, '唐僧')
print('insert后的列表:', stus)
在 stus 列表索引值为 1 的位置,插入一个元素 唐僧 ,打印结果如下:
原列表: ['孙悟空', '猪八戒', '沙和尚']
insert后的列表: ['孙悟空', '唐僧', '猪八戒', '沙和尚']
清除元素(clear)
清空列表中的所有元素:
stus = ['孙悟空', '猪八戒', '沙和尚']
stus.clear()
print(stus)
所有元素都被清空了,打印结果如下:
[]
根据索引删除元素(pop)
根据索引值,删除一个元素并返回被删除的元素值:
stus = ['孙悟空', '猪八戒', '沙和尚']
a = stus.pop(2)
print('删除的元素:', a)
print('删除后的列表:', stus)
这样,就删除了列表中索引值为 2 的元素。打印结果如下:
删除的元素: 沙和尚
删除后的列表: ['孙悟空', '猪八戒']
del() 也能实现类似的效果,但是没有返回值:
stus = ['孙悟空', '猪八戒', '沙和尚']
del(stus[2])
print('删除后的列表:', stus)
根据值删除元素(remove)
删除指定值的元素,如果有多个值相同,只删除靠前的一个:
stus = ['孙悟空', '猪八戒', '沙和尚', '孙悟空']
stus.remove('孙悟空')
print('删除后的列表:', stus)
上面,把第一个元素 孙悟空 给删除了,打印结果如下:
删除后的列表: ['猪八戒', '沙和尚', '孙悟空']
反转列表(revert)
将列表的元素位置反转:
stus = ['孙悟空', '猪八戒', '沙和尚']
stus.reverse()
print(stus)
元素的位置改变了,打印结果如下:
['沙和尚', '猪八戒', '孙悟空']
元素排列(sort)
通过 sort() 方法进行排列:
stus = ['a', 'c', 'b', 'f', 'g', 'm', 'i']
stus.sort()
print(stus)
上面,安装 ASCII 码表进行 升序排列,打印结果如下:
['a', 'b', 'c', 'f', 'g', 'i', 'm']
如果需要 ASCII 表 降序排列 ,打印结果如下:
stus = ['a', 'c', 'b', 'f', 'g', 'm', 'i']
stus.sort(reverse=True)
print(stus)
通过反转 reverse 参数,安装 ASCII 码表进行降序排列,打印结果如下:
['m', 'i', 'g', 'f', 'c', 'b', 'a']
遍历列表
可以使用循环语句来实现,比如使用 while 语句:
stus = [1, 2, 3]
i = 0
while i < len(stus):
print(stus[i])
i += 1
列表中的元素将被依次打印出来。更好 方法是用 for 语句:
stus = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for s in stus:
print(s)
元组(tuple)
元组是一个不可变的序列,它的操作的方式基本和列表一致。**一般当我们希望数据不改变时,就使用元组,其余情况都使用列表。
创建元组
使用括号 () 来创建元组,例如:
my_tuple = ()
print(my_tuple, type(my_tuple))
这样就创建了一个空元组,打印结果如下:
() <class 'tuple'>
元组不可改变
如果你尝试对元组进行赋值操作,那么会抛出异常:
my_tuple = (1, 2, 3, 4, 5)
my_tuple[2] = 5 # 这是错误的 TypeError: 'tuple' object does not support item assignment
元组省略写法
元组可以不用括号 () 创建,可以是这样:
my_tuple = 1, 2, 3, 4
print(my_tuple, type(my_tuple))
上面就是创建元组,至少包含一个逗号 , :
my_tupe = 4,
print(my_tuple, type(my_tuple))
元组的解包(解构)
解包就是指将元组当中每一个元素都赋值给一个变量:
my_tuple = (1, 2, 3, 4)
a, b, c, d = my_tuple
print(a, b, c, d)
上面将元组分解开来,元组中的元素依次分配给 a, b, c, d 这四个变量。打印结果如下:
1 2 3 4
在对一个元组进行解包是,变量的数量必须和元组中的元素的数量一致。也可以在其中一个(只能一个)变量前边添加一个星号 *,这个变量就将会获取元组中所有的所有的元素
my_tuple = 1, 2, 3, 4
*a, b, c = my_tuple
print(a, b, c)
将前面两个元素放在变量 a 中,其余对象一一对应,打印结果如下:
[1, 2] 3 4
两个变量值互换
可以使用元组解包的方式,将两个值进行互换:
a = 1
b = 2
a, b = b, a
print(a, b)
这样就可以将 a 和 b 的值进行了换行,打印结果如下 :
2, 1
字典(dict)
什么是字典?
- 字典属于一种新的数据结构,称为映射(mapping)。
- 字典的作用和列表类似,都是用来存储对象的容器。
- 列表存储数据的性能很好,但是查询数据的性能很差。
- 在查询元素时,字典的效率是非常快的。
- 在字典中,每一个元素都有一个唯一的名字,通过这个唯一的名字,可以快速的查找到指定的元素。
在字典中,可以保存多个对象,每个对象都会有一个唯一的名字:
- 这个唯一的名字,我们称其为 键(key),通过 key 可以快速的查询 value 。
- 这个对象,我们称其为值(value)。
- 所以字典,我们也叫做 键值对(key-value) 结构 。
- 每一个键值对,我们称其为一项(item)。
创建字典
字典通过大括号 { } 进行创建,例如:
d = {} # 创建了一个空字典
字典创建的语法如下:
# 字典的值可以是任意对象
# 字典的键可以是任意的不可变对象(int、str、bool、tuple ...)
# 字典的键不能重复,一般用字符串 str。
{key:value, key:value, key:value}
例如:
d = {
'name': '孙悟空',
'age': 18,
'gender': '男'
}
print(d['name'], d['age'], d['gender'])
通过 键 索引到了 值 ,打印结果如下:
孙悟空 18 男
dict() 函数创建字典
使用 dict() 函数创建字典:
d = dict(name='孙悟空', age=18, gender='男')
print(d, type(d))
这种方式创建的字典,key 都是字符串,打印结果如下:
{'name': '孙悟空', 'age': 18, 'gender': '男'} <class 'dict'>
dict() 函数,也可以将一个包含有 双值子序列的序列 转换为字典:
d = dict([('name', '孙悟空'), ('age', 18)])
print(d, type(d))
子序列中,前面一个元素为键,后面一个元素为值,打印结果如下:
{'name': '孙悟空', 'age': 18} <class 'dict'>
检查是否包含某个键
可以使用 in 检查字典中是否包含某个键,比如:
d = {'a': 1, 'b': 2, 'c': 3}
print('hello' in d)
上面,检查 hello 这个键在不在 d 这个字典中,打印结果如下:
False
get() 方法获取键
可以使用 get() 方法,来获取键,如果键不存在,返回 None 。比如:
d = {
'name': '孙悟空',
'age': 18,
'gender': '男'
}
print(d.get('name')) # 返回键值孙悟空
print(d.get('hello')) # hello 键没有,返回 None
print(d.get('hello', '没有hello')) # 没有hello键时,返回 '没有hello'
如果直接使用 d['hello'] 去索引字典,程序会因为字典没有这个键而报错,通过get()方法则不会报错,打印结果如下:
孙悟空
None
没有hello
修改字典
可以通过 d[key] = value 的方式,来进行字典元素值的修改:
d = {'name': '孙悟空','age': 18,'gender': '男'}
d['name'] = 'sunwukong'
print(d)
上面,修改了键为 name 的值,打印结果如下:
{'name': 'sunwukong', 'age': 18, 'gender': '男'}
如果没有对应的键,则在字典末尾自动添加,比如:
d = {'name': '孙悟空','age': 18,'gender': '男'}
d['address'] = '花果山'
print(d)
上面,因为没有 address 这个键,所以,就自动在末尾添加一个键值对 'address': '花果山',打印结果如下:
{'name': '孙悟空', 'age': 18, 'gender': '男', 'address': '花果山'}
可以使用 setdefualt(key[, default]) 方法,来向字典添加 key-value :
- 如果 key 已经存在于字典中,则返回 key 值,不会对字典做任何操作。
- 如果 key 不存在,则向字典中添加这个 key ,并设置 value 。
d = {'name': '孙悟空', 'age': 18, 'gender': '男'}
d.setdefault('name', '猪八戒')
print(d)
d.setdefault('address', '花果山')
print(d)
所以,打印结果如下:
{'name': '孙悟空', 'age': 18, 'gender': '男'}
{'name': '孙悟空', 'age': 18, 'gender': '男', 'address': '花果山'}
合并字典
可以使用 update() 方法,合并字典,如果遇到相同的键,则更新这个键的值。比如:
d = {'a': 1, 'b': 2, 'c': 3}
d2 = {'d': 4, 'e': 5, 'f': 6, 'a': 7}
d2.update(d)
print(d2)
d 被合并到了 d2 后面,相同的键名 a 的对应的值被更新了,打印结果如下:
{'d': 4, 'e': 5, 'f': 6, 'a': 1, 'b': 2, 'c': 3}
值对个数
可以通过 len() 函数,获取字典中键值对的个数,比如:
d = {'a':1, 'b':2, 'c':3}
print(len(d))
删除字典中的键值对
可以使 del 删除字典中的一个键值对,例如:
d = {'a': 1, 'b': 2, 'c': 3}
del d['a']
print(d)
上面,就会把键值对 'a': 1 删除掉,打印结果如下:
{'b': 2, 'c': 3}
如果没有对应的键名,则会报错,比如:
d = {'a': 1, 'b': 2, 'c': 3}
del d['f'] # 这里会报错
还可以使用 popitem(), 随机删除 字典中的一个键值对,一般都会删除最后一个键值对:
- 删除之后,它会将删除的 key-value 作为返回值返回。
- 返回的是一个元组,元组中有两个元素,第一个是已经删除的 key,第二个是删除的 value 。
d = {'a': 1, 'b': 2, 'c': 3}
result = d.popitem()
print('result =',result)
print(d)
打印结果如下:
result = ('c', 3)
{'a': 1, 'b': 2}
还可以使用 pop() 方法进行删除指定的键值对:
- 根据 key 删除字典中的 key-value 。
- 如果删除不存在的 key ,会抛出异常。
- 如果指定了默认值,当遇到不存在的 key 时,不会报错,而是直接返回默认值。
d = {'a': 1, 'b': 2, 'c': 3}
result = d.pop('a')
print('result =', result)
print(d)
result = d.pop('f', '键值不存在')
print('result =', result)
打印结果如下:
result = 1
{'b': 2, 'c': 3}
result = 键值不存在
清空字典(clear)
可以使用 clear() 方法,清空字典内的所有键值对:
d = {'a': 1, 'b': 2, 'c': 3}
d.clear()
print(d)
上面,清空了字典 d 内的所有键值对,打印结果如下:
{}
遍历字典
字典的遍历,有多种方法,可以使用 keys() 方法,获取 键值对 :
d = {'a': {'name': '猪八戒'}, 'b': 2, 'c': 3}
for k in d.keys():
print(k, d[k])
上面,打印结果如下:
name 沙和尚
b 2
c 3
也可以通过 value() 方法,但这样只获取到 值 :
d = {'name': '沙和尚', 'b': 2, 'c': 3}
for v in d.values():
print(v)
上面,打印结果如下:
沙和尚
2
3
还可以使用 items() 方法,它会返回序列,序列中包含有 双值子序列 :
d = {'name': '沙和尚', 'b': 2, 'c': 3}
print(d.items())
for k, v in d.items():
print(k, '=', v)
打印结果如下:
dict_items([('name', '沙和尚'), ('b', 2), ('c', 3)])
name = 沙和尚
b = 2
c = 3
集合(set)
这里这个集合,就是数学上说的那个集合。集合和列表相似,不同点在于:
- 集合中只能存储不可变对象。
- 集合中存储的对象时无序(不是按照元素的插入顺序保存)。
- 集合中不能出现重复的元素。
集合的创建
通过大括号 { } 创建集合:
s = {10, 2, 2, 2, 2, 2}
print(s, type(s))
上面,相同的集合元素 2 会被去掉,只保留一个 2,打印结果如下:
{10, 2} <class 'set'>
集合中,是不能有可变对象的,比如,下面写法会报错:
s = {[1, 2, 3], [4, 5, 6]} # TypeError: unhashable type: 'list'
还可以通过 set() 方法创建一个集合:
s = set() # 创建了一个空集合
s = set([1, 2, 3, 2, 3, 5]) # 列表转换为集合
print(s)
s = set('hello') # 字符串转换为集合
print(s)
s = set({'a': 1, 'b': 2}) # 使用set()将字典转换为集合时,只保留了键,值会被去掉
print(s)
上面代码,打印结果如下:
{1, 2, 3, 5}
{'h', 'o', 'l', 'e'}
{'b', 'a'}
集合是 无序的,无法直接索引:
s = {'b', 'a', 1, 2, 30}
print(s, type(s))
# print(s[0]) # 集合时无序的,不能这样直接索引
print(list(s)[0]) # 可以先通过list转换为列表,再索引,但用处不大
集合检查元素
可以使用 in 或者 not in 关键字来检查集合中元素存在情况。例如:
s = {'b', 'a', 1, 2, 30}
print('c' in s)
print('c' not in s)
字符 c 不在集合 s中,返回 False 和 True ,打印结果如下:
False
True
集合添加元素(add)
通过 add() 方法向一个集合中添加元素:
s = {1, 2, 7}
s.add(4)
print(s)
上面代码,打印结果如下:
{1, 2, 4, 7}
集合添加集合(update)
可以使用 update() 方法,将一个集合中的元素添加到当前集合中。例如:
s1 = set('hello')
s2 = set([1, 2, 3, 4])
s1.update(s2)
print(s1)
上面,将 s2 中的元素添加到了 s1 中,打印结果如下:
{1, 'l', 'e', 2, 3, 4, 'o', 'h'}
集合随机删除元素(pop)
可以使用 pop() 方法,随机删除集合中的一个元素,并返回被删除的元素:
s = {1, 2, 3, 4}
result = s.pop()
print(result)
print(s)
如果集合为空,程序会抛出异常。上面代码打印结果如下:
1
{2, 3, 4}
集合删除指定元素(remove)
使用 remove() 方法,删除集合中指定的元素:
s = {1, 2, 3, 4}
s.remove(2)
print(s)
只能删除集合里面有的元素,否则会抛出异常。打印结果如下:
{1, 3, 4}
交集运算
使用符号 & ,进行交集运算:
s1 = {1, 2, 3, 4}
s2 = {3, 4, 5, 6}
result = s1 & s2
print(result)
交集运算,不会影响 s1 和 s2 原来的值。打印结果如下:
{3, 4}
集合并集运算
可以使用符号 | 进行并集计算:
s1 = {1, 2, 3, 4}
s2 = {3, 4, 5, 6}
result = s1 | s2
print(result)
打印结果如下:
{1, 2, 3, 4, 5, 6}
集合差集运算
可以使用符号 - 进行差集运算:
s1 = {1, 2, 3, 4}
s2 = {3, 4, 5, 6}
result = s1 - s2
print(result)
上面,计算得到了,只在 s1 中有,在 s2 中没有的元素,打印结果如下:
{1, 2}
集合异或集运算
去掉两个集合中都有的元素,称为异或集运算 :
s1 = {1, 2, 3, 4}
s2 = {3, 4, 5, 6}
result = s1 ^ s2
print(result)
上面代码中,打印结果中,不包含两个集合中相同的元素(也就是交集部分的元素):
{1, 2, 5, 6}
集合子集检查
首先,要理解这么一个数学概念:
如果a集合中的元素全部都在b集合中出现,那么这个b集合就是a集合的子集,b集合是a集合的超集。
使用符号 <= ,检查一个集合是否是另一个集合的子集:
s1 = {1, 2, 3}
s2 = {1, 2, 3, 4, 5, 6}
print(s1 <= s2)
上面打印结果如下:
True
使用符号 >= ,检查一个集合是否是另一个集合的超集:
s2 = {1, 2, 3, 4, 5, 6}
s1 = {1, 2, 3}
print(s1 >= s2)
上面打印结果如下:
True
数学上:
如果超集b中含有子集a中的所有元素,并且b中还有a中没有的元素,则b就是a的真超集,a 是 b 的真子集。
使用符号 < 检查真超集,> 检查是否是真子集。
序列的元素排序
sort() 方法排序
sort() 方法,用来对列表中的元素进行排序。例如:
my_list = ['cc', 'asds', 'bbb', 'ddddddd']
my_list.sort()
print(my_list)
使用了 sort() 方法之后,重新进行了排序(按照unicode表排序),打印结果如下:
['asds', 'bbb', 'cc', 'ddddddd']
在 sort() 中,有接收一个关键字参数 key :
key 需要一个函数作为参数,当设置了函数作为参数
list.sort(key=函数),则列表的值依次作为函数的参,函数有返回值,sort() 就会在这些返回值中比较。
比如:
my_list = ['cc', 'asds', 'bbb', 'ddddddd']
my_list.sort(key=len)
print(my_list)
上面, my_list.sort(key=len),那么 my_list 中的元素,依次作为 len 的参数,len 返回字符串的长度,然后 sort() 方法就比较这些长度。直观效果就根据字符串长度进行排序,打印结果如下:
['cc', 'bbb', 'asds', 'ddddddd']
sort() 方法只能对元素是同一数据类型的序列进行排序,比如 [1, 'abc'] 这个列表 sort() 就不能排序,会报错,可以使用类型转:
my_list = ['cc', 'asds', 'bbb', 'ddddddd', 1, 2, 3, 4]
my_list.sort(key=str)
print(my_list)
sorted() 函数排序
sort() 方法只能对列表进行排序,而且会影响原来的对象。而 sorted() 函数:
- 可以对任意的序列进行排序。
- 不会对原序列有影响,而是返回一个新的对象。
例如:
my_char = '369852147789456321'
print('排序前:my_char =', my_char)
temp = sorted(my_char,key=int)
print(temp)
print('排序后:my_char =', my_char)
上面对字符串转换为 int 数据类型,并对其进行排序,打印结果如下:
排序前:my_char = 369852147789456321
['1', '1', '2', '2', '3', '3', '4', '4', '5', '5', '6', '6', '7', '7', '8', '8', '9', '9']
排序后:my_char = 369852147789456321
双值序列
序列中只有两个字值的序列叫做 双值序列,比如下面的都是双值序列:
[1, 3]
['a', 1]
'ab'
子序列
如果序列中的元素也是序列,那么称这个元素为 子序列。比如:
[(1, 3), (3, 5)]
这就获取了字典 d 中的键值对个数,打印结果如下:
3
浅复制
可以使用 copy() 方法进行复制操作,将一个字典 d 复制到 d2 中。注意:
- 浅复制只是简单复制对象的值,如果值是一个 可变对象,这个可变对象不会被复制。
d = {'a': {'name': '猪八戒'}, 'b': 2, 'c': 3}
d2 = d.copy()
d2['a']['name'] = '孙悟空'
d2['b'] = 11
print('d =', d, id(d))
print('d2 = ', d2, id(d2))
上面, d 和 d2 是相互对立的,但键值对 'a': {'name': '猪八戒'} 的值是一个可变对象,d2['a']['name'] = '孙悟空' 会使得 d 和 d2都发生改变。可以理解为,字典 d 和 d2 的键值对的值只是保存了变量的地址。
第五章 函数(Function)
函数简介
什么是函数?
- 函数也是一个对象。
- 对象时内存中专门用来存储数据的一块区域。
- 函数可以用来保存一些可执行的代码,并且可以再需要时,对这些语句进行多次的调用。
- 函数代码不会立即执行,需要调用时才执行。
函数的创建
创建一个函数这个操作,被称为 函数定义:
def 函数名([形参1, 形参2, ... 形参n]):
代码块
例如:
def fn():
print('这是一个函数!')
函数的使用
函数的使用被称为 函数调用,语法如下:
函数名()
例如:
def fn():
print('这是一个函数!')
fn()
函数的参数
在函数定义时,可以在函数名后的括号 () 中定义数量不等的形参,多个形参之间用逗号 , 隔开。
- 形参(形式参数):定义形参就相当于在函数内部声明了变量,但并不赋值。
- 实参(实际参数):如果函数定义时,指定了形参,那么在调用函数时也必须传递实参。
例如:
def fn2(a, b):
print('a =', a)
print('b =', b)
fn2(1, 2)
函数默认值
定义函数时,可以赋值一个默认参数:
def fn2(a, b=10):
print('a =', a)
print('b =', b)
fn2(1)
上面代码中,b=10就是给参数b一个默认值,如果在调用函数时,不给这个参数赋值,则使用默认参数。
实参的传递方式
参数的传递有多种:
- 位置参数:通过位置判断是哪个参数。
- 关键字参数:根据参数名去传递参数。
例如:
def sum(a, b):
print('a =', a)
print('b =', b)
sum(1, 2) # 位置传递参数的方式
sum(b=1, a=2) # 参数名传递参数的方式
sum(1, b=2) # 位置和关键字混合,关键字参数必须放在后面
注意:位置和关键字混合,关键字参数必须放在后面。
实参的类型
对于一个函数,它的参数可以是任意类型。比如:
def fn4(a, b, c):
print(a, b, c)
def fn3(a, b):
print(a, b)
fn4(1, 2, 3)
fn3('hello',fn4)
打印结果如下:
hello <function fn4 at 0x0000000003E8C950>
1 2 3
函数对外部变量的影响
如果在函数中,对形参变量进行重新赋值,那么不会影响其它变量。
def fn5(a):
a = 0
print(a)
c = 10 # 变量重新赋值,指向新的对象。
fn5(c)
print(c) # 变量 c 没有受到影响。
上面代码中,函数 fn5 对 a 变量重新赋值,使其指向新的对象 0,那么 c 的值不会改变,打印结果如下:
0
10
如果操作中,是修改对象,将对象里面的值 value 修改了,那么 会影响到其它变量 :
def fn6(a):
a[0] = 30 # a 和 c 指向了同一个对象
print(a)
c = [1, 2, 3] # c 指向 [1, 2, 3]
fn6(c)
print(c)
上面代码中,a 和 c 指向了同一个对象,修改 a 就会导致 c 也改变。打印结果如下:
[30, 2, 3]
[30, 2, 3]
如果你不想它改变,可以通过浅复制,传入一个对象的副本:
def fn6(a):
a[0] = 30 # 这时,a 指向了 [1, 2, 3] 的副本,和 c 指向的不是一个对象
print(a)
c = [1, 2, 3] # c 指向 [1, 2, 3]
# 也可以这样传一个列表的副本:fn6(c[:])
fn6(c.copy()) # 传入一个 [1, 2, 3] 的副本
print(c)
不定长参数
在定义函数是,可以再形参前边加上一个星号 * ,这样这个形参将会获取到 所有的实参,它将会将所有的实参保存到了 一个元组中。例如:
def fn(*a):
print(a, type(a))
fn(1, 2, 3, 4)
上面代码,*a 会接受所有的位置参数,并且会将这些实参统一保存到一个元组中(装包)。打印结果如下:
(1, 2, 3, 4) <class 'tuple'>
此外,带星号的参数,可以和其它参数配合使用,而且带星号的形参只能有一个:
def fn(a, b, *c):
print('a =', a)
print('b =', b)
print('c =', c)
fn(1, 2, 3, 4, 5, 6)
打印结果如下:
a = 1
b = 2
c = (3, 4, 5, 6)
注意:星号 * 参数,不一定要放在最后,可以放在任意位置,如果还有其它参数,需要用关键字指定说明:
def fn(*a, b, c):
print('a =', a)
print('b =', b)
print('c =', c)
fn(3, 4, 5, 6, b=1, c=2)
限定参数传递方式
函数参数传递,有位置传递和关键字传递,如果你想限定所有参数,必须以关键字方式传递,那么可以给函数参数先写一个星号 * :
def fn(*, a, b, c):
print('a =', a)
print('b =', b)
print('c =', c)
fn(b=1, c=2, a=3)
不定长关键字参数列表
如果是 ** 形参,那么可以接收任意多个 关键字参数 ,它会将这些参数统一保存到了一个字典中:
def fn12(a, b, **c):
print('a =', a)
print('b =', b)
print('c =', c)
fn12(a=1, b=2, f=3, d=4)
类似于 **c 这样的参数,必须放在形参列表最后。打印结果如下:
a = 1
b = 2
c = {'f': 3, 'd': 4}
函数参数解包
有时,你可能需要将一个序列中的元素一一传给函数,作为相应的实参,比如:
def fn(a, b, c):
print(a, b, c)
t = (1, 2, 3)
fn(t[0], t[1], [2])
但是这样写起来比较麻烦,所以这样写:
def fn(a, b, c):
print(a, b, c)
t = (1, 2, 3)
fn(*t)
上面 *t 这个操作,叫做 参数解包 。只要序列元素个数和函数形参个数一致即可。如果 要对字典进行解包,则需要 ** 符号 :
def fn(a, b, c):
print(a, b, c)
d = {'a': 1, 'b': 2, 'c': 3}
fn(**d)
将字典各个键值对的值分别作为函数的实参,打印结果如下:
1 2 3
函数的返回值
函数执行以后,可以给它返回一个结果,这个结果称为函数返回值。例如:
def fn():
return 100
print(fn())
return 后面可以跟任意类型的对象。如果不写 return 或者只写 return ,那么相当于 return None,这时函数返回 None 。return 之后,函数就返回了,函数就结束了,return 后面的代码将不再执行。
函数的递归
递归,就是自己引用自己。递归式函数,在函数中自己调用自己。比如:
def fn():
fn()
fn() # 这是一个无穷递归,内存会溢出。
上面是无穷递归函数,程序内存会溢出,不要去运行它。递归的整体思想是:
将一个大问题分解为一个个的小问题,直到问题无法分解时,再去解决问题。
递归式函数的两个主要条件:
- 基线条件: 问题可以被分解为的最小问题,当满足基线条件时,递归就不再执行了。
- 递归条件: 将问题继续分解的条件。
下面,使用递归方式,求 n 的阶层:
def factorial(n):
'''
该函数用来求任意数的阶乘
参数:
n 将要求的阶层的数字,对它求阶层
n 的阶层:n! = n * (n-1) * (n-1) * ... * 2 * 1
'''
# 基线条件
if n == 1:
return 1
# 递归条件
return n * factorial(n-1)
print(factorial(10))
上面,自定义一个函数 factorial() ,可以求一个数 n 的阶乘。打印结果如下:
3628800
获取函数的说明
可以通过文档内置函数 help() ,可以获取相关帮助信息:
help(print)
这样,就打印出函数的使用说明。这里是 print() 函数的说明,打印结果如下:
Help on built-in function print in module builtins:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
自定义文档字符串(doc str)
在定义函数是,可以再函数内部编写文档字符串,文档字符串就是函数的说明。当我们编写了文档字符串是,就可以通过 help() 函数来查看函数的说明。比如:
def fn(a, b, c):
'''
这是一个文档字符串的示例
函数的作用:
函数的参数:
a,
b,
c,
'''
return 1
help(fn)
这样,通过 help(fn) 就可以把函数的描述信息打印了处理,打印结果如下:
Help on function fn in module __main__:
fn(a, b, c)
这是一个文档字符串的示例
函数的作用:
函数的参数:
a,
b,
c,
形参的说明
可以在形参后面,加上:类型 ,对参数的对象数据类型进行说明:
def fn1(a:int, b:bool, c:str='hello') -> int:
-> int 意思是返回值是 int 类型的。 事实上,这样做只起到说明作用,方便阅读而已,对功能没有任何影响。比如,a 的值还是可以传任意类型的对象。
高阶函数
什么是高阶函数?
高阶函数,参数可以是函数,或者将函数作为返回值。
参数是函数
下面,给出一个示例,函数的参数是一个函数:
def fn2(func, lists): # 参数 func 是一个函数
temp = []
for n in lists:
if func(n) == True: # 参数 func 使用的位置
temp.append(n)
return temp
def fn(i):
if i % 2 == 0: # 判断是否是偶数
return True
return False
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(fn2(fn, my_list)) # 传了一个 fn 函数给 fn2
内建函数 filter()
内建函数 filter() ,可以从一个已知的序列中,过滤出符合条件的元素,并保存到一个新序列中,对原序列不影响。例如:
def fn(i):
if i % 2 == 0: # 判断是否是偶数
return True
return False
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
r = filter(fn, my_list) # 传了一个 fn 函数给 filter
print(list(r))
filter() 函数第一个参数,传入一个函数,规定了 过滤规则,第二个参数是原序列,返回值是过滤以后的序列。打印结果如下:
[2, 4, 6, 8]
匿名函数 lambda
lambda 函数表达式专门用来创建 一些简单的函数 ,它是函数创建的又一种方式。语法如下:
lambda 参数列表 : 返回值
例如:
lambda a, b: a + b
等价于:
def fn5(a, b):
return a + b
经常结合 filter() 函数:
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
r = filter(lambda i : i % 2 == 0, my_list)
print(list(r))
上面代码,打印结果如下:
[2, 4, 6, 8]
lambda 一般只写一些简单的表达式,不会去写一些太复杂的函数。匿名函数一般都是作为参数使用的,其它地方一般不使用。
遍历函数 map()
map() 函数可以对可迭代对象的所有元素做指定的操作,然后将其添加到一个新的对象中返回。例如:
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
r = map(lambda i : i + 1, my_list)
print(list(r))
上面,map() 函数对每个元素进行操作,打印结果如下:
[2, 3, 4, 5, 6, 7, 8, 9, 10]
闭包
将函数作为返回值返回时,也是一个高阶函数,也叫做 闭包 。例如:
def fn():
# 函数内部定义一个函数
def inner():
print('内部函数‘)
return inner # 将内部函数 inner 作为返回值返回
r = fn()
r()
上面代码,就形成一个闭包:
内部函数
什么时候用闭包函数?
用闭包函数,其中一个用途,通过命名空间的作用域特性,防止变量被意外修改。
比如:
def make_averager():
nums = [] # 内部变量,外部不能直接修改,起到保护作用。
def averager(n):
nums.append(n)
# 求平均值
return sum(nums) / len(nums)
return averager
averager = make_averager()
print(averager(10))
print(averager(20))
上面代码,我们的目标是使用 make_averager() 内部的 averager(n) 函数。如果我们把 nums 变量定义在外面,为全局变量,那么这个变量容被其它函数修改。如果定义在 averager(n) 函数内部,也不行,函数 averager(n) 执行完以后,就会把相关的变量释放掉。打印结果如下:
10.0
15.0
装饰器
同通过装饰器,可以在不修改原来函数的情况下,来对函数进行功能扩展。例如:
def hello():
print('hello!')
def new_hello(old):
'''
参数:
old 要扩展的函数
'''
def new_function(*args, **kwarges):
print('开始执行。。。') # 扩展的内容
result = old(*args, **kwarges) # 原来的函数
print('结束执行。。。') # 扩展的内容
return result
# 返回新函数
return new_function
f = new_hello(hello) # 对 hello 函数进行扩展
f() # hello 函数扩展后的新函数
上面代码,对 hello 函数在功能上进行了扩展,得到扩展后的函数 。打印结果如下:
开始执行。。。
hello!
结束执行。。。
上面效果,就是装饰器要实现的效果,使用装饰器的写法是:
def new_hello(old):
'''
参数:
old 要扩展的函数
'''
def new_function(*args, **kwarges):
print('开始执行。。。') # 扩展的内容
result = old(*args, **kwarges) # 原来的函数
print('结束执行。。。') # 扩展的内容
return result
# 返回新函数
return new_function
@new_hello # 这是装饰器
def say_hello():
print('hello')
其中, new_hello 就是 装饰器。函数 say_hello 前加上 @new_hello ,这是对函数 say_hello() 使用装饰器 new_hello ,say_hello() 的内容就会替代 old() 函数 。实现的效果和上面的一样。如果有多个装饰器,还可以进行嵌套:
@装饰器1
@装饰器2
@装饰器n
def 新函数名():
扩展的代码块。
从上面也可以看出,装饰器只能从外部扩展一个函数,不会修改函数内部代码内容结构。
Python的内置函数
input() 函数
input 函数,是 用来获取用户的输入:
- input() 函数调用后,程序会立即暂停,等待用户输入。
- 用户输入完内容以后,点击回车,程序才会继续向下运行。
a = input()
print('用户输入的内容:', a)
input() 函数可以带参数,在等待用户输入之前,提前打印出一个字符串:
a = input('请输入内容后回车:')
print('用户输入的内容:', a)
你可以使用 input() 函数,使得程序暂时结束:
print('Hello')
input('按回车退出...')
input() 函数返回值是 str 类型,所以,有时我们需要类型转换:
age = int(input('请输入你的年龄:'))
if age >= 18 :
print('你已经成年了!')
range()函数生成自然数序列
range() 函数,用于生成一个自然数序列,比如:
r = range(5)
print(r)
print(list(r))
上面,生成一组 0, 1, 2, 3, 4 的序列数,打印结果如下:
range(0, 5)
[0, 1, 2, 3, 4]
此外,还可以指定 范围和步长,例如:
# range(start, end, step)
r = range(3, 10, 2)
print(r)
print(list(r))
上面,起始位置为3,结束位置为(10-1=9),步长为 2 ,打印结果如下:
[3, 5, 7, 9]
range() 和 for 循环配合使用
range() 经常和 for 循环配合使用:
for i in range(5):
print(i)
range(5) 生成一个序列 [0, 1, 2, 3, 4],for 循环一次将这个向量的元素赋值给变量 i,打印结果如下:
0
1
2
3
4
第六章 对象(Object)
什么是对象
对象有以下特点:
- 对象是内存中专门用来存储数据的一块区域。
- 对象中可以存放各种数据(比如:数字、布尔值、代码)
- 对象由三部分组成:
- 对象的标识(id)
- 对象的类型(type)
- 对象的值(value)
可变对象
列表就是一个可变对象。可变对象的可变,指的是 value值 可变,而不是 id 和 type 可变,标识和类型是不可变的。
- 列表就是一个可变对象
a = [1,2,3]
- a[0] = 10 (改对象)
- 这个操作是在通过变量去修改对象的值
- 这种操作不会改变变量所指向的对象
- 当我们去修改对象时,如果有其他变量也指向了该对象,则修改也会在其他的变量中体现
- a = [4,5,6] (改变量)
- 这个操作是在给变量重新赋值
- 这种操作会改变变量所指向的对象
- 为一个变量重新赋值时,不会影响其他的变量
- 一般只有在为变量赋值时才是修改变量,其余的都是修改对象 ### 面向对象(oop)
Python 是一门 面向对象 的编程语言。所谓面向对象的语言,简单理解就是:
语言中的所有操作都是通过对象来进行的。
而面向过程的编程的语言,简单理解就是:
面向过程指将我们的程序的逻辑分解为一个一个的步骤,来完成程序。
面向过程的编程方式,符合我们人类的思维,编写起来相对比较简单。但可复用性比较低,难于维护。而面向对象:
- 面向对象的编程思想,将所有的功能统一保存到对应的对象中。
- 面向对象的编程方式,便于阅读,易于维护。
- 面向对象同样要有面向过程的编程基础,只是过程要写入到一个对象中,在对象中实现。
- 面向对象,不太符合常规思维,编写起来麻烦一点。
面向对象的思想,应该是 找对象 -> 实现对象功能 。
面向对象的三大特征
封装
- 确保对象中的数据安全
继承
- 确保了对象的可扩展性
多态
- 保证了程序的灵活性
类(class)
什么是类?类,简单理解就是:
类相当于图纸,在程序中我们需要根据类来创建对象。
比如,建房子,我们根据设计图来建房子。设计图就是类,房子就是对象。其中:
我们也称对象是类的实例(instance)
此外:
如果多个对象是通过一个类创建的,我们称这些对象是一类对象。
事实上,我们使用的 int 、float 、str 等都是类:
a = int(10)
# 等价于
a = 10 # 事实上,就是使用了 int() 这个类的构造器,创建了一个 int 类的一个实例 a
类的创建
类的创建被称为 类的定义,现实生活中,实际上,所有的事物都由两部分构成:
- 数据(属性)
- 行为(方法)
我们可以在类的代码块中,定义变量和函数,变量就是该类的公共属性,函数就是该类的公共方法。使用 关键字 class 创建一个类,类名单词首字母一般大写:
class MyClass():
# 目前这个类是空的,没有内容
pass
类的实例
可以使用一个类,创建一个类的实例:
class MyClass():
pass
mc = MyClass() # 使用类 MyClass 创建了一个实例 mc
类的实例检查
使用 isinstance() 函数,可以检查一个对象是否是某个类的实例:
class MyClass():
pass
mc = MyClass() # 使用类 MyClass 创建了一个实例 mc
result = isinstance(mc, MyClass)
print(result)
对象 mc 是使用类 MyCalss 创建的实例,所以,打印结果如下:
True
这样就定义了一个名为 MyClass 的类。
类的属性
在类内部定义的变量,叫做 类的属性 。例如:
# 定义一个代表人类的类
class Person:
name = '孙悟空' # 公共属性,所有实例都可以访问
# 创建一个 Person 类的实例
p1 = Person()
print(p1.name) # 调用属性
上面,在类内部,创建了一个公共属性 name 。
类的方法
在类内部定义的函数,叫做 类的方法 。例如:
# 定义一个代表人类的类
class Person:
# 类内的函数,叫做类的方法
def say_hello(self):
print('你好!')
# 创建一个 Person 类的实例
p1 = Person()
# 方法调用:对象.方法名()
p1.say_hello() # 调用方法
上面,在类 Person 内部,创建了一个方法 say_hello() 。使用该类创建的对象,也就具有了该方法。注意:定义方法是,方法必须至少要有一个形参。 方法的第一个参数,就是对象本身,一般命名为 self :
# 定义一个代表人类的类
class Person:
# 类内的函数,叫做类的方法
def say_hello(self):
print('self: ', self)
# 创建一个 Person 类的实例
p1 = Person()
# 方法调用:对象.方法名()
p1.say_hello() # 调用方法
print('p1: ', p1)
上面代码,p1 和 self 都指向同一个地址,打印结果如下:
self: <__main__.Person object at 0x0000000003F37BE0>
p1: <__main__.Person object at 0x0000000003F37BE0>
类的实例的属性
有时,类内缺少某个你想要的属性,除了修改类以外,你还可以在创建实例后,在对象中添加一个你需要的属性:
# 定义一个代表人类的类
class Person:
name = '孙悟空' # 公共属性,所有实例都可以访问
p = Person()
p.age = 18 # 在创建对象实例以后,为实例添加了一个 age 属性
print(p.age)
上面的 age 经常被称为 类实例的属性 ,只有当前实例 p 有这个属性,使用 Person 类创建的其它对象,没有该属性。
类的__init__方法
在类中,可以定义一些特殊方法,这些方法命名是以双下划线 __ 开头,以双下划线 __ 结尾,比如:
__init__(self)
这些方法都是基本上都是内置的特殊方法,有专门用途,比如 __init__() 方法,是创建类时,用来初始化一些数据或打印一些信息。例如:
class Person:
# 2.类创建结束后调用
def __init__(self, name):
self.name = name
print('这里是init语句')
# 3.外部调用
def say_hello(self):
print('这里是say_hello方法')
# 1.创建类时,顺序执行
print('这里是Person类')
# 依次执行步骤1、步骤2
p1 = Person('wwy') # 创建实例
# 外部调用方法,执行步骤3
p1.say_hello() # 调用类方法
上面,创建 Person 类实例时,先执行类内相关代码块,实例创建好以后,离开创建实例那一刻,调用 __init__() 特殊方法。打印结果如下:
这里是Person类
这里是init语句
这里是say_hello方法
类的创建流程
从上面 特殊方法 中,你也可以看出,执行 p1 = Person('wwy') 语句创建对象的流程如下:
- 首先,创建一个变量
p1。 - 然后,在内存中创建一个新对象,对象类型是
Person类,p1指向这个新对象,通过变量p1能索引到这个对象。创建过程,会执行类内部的代码块。 - 最后,对象创建结束好,创建过程结束后,类自动调用 特殊方法 执行,比如上面的
__init__()方法。
上面的步骤中, 类似于 __init__() 这样的特殊方法,在创建类的实例时,可以用来初始化变量和打印一些信息等。
类的基本结构
下面是类的基本结构:
class 类名([父类]) :
公共的属性...
# 对象的初始化方法
def __init__(self,...):
...
# 其他的方法
def method_1(self,...):
...
def method_2(self,...):
...
...
类的封装
封装是面向对象程序设计的三大特性之一 。封装是指:
隐藏对象中一些不希望被外部所访问到的属性和方法。
类的 setter 方法
在类内部,添加一个关于 setter() 设置属性值的方法,那么,就可以通过这个方法对属性进行赋值操作,例如:
class Person:
def __init__(self, name):
self.name = name
def set_name(self, name): # 添加 setter 方法
self.name = name
p = Person('魏文应')
p.set_name('孙悟空') # 使用 setter 方法
print(p.name)
上面代码,在类中添加 set_name() 方法,通过 p.set_name('孙悟空') 方式修改类的属性,而不是使用 p.name = '孙悟空' 这种方式修改属性。
类的 getter 方法
在类内部,添加一个关于 getter() 获取属性值的方法,那么,就可以使用这个方法获取到类的实例中的属性值,例如:
class Person:
def __init__(self, name):
self.name = name
def get_name(self): # 添加 getter 方法
return self.name
p = Person('魏文应')
print(p.get_name()) # 使用 getter 方法
上面代码,在类中添加 getter() 方法,通过 p.get_name() 方法获取属性值,而不是使用 p.name 的方式获取属性值。
类的属性伪隐藏
双下划线 __ 开头的属性,属性名会发生转换,比如:
class Person:
def __init__(self, name):
self.__name = name
def print_name(self):
print(self.__name) # 类内部可以这样使用
p = Person('孙悟空')
# print(p.__name) # 不可以这样使用,会报异常
print(p._Person__name) # __name 被转换为了 _Person__name
p.print_name()
上面,__name 被转换为了 _Person__name ,也就是说 __属性 转换为了 _类名__属性 。达到隐藏的目的,但这只是 伪隐藏,外部依然可以访问和修改属性 。
类的属性提醒隐藏
使用单下划线 _ 开头的属性,只是提醒程序员,最好不要在外部直接修改属性,例如:
class Person:
def __init__(self, name):
self._name = name
def print_name(self):
print(self._name) # 类内部可以这样使用
p = Person('孙悟空')
print(p._name) # 提醒程序员,最好不要这样使用属性
p.print_name()
上面代码中,可以看到,p._name 依然还是可以再外部可以直接使用,只是 起到提醒作用,建议不要这么使用而已 。
类的属性设置
在属性设置时,有时我们还是希望像 p.name='孙悟空' 一样来设置属性值,而不是使用 p.set_name('孙悟空')。但我们还是必须要使用 set 方法,例如:
class Person:
def __init__(self, name):
self._name = name
# 添加为 property 装饰器,用来将一个get方法,转换为对象的属性
# 使用 property 装饰的方法,必须和属性名是一样的
@property
def name(self):
print('这里是get方法:')
return self._name
# 必须要有 getter 方法
@name.setter
def name(self, name):
print('setter 方法调用:')
self._name = name
p = Person('猪八戒')
# print(p.name())
print(p.name) # 实际调用了 name get方法,相当于 p.name()
p.name = '孙悟空' # 实际调用了 name set方法,相当于 p.name('孙悟空')
print(p.name)
这时,方法名应该和属性名name保持一致,在 get 方法前加上 @property 装饰器,这样就可以使用 p.name 获取 name 属性的值了,而实际是调用的是 p.name() 方法。如果要在 set 方法加 @name.setter 装饰器,则必须先要有对应的 get 方法。其实:
上面操作,只是看起来像对属性直接操作,实则调用了方法。
类的继承
继承是面向对象编程的三大特性之一 。例如:
class Animal:
def run(self):
print('动物会跑~~~')
def sleep(self):
print('动物睡觉~~~')
# Dog 类继承了 Animal 的一些特性
class Dog(Animal):
def bark(self):
print('汪汪汪~~~')
d = Dog()
d.run() # 继承了 Animal run()方法
d.sleep() # 继承了 Animal sleep()方法
d.bark() # bark() 是 Dog 特有的方法
打印结果如下:
动物会跑~~~
动物睡觉~~~
汪汪汪~~~
如果不写父类,那么默认的父类是 object 。 在上面代码的基础上,添加下面代码:
class Hashiqi(Dog):
def fang_sha(self):
print('我是哈士奇,狗的一种!')
h = Hashiqi()
h.run() # 继承了父类的父类的方法
上面可以看出,子类可以继承父类的父类的方法。可以使用 issubclass() 函数,用来检测类 a 是否是类 b 的子类,例如:
print(issubclass(Hashiqi, Animal))
上面 Hashiqi 是 Animal 的一个子类,所以返回结果为 True 。
类的方法的重写
在子类中,重写了父类中的方法:
class Animal:
def run(self):
print('动物会跑~~~')
class Dog(Animal):
# 子类Dog中,重写了run方法
def run(self):
print('狗跑~~~')
d = Dog()
d.run() # 优先使用当前对象的方法,如果没有再去父类中查找。
上面代码,打印结果如下:
狗跑~~~
类的 __init__ 方法的重写
下面,子类对父类的 __init__ 方法进行重写:
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, age): # 对 __init__ 进行重写
self.name = name
self.age = age
d = Dog('孙悟空', 18)
print(d.name)
print(d.age)
不过,我们还希望要执行父类的一些初始化操作,可以这样:
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, age):
# super()获取父类,
# 并且通过 super() 返回对象调用父类方法时,不需要传递 self
super().__init__(name)
self.age = age
d = Dog('孙悟空', 18)
print(d.name)
print(d.age)
这样,父类的 __init__() 也得到了执行。
类的多重继承
class A(object):
def test(self):
print('A')
class B(object):
def test(self):
print('B')
class C(A, B): # 父类有两个:A和B
pass
c = C()
c.test()
c.test()
上面类C父类有两个,一个类A,一个类B。**如果父类A和父类B,有相同名称的方法,则先使用写在 C 父类列表前面的那个:C(A, B) , 类A在前面,所以使用了类A的方法test。打印结果如下:
A
A
为了性能考虑:
没有特殊要求,尽量不使用多重继承。
类的查看父类
我们可以通过查看 __bases__ 属性值,查看一个类的父类有哪些:
class A(object):
pass
class B(object):
pass
class C(A, B): # 父类有两个:A和B
pass
print(C.__bases__)
上面代码,会打印出类C的父类,返回一个元组。打印结果如下:
(<class '__main__.A'>, <class '__main__.B'>)
类的多态
多态是面向对象编程的三大特征之一。 一个对象可以以不同的形态去呈现。
class A:
name = 'A'
class B:
name = 'B'
# 只要对象中含有name属性,它就可以作为参数传递
def say_hello(obj):
print('%s: hello!' % obj.name)
a = A()
b = B()
say_hello(a)
say_hello(b)
上面代码,体现的就是多态性,打印结果如下:
A: hello!
B: hello!
下面,通过 len() 方法,可以更好地说明多态性:
class A:
name = 'A'
class B:
name = 'B'
def __len__(self):
return 10
a = A()
b = B()
print(len(b))
# print(len(a)) # 没有len方法,会报错
my_list = [1, 2, 3, 4]
my_str = 'hello'
print(len(my_list))
print(len(my_str))
上面,只要类中有 __len__ 魔术方法的,都可以作为 len() 内建函数的参数,而不管参数是什么类型。这就是说,不管鸭子是白的、黑的、还是鹅黄色的,只要会 “嘎嘎叫” 的,我们就把它认为是鸭子。
类属性和实例属性
类属性:
直接在类中定义的属性,叫做 类属性 。
例如:
类方法、实例方法、静态方法
什么是实例方法?
在类定义中,凡是以
self作为第一个参数的方法都是 实例方法。
什么是类方法?
在类定义中,使用
@classmethod来修饰的方法属于 类方法。类方法的第一个参数是cls。
什么是静态方法?
在类定义中,使用
@staticmethod来修饰的方法属于 静态方法。
类的垃圾回收
就像我们生活中会产生垃圾一样,程序在运行过程当中也会产生垃圾。
在程序中,没有被引用的对象就是垃圾。
例如:
class A:
def __init__(self):
self.name = 'A类'
a = A()
print(a.name)
a = None # 将a设置为了None,此时没有任何的变量对A()对象进行引用,它就变成了垃圾。
上面代码,一开始,变量 a 指向一个用类 A 创建的实例,当 a = None 时,a 变量被重新赋值了,这时又没有其它变量指向刚才用类 A 创建的那个实例,这时,这个实例占用的内存,就是垃圾 。事实上:
在 Python 中,有自动的垃圾回收机制,它会自动将这些没有引用的对象删除,所以我们不用手动处理垃圾回收。
python 会自动调用相应的 __del__() 魔术方法进行垃圾回收:
class A:
def __init__(self):
self.name = 'A类'
def __del__(self):
print('A() 对象被删除了', self)
a = A()
print(a.name)
a = None # python 自动调用 __del__() 魔术方法,进行垃圾回收。
当 a = None 时,没有变量指向 a = A() 时创建的实例,python 就自动调用 __del__() 方法进行内存回收处理。打印结果如下:
A类
A() 对象被删除了 <__main__.A object at 0x0000000003AACC88>
类的特殊方法(魔术方法)
特殊方法,也叫做 魔术方法。之前有说过,在类中,以双下划线 __ 开头和双下划线 __ 结尾命名的方法,叫做魔术方法。例如我们前面将到的 __init__() 方法。
类的 __str__ 方法
当我们打印一个对象时,实际上打印的是对象中特殊方法 __str()__ 的 返回值 :
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person('孙悟空', 18)
p2 = Person('猪八戒', 28)
print(p1)
print(p2)
上面代码,打印 Person 类的两个实例 p1 和 p2 ,打印结果如下:
<__main__.Person object at 0x0000000003B98BE0>
<__main__.Person object at 0x0000000003B98A20>
我们可以手动修改 __str()__ 方法:
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
# 修改了默认的 __str__ 方法
def __str__(self):
return 'Person [name=%s, age=%d]' % (self.name, self.age)
p1 = Person('孙悟空', 18)
p2 = Person('猪八戒', 28)
print(p1)
print(p2)
上面代码,在类中显式地添加了 __str__() 方法。打印结果如下:
Person [name=孙悟空, age=18]
Person [name=猪八戒, age=28]
__str()__ 方法在什么时候调用呢?
__str()__这个特殊方法,会在尝试将对象转换为字符串的时候调用。
有什么作用呢?
用来指定对象转换为字符串的结果。
例如,p1 转换为字符串时,转换结果为 Person [name=孙悟空, age=18] 。
类的 __repr__ 方法
它的作用和 __str__() 相近,只是 __repr__() 是在交互模式下使用的。我们在 Python 交互模式下:
>>> a = 'hello'
>>> print(a) # 这里调用的是 __str__() 方法
hello
>>> a # 这里调用的是 __repr__() 方法
'hello'
>>>
类的 __gt__ 方法
类的实例是不可直接比较大小的,需要提供相应的魔术方法,例如:
class Person(object):
def __init__(self, age):
self.age = age
def __gt__(self, other):
return self.age > other.age
p1 = Person(18)
p2 = Person(28)
# print(p1 <= p2) # 没有提供 __le__() 魔术方法,会报错
print(p1 > p2) # __gt__() 提供了比较大小的魔术方法,不会报错
上面,只有提供了相应的魔术方法,才可以比较大小。p1 > p2 的返回值,就是 __gt__() 方法的返回值。打印结果如下:
False
比较运算的魔术方法有(Documentation » The Python Language Reference » Data model):
object.__lt__(self, other) # 小于
object.__le__(self, other) # 小于等于
object.__eq__(self, other) # 等于
object.__ne__(self, other) # 不等于
object.__gt__(self, other) # 大于
object.__ge__(self, other) # 大于等于
类的 __bool__ 方法
当你想把对象转化为一个 bool 值时, __bool__() 可以设定转化规则。比如:
class Person:
def __init__(self, age):
self.age = age
def __bool__(self):
return self.age > 17
p1 = Person(16)
print(bool(p1))
上面代码,打印结果是 Flase 。
模块(module)
模块化,将一个完整的程序分解为一个一个小的模块,通过将模块组合,来搭建出一个完整的程序。采用模块化,将程序分别编写到多个文件中。模块化的优点:
- 方便开发
- 方便维护
- 模块可以复用
在 Python 中一个 .py 文件就是一个模块。
模块创建
要想创建一个模块,就是创建一个 .py 文件。
使用模块
在一个模块中,引入一个外部模块,语法如下:
import 外部模块名(后缀名.py不需要)
比如,现在有一个模块 test_module.py 内容如下:
print('这是test模块')
然后再创建一个 test.py 文件,在这个文件中引入 test_module.py 模块:
import test_module.py
打印结果如下:
这是test模块!
同一个模块可以被多次引入,但模块的实例只会创建一个。 比如:
import test_module
import test_module
import test_module
打印结果如下:
这是test模块!
模块别名
导入模块时,可以给模块起一个别名:
import test_module as tm
获取模块名
可以获取模块名称,如果直接执行下面语句:
print(__name__)
因为当前模块是主模块,所以打印结果如下:
__main__
如果在 test_module.py 中写入下面语句:
print(__name__)
在 test.py 中导入 test_module 模块:
import test_module
这时,print(__name__) 打印的结果就是当前模块名称:
test_module
访问模块中的变量、函数、类
访问语法:
模块名.变量名
例如,test_module.py 文件中,内容如下:
a = 10
b = 20
在 test.py 文件中,内容如下:
import test_module as tm
# print(test_module.a, test_module.b) # 这样也可以
print(tm.a, tm.b) # 为了方便,我们使用别名进行简写
这样,就可以引用 test_module 模块中的变量了,打印结果如下:
10 20
函数和类也是一样的。 比如,test_module.py 文件中,内容如下:
def fn():
print('hello')
class Person:
name = '魏文应'
在 test.py 文件中,内容如下:
import test_module as tm
tm.fn()
p = tm.Person()
print(p.name)
打印结果如下:
hello
魏文应
引入模块中的部分内容
比如,test_module.py 文件中,内容如下:
def fn():
print('hello')
class Person:
name = '魏文应'
在 test.py 文件中,内容如下:
from test_module import Person, fn
# from test_module import * # 引入test_module的全部内容,不推荐使用
fn() # 这样可以直接使用 fn(),而不用tm.fn()
p = Person() # 这样可以直接使用 Person(),而不用tm.Person()
print(p.name)
我们不推荐使用 from test_module import * ,因为这样会导入模块中的所有内容,容易导致变量、函数、类因为重名而发生意外改变,导致程序异常。
为引入的变量使用别名
在文件 test_module.py 中,内容如下:
def fn():
print('test_module 中的fn()函数')
比如,在 test.py 中,这样:
from test_module import fn as tm_fn
def fn():
print('test 中的fn()函数')
tm_fn()
这样,就可以很好的区分 test.py 和 test_module.py 中的 fn() 函数。
不引入模块中的变量
在模块中,以下划线 _ 开头的变量,不会被引入,只能在模块内部访问。比如:
_a = 30
b = 10
在 test.py 中,内容如下:
from test_module import *
# print(_a) # 这样会报错
print(b)
上面,b 变量可以正常引用,但变量 _a 不能。
编写测试代码
编写测试代码,这部分代码,只有当前文件作为主模块的时候才需要执行。而当模块被其它模块引入时,不需要执行。如何实现这个功能呢?此时:
只需我们在执行前,检查当前模块是否是主模块即可。
例如,在 test_module.py 中,内容如下:
def fn():
print('hello')
if __name__ == '__main__':
'''
请将模块的测试代码写在下面,当前模块被作为模块导入到其它模块时,这里的代码不会执行。
'''
fn()
在 test.py 中,内容如下:
import test_module as tm
这样,如果直接执行 test_module.py 模块,这时 test_module.py 作为 __main__ 模块,fn() 函数得到执行。而当 test_module.py 作为模块,导入 test.py 中,这时 test_module.py 不是 __main__ 模块,fn() 函数得不到执行。
包(Package)
包也是一个模块。当我们模块中代码过多时, 或者一个模块需要被分解为多个模块时,这时就需要使用到包。
普通的模块就是一个 py 文件,而包是一个 文件夹 。
包中必须包含一个 __init.py__ 文件。例如,创建一个 main.py ,内容如下:
from mypg import test1, test2
print(test1.a)
print(test2.b)
在和 main.py 同一个目录下,创建一个 mypg 文件夹,在这个 mypg 文件夹内,创建 __init__.py 、test1.py 、test2.py 三个文件。test1.py 内容如下:
a = 10
test2.py 内容如下:
b = 20
执行 main.py 文件,执行结果如下:
10
20
__pycache__ 文件夹
源代码文件夹中,解释器一般会自动生成一个 __pycache__ 的缓存文件夹,目的是提高执行速度,在第一次执行代码是,解释器生成二进制代码,下次执行时,就不需要重新生成二进制代码了。
Python 标准库
在标准库中,有很多功能的强大的库,随着 python 的安装而被安装。
sys模块
它里面提供了一些变量和函数,使我们可以获取到 Python 解析器的信息,或者通过函数来操作 Python 解析器。比如,可以使用 sys.argv 获取参数:
import sys
print(sys)
print(sys.argv)
打印结果如下:
<module 'sys' (built-in)>
['d:\\01_wwy-dir\\08_python-code-workspace\\02-learn-python\\lesson07\\07-03-sys.py']
sys.argv 的第一个参数是模块名,如果在命令行模式下,执行下面命令 python 07-07-sys.py a b c ,那么 a b c 分别是第2个参数,第3个参数, 第4个参数。另外,可以使用 sys.modules 查看当前引入的所有模块:
import sys
print(sys.modules)
sys.platform 打印当前运行平台:
import sys
print(sys.platform) # 我当前在 windows 上运行,打印结果是 win32 。
sys.exit() 程序结束执行:
import sys
print('在exit()之前!')
sys.exit() # 执行之后,就异常退出了。退出前还可以打印信息:sys.exit('程序异常结束了')
print('在exit()之后!')
pprint() 函数格式化打印
以易于阅读的格式,打印出内容:
import pprint
pprint.pprint(sys.modules)
os 模块
import pprint
import os
pprint.pprint(os.environ['path']) # 打印环境变量
使用 listdir() 方法,可以 获取指定目录的目录结构:
import os
from pprint import pprint
# 指定目录下的目录结构,返回列表,元素是文件或者文件夹名称
r = os.listdir('../')
pprint(r)
打印结果如下:
['01-03-布尔值和空值.py',
'01-04-类型检查.py',
'01-lesson-01.py',
'01_复制字符串.py',
'01_格式字符串.py']
获取当前所在的目录
使用 os.getcwd() 方法,可以获取当前所在的目录:
import os
from pprint import pprint
r = os.getcwd() # 获取当前所在目录
pprint(r)
打印结果如下:
'D:\\01_wwy-dir\\08_python-code-workspace\\02-learn-python\\lesson07'
切换目录
使用 os.chdir() 方法,切换当前所在的目录:
import os
from pprint import pprint
r = os.getcwd()
pprint(r)
os.chdir('../') # 修改当前所在目录
r = os.getcwd()
pprint(r)
打印结果如下:
'D:\\01_wwy-dir\\08_python-code-workspace\\02-learn-python\\lesson07'
'D:\\01_wwy-dir\\08_python-code-workspace\\02-learn-python'
### 创建和删除目录
就是创建或者删除文件夹,创建使用 os.mkdir() 方法,删除使用 os.rmdir() 方法:
import os
from pprint import pprint
# 创建一个目录
os.mkdir('test-dir')
pprint(os.listdir())
# 删除一个目录
os.rmdir('test-dir')
pprint(os.listdir())
第七章 异常和文件
异常
异常,就是程序中的错误。一旦发生异常,会导致程序立即终止,比如:
print(25 / 0)
上面代码,除以0,我们指定除数不能为0,程序执行报错:
ZeroDivisionError: division by zero
异常处理
使用 try 语句进行处理,语法如下:
try:
代码块(可能出现错误的语句)
except:
代码块(出现错误以后的处理方式)
else:
代码块(没有错误时要执行的语句)
上面的操作,就是将可能出错的代码放在 try 代码块中,如果没有出错,还会执行 else 的代码块。如果出错了,就执行 except 代码块,进行处理。例如:
try:
print(10 / 0) # 10/0,除数为0,除法错误,发生异常。
except:
print('哈哈哈,出现了异常!') # 发生异常以后,这里的代码得到执行。
else:
print('没有错误!')
异常的传播
异常发生以后,如果不处理,会抛给上一层调用函数,例如下面代码:
def fn():
print('Hello fn')
print(10 / 0)
def fn2():
print('Hello fn')
fn()
def fn3():
print('Hello fn')
fn2()
fn3()
抛出异常:
Traceback (most recent call last):
File "lesson07\07-04-异常.py", line 25, in <module>
fn3()
File "lesson07\07-04-异常.py", line 22, in fn3
fn2()
File "lesson07\07-04-异常.py", line 17, in fn2
fn()
File "lesson07\07-04-异常.py", line 12, in fn
print(10 / 0)
ZeroDivisionError: division by zero
如果没有处理异常,异常会抛给调用者,比如上面的,fn() 函数抛给 fn2() 函数,fn2() 函数抛给 fn3() 函数,最后 fn3() 函数抛给了 __main__ 代码块,都没有处理,程序终止并抛出异常。抛出的异常是一个类对象,比如 ZeroDivisionError 是专门用来记录除0错误:
print(ZeroDivisionError)
打印结果如下:
<class 'ZeroDivisionError'>
异常错误捕获
如果 except 后不跟任何内容,则它会处理捕获到的所有的异常。比如:
try:
print(c)
print(10 / 0)
except:
print('出现了错误!')
上面代码会出现 NameError 和 ZeroDivisionError 两种异常,而且都被捕获到了,except 代码块不加选择的就执行了。其实可以这样:
try:
print(c)
print(10 / 0)
except NameError:
print('出现了NameError错误!')
except ZeroDivisionError:
print('出现了ZeroDivisionError错误!')
在 except 后紧跟一个 异常类型(比如 NameError、ZeroDivisionError 等),这样,就可以区分不同的异常了。
捕获所有异常
Exception 是所有异常类的父类,所以如果 except 后跟的是 Exception ,它也会捕获到所有的异常(和不写的效果一样):
try:
print(10 / 0)
except Exception:
print('未知异常!')
查看异常对象
通过在下面方式,可以查看异常的一些信息:
try:
print(10 / 0)
except Exception as e: # 使用 as 将异常信息的对象赋值给了e
print('出现了异常:')
print(e)
print(type(e))
将保存有异常信息的对象,赋值给一个变量,打印信息如下:
出现了异常:
division by zero
<class 'ZeroDivisionError'>
完整的异常处理结构
语法结构如下:
try:
代码块(可能出现错误的语句)
except 异常类型 as 异常名:
代码块(出现错误以后的处理方式)
except 异常类型 as 异常名: # 这个结构的语句可以有多个
代码块(出现错误以后的处理方式)
else:
代码块(没有异常时要执行的语句)
finally:
代码块(该代码块总会执行)
try 必须要有,except 和 finally 只是要有一个,else 可有可无。
抛出异常
可以使用 raise 关键字,向外部抛出指定的异常。例如:
def add(a, b):
if a < 0 or b < 0:
raise Exception('两个参数中不能有负数!') # 手动抛出异常,强制处理。
r = a + b
return r
print(add(-1, 2))
出现异常,强制调用者处理,不处理程序就停止运行。打印结果如下:
Traceback (most recent call last):
File "./lesson07/07-05-异常.py", line 8, in <module>
print(add(-1, 2))
File "./lesson07/07-05-异常.py", line 3, in add
raise Exception('两个参数中不能有负数!')
Exception: 两个参数中不能有负数!
自定义异常类
自定义异常类,只需要创建一个类继承 Exception 即可:
class MyError(Exception):
pass
def add(a, b):
if a < 0 or b < 0:
raise MyError('自定义的异常')
r = a + b
return r
add(-1, 3)
打印结果如下:
Traceback (most recent call last): File "./lesson07/07-05-异常.py", line 22, in <module>
add(-1, 3)
File "./lesson07/07-05-异常.py", line 17, in add
raise MyError('自定义的异常')
__main__.MyError: 自定义的异常
文件(File)
对于文件,我经常对其进行操作:
- 通过 Python 程序来对计算机中的各种文件进行增、删、改、查等操作。
-
I/O(Input / Output)输入输出,有时也指文件操作。
对文件操作的步骤:
- 打开文件。
- 对文件进行各种操作(读、写), 然后保存。
- 关闭文件。
先创建一个 demo.txt 文本,测试用的内容如下(Lorem文本内容,是一个经典的测试文本):
Lorem ipsum dolor sit amet, te alia iudico delenit est, an ceteros neglegentur nec. Ut tacimates definitiones mei, vix nibh insolens ad, perfecto reformidans has ea. Homero fabellas insolens cu pri. Erant discere est at. Ius numquam vivendum ei, quodsi singulis usu in. Tota nobis mei no, eos saperet facilis ex.
Eos quot omnesque ea, expetendis definiebas et has, nominati iracundia quo no. At eum fugit electram. Te duo omnis mollis, eius partem referrentur ei duo, nec ea cetero appareat. Eum at error nihil nonumes. Mea id populo salutandi dissentiet, ea iudico reprimique sit, at vim debitis placerat. Eam id diam ridens.
Vel viris salutatus ea, eum ea temporibus accommodare. In indoctum convenire eos, at pri nemore meliore tractatos. Usu id errem mucius consulatu, et viris prodesset contentiones duo, eu mel dolorum periculis sadipscing. Eam no omnis legendos recusabo. Ei eos vide definitiones, animal tibique percipit in mel. Velit inimicus rationibus eam ut, eu hinc definitiones sit, sed solum tractatos dissentiunt ne.
Doming maiorum sea an, ei vel dolor philosophia. Nonumy utamur ad vix, in nam dico stet nullam. Cu vim illum dolores, ne prompta tibique aliquando mel. Has cu impedit vocibus cotidieque, vim tollit labore lobortis id. Alia eligendi mea no, quem mandamus salutatus eum et. Et decore facilisi forensibus sea.
Wisi aliquam complectitur eam ne. An meis electram nec. Sed adhuc autem accusata ei, ferri etiam an eam. Nec quaeque intellegam ut, nam eu alii viderer.
打开文件
在帮助文档 Documentation » The Python Standard Library » Built-in Functions 中,可以找到 open() 函数,使用这个函数打开文件:
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
例如:
file_name = 'demo.txt' # 当前目录下,你得有这个.txt文件
# 没有报错,说明打开无误
file_obj = open(file_name)
print(file_obj)
这样,文件 demo.txt 就被打开了,打印结果如下:
<_io.TextIOWrapper name='demo.txt' mode='r' encoding='cp936'>
文件可以使用相对路径,有下面三种写法:
file_name = '../test-dir/demo.txt' # 推荐用发
file_name = r'..\test-dir\demo.txt' # 前面加 r ,使用原始字符串
file_name = '..\\test-dir\\demo.txt' # windows 下还可以这么使用,但不推荐。
读取文件
可以使用 read() 方法,读取文件中的内容:
file_name = 'demo.txt'
# 没有报错,说明打开无误
file_obj = open(file_name)
content = file_obj.read()
print(content)
这样就打印出 demo.txt 文件中的内容。
关闭文件
调用 close() 方法关闭文件:
file_name = 'demo.txt'
# 没有报错,说明打开无误
file_obj = open(file_name)
content = file_obj.read()
print(content)
# 关闭文件
file_obj.close()
# content = file_obj.read() # 关闭文件之后,再执行这个语句会报错
with as 方式关闭文件
with ... as... 语句,可以自动关闭文件:
file_name = 'demo.txt'
with open(file_name) as file_obj:
# with 语句中,file_obj 是返回值。
# 此时这个文件只能在 with 中使用,一旦 with 结束则文件会自动 close()
print(file_obj.read())
# content = file_obj.read() # 文件已经被 with 语句关闭了,再执行这个语句会报错
文件不存在
我们经常要处理一个文件不存在的情况,当一个文件不存在时,就会抛出异常,我们可以使用下面方式进行防止抛出异常:
file_name = 'hello.txt'
try:
with open(file_name) as file_obj:
print(file_obj.read())
except FileNotFoundError:
print(f'{file_name} 文件不存在!') # 文件不存在
上面 hello.txt 这个文件不存在,需要进行异常处理,否则程序就终止了。上面使用了 try ... except ... 语句,进行异常处理。
文本内容为中文
现在有一个 demo2.txt 文件,文件内容是中文的,内容如下:
锄禾日当午
汗滴禾下土
谁知盘中餐
粒粒皆辛苦
当一个文本的编码不是 ASCII 编码时,打开文件就要指定编码方式:
file_name = 'demo2.txt'
try:
'''
调用open()来打开一个文件,可以分为两种类型:
一种是,纯文本文件(utf-8等编码)
一种是,二进制文件(图片、mp3、PPT等)
open() 默认的编码为None,所以处理文本文件时,必须指定编码
'''
with open(file_name,encoding='utf-8') as file_obj:
content = file_obj.read()
print(content)
except FileNotFoundError:
print(f'{file_name} 这个文件不存在!')
比如上面的 demo2.txt ,内容有中文,使用的是 utf-8 ,那么打开文件时就要指定编码方式为 utf-8 。
读取大文件
对于较大的文件,不要直接使用 read() 方法。read() 方法可以接收一个 size 参数,该参数用来指定要读取的字符的数量,默认值 size = -1 会将所有字符读取。例如:
file_name = 'demo2.txt'
try:
with open(file_name,encoding='utf-8') as file_obj:
content = file_obj.read(6)
print('第一次读:', content)
content = file_obj.read(6)
print('第二次读:', content)
except FileNotFoundError:
print(f'{file_name} 这个文件不存在!')
上面,每次只读 6 个字符。每一次读取都是在上次读取到的位置开始读取。读取完了,还继续读,返回一个空 '' 的字符串。打印结果如下(回车也算一个字符):
第一次读: 锄禾日当午
第二次读: 汗滴禾下土
可以使用 while 语句循环读取:
file_name = 'demo.txt' # 你需要有一个这样的测试文本文档
try:
with open(file_name, encoding='utf-8') as file_obj:
# 定义一个变量,来指定每次读取的大小
chunk = 100
# 创建一个循环来读取文件内容
while True:
# 每次读取 chunk 大小的内容
content = file_obj.read(chunk)
# 检查是否读取到内容
if not content:
# 内容读取完毕,退出循环
break
print(content, end='')
except FileNotFoundError:
print(f'{file_name} 这个文件不存在!')
单行读取(readline())
readline() 函数,也是读取文本文件的函数,每次只读取一行,一行一行读取(遇到回车表示一行结束):
file_name = 'demo.txt'
with open(file_name, encoding='utf-8') as file_obj:
print(file_obj.readline())
readlines() 函数,将文本中的每一行作为一个元素,保存到一个列表中:
file_name = 'demo.txt'
with open(file_name, encoding='utf-8') as file_obj:
r = file_obj.readlines()
print(r)
文件写入内容
向文本文件写入内容:
file_name = 'demo.txt'
# r 参数: 表示只读的。
# w 参数: 写入文件,
# 如果文件不存在,则创建文件,并写入内容。如果文件存在,则截断文件(删除原来的内容)。
# 可多次向文件中写入内容。
# a 参数: 追加内容,append
# 将新写入的内容添加到后面,原来内容不变。
#
# r+ : 可读可写,文件不存在会报错
# w+ : 可读可写,其它功能和 w 参数一致。
# a+ : 可读可写,追加内容。
with open(file_name, 'w', encoding='utf-8') as file_obj:
r = file_obj.write('Hello, How are you!')
print(r)
上面,想 open() 函数传递不一样的参数,就会有不一样的效果,write() 方法返回值是写入内容字符数。如果 不想覆盖已有的文件,那么就使用 x 参数:
# x 参数:如果文件存在,则报错; 如果不存在,就创建,防止文件被覆盖。
with open(file_name, 'x', encoding='utf-8') as file_obj:
r = file_obj.write('Hello, How are you!')
print(r)
读取二进制文件
默认情况下,读取的是文本文件,当需要读取二进制文件时,需要使用参数 b (binary) :
file_name = 'Python基础_130_文件的其他操作.avi' # 你需要当前文件夹下有这个视频文件
with open(file_name, 'rb') as file_obj:
r = file_obj.read(100)
print(r)
上面读取的是一个视频文件。打印结果如下:
b'RIFF\xb4H\x9e\x03AVI LIST\x94\x01\x00\x00hdrlavih8\x00\x00\x00k\x04\x01\x00\xaf\xa7\x1b\x00\x00\x00\x00\x00\x10\x08\x00\x00)*\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\xd3\x88\x0e\x00\xa0\x05\x00\x00\x84\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00LIST\xa2\x00\x00\x00strl'
你还可以将视频文件,读取出来,并保存到另外一个文件中:
file_name = 'Python基础_130_文件的其他操作.avi'
with open(file_name, 'rb') as file_obj:
new_name = 'test.avi'
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)
查看当前位置
当读取文件时,我们经常需要查看读取到哪个位置了,比如:
with open('demo.txt', 'rb') as file_obj:
file_obj.read(100)
# 查看当前到读取的位置
print('当前读到那了:', file_obj.tell())
这样,就可以查看到当前读取到哪个字符了。打印结果如下:
当前读到那了: 100
修改读取位置
读取文件时,可以通过 seek() 方法,改变当前读取的起始位置。例如:
with open('demo.txt', 'rb') as file_obj:
#file_obj.read(100)
# 指定读取起始位置
file_obj.seek(55)
file_obj.seek(10, 0) # 从文件起始位置数起
file_obj.seek(10, 1) # 从当前位置数起
file_obj.seek(-10, 2) # 从最后位置数起,负数时倒数。
# 查看当前到读取的位置
print('当前读到那了:', file_obj.tell())
删除文件
可以使用 os.remove() 方法删除文件:
import os
os.remove('aa.txt') # 把 aa.txt 这个文件
文件重命名
可以使用 os.rename() 方法对文件进行重命名:
import os
# os.rename('旧名字', '新名字')
os.remove('aa.txt', 'bb.txt') # 把 aa.txt 这个文件 该名为 bb.txt
实现剪切功能
可以使用 os.rename() 来实现文件的剪切的功能,移动一个文件的位置:
import os
# os.rename('旧名字', '新名字')
os.remove('aa.txt', 'C:/Users/331075/Desktop/aa.txt') # 改名后,保存到其它位置去