python学习笔记
该笔记记录了了自己学习python基础语法的一些记录,为了方便以后的复习
该笔记为学习阮一峰python教程过程中的记录
第一个python程序
python代码的运行
- 通过文本编译器,代码可以保存,但在运行过程中不能加入其他指令
- 使用python的交互命令,没输入一行指令,均会进行相应的运算
在写程序的过程中可以结合使用,将文本编译器的代码片段子在python交互命令中查看正确性
输入和输出
输入 input()
1
2
3
4
5
6# input返回值类型为字符串,优势需要类型转换
name = input()
# 输入该命令后,程序会等待用户输入,输入完成后回车,程序继续运行
# 为了有更好的交互,可以输出提示
name = input('请输入您的姓名:')输出 print()
1
2
3# 可以输出字符串,也可以输出变量,可以一次输出多个,以逗号隔开
# python会在,(逗号)分隔的变量间自动加空格 即运行结果为 (hello xiaoming)
print('hello', name)
python基础
python采用缩进的方式区分代码块
python的数据类型
整数
浮点数 (可用科学计数法表示 1.23e6 = 1230000)
复数
Number数据类型的转换 参考
字符串
- 单引号和双引号不能混用
- python允许使用三引号’’’内容’’’来实现格式不变的输出
- 在输出字符床前加r,表示忽略转移字符
- 如:print(r’aaa\nbbb’) 输出结果为: aaa\nbbb
布尔值 (首字母大写 True, False)
- 逻辑运算符 and与 or或 not非
空值 None
变量
- 变量在程序中就是用一个变量名表示了,变量名必须是大小写英文、数字和_的组合,且不能用数字开头
- python属于动态语言(即变量本身类型不固定)
- 与之对应的是静态语言(如C语言)静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。
- 理解变量在内存中的表示
常量
- 不能变的量,python中用大写字母来表示
字符串
- 字符编码(扩展)
- 由于计算子只处理数字,所以要想处理文本,就需要把文本转化为数字,这就需要用到编码表
- 最早计算机设计时,是8bit一个字节,所以一个字节可以表示的最大整数为255(二进制11111111)
- 由于计算机是由美国人发明的,所以最早只有127歌编码被储存到计算机中,这个编码表被称为ASCII编码。但随着计算机发展,各国都要用到计算机,如要处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去;各国有各国的标准,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。
- 因此,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
- ASBCII编码和Unicode编码区别
- 三种编码之间的关系
- python中的字符串
- 采用Unicode,即支持多国语言
- 编码encode(‘相应的编码方式’),解码decode() 详细解释
- 字符串的格式化表示
- %s等
- format()
- 字符编码(扩展)
列表 list
- 定义: list是一种有序的集合,可以随时添加和删除其中的元素
- 通过索引来随机list每个位置的元素 (也可以使用a[-1], 表示最后一个元素,-2,-3… 均可)
- list中数据元素类型可以不同 p = [‘qwe’, 12, [34, ‘0’]]
- 常用函数 len()长度 append()追加 insert(i, ‘fa’)插入指定位置 pop(i)删除指定位置元素
元组 tuple
- 另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦 初始化就不能修改
- 使用和list一样
- 注意: 只有1个元素的tuple定义时必须加一个逗号,否则()解释为运算中的小括号
- ‘可变的tuple’
字典 dict (无序,其数据的先后与存入的顺序无关)
定义: 在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度
和list的区别与比较
1
2
3>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95注意:
- 一个key只能对应一个value,多次对一个key放入value,后面的值会把前面的值冲掉
- 如果key不存在,则会报错(下面为两种避免的方法)
- 通过in判断key是否存在
- 通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value
- 注意:返回None的时候Python的交互环境不显示结果
set
- set和dict一样,也是一个key的集合,不过set不能添加value
- set的key也不能重复
- set的创建: s = set([1,2,3]) 需要传入一个list
- set的添加: s.add(key) 添加重复的key,原set不变
- set的删除: s.remove(key) 若删除的key不存在,会报错
类型转换了,
- 如: int(‘123’) float,tuple,list…
条件判断
1
2
3
4
5
6if a == 0:
代码块
elif a == 1:
代码块
else:
代码块循环
1
2
3
4
5
6
7
8
9
10
11#第一种
for item in items:
print(item)
#第二种
while a>0:
代码块
else: # 党条件不满组时执行else语句
代码块
#break , continue
函数
定义: 和大多数语言中的函数概念一样,为了避免代码的重复
注:
1、 函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”
1
2
3>>>a = abs
>>>a(-1)
12、 pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。pass还可以用在其他语句里,比如:
1
2if a>14:
pass3、 数据类型检查可以用内置函数isinstance()实现
函数的定义
1
2
3
4
5def my_abs(x):
if x>0:
return x
elif:
return -x
默认参数: 可以减小调用函数的复杂性
- 注: 定义默认参数要牢记一点:默认参数必须指向不变对象!
反例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def add_end(L=[]):
L.append('END')
return L
#正常调用时:
>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
#非正常调用时:
#第一次:结果正常
>>>add_end()
['END']
#第二次往后,结果不是预期效果
>>>add_end()
['END', 'END']
- 原因: Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
- 上述例子的修改
1
2
3
4
5
6#默认参数设为一个不变的量
def add_end(L=None):
if L == None:
return L = []
else
return L.append('END')
- 定义可变参数的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25def a(*numbers):
sum = 0
for n in numbsers:
sum = sum + n
return sum
#函数调用 在函数内部,参数numbers接收到的是一个tuple
>>>a(1,2,3)
6
#作用和传入一个元组和列表类似
def a_d(L):
sum = 0
for n in L:
sum = n + sum
return sum
>>>a_d([1,2,3])
6
#在函数内部,参数numbers接收到的是一个tuple
>>> nums = [1, 2, 3]
>>> calc(*nums)
14
- 注: 定义默认参数要牢记一点:默认参数必须指向不变对象!
关键字参数
- 两个**,相当于传入一个dict
1
2
3
4
5
6
7
8
9
10
11
12
13
14#定义
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
#传参的书写
#1. 不错的书写格式, 3种
>>> extra = {'city': 'Beijing', 'job': 'Engineer', sex='boy'}
>>> person('Jack', 24, city=extra['city'], job='Engnieer')
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer','sex: 'boy'}
#2. 简便写法
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
- 两个**,相当于传入一个dict
命名关键字参数
- 和关键字参数不同,这里是 命名,即传入关键字的名字必须满足参数要求(限制关键字参数的名字)
- 和关键字参数 ** kw不同,命名关键字参数需要一个特殊分隔符,后面的参数被视为命名关键字参数
- 命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错
- 命名关键字函数的参数初值可以不从最后一个开始赋(如例2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def person(name, age, *, city, job):
print(name, age, city, job)
#调用方式
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
#特例
def person(name, age, *args, city, job):
print(name, age, args, city, job)
#此函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就 **不再需要** 一个特殊分隔符*了
#例2
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
#调用
person('hu', 10, job='stu')
参数组合
- 在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
函数递归中的问题
- 在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
- 了解部分
- 尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
- 遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。
- 尾递归是指,在函数返回的时候,调用自身本身,并且,return语句 不能包含表达式 。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
1
2
3
4
5
6
7
8#例题:阶乘
def fact(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
高级特性
切片
使用
对于list L[1,2,3,4],如果想去前三个元素,就可以使用切片L[0:3]
支持负数,前后可省略传三个参数 L[::5] 表示所有的数,每5个取一个
L[:10:2] 表示前10个数中,每2个取一个使用对象
list, tuple,str
迭代(循环)
1
2
3nums = [1,2,3,4,5]
for n in nums:
print(n)使用对象
str,tuple,list,dict
dict使用解析
默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()
判断迭代对象是否为可迭代对象
方法是通过collections模块的Iterable类型判断
1
2
3
4
5
6
7>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False对list实现类似Java那样的下标循环
Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身。
1
2
3
4
5
6>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
列表生成器
- 入门例子: range(1, 11) [1,2,3,4,5,6,7,8,9,10] 前开后闭
- 生成一些特殊数组list
- 如果要生成一个[1,4,9,16,25 … 100],则可以通过 [x*x for x in range(1, 11)]
- 生成各种可以通过循环得到的数组(如: 全排列,
生成器
(总结已近似于ctrl+c/ctrl+v 😑, 廖雪峰详细教程)
介绍
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
1
2
3
4
5
6>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>
创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
- **打印出生成器内容**
- 如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:
- 一个一个太过复杂,也不现实,正确的方法是使用for循环,因为generator也是可迭代对象;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81
所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。
- 定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就 **不再是**一个普通函数,而是一个 **generator**,即可迭代的对象
- **generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#简单的例子
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
#结果 (当然,我们通常使用for,而不是一次一次的调用next()
>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
生成器只能遍历一次,之后访问抛出StopIteration。
迭代器
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()判断一个对象是否是Iterator对象:
1
2
3
4
5
6
7
8
9>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数:
1
2
3
4>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
函数式编程
高阶函数
map(func, iterable) 接收两个参数,第一个为函数,第二个为可迭代对象,map作用是将可迭代对象都作用于func,返回一个interator
1
2
3
4
5
6>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]reduce(func, list), reduce作用是把func作用到一个序列上, func必须接收两个参数,reduce把结果继续和序列的下一个元素做累计计算。
1
2
3
4
5
6>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25- 应用/练习 ->示例
filter(过滤器),和map相似,也是接收一个函数和序列,并对每个元素进行函数作用,但不同点是,filter会根据返回值为True或False决定保留还是丢弃元素
1
2
3
4
5
6#例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]sorted(),排序函数,默认按递增排序
1
2>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序
1
2>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]key指定的函数作用在list的每个元素上,并根据key函数返回的结果进行增序排列
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
1
2>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
返回函数(函数作为返回值)
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
# 当我们调用lazy_sum函数时,它返回的并不是求和结果,而是求和函数sum
>>>f = lazy_sum([2,4,1,6])
>>>f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
# 只有在我们调用f函数时,才会得到求和结果
>>>f()
13需要注意的是,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
1
2
3
4>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False由于闭包,返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。
匿名函数
在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:
1
2>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]关键字lambda表示匿名函数,冒号前面的x表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果
I/O编程
文件读写(文件操作详解)
读文件
在进行文件读写时,要先建立一个文件对象,open()函数。该函数有两个参数,第一个时文件路径,第二个参数是文件的操作方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# ##### 文本文件
>>> f = open('/Users/michael/test.txt', 'r')
# 标识符r表示以读的方式打开 **文本** 文件
# 当文件不存在时,会报错;否则打开成功
# 文件打开成功,可以进行接下来的操作,调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示
>>>f.read()
# 最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的
>>>f,close()
# 为在文件读写时出错也可以close文件,python提供了渐变方法
with open('/path/to/file', 'r') as f:
print(f.read())
# ##### 二进制文件
>>> f = open('/Users/michael/test.jpg', 'rb')
>>> f.read()
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节- 注意:
- 调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。另外,调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。因此,要根据需要决定怎么调用。
- 写文件
和读文件一样,只是mode改为w或rw
注意:在写文件时,如果忘记close,可能会导致数据的丢失
- 注意:
StringIO和BitesIO (具体作用还不清楚)
转到操作文件/目录
1
2
3
4
5listdir()找出该路径下文件
isfile()判断是否为文件
splitext()获取文件后缀名
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']通过python内置的os模块和os.path模块,我们可以实现对文件和目录的处理, 详细教程
常用内建模块
contentlib
在Python中,读写文件这样的资源要特别注意,必须在使用完毕后正确关闭它们。正确关闭文件资源的一个方法是使用try…finally
写try…finally非常繁琐。Python的with语句允许我们非常方便地使用资源,而不必担心资源没有关闭。
@contextmanager
只要是可以实现上下文管理的,均可以用于with语句,阮一峰教程,而python的contextlib库的@contextmanager可以简化这个(上下文管理)实现过程@closing
如果一个对象没有上下文,我们可以通过该方法使其变为具有上下文的对象,并支持with语句。